1
0
mirror of https://github.com/yattee/yattee.git synced 2025-04-27 15:30:33 +05:30

Compare commits

...

2112 Commits
v1.0 ... main

Author SHA1 Message Date
Arkadiusz Fal
2a597ab3cb Add missing bundle platform 2025-03-23 15:08:04 +01:00
Arkadiusz Fal
4d662115e4 Update GitHub release workflow 2025-03-23 15:07:15 +01:00
Arkadiusz Fal
e068257f14 Bump build number to 200 2025-03-23 15:04:56 +01:00
Arkadiusz Fal
8b809fb0f1 Update GitHub release workflow 2025-03-23 15:04:16 +01:00
Arkadiusz Fal
d3e80f500e Update GitHub release workflow 2025-03-23 13:38:51 +01:00
Arkadiusz Fal
9343e9d023 Bump build number to 199 2025-03-23 13:35:52 +01:00
Arkadiusz Fal
e4b25b0f80 Update CHANGELOG 2025-03-23 13:35:52 +01:00
Arkadiusz Fal
09c2fb19a9 Fix swiftformat offenses 2025-03-23 13:32:46 +01:00
Arkadiusz Fal
043b07274e Update packages 2025-03-23 13:32:19 +01:00
Arkadiusz Fal
7f7e12d719
Merge pull request #851 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2025-03-23 13:27:47 +01:00
Arkadiusz Fal
d990c6630e
Merge pull request #863 from lifo9/add-support-for-invidious-companion
Add support for invidious companion
2025-03-23 13:27:33 +01:00
Jakub Filo
5239b36cfe Add support for invidious companion 2025-03-18 22:56:45 +01:00
Ghost of Sparta
addc13ebfb
Translated using Weblate (Hungarian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hu/
2025-03-01 17:01:56 +00:00
azazaazur
2a6f26ec68
Translated using Weblate (Turkish)
Currently translated at 97.3% (547 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2025-02-15 17:01:58 +01:00
Mohammed Al Otaibi
2e2f502d97
Translated using Weblate (Arabic)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2025-01-30 15:02:02 +01:00
Sophia Park
59afc2f4c7
Translated using Weblate (Korean)
Currently translated at 14.2% (80 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ko/
2025-01-06 08:07:37 +01:00
Finn
2f902e74bb
Translated using Weblate (German)
Currently translated at 94.8% (533 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2025-01-03 12:01:20 +00:00
Ghost of Sparta
500b75da4f
Translated using Weblate (Hungarian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hu/
2024-12-28 13:00:32 +01:00
Arkadiusz Fal
3a17cc4dee Add missing bundle platform 2024-12-26 19:43:42 +01:00
Arkadiusz Fal
16897338e6 Bump build number to 198 2024-12-26 19:41:35 +01:00
Arkadiusz Fal
7c870b8e61 Update changelog 2024-12-26 19:41:26 +01:00
Arkadiusz Fal
75d9c5c747 Add Hungarian locale 2024-12-26 19:10:51 +01:00
Arkadiusz Fal
9e0f1a72ab Update packages 2024-12-26 19:09:28 +01:00
Arkadiusz Fal
7f3b3ac0ab
Merge pull request #849 from derspyy/0length
Stop making videos with unknown length shorts.
2024-12-26 18:57:13 +01:00
Arkadiusz Fal
84b70b794b
Merge pull request #845 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-12-26 18:56:34 +01:00
Ghost of Sparta
e6bae84162
Translated using Weblate (Hungarian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hu/
2024-12-25 21:00:36 +01:00
piuvas
9efbac3d15
make 0-length videos not shorts. 2024-12-21 12:55:44 -03:00
Blueberry
1289f57f60
Translated using Weblate (Russian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2024-12-13 23:00:53 +01:00
Ghost of Sparta
cc03ab059b
Translated using Weblate (Hungarian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hu/
2024-12-10 18:00:28 +00:00
Ghost of Sparta
17484f65fd
Translated using Weblate (Hungarian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hu/
2024-12-09 16:00:36 +01:00
Ghost of Sparta
65247227e7
Translated using Weblate (Hungarian)
Currently translated at 93.2% (524 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hu/
2024-12-08 11:00:44 +00:00
Ghost of Sparta
625c01aaac
Translated using Weblate (Hungarian)
Currently translated at 56.5% (318 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hu/
2024-12-06 20:00:30 +01:00
Twig
7465ff9c5c
Translated using Weblate (Turkish)
Currently translated at 96.0% (540 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2024-11-24 18:00:26 +01:00
Arkadiusz Fal
41de28a698 Bump build number to 197 2024-11-08 16:29:59 +01:00
Arkadiusz Fal
7baab7a88a Update github actions checkout version 2024-11-08 15:26:55 +01:00
Arkadiusz Fal
43599632b2 Update changelog 2024-11-08 15:20:06 +01:00
Arkadiusz Fal
e4f413ed2d Update packages 2024-11-08 15:20:06 +01:00
Arkadiusz Fal
661b7547c5 Fix application groups 2024-11-08 15:09:59 +01:00
Arkadiusz Fal
d69f410d92 Add Tamil language 2024-11-08 15:07:29 +01:00
Arkadiusz Fal
db7abe31ea
Merge pull request #836 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-11-08 15:05:22 +01:00
ButterflyOfFire
fff36ece26
Translated using Weblate (Kabyle)
Currently translated at 17.0% (96 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/kab/
2024-11-08 14:04:18 +00:00
ButterflyOfFire
8a0e9ae75a
Added translation using Weblate (Kabyle) 2024-11-08 14:04:17 +00:00
trilame
d6e5b5ed76
Translated using Weblate (French)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2024-11-08 14:04:16 +00:00
தமிழ்நேரம்
cef1a1caea
Translated using Weblate (Tamil)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ta/
2024-11-08 14:04:16 +00:00
தமிழ்நேரம்
6c6abe8c84
Added translation using Weblate (Tamil) 2024-11-08 14:04:15 +00:00
Istvan Tanczos
9732537602
Translated using Weblate (Hungarian)
Currently translated at 1.0% (6 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hu/
2024-11-08 14:04:15 +00:00
Istvan Tanczos
c0deeabaed
Added translation using Weblate (Hungarian) 2024-11-08 14:04:14 +00:00
Arkadiusz Fal
f29dbcbe36
Merge pull request #834 from mmaalo/norwegian100
Norwegian Language
2024-11-08 15:04:05 +01:00
Arkadiusz Fal
c7e1a50e56
Merge pull request #833 from blennster/main
Fix uneven playback when using MPV and not syncing refreshrate
2024-11-08 15:03:51 +01:00
Arkadiusz Fal
dd205db15f
Merge pull request #824 from yattee/mpvkit-0-39-0
update MPVKit to v0.39.0
2024-11-08 15:03:18 +01:00
Arkadiusz Fal
9ca5d292ec
Merge pull request #820 from yattee/video-details-gestures
add drag gestures to video details
2024-11-08 15:03:07 +01:00
Arkadiusz Fal
748bc16342
Merge pull request #818 from yattee/changes-to-opengl-view
improvements to MPVGLView
2024-11-08 15:02:15 +01:00
Arkadiusz Fal
798d2fc67f
Merge pull request #817 from yattee/fix-subtitles
improved subtitle handling
2024-11-08 15:01:56 +01:00
Arkadiusz Fal
a5a88f8890
Merge pull request #815 from yattee/refined-audio-ducking
proper audio interrupt and route change handling
2024-11-08 15:01:27 +01:00
Arkadiusz Fal
f69ccb6bd6
Merge pull request #814 from yattee/fullscreen-gesture-toggle
allow users to disable fullscreen swipe gesture
2024-11-08 15:01:06 +01:00
Arkadiusz Fal
892b3dea17
Merge pull request #813 from yattee/update-introspect
Update SwiftUI-Introspect
2024-11-08 15:00:35 +01:00
mmaalo
9a11e9f9f5 completed Localizable.strings for norwegian language 2024-10-07 21:23:12 +02:00
Emil Blennow
055d5575ba
fix uneven playback when using MPV and not syncing refreshrate 2024-10-06 17:32:02 +02:00
Toni Förster
28b6a517b6
update MPVKit to v0.39.0
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-24 10:41:02 +02:00
Toni Förster
b4bcd0c0a0
add drag gestures to video details 2024-09-15 20:56:04 +02:00
Toni Förster
e62010d5d5
improvements to MPVGLView
- add Pixel Buffer Object to (PBO)
- add some debug logging
- add scissor testing
- add dirty region checking

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-14 15:04:17 +02:00
Toni Förster
3339e8cb1f
improved subtitle handling
- fix subtitle disabling not working
- make subtitle adding/removing async
- make subtitle menu non blocking

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-14 11:33:12 +02:00
Toni Förster
4855f9bead
fix tvOS build
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-13 11:48:40 +02:00
Toni Förster
a65ed67751
proper audio interrupt and route change handling
- set AVAudioSession inactive on pause and stop
- handle audio route changes
2024-09-13 11:21:07 +02:00
Toni Förster
72dcbe4515
allow user to disable fullscreen swipe gesture
furthermore, some rewording

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-12 16:57:48 +02:00
Toni Förster
7e02b08933
update SwiftUI-Introspect 2024-09-11 20:55:00 +02:00
Arkadiusz Fal
8596ee8811 Bump build number to 196 2024-09-11 09:37:13 +02:00
Arkadiusz Fal
894439ad5e Update CHANGELOG 2024-09-11 09:35:32 +02:00
Arkadiusz Fal
5dad7a1b47 Update dependencies 2024-09-11 09:31:24 +02:00
Arkadiusz Fal
6d48a825cd
Merge pull request #809 from yattee/refactor-search
refactor Search
2024-09-11 09:29:43 +02:00
Arkadiusz Fal
ed11e593ff
Merge pull request #810 from yattee/auto-retry-video-loading
Retry loading video before presenting error
2024-09-11 09:29:28 +02:00
Arkadiusz Fal
102dfba751
Merge pull request #805 from yattee/mpv-better-performance
MPV: improved A/V sync
2024-09-11 09:29:14 +02:00
Arkadiusz Fal
4202b27c03
Merge pull request #807 from yattee/more-robust-resolution-handling
more robust resolution handling
2024-09-11 09:29:00 +02:00
Arkadiusz Fal
2f937f74fa
Merge pull request #806 from yattee/orientation-location-cleanup
Orientation/Fullscreen fixes and cleanup
2024-09-11 09:28:39 +02:00
Toni Förster
34a957b28e
use system background color for background
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-11 08:55:18 +02:00
Toni Förster
0bef798341
add border around search field
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-10 19:01:41 +02:00
Toni Förster
28a7b6e981
auto retry loading the video before showing error
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-10 11:07:20 +02:00
Toni Förster
4663aab3da
refactor Search
- have a separate body view for each os
- macOS: set navigation title for search
- macOS: set min width to 835 for main content
- macOS: set main window min height to 600
- macOS: don’t have text behind the cancel button
- split SearchTextField into macOS and iOS/tvOS
- iOS: move search menu to the right
- iOS: unified search field
- make min width a constant
- add option to disable search suggestions

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-10 09:38:14 +02:00
Toni Förster
0de0445805
check if subtitles are added before removing them
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-09 14:18:49 +02:00
Toni Förster
9cb0325503
more robust resolution handling
Currently, we have a hard-coded list of resolutions. Since Invidious reports the actual resolution of a stream and does not hard-code them to a fixed value anymore, resolutions that are not in the list won’t be handled, and the stream cannot be played back.

Instead of hard-coding even more resolutions (and inadvertently might not cover all), we revert the list back to a finite set of resolutions, the users can select from. All other resolutions are handled dynamically and compared to the existing set of defined resolutions when selecting the best stream for playback.

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-09 12:59:39 +02:00
Toni Förster
5e85fd294c
MPV: improved A/V sync
- use displays refresh rate
- execute needs drawing with higher priority
- run create() with higher priority
- determine the number of threads used for rendering
- enable VSYNC and change video-sync  to display-resample
- iOS/tvOS: set new display refresh rate on change
- run setSize with higher priority
- add more options to MPVClient
- get refresh rate updates
- sync refresh rate to fps
- update CADisplayLink to current refresh rate
- update refresh rate on macOS
- Add experimental feature to sync display  with content fps

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-08 15:59:42 +02:00
Toni Förster
b2421da95d
apply new fullscreen logic to AppleAVPlayerView
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-06 17:13:08 +02:00
Toni Förster
4e4add3c42
fix double rotation
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-06 16:47:15 +02:00
Toni Förster
2185718d50
orientation fullscreen code cleanup
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-06 15:46:58 +02:00
Arkadiusz Fal
b0264aaabe Bump build number to 195 2024-09-05 23:01:29 +02:00
Arkadiusz Fal
035f3503c4 Update CHANGELOG 2024-09-05 23:01:19 +02:00
Arkadiusz Fal
e3ac11c172
Merge pull request #786 from stonerl/simplified-fullscreen-and-orientation
iOS: Simplified fullscreen and orientation
2024-09-05 22:59:54 +02:00
Arkadiusz Fal
7aed6ac0d9
Merge pull request #799 from stonerl/controls-background
player controls: add background opacity selection
2024-09-05 22:54:30 +02:00
Arkadiusz Fal
457c0ce7b3
Merge pull request #797 from stonerl/shorts-resolutions
add missing Shorts resolutions
2024-09-05 22:53:42 +02:00
Arkadiusz Fal
747baf3edd
Merge pull request #801 from stonerl/O2-for-macOS
use -O1 on macOS
2024-09-05 22:53:26 +02:00
Arkadiusz Fal
cd24a0322f
Merge pull request #802 from stonerl/buttons-interfere-with-search
macOS: only apply player shortcuts when window is active
2024-09-05 22:53:16 +02:00
Toni Förster
d525a22215
macOS only apply player shortcuts when window is active 2024-09-05 21:53:25 +02:00
Toni Förster
322a550666
simplified fullscreen and orientation handling
- iPad: rotate to device orientation on startup
- fixed controls in portrait fullscreen
- iOS: don’t call setNeedsDrawing multiple times
- On iOS we call set needs drawing only once.
- Added cooldown time to MPV.Client setNeedsDrawing to avoid multiple successive calls
- make fullscreen animation smoother
- dragGesture now calls toggleFullScreenAction
- fix tvOS and macOS build

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-05 18:17:14 +02:00
Toni Förster
98fa0b98e5
use -O1 on macOS
On macOS optimisation level -O3 seems to be a bit aggressive and can cause crashes when opening MPV.

- fixes #783

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-05 17:35:52 +02:00
Toni Förster
5313e4ead0
player controls: add background opacity selection
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-05 15:14:39 +02:00
Toni Förster
fa7b897e76
add missing Shorts resolutions
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-04 12:44:43 +02:00
Arkadiusz Fal
9bf3df1a29 Bump build number to 194 2024-09-04 09:38:15 +02:00
Arkadiusz Fal
34a805b986 Fix build issue 2024-09-04 09:37:38 +02:00
Arkadiusz Fal
36f680be62 Update CHANGELOG 2024-09-04 09:36:05 +02:00
Arkadiusz Fal
a27ab02433 Update dependencies 2024-09-04 09:33:23 +02:00
Arkadiusz Fal
59dd0785b3
Merge pull request #778 from stonerl/swipe-up-for-fullscreen
Gestures: swipe up toggles fullscreen
2024-09-04 09:16:23 +02:00
Arkadiusz Fal
d7be915e7e
Merge pull request #779 from stonerl/better-audio-ducking
Better audio ducking
2024-09-04 09:15:35 +02:00
Arkadiusz Fal
3752f67630
Merge pull request #780 from stonerl/add-overlay-to-video-context-menu
don’t open video when dismissing context menu
2024-09-04 09:15:03 +02:00
Arkadiusz Fal
dfe7565138
Merge pull request #789 from stonerl/fix-picture-in-picture
fix picture in picture
2024-09-04 09:14:34 +02:00
Arkadiusz Fal
4d02538cb9
Merge pull request #793 from stonerl/mpv-remove-video-layer
mpv: remove video layer when entering background
2024-09-04 09:14:05 +02:00
Arkadiusz Fal
3229528a09
Merge pull request #794 from stonerl/enable-o3-optimization
enable -O3
2024-09-04 09:13:23 +02:00
Arkadiusz Fal
fffc4f4a5f
Merge pull request #791 from stonerl/hi-res-invidious-logo
hi-res invidious logos
2024-09-04 09:13:01 +02:00
Toni Förster
e85bfe5007
enable -O3
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 21:40:48 +02:00
Toni Förster
b00b733fd5
don’t open video when dismissing context menu
fixes #510

fix tvOS build

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 21:21:34 +02:00
Toni Förster
119c663436
Gestures: swipe up toggles fullscreen 2024-09-03 21:20:56 +02:00
Toni Förster
e8fcee23ef
make audio ducking and interruption more robust
Signed-off-by: Toni Förster <toni.foerster@gmail.com>

fix audio ducking and bluetooth play/pause

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 21:19:30 +02:00
Toni Förster
d56ef74a99
fix picture in picture
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 21:17:20 +02:00
Toni Förster
98f5b1a22b
mpv: remove video layer when entering background
- fixes #792

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 14:58:18 +02:00
Toni Förster
f0b7bd3ab8
hi-res invidious logos
second try

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 01:01:52 +02:00
Arkadiusz Fal
2d7a101ce0 Bump build number to 193 2024-08-31 17:08:23 +02:00
Arkadiusz Fal
b2114174b4 Update CHANGELOG 2024-08-31 17:08:09 +02:00
Arkadiusz Fal
e9f502a486
Merge pull request #775 from stonerl/fix-crash-on-hls-live-playback 2024-08-31 14:54:08 +02:00
Toni Förster
6978e9437c
fix crash on HLS live playback
fixes #774

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-31 14:22:28 +02:00
Arkadiusz Fal
2026201a5f
Merge pull request #765 from stonerl/partial-fix-for-503
Update now playing info when using system controls – Partial fix for 503
2024-08-31 13:21:41 +02:00
Toni Förster
633af02577
don’t activate AVAudioSession on app start
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-31 13:19:54 +02:00
Arkadiusz Fal
1fd62f04aa Update dependencies 2024-08-31 13:08:46 +02:00
Arkadiusz Fal
e749307a0e
Merge pull request #772 from stonerl/re-enter-fullscreen-on-background-return
fixed fullscreen handling for backgrounding
2024-08-31 13:08:40 +02:00
Toni Förster
d76ec881be
fixed fullscreen handling for backgrounding
Currently when returning from background, the app is fullscreen but the video is in portrait mode.

Now when entering background we leave fullscreen and enter it again the the app is in foreground.

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-31 13:00:50 +02:00
Arkadiusz Fal
72a39a2c75
Merge pull request #767 from stonerl/new-defaults
Changes to defaults
2024-08-31 12:48:13 +02:00
Arkadiusz Fal
8a84db5a2d
Merge pull request #766 from stonerl/music-mode-thumbnail
make thumbnail fill the view in music mode
2024-08-31 12:47:52 +02:00
Arkadiusz Fal
663c37e3d2
Merge pull request #768 from stonerl/improved-image-loading
Video Thumbnails: retry 3 times fetching from URL
2024-08-31 12:47:31 +02:00
Arkadiusz Fal
ea2b329df2
Merge pull request #769 from stonerl/new-invidious-logo
circular invidious logo
2024-08-31 12:46:55 +02:00
Arkadiusz Fal
bd79f56800
Merge pull request #770 from stonerl/correct-landscape-setting
apply correct orientation
2024-08-31 12:46:41 +02:00
Arkadiusz Fal
9a650b4ac0
Merge pull request #762 from stonerl/allow-username-and-password-in-url
Invidious: propper HTTP basic auth support
2024-08-31 12:46:33 +02:00
Toni Förster
13382270d5
Revert "fixed some potential crashes"
This reverts commit bde9aade1173a8a466e3d54e2adc39bea49efe97.
2024-08-31 02:50:56 +02:00
Toni Förster
24626c2299
apply correct orientation
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 23:20:47 +02:00
Toni Förster
18ac577c7f
increase retry delay to 1 second
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 23:14:41 +02:00
Toni Förster
617af2cd20
format ordering adapted
- MP4 contains av1 encoded video which is not hardware accelerated.
- MP4 format removed from AVPlayer

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 22:04:31 +02:00
Toni Förster
1b778318dc
circular invidious logo
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 20:58:42 +02:00
Toni Förster
c9ce574c7a
VideoThumbnails: retry 3 times fetching from URL
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 20:01:25 +02:00
Toni Förster
9a1f0d7aaa
Changes to defaults
- Don’t use System Controls with AVPlayer by default
- Changed default order of the formats
- no default AVPlayer profiles except for tvOS
- new constants isIOS, isTvOS, isMacOS
- ArtWork for nowPlaying is .medium on iPhone .maxres on all other platforms
- changes to the player bar defaults

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 16:03:35 +02:00
Toni Förster
1cb695848c
make thumbnail fill the view in music mode
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 03:43:00 +02:00
Toni Förster
740a2f85ac
updateNowPlayingInfo also with System controls
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-29 15:10:04 +02:00
Toni Förster
1a22ac71be
move AVAudioSession configuration to AppDelegate
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-29 15:09:16 +02:00
Toni Förster
f0d581d512
remove autocorrect from location url TextField
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 18:05:40 +02:00
Toni Förster
049a42f2e8
allow basic auth with auth endpoint
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 17:40:16 +02:00
Toni Förster
cea2684a29
sanitise user and password in url
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 16:21:49 +02:00
Toni Förster
772e5016c4
make sure no log entries are lost
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 15:43:15 +02:00
Toni Förster
ed3d9a7d7c
invidious support for basic auth urls
This adds user, password and port to the proxy and thumbnail url, if they exist.

fixes #614 & #731

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 15:42:35 +02:00
Toni Förster
bde9aade11
fixed some potential crashes
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 15:39:03 +02:00
Arkadiusz Fal
a194738bb6 Bump build number to 192 2024-08-28 13:40:12 +02:00
Arkadiusz Fal
45567254f2 Update CHANGELOG 2024-08-28 13:40:00 +02:00
Arkadiusz Fal
a3139ad059 Fix swiftformat offense 2024-08-28 13:34:36 +02:00
Arkadiusz Fal
598f17479f
Merge pull request #760 from stonerl/hide-video-actions
Hide VideoActions Bar when no buttons is visible
2024-08-28 13:33:43 +02:00
Arkadiusz Fal
e888abfba9
Merge pull request #761 from stonerl/revert-new-appicons
Revert new AppIcons
2024-08-28 13:33:32 +02:00
Toni Förster
e1e53b2d36
Revert new AppIcons
This reverts commit 59da0e71b634080e98421e05d3e20242dd7d936d.

Revert "Merge pull request #758 from stonerl/new-app-icons-second-try"

This reverts commit 7b26fdf4008b08237c7554533523c005324897dc, reversing
changes made to 67b41e36d5f4ec210759bebb83b4e2c050e04151.

Revert "Merge pull request #756 from stonerl/new-app-icons-second-try"

This reverts commit b51eadc7a9d9c8746aeca3994a3f5bc70d88d203, reversing
changes made to 0c1fb02d508985b0f394a1a8b25d2ed9776fee0e.
2024-08-28 12:42:57 +02:00
Toni Förster
9510d91d61
Hide VideoActions Bar when no buttons is visible
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 12:32:59 +02:00
Arkadiusz Fal
59da0e71b6 Fix bundle identifier 2024-08-27 22:46:07 +02:00
Arkadiusz Fal
6bdfb7368c
Merge pull request #759 from stonerl/videoactions-color
Color changes to VideoActions
2024-08-27 22:26:56 +02:00
Arkadiusz Fal
7b26fdf400
Merge pull request #758 from stonerl/new-app-icons-second-try
change AppStore icon
2024-08-27 22:26:31 +02:00
Arkadiusz Fal
67b41e36d5
Merge pull request #757 from stonerl/zh-hans
Add Chinese (Simplified) - zh-Hans to LanguageCodes
2024-08-27 22:26:17 +02:00
Toni Förster
c9c60349df
Color changes to VideoActions
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 22:02:00 +02:00
Toni Förster
6a70663f06
add more iOS icons
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 17:59:59 +02:00
Toni Förster
3d556d836f
change the App Store Icon
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 16:26:56 +02:00
Toni Förster
8feeb33a55
distinguish between iOS and macOS AppIcons
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 15:45:54 +02:00
Toni Förster
497c3bfc12
Add Chinese (Simplified) - zh-Hans to LanguageCodes
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 15:33:06 +02:00
Arkadiusz Fal
b51eadc7a9
Merge pull request #756 from stonerl/new-app-icons-second-try
refreshed icons for iOS and macOS
2024-08-27 14:11:20 +02:00
Toni Förster
7d0c1180c4
refreshed icons for iOS and macOS
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 13:31:13 +02:00
Arkadiusz Fal
0c1fb02d50
Merge pull request #755 from yattee/revert-752-new-app-icons
Revert "refreshed icons for iOS and macOS"
2024-08-27 08:21:46 +02:00
Arkadiusz Fal
6f358fab56
Revert "refreshed icons for iOS and macOS" 2024-08-27 08:19:45 +02:00
Arkadiusz Fal
7631e2a8ed Bump build number to 191 2024-08-27 08:00:24 +02:00
Arkadiusz Fal
3a5f3fdfde Update CHANGELOG 2024-08-27 08:00:17 +02:00
Arkadiusz Fal
e3633bdaf7
Merge pull request #752 from stonerl/new-app-icons
refreshed icons for iOS and macOS
2024-08-27 07:56:31 +02:00
Arkadiusz Fal
e912d910bc
Merge pull request #754 from stonerl/fix-mpv-crash-on-macos
fix mpv crashing on macOS
2024-08-27 07:56:03 +02:00
Arkadiusz Fal
5ccb0f90d5
Merge pull request #753 from stonerl/use-new-mpvkit-repo
add new MPVKit repo
2024-08-27 07:55:55 +02:00
Toni Förster
278bc343c2
fix mpv crashing on macOS
fixes #712

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 04:04:23 +02:00
Toni Förster
5dc197664d
add new MPVKit repo
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-26 22:59:24 +02:00
Toni Förster
192550ba7a
refreshed icons for iOS and macOS
- added support for adaptive/tinted Icons on iOS.
- closes #700
- new macOS icon set, courtesy of @Kongolabongo (Discourse)

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-26 15:55:36 +02:00
Arkadiusz Fal
3369e23e74 Bump build number to 190 2024-08-26 08:20:57 +02:00
Arkadiusz Fal
4381511c91 Update CHANGELOG 2024-08-26 08:20:51 +02:00
Arkadiusz Fal
af99df9b8a
Merge pull request #750 from stonerl/refined-font-sclaing-for-captions
refined chapter font scaling
2024-08-26 08:16:55 +02:00
Arkadiusz Fal
21f21cc944
Merge pull request #749 from stonerl/fix-chapter-regression
fix regression and improve curentChapter handling
2024-08-26 08:16:45 +02:00
Arkadiusz Fal
e1d8bb8125
Merge pull request #748 from stonerl/fix-potential-crashes
fix some potential crashes
2024-08-26 08:16:28 +02:00
Arkadiusz Fal
d948ea6887
Merge pull request #747 from stonerl/fix-endless-loading-of-streams
Improved stream resolution handling
2024-08-26 08:16:19 +02:00
Toni Förster
66eb8051bf
refined chapter font scaling
adapted the scaling of chapter fonts after some user feedback

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-26 01:27:09 +02:00
Toni Förster
95d3170d31
fix regression and improve curentChapter handling
with #745 I left som testing changes in the PR that resulted in currentChapter index not being updated. This is fixed now.

Also, the ScrollViewReader waiter 0.5s before jumping to the current Chapter. So it is always drawn correctly.

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-26 00:46:35 +02:00
Toni Förster
74b6adb247
fix some potential crashes
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-25 23:41:06 +02:00
Toni Förster
a45522f710
Improved stream resolution handling
Invidious now reports the actual resolution and doesn’t hardcode them anymore.

See: https://github.com/iv-org/invidious/pull/4586

- Extended the list of possible resolutions in the StreamModel
- trigger videoLoadFailureHandler if no streams are available
- more logging for backend.bestPlayable

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-25 17:23:04 +02:00
Arkadiusz Fal
0b01adf6eb Bump build number to 189 2024-08-24 17:55:30 +02:00
Arkadiusz Fal
444f6bcc03 Update CHANGELOG 2024-08-24 17:55:22 +02:00
Arkadiusz Fal
3f871bce2c Fix possible crashes 2024-08-24 17:52:35 +02:00
Arkadiusz Fal
dc3492fd96 Fix tvOS comments 2024-08-24 14:30:51 +02:00
Arkadiusz Fal
c7365a7dc1 Fix build issues 2024-08-24 14:04:02 +02:00
Arkadiusz Fal
fc9fc194f2 Bump build number to 188 2024-08-24 13:58:59 +02:00
Arkadiusz Fal
317ac63a3f Update CHANGELOG 2024-08-24 13:58:46 +02:00
Arkadiusz Fal
dc7cee7388 Revert "Stay fullscreen when opening notification center"
This reverts commit 08c922f57c81ca453a3e89d8e1d3a0e13ba141e3.
2024-08-24 13:56:25 +02:00
Arkadiusz Fal
1c5d909201 Revert mpvkit package location upgrade 2024-08-24 13:27:56 +02:00
Arkadiusz Fal
146da6b9cc
Merge pull request #746 from stonerl/SwiftyJSON-fixes
add missing SwiftyJSON arguments
2024-08-24 13:20:57 +02:00
Toni Förster
6be13451e0
add missing SwiftyJSON arguments
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-24 13:17:26 +02:00
Arkadiusz Fal
0c41ab6aa2 Update packages 2024-08-24 12:59:38 +02:00
Arkadiusz Fal
1b1e95711a Fix typo 2024-08-24 12:59:38 +02:00
Arkadiusz Fal
13d4a592bc
Merge pull request #570 from stonerl/close-fullscreen-on-end
add option to exit fullscreen on end
2024-08-24 12:16:26 +02:00
Arkadiusz Fal
ab493614ba
Merge pull request #744 from stonerl/hide-comments
Allow hiding comments
2024-08-24 12:13:44 +02:00
Arkadiusz Fal
0b57187435
Merge pull request #738 from stonerl/chore-linter-warnings
chore: address linter warnings
2024-08-24 12:12:25 +02:00
Arkadiusz Fal
e7928d1016
Merge branch 'main' into chore-linter-warnings 2024-08-24 12:12:12 +02:00
Arkadiusz Fal
b3d73aae92
Merge pull request #735 from stonerl/update-dependencies
update dependencies
2024-08-24 12:11:16 +02:00
Arkadiusz Fal
bafcacb9a1
Merge pull request #724 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-08-24 12:10:54 +02:00
maboroshin
931d373d41
Translated using Weblate (Japanese)
Currently translated at 98.5% (554 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2024-08-24 10:09:45 +00:00
Limonov
5d1620b0a0
Translated using Weblate (Russian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2024-08-24 10:09:44 +00:00
Limonov
44cfe8e6bc
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2024-08-24 10:09:44 +00:00
Limonov
8220bbc3fe
Translated using Weblate (Russian)
Currently translated at 98.9% (556 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2024-08-24 10:09:43 +00:00
Limonov
f01836010c
Translated using Weblate (Russian)
Currently translated at 98.5% (554 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2024-08-24 10:09:43 +00:00
Limonov
37ccb27c8e
Translated using Weblate (Russian)
Currently translated at 99.2% (558 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2024-08-24 10:09:42 +00:00
Kaambiz
93eb2eb258
Translated using Weblate (Persian)
Currently translated at 75.4% (424 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fa/
2024-08-24 10:09:41 +00:00
Saukki
ce9db8cd12
Translated using Weblate (Finnish)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fi/
2024-08-24 10:09:41 +00:00
Saukki
d361690d13
Added translation using Weblate (Finnish) 2024-08-24 10:09:40 +00:00
Limonov
ded9699a3a
Translated using Weblate (Russian)
Currently translated at 98.9% (556 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2024-08-24 10:09:40 +00:00
Limonov
101ddf79d5
Translated using Weblate (Russian)
Currently translated at 99.6% (560 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2024-08-24 10:09:39 +00:00
Limonov
d11c5a8c42
Translated using Weblate (Ukrainian)
Currently translated at 88.7% (499 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2024-08-24 10:09:39 +00:00
Limonov
b2a84ef01b
Translated using Weblate (Russian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2024-08-24 10:09:38 +00:00
Limonov
6c4eb0c840
Translated using Weblate (Russian)
Currently translated at 97.6% (549 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2024-08-24 10:09:38 +00:00
Arkadiusz Fal
2b41c73d56
Merge pull request #743 from stonerl/controllcenter-swipe
Stay fullscreen when opening notification center
2024-08-24 12:09:28 +02:00
Arkadiusz Fal
516e305cd3
Merge pull request #742 from stonerl/subscription-button
Improvements to opening channels from Videos
2024-08-24 12:09:07 +02:00
Arkadiusz Fal
8921a38504
Merge pull request #741 from stonerl/timecodes-in-comments
iOS: make timestamps in comments touchable
2024-08-24 12:08:27 +02:00
Arkadiusz Fal
61d589a9b5
Merge pull request #740 from stonerl/thumbnails
Improved thumbnail handling
2024-08-24 12:07:56 +02:00
Arkadiusz Fal
55cbd1fe80
Merge pull request #737 from stonerl/xcode16-recommended-settings
Xcode 16 - update recommended settings
2024-08-24 12:06:35 +02:00
Arkadiusz Fal
d501cb938c
Merge pull request #745 from stonerl/updateWatch-status
only updateWatch status while video is playing
2024-08-24 12:02:42 +02:00
Toni Förster
8e97d3f42f
set playingFullscreen to proper value
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-20 22:56:55 +02:00
Toni Förster
64a18678ce
add option to exit fullscreen on end 2024-08-20 22:19:19 +02:00
Toni Förster
522aecfbc1
only updateWatch status while video is playing
This should circumvent edge cases where videos are marked as watch when they failed to play back.

Fixes #660

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-20 22:10:35 +02:00
Toni Förster
f5e509c091
Allow hiding comments 2024-08-20 20:39:21 +02:00
Toni Förster
08c922f57c
Stay fullscreen when opening notification center
The upper 5% of the screen ignore swipe down gestures when in fullscreen, to avoid leaving fullscreen when opening the notification center.

fixes #702

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-20 16:14:57 +02:00
Toni Förster
0e8436ab40
VideoDetails: click on channel name opens channel
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-20 14:13:46 +02:00
Toni Förster
f3c876acf6
VideoDetails: open channel when touching the logo
The touch was consumed by the double touch action and the channel did not open.

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-20 14:00:43 +02:00
Toni Förster
70d821fe5d
use correct systemImage for the Subscribe button
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-20 13:18:32 +02:00
Toni Förster
9a450c9503
minor tweak to comment replies
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-20 01:23:54 +02:00
Toni Förster
7e346bf49c
iOS: make timestamps in comments touchable
Timestamps in comments can now be touched and jump to the corresponding part in the video.

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-20 00:52:04 +02:00
Toni Förster
35534bcbb1
Improved thumbnail handling
- ThumbnailsModel optionally returns the quality
- Have constants for 4:3 and 16:9 aspect ratio
- Add high quality options for thumbnails
- Rename Highest quality to Best quality
- make 4:3 thumbnails fill the VideoCell
- use .maxes instead of .maxresdefault (the latter sometimes returns white images)

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-19 16:35:29 +02:00
Toni Förster
94577332a1
chore: address linter warnings
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-18 14:46:51 +02:00
Toni Förster
56b17b0aa1
Xcode 16 - update recommended settings
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-18 10:49:50 +02:00
Toni Förster
5512337984
update dependencies
MPVKit has a new home.

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-15 02:59:39 +02:00
Arkadiusz Fal
af75afa912 Bump build number to 187 2024-07-06 13:51:40 +02:00
Arkadiusz Fal
f32bcae5eb Update CHANGELOG 2024-07-06 13:51:04 +02:00
Arkadiusz Fal
c55161b6b6 Fix typo 2024-07-06 13:44:40 +02:00
Arkadiusz Fal
3fd99f464a Revert change to OSD positions 2024-07-06 13:44:40 +02:00
Arkadiusz Fal
43c8484514 Allow import of accounts to manually added (not imported) instances 2024-07-06 13:44:40 +02:00
Arkadiusz Fal
7755b392b7 Fix seek OSD layout on tvOS
Fix #707
2024-07-06 12:51:07 +02:00
Arkadiusz Fal
e0998638b1 Fix macOS settings windows
Fix #699
Fix #710
2024-07-06 12:45:44 +02:00
Arkadiusz Fal
511a528eb6 Add import export of missing settings 2024-07-06 12:32:31 +02:00
Arkadiusz Fal
89dfbdb5c7 Fix swiftformat offenses 2024-07-06 11:48:49 +02:00
Arkadiusz Fal
f88a1d17d6 Update dependecies 2024-07-06 11:45:15 +02:00
Toni Förster
9e05909659 fix duplicated share button
fixes #691

add `--initial-audio-sync=<yes|no>` to MPV settings. Might fix #690 but needs to be toggled by the user. We leave the standard settings.

Also added links to the icons on macOS.
2024-07-06 11:35:21 +02:00
Arkadiusz Fal
b966f4509a
Merge pull request #704 from patelhiren/subscriptions-account-picker
tvOS: Allow account picker by long pressing channels button in subscriptions view
2024-07-06 11:35:15 +02:00
Arkadiusz Fal
dc7073dcb5
Merge pull request #701 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-07-06 11:28:49 +02:00
Hyunjae Chung
a258ee3be4
Translated using Weblate (Korean)
Currently translated at 11.2% (63 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ko/
2024-06-19 15:09:16 +02:00
Hiren Patel
b626f50adc - tvOS: Allow account picker by long pressing channels button in subscription view. 2024-06-16 23:21:49 -04:00
Pieter Janssens
78dc47dc24
Translated using Weblate (Dutch)
Currently translated at 80.7% (454 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nl/
2024-06-14 19:09:21 +00:00
Arkadiusz Fal
46725bf4d9 Bump build number to 186 2024-06-13 19:12:19 +02:00
Arkadiusz Fal
8697ec8faf Update packages 2024-06-13 19:12:19 +02:00
Arkadiusz Fal
8a015d29c3 Update CHANGELOG 2024-06-13 19:12:19 +02:00
Arkadiusz Fal
4097d11b5e Fix build issues 2024-06-13 19:12:19 +02:00
Arkadiusz Fal
5323d53f9e Fix swiftformat offenses 2024-06-13 19:05:09 +02:00
Arkadiusz Fal
e3e0c4a92f Remove duplicated button 2024-06-13 19:04:46 +02:00
Arkadiusz Fal
9e5efc1aa6
Merge pull request #697 from patelhiren/main
tvOS: Refined Subscriptions View
2024-06-13 19:00:15 +02:00
Arkadiusz Fal
1ed4c20c3a
Merge pull request #696 from stonerl/improved-conditional-proxying
improved conditional proxying
2024-06-13 18:59:42 +02:00
Arkadiusz Fal
ced9eb28d7
Merge pull request #695 from stonerl/more-async-work
more responsive UI when Favorites are used.
2024-06-13 18:59:30 +02:00
Arkadiusz Fal
ea49758ed2
Merge pull request #694 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-06-13 18:58:58 +02:00
Hiren Patel
65ec675859 - Improved Subscription View for tvOS
- Refined Subscriptions view on tvOS

- Refined Subscriptions view on tvOS

- Refined Subscriptions view on tvOS

- Refined Subscriptions view on tvOS
2024-05-31 19:06:15 -04:00
Hyunjae Chung
9a650799d3
Translated using Weblate (Korean)
Currently translated at 6.4% (36 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ko/
2024-05-25 08:09:13 +02:00
Ophiushi
ddd1f243f7
Translated using Weblate (French)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2024-05-25 08:09:12 +02:00
Toni Förster
94f19d55c8
more responsive UI when Favorites are used.
- The reloading of items in the favorite widgets is now done async, so it does not block the UI when opening Home.
- Tasks that check for changes of the widget and favorites are now terminated when another view e.g. Subscriptions is opened.
- We only update the Favourites when the player is not in the foreground, to avoid unresponsiveness.
2024-05-24 19:46:42 +02:00
Toni Förster
30cdaf88e1
improved conditional proxying
We don't test every single stream anymore. If we get an 200, 206 or 403 we immediately stop testing streams. Unknown streams are also skipped. This speeds up starting playback, since we don'T have to wait for the network anymore.
2024-05-24 18:44:41 +02:00
Hyunjae Chung
8139bba31e
Added translation using Weblate (Korean) 2024-05-24 07:37:13 +02:00
Arkadiusz Fal
d6cfadab9a Bump build number to 185 2024-05-23 12:11:58 +02:00
Arkadiusz Fal
5b917ef91d Update CHANGELOG 2024-05-23 11:55:31 +02:00
Arkadiusz Fal
34cb7860b3 Fix build issues 2024-05-23 11:55:31 +02:00
Arkadiusz Fal
934bd65752 Fix swiftformat offenses 2024-05-23 11:44:58 +02:00
Arkadiusz Fal
e53985534e Update packages 2024-05-23 11:44:26 +02:00
Arkadiusz Fal
03e4c6d4e6
Merge pull request #689 from stonerl/more-mpv-settings
MPV: speed up playback start
2024-05-23 11:41:56 +02:00
Arkadiusz Fal
335e99cb7b
Merge pull request #680 from stonerl/add-user-agent-to-header
Add User-Agent to request
2024-05-23 11:37:53 +02:00
Arkadiusz Fal
ae9aa6fac7
Merge branch 'main' into add-user-agent-to-header 2024-05-23 11:37:46 +02:00
Arkadiusz Fal
2f4fb9fc67
Merge pull request #684 from stonerl/better-caption-handling
Improved Captions handling
2024-05-23 11:37:03 +02:00
Arkadiusz Fal
f6bea6e045
Merge pull request #685 from stonerl/chapter-images-for-invidious
Invidious: add images to chapters
2024-05-23 11:35:51 +02:00
Arkadiusz Fal
fa712d8177
Merge pull request #688 from patelhiren/main
Fix thumbnails failing to load on tvOS
2024-05-23 11:34:19 +02:00
Arkadiusz Fal
03d24fbc42
Merge pull request #682 from stonerl/faster-chapter-extraction
faster chapter extraction
2024-05-23 11:34:07 +02:00
Arkadiusz Fal
4fd3a37705
Merge pull request #681 from stonerl/speed-up-sorting
speed up sorting for Stream
2024-05-23 11:33:51 +02:00
Arkadiusz Fal
a66857b1fb
Merge pull request #683 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-05-23 11:33:14 +02:00
Toni Förster
e44c7f84c8
MPV: speed up playback start
We initialize MPV with more options so it does not have to guess. This dramatically speeds up playback start.
2024-05-23 10:53:45 +02:00
Hiren Patel
6b5ecbdd8b - Fix thumbnails failing to load on tvOS
Thumbnails fail to load on tvOS when using SDImageAWebPCoder. Use SDImageWebPCoder on tvOS.
2024-05-21 22:58:14 -04:00
Toni Förster
15ce82a686
added en-GB to LanguageCodes 2024-05-20 21:53:48 +02:00
Toni Förster
7e3e393c65
Invidious: add images to chapters
Invidious, by design, has no images attached to chapters, in contrast to Piped.

Since the majority of videos with chapters don't have chapter-specific images and only use the videos' thumbnail, there is no difference here when compared to Piped's native thumbnail support.
2024-05-20 20:11:41 +02:00
Toni Förster
108b4de483
allow user to choose captions color 2024-05-20 17:50:47 +02:00
Toni Förster
7c9810ddf0
tvOS does not support WebKit 2024-05-20 16:03:13 +02:00
Toni Förster
96df7fdec5
let the user select caption size 2024-05-20 15:47:35 +02:00
Toni Förster
4fa5a15ad4
fallback language for captions 2024-05-20 14:42:24 +02:00
Toni Förster
c9125644ed
improvements to captions on tvOS 2024-05-20 14:20:08 +02:00
Toni Förster
4db02b2638
Improved Captions handling
New options for captions in `Settings-Player`:

- Always show captions
- Default language

User can now select whether they want to show captions automatically when the video starts, and select the language.

Captions selector now shows proper name -> `English (en)` instead of only `en`
2024-05-20 03:50:51 +02:00
Mohammed Al Otaibi
9c5f066e55
Translated using Weblate (Arabic)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-05-19 23:01:50 +02:00
joaooliva
c7908d08ae
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2024-05-19 23:01:49 +02:00
Toni Förster
c9fb41c8e8
faster chapter extraction
The extraction of chapters is now faster since it is run in parallel for each pattern. Also a new pattern hast been added: "(start) title"
2024-05-19 17:43:35 +02:00
Toni Förster
2e9cceafa5
Add User-Agent to request
We generate a User-Agent for the platform we are running on and add it to the URLTester requests and also to MPV and AVPlayer
2024-05-19 13:46:14 +02:00
Toni Förster
fa09b2021c
speed up sorting for Stream
This should help to start playback a bit faster.
2024-05-19 12:39:47 +02:00
Arkadiusz Fal
90777d91f6 Bump build number to 184 2024-05-19 11:52:38 +02:00
Arkadiusz Fal
6959778775 Update CHANGELOG 2024-05-19 11:52:38 +02:00
Arkadiusz Fal
0f43efef6f Fix build issue 2024-05-19 11:52:37 +02:00
Arkadiusz Fal
959fb0d1fc
Merge pull request #676 from stonerl/fix-pip-broken-with-mpv
fix PiP Mode Not Working Using MPV
2024-05-19 09:51:38 +02:00
Arkadiusz Fal
81be57904b
Merge pull request #679 from stonerl/mpv-settings
Advanced Settings: cache-pause-initial
2024-05-19 09:51:26 +02:00
Arkadiusz Fal
a42345896d
Merge pull request #677 from stonerl/quality-reorderdering-iOS16
changed description for Format reordering
2024-05-19 09:51:10 +02:00
Toni Förster
43fc9e20c0
Advanced Settings: cache-pause-initial
`cache-pause-initial` status can now be selected by the user.

Also, on macOS and iOS, a link next to the option leads the user to the info on the mpv website.
2024-05-19 03:50:33 +02:00
Toni Förster
1a1bd1ba5b
fix PiP Mode Not Working Using MPV
fixes #674

I accidentally broke PiP when using MPV. While testing this, I noticed that PiP sometimes does not start, so I tried to make MPV to PiP a bit more robust.
2024-05-19 00:58:52 +02:00
Toni Förster
99aca8e23c
changed description for Format reordering
reordering Formats only works on iOS 16 and newer
2024-05-19 00:46:01 +02:00
Arkadiusz Fal
ddee3b74f0 Bump build number to 183 2024-05-18 11:55:59 +02:00
Arkadiusz Fal
b271aed52b Update CHANGELOG 2024-05-18 11:55:32 +02:00
Arkadiusz Fal
1c608c78a1 Update packages 2024-05-18 11:49:32 +02:00
Arkadiusz Fal
0ec227ba80 Fix application groups 2024-05-18 11:48:32 +02:00
Arkadiusz Fal
2a93ff52a3
Merge pull request #673 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-05-18 11:42:26 +02:00
Arkadiusz Fal
896d46d0cf
Merge pull request #662 from stonerl/piped-proxy
Conditional proxying
2024-05-18 11:42:11 +02:00
mere
ad79180530
Translated using Weblate (Romanian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2024-05-18 11:42:04 +02:00
gallegonovato
101f20c538
Translated using Weblate (Spanish)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2024-05-18 11:42:03 +02:00
Arkadiusz Fal
f28cec79ba
Merge pull request #672 from stonerl/favorites-view-tweaks
HomeView: Changes to Favourites and History Widget
2024-05-18 11:41:57 +02:00
Arkadiusz Fal
a12755ec4b
Merge pull request #671 from stonerl/snappy-ui
Snappy UI - Offloading non UI task to background threads
2024-05-18 11:41:09 +02:00
Toni Förster
38c4ddbe43
HomeView: Changes to Favourites and History Widget
The History Widget in the Home View was hard-coded to 10 items. Now it uses the limit set in the settings.

The items weren't immediate updated when the limit was changed.

List Views had a hard-coded limit of 10 items. Now they use the limit supplied in the parameter.
2024-05-18 00:36:40 +02:00
Toni Förster
e35f8b7892
Snappy UI - Offloading non UI task to background threads
This gives a huge increase in perceived performance. The UI is now much more responsive since some tasks are run in the background and don't block the UI anymore.
2024-05-17 21:36:02 +02:00
Toni Förster
c3e4c074d6
Add HTTP Response StatusCode List and fix potential race condition 2024-05-17 19:30:24 +02:00
Toni Förster
6eba2a45c8
Conditional proxying
I added a new feature. When instances are not proxied, Yattee first checks the URL to make sure it is not a restricted video. Usually, music videos and sports content can only be played back by the same IP address that requested the URL in the first place. That is why some videos do not play when the proxy is disabled.

This approach has multiple advantages. First and foremost, It reduced the load on Invidious/Piped instances, since users can now directly access the videos without going through the instance, which might be severely bandwidth limited. Secondly, users don't need to manually turn on the proxy when they want to watch IP address bound content, since Yattee automatically proxies such content.

Furthermore, adding the proxy option allows mitigating some severe playback issues with invidious instances. Invidious by default returns proxied URLs for videos, and due to some bug in the Invidious proxy, scrubbing or continuing playback at a random timestamp can lead to severe wait times for the users.

This should fix numerous playback issues: #666, #626, #590, #585, #498, #457, #400
2024-05-16 19:35:31 +02:00
Arkadiusz Fal
1fe8a32fb8 Bump build number to 182 2024-05-16 19:09:12 +02:00
Arkadiusz Fal
5a0c1bbae3 Update CHANGELOG 2024-05-16 19:07:50 +02:00
Arkadiusz Fal
4ce9dc6729 Fix build issues 2024-05-16 19:05:00 +02:00
Arkadiusz Fal
b783db30b6 Update dependencies 2024-05-16 19:01:02 +02:00
Arkadiusz Fal
7741e531f4 Swiftformat fixes 2024-05-16 19:01:02 +02:00
Arkadiusz Fal
4b21cd48e3
Merge pull request #669 from yattee/revert-652-seek-gesture
Revert "small delay before vertical scrubbing is possible"
2024-05-16 19:00:30 +02:00
Arkadiusz Fal
d67e375f16
Revert "small delay before vertical scrubbing is possible" 2024-05-16 18:59:54 +02:00
Arkadiusz Fal
db2417e455
Merge pull request #627 from 0x000C/bugfix/619
Fix #619: Remove ports from shared YouTube links
2024-05-16 18:25:35 +02:00
Arkadiusz Fal
7f8aa51c78
Update Model/Applications/VideosAPI.swift 2024-05-16 18:25:15 +02:00
Arkadiusz Fal
4038f7fdb9
Update Model/Applications/VideosAPI.swift 2024-05-16 18:25:08 +02:00
Arkadiusz Fal
8a7e4c84b5
Merge pull request #653 from stonerl/mpvkit-0-38-0
upgrade to mpvkit-0.38.0
2024-05-16 18:24:41 +02:00
Arkadiusz Fal
eb491a890d
Merge pull request #654 from stonerl/upgrade-dependencies
upgraded some dependencies
2024-05-16 18:23:31 +02:00
Arkadiusz Fal
c6724472a6
Merge pull request #667 from stonerl/hls-set-target-quality
HLS: set target bitrate / AVPlayer: higher resolution
2024-05-16 18:23:05 +02:00
Arkadiusz Fal
201de81351
Merge pull request #650 from stonerl/rework-qualitiy-settings
Rework qualitiy settings
2024-05-16 18:22:58 +02:00
Arkadiusz Fal
ae12eefafc
Merge pull request #665 from stonerl/chapter-pictures
allow user to disable thumbnails and jump to current chapter in horizontal view
2024-05-16 18:22:19 +02:00
Arkadiusz Fal
46f89db11a
Merge pull request #644 from stonerl/music-mode
MusicMode: don't bindPlayerToLayer when entering foreground
2024-05-16 18:21:35 +02:00
Arkadiusz Fal
c9d20d28de
Merge pull request #645 from timonus/make-url-scheme-work
Handle deep links.
2024-05-16 18:21:15 +02:00
Arkadiusz Fal
094461d359
Merge pull request #641 from stonerl/switch-to-previous-backend
switch to previous backend when leaving PiP
2024-05-16 18:20:49 +02:00
Arkadiusz Fal
b9649b6356
Merge pull request #651 from stonerl/time-reset-on-stream-change
preserve time on stream change
2024-05-16 18:20:19 +02:00
Arkadiusz Fal
3d8feda808
Merge pull request #661 from stonerl/number-fields
Advanced settings: make number fields .numPad
2024-05-16 18:19:59 +02:00
Arkadiusz Fal
d9aa5105fa
Merge pull request #636 from stonerl/captions
fix handling and displaying captions
2024-05-16 18:18:49 +02:00
Arkadiusz Fal
42110f32da
Merge pull request #664 from stonerl/call-correct-class
call correct class of  SDImageAWebPCoder
2024-05-16 18:17:47 +02:00
Arkadiusz Fal
a6c5c3905a
Merge pull request #648 from stonerl/sponsorblock-jump-to-end
SponsorBlock jump to end instead of pausing
2024-05-16 18:17:32 +02:00
Arkadiusz Fal
7b484e80b8
Merge pull request #646 from stonerl/EOF-start-playback-again
Restart finished video
2024-05-16 18:17:13 +02:00
Arkadiusz Fal
68f3d5c631
Merge pull request #655 from stonerl/chapter-title-on-jump
Chapter title on jump
2024-05-16 18:16:24 +02:00
Arkadiusz Fal
9d291cca28
Merge pull request #639 from stonerl/sponsor-block
SponsorBlock Improvements
2024-05-16 18:15:38 +02:00
Arkadiusz Fal
7c108f18ff
Merge pull request #652 from stonerl/seek-gesture
small delay before vertical scrubbing is possible
2024-05-16 18:14:13 +02:00
Arkadiusz Fal
1a3012853d
Merge pull request #642 from stonerl/queue-header
only show Queue header in sidebar view
2024-05-16 18:13:53 +02:00
Arkadiusz Fal
5b64290bc5
Merge pull request #640 from stonerl/audio-session-interrupts
handle audio session interrupts by other media
2024-05-16 18:13:36 +02:00
Arkadiusz Fal
34989a4f0f
Merge pull request #638 from stonerl/log-streaming
XCode enable IDEPreferLogStreaming
2024-05-16 18:13:22 +02:00
Arkadiusz Fal
4ab60080f6
Merge pull request #635 from stonerl/related-videos
don't show related in sidebar when disabled in settings
2024-05-16 18:13:05 +02:00
Arkadiusz Fal
a3a198a32d
Merge pull request #637 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-05-16 18:12:36 +02:00
Toni Förster
b54044cbc5
HLS: set target bitrate / AVPlayer: higher resolution
HLS: try matching the set resolution. This works okay with AVPlayer. With MPV it is hit and miss, most of the time MPV targets the highest available bitrate, instead of the set bitrate.

AVPlayer now supports higher resolution up to 1080p60.
2024-05-13 07:54:24 +02:00
Toni Förster
ebc48fde90
jump to current chapter in horizontal view 2024-05-11 23:49:46 +02:00
Toni Förster
7c50db426f
Chapters: allow user to disable thumbnails
Chapters have their own section is the settings. The users can decide if chapter thumbnails should be shown or not. Also they can decide to only show thumbnails if they are not the same.

The VideoDetailsView had to be modified, to avoid compiler type-checking errors.
2024-05-11 22:12:10 +02:00
Toni Förster
a1bde07ee1
don't draw chapter mark if start is 0 2024-05-11 17:51:35 +02:00
Toni Förster
fba01e35a3
apply best resolution from non HLS to HLS stream
This makes sure that HLS Streams can be compared with non HLS streams, otherwise HLS would have been the first selected since the maxResolution.value might have been higher then what could actually be the highest resolution served.
2024-05-11 14:08:40 +02:00
Toni Förster
16bf83274f
call correct class of SDImageAWebPCoder
We added a non-existing class and this caused a lot of severe hangs.
2024-05-10 15:38:19 +02:00
Toni Förster
d8c8f8084b
fix a crash when format is hls 2024-05-09 21:15:14 +02:00
Toni Förster
2590f041c2
Advanced settings: make number fields .numPad 2024-05-09 15:02:31 +02:00
Toni Förster
790cb5ce1d
upgraded some dependencies 2024-05-03 17:15:40 +02:00
Toni Förster
7b7f877fa5
upgrade to mpvkit-0.38.0
subtitles are working gain (was broken with 0.37.0)
2024-05-03 17:08:30 +02:00
Toni Förster
1d86154012
make heading equal size to related heading 2024-05-03 16:51:12 +02:00
Toni Förster
03fbb4933a
new SeekType chapterSkip
When a user taps on a chapter, the pop now also shows the name of the chapter. The chapters are now marked in AppRedColor instead of orange.

This is based on PR #639

That one needs to be merged first before this one can go in.
2024-05-03 15:20:51 +02:00
Kaambiz
bb1d3cd273
Translated using Weblate (Persian)
Currently translated at 74.7% (420 of 562 strings)

Co-authored-by: Kaambiz <kambizx@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fa/
Translation: Yattee/Localizable.strings
2024-05-02 15:07:40 +02:00
José Alves Amaro
fa978dc6d0
Translated using Weblate (Portuguese)
Currently translated at 96.2% (541 of 562 strings)

Co-authored-by: José Alves Amaro <Nykold@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt/
Translation: Yattee/Localizable.strings
2024-05-02 15:07:39 +02:00
Toni Förster
3da081b40c
small delay before vertical scrubbing is possible
This avoids accidentally scrubbing. The screen now needs to be touched at least 250 ms before time scrubbing is possible.

should fix #393
2024-05-01 19:11:42 +02:00
Toni Förster
0d5a907517
preserve time on stream change
fixes #371
2024-05-01 17:01:54 +02:00
Toni Förster
ef7a486fd4
add migration for old profiles to new format 2024-05-01 14:30:19 +02:00
Toni Förster
54915dcea1
rework quality settings
- The order of the formats can now be changed in the Quality Settings.
- sortingOrder is now part of QualitiyProfile.
- bestPlayable is now part of PlayerBackend.
- hls and stream aren't treated as unknown anymore.
2024-04-29 20:03:51 +02:00
Toni Förster
86b91916a7
Restart finished video
After the video has ended, hitting play restarts the video from the beginning

fixes #449

If I understand the issue correctly, however, videos can now be played again without hitting the "Start from the beginning" button.
2024-04-27 00:37:04 +02:00
Toni Förster
4144a29608
only bind Player if the active backend is AVPlayer 2024-04-26 19:42:13 +02:00
Toni Förster
c41b635276
MusicMode: don't bindPlayerToLayer when entering foreground
fixes #617
2024-04-26 14:48:07 +02:00
Toni Förster
c118c77c14
SponsorBlock jump to end instead of pausing
When the time stamp for the last segment is greater the duration currently the video pauses. This interferes with PR #646.

Now instead of pausing we jump to the duration (end time) of the video instead.
2024-04-26 14:03:13 +02:00
Tim Johnsen
626652c421
Update iOS/AppDelegate.swift 2024-04-25 10:18:10 -07:00
Tim Johnsen
fb42b80276
Handle deep links. 2024-04-25 10:01:44 -07:00
Toni Förster
00baf60970
only show Queue header in sidebar view 2024-04-25 13:15:55 +02:00
Toni Förster
0230106a1e
switch to previous backend when leaving PiP
Currently, when leaving PiP the backend doesn't switch to the one that was used before starting PiP.

Now, when the backend was MPV, it switches back to it after leaving PiP.
2024-04-24 21:32:32 +02:00
Toni Förster
169b9451ed
handle audio session interrupts by other media
fixes #495
2024-04-24 14:43:51 +02:00
Toni Förster
ae65acdd16
Interface tweaks for SponsorBlock during playback
Show/Hide categories in timeline.
Show/Hide notice after skip.
Show adjusted or actual total time.
2024-04-23 22:08:08 +02:00
Toni Förster
b5ac760af2
SponsorBlock set colors for each category
Default colors are defined in alignment to upstream. These can be changed by the user.
The colors are also used in the Timeline and the seek view.
2024-04-23 17:16:31 +02:00
Toni Förster
321eaecd21
SponsorBlock align categories with upstream 2024-04-23 11:43:05 +02:00
Toni Förster
0e7d66849d
whitespace fixes 2024-04-23 11:39:32 +02:00
Toni Förster
25208c2b5c
XCode enable IDEPreferLogStreaming
helps when debugging over WiFi
2024-04-23 10:11:26 +02:00
Toni Förster
f3637e2426
fix handling and displaying captions
fixes #490

It also fixes the caption picker being empty when a caption was selected in the previous watched video.
2024-04-21 01:04:31 +02:00
Toni Förster
dd6106447f
don't show related in sidebar when disabled in settings
fixes #634
2024-04-20 13:35:36 +02:00
Arkadiusz Fal
d1cf45c6a1 Bump build number to 181 2024-04-02 23:14:51 +02:00
Arkadiusz Fal
07f3d841b3 Update CHANGELOG 2024-04-02 23:14:18 +02:00
Arkadiusz Fal
b488f86160 Downgrade MPVKit to 0.36.0-1
commit dca1e345a26d09a3d621d7656a94e6427f3f7b83
2024-04-02 23:14:18 +02:00
Arkadiusz Fal
e64c3a3c77 Update packages 2024-04-02 23:14:17 +02:00
Arkadiusz Fal
576a993faf
Merge pull request #631 from stonerl/comments-piped
hopefully fixes #629
2024-04-02 22:51:07 +02:00
Toni Förster
c77c5a6d21
don't add empty comments 2024-04-02 15:08:36 +02:00
Toni Förster
ae16680fc2
removed some unnecessary print() 2024-04-02 14:28:06 +02:00
Toni Förster
807c0a1e2e
hopefully fixes #629
should also get rid of empty comments if they couldn't be loaded
2024-04-02 14:28:06 +02:00
Arkadiusz Fal
96a2119a05
Merge pull request #632 from stonerl/invidious-html-comments
iv: use html comments instead of plain text
2024-04-01 23:12:12 +02:00
Arkadiusz Fal
7e940d6304
Merge pull request #624 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-04-01 22:59:05 +02:00
mere
11402cc2a6
Translated using Weblate (Romanian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2024-04-01 22:58:48 +02:00
floe nele
975d8b0ba0
Translated using Weblate (Dutch)
Currently translated at 44.3% (249 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nl/
2024-04-01 22:58:48 +02:00
jonnysemon
e349898d9e
Translated using Weblate (Arabic)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-04-01 22:58:48 +02:00
rexcsk
a8802da5a7
Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/zh_Hant/
2024-04-01 22:58:48 +02:00
maboroshin
19993dfc04
Translated using Weblate (Japanese)
Currently translated at 98.3% (553 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2024-04-01 22:58:48 +02:00
joaooliva
e99dd442e1
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2024-04-01 22:58:48 +02:00
gallegonovato
d886113f27
Translated using Weblate (Spanish)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2024-04-01 22:58:48 +02:00
Ophiushi
ea9b759887
Translated using Weblate (French)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2024-04-01 22:58:48 +02:00
rexcsk
0e784be231
Translated using Weblate (Chinese (Traditional))
Currently translated at 99.6% (560 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/zh_Hant/
2024-04-01 22:58:48 +02:00
rexcsk
d0ab73eeb2
Translated using Weblate (English)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2024-04-01 22:58:48 +02:00
Arkadiusz Fal
2193129818
Merge pull request #622 from rickykresslein/limit-recents-shown
Show/hide recents and limit number of recents shown
2024-04-01 22:58:42 +02:00
Toni Förster
f84c6d319a
iv: use html comments instead of plain text
It now correctly displays emojis hyphens
2024-04-01 15:08:08 +02:00
0x000C
1f667818db Change shareURL() in VideosAPI and callers to use URLs instead of hostnames 2024-03-20 20:21:26 -07:00
0x000C
784893048d Merge branch 'bugfix/619' of https://github.com/0x000C/yattee into bugfix/619 2024-03-18 22:06:23 -07:00
0x000C
6ec516dc3d fix: Remove ports from shared YouTube links
Fix #619: Remove ports from shared YouTube links
2024-03-18 22:01:34 -07:00
0x000C
1c7da30caf fix: Remove ports from shared YouTube links 2024-03-18 21:58:16 -07:00
Ricky Kresslein
87337f31a5 Updated importer and exporter to include new defaults 2024-02-28 18:35:03 +01:00
Arkadiusz Fal
cf5262a86e Bump build number to 180 2024-02-28 14:35:54 +01:00
Arkadiusz Fal
d6be0ffa5b Update CHANGELOG 2024-02-28 14:35:54 +01:00
Arkadiusz Fal
1df8241a01 Update packages 2024-02-28 14:35:54 +01:00
Arkadiusz Fal
43e5eae658 Use latest stable Xcode for build 2024-02-28 13:50:03 +01:00
Arkadiusz Fal
71b4560ff8
Merge pull request #623 from rickykresslein/help-text
Add help text to all header buttons
2024-02-28 13:38:33 +01:00
Arkadiusz Fal
f6bb2fe5d1
Merge pull request #621 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-02-28 13:38:00 +01:00
rexcsk
272aafe504
Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/zh_Hant/
2024-02-28 13:36:29 +01:00
rexcsk
580d782c56
Translated using Weblate (Chinese (Simplified))
Currently translated at 93.9% (528 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/zh_Hans/
2024-02-28 13:36:25 +01:00
Ricky Kresslein
238ddc7ad9 Add help text to all header buttons 2024-02-28 01:03:33 +01:00
Ricky Kresslein
5559e78bc0 Add setting to show/hide recents and limit number of recents shown 2024-02-28 00:56:12 +01:00
rexcsk
6cc38df4e9
Added translation using Weblate (Chinese (Traditional)) 2024-02-27 15:30:55 +01:00
Arkadiusz Fal
7b34c7e72b Bump build number to 179 2024-02-21 10:17:38 +01:00
Arkadiusz Fal
0dd7943849 Update CHANGELOG 2024-02-21 10:17:22 +01:00
Arkadiusz Fal
6745934a78 Update packages 2024-02-21 10:16:05 +01:00
Arkadiusz Fal
76801a34ee
Merge pull request #616 from rickykresslein/main
Add skip, play/pause, and fullscreen shortcuts to macOS player
2024-02-21 10:11:57 +01:00
Arkadiusz Fal
4d0318d4b0
Merge pull request #612 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-02-21 10:11:19 +01:00
Ricky Kresslein
9d4446a6ef Add skip, play/pause, and fullscreen shortcuts to macOS player 2024-02-17 10:40:27 +01:00
mere
b74017894c
Translated using Weblate (Romanian)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2024-02-09 22:02:08 +01:00
Mohammed Al Otaibi
9fef6c0276
Translated using Weblate (Arabic)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-02-08 10:01:56 +01:00
gallegonovato
fcbeb45d1e
Translated using Weblate (Spanish)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2024-02-08 10:01:55 +01:00
maboroshin
66f7286cdc
Translated using Weblate (Japanese)
Currently translated at 98.5% (554 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2024-02-06 11:01:58 +01:00
jonnysemon
e1e068ba11
Translated using Weblate (Arabic)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-02-06 11:01:58 +01:00
Ophiushi
524c99dd54
Translated using Weblate (French)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2024-02-06 11:01:58 +01:00
joaooliva
b57ed7055c
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2024-02-04 22:01:56 +01:00
Arkadiusz Fal
d84d701b07
Translated using Weblate (Polish)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2024-02-04 22:01:56 +01:00
Arkadiusz Fal
bcfd4126b6
Translated using Weblate (English)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2024-02-04 22:01:56 +01:00
Arkadiusz Fal
97b16cfd04 Fix Package.resolved 2024-02-03 22:02:14 +01:00
Arkadiusz Fal
d5b81ceba1 Use macOS 13 in release workflow 2024-02-03 22:00:00 +01:00
Arkadiusz Fal
f3ba61a168 Bump build number to 178 2024-02-03 21:55:29 +01:00
Arkadiusz Fal
c68aa1d30c Update CHANGELOG 2024-02-03 21:55:17 +01:00
Arkadiusz Fal
d187fc322c Update packages 2024-02-03 21:55:09 +01:00
Arkadiusz Fal
e616022278 Use Xcode 14.3.1 for fastlane builds 2024-02-03 21:49:45 +01:00
Arkadiusz Fal
1b0486df05 Localizations improvements 2024-02-03 21:49:45 +01:00
Arkadiusz Fal
e6deb9ef26 Add import on tvOS, other export/import improvements 2024-02-03 21:49:45 +01:00
Arkadiusz Fal
0216c17b95
Merge pull request #610 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-02-03 21:47:22 +01:00
Arkadiusz Fal
1eff757caf
Translated using Weblate (Polish)
Currently translated at 100.0% (561 of 561 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2024-02-03 21:47:09 +01:00
Arkadiusz Fal
4cfd00b307
Translated using Weblate (English)
Currently translated at 100.0% (561 of 561 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2024-02-03 21:47:09 +01:00
Arkadiusz Fal
8075db3ac8
Merge pull request #609 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-02-03 21:30:20 +01:00
Arkadiusz Fal
2cd867e344
Translated using Weblate (Polish)
Currently translated at 100.0% (560 of 560 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2024-02-03 21:30:07 +01:00
Arkadiusz Fal
b5b2e7f13d
Translated using Weblate (English)
Currently translated at 100.0% (560 of 560 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2024-02-03 21:30:07 +01:00
Arkadiusz Fal
cbd7c417d2
Merge pull request #608 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-02-03 21:21:11 +01:00
Arkadiusz Fal
ed7a233c9b
Translated using Weblate (Polish)
Currently translated at 100.0% (554 of 554 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2024-02-03 21:20:49 +01:00
Arkadiusz Fal
d75e3e9a61
Translated using Weblate (English)
Currently translated at 100.0% (554 of 554 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2024-02-03 21:20:49 +01:00
Arkadiusz Fal
8b0c9d3d0a Fix syntax 2024-02-02 11:48:03 +01:00
Arkadiusz Fal
371471ad81 Fix syntax 2024-02-02 11:38:01 +01:00
Arkadiusz Fal
d5464186af Use old Previews syntax 2024-02-02 11:31:34 +01:00
Arkadiusz Fal
f4c310846a Update CHANGELOG 2024-02-02 11:16:27 +01:00
Arkadiusz Fal
2413526d70 Style fixes 2024-02-02 11:16:27 +01:00
Arkadiusz Fal
55f4a4a2a1 Fix possible crash 2024-02-02 11:16:27 +01:00
Arkadiusz Fal
5b35c03bc5 Add MPV deinterlace filter setting
Fix #601
2024-02-02 11:16:27 +01:00
Arkadiusz Fal
93ea943c54 Add action buttons label setting 2024-02-02 11:16:27 +01:00
Arkadiusz Fal
5ae6f321cd Update dependencies 2024-02-02 11:16:27 +01:00
Arkadiusz Fal
2be6f04e37 Import export settings 2024-02-02 11:16:27 +01:00
Arkadiusz Fal
9826ee4d36 Remove old references 2024-02-02 11:06:32 +01:00
Arkadiusz Fal
39a109216b Style fix 2024-02-02 11:06:32 +01:00
Arkadiusz Fal
05b25b65bc Use mpvkit 0.37.0 2024-02-02 11:06:32 +01:00
Mohammed Al Otaibi
195db01602 Translated using Weblate (Arabic)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-02-02 11:06:32 +01:00
maboroshin
292af65ea5 Translated using Weblate (Japanese)
Currently translated at 99.6% (535 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2024-02-02 11:06:32 +01:00
Mohammed Al Otaibi
5ee46fe87a Translated using Weblate (Arabic)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-02-02 11:06:32 +01:00
Arkadiusz Fal
179b4358ae Bump build number to 177 2024-02-02 11:06:32 +01:00
Arkadiusz Fal
5be8a663e0 Update CHANGELOG 2024-02-02 11:06:32 +01:00
Arkadiusz Fal
1d81f710a9 Bump build number to 176 2024-02-02 11:06:32 +01:00
Arkadiusz Fal
49e051c70d Use mpvkit 0.36.0 2024-02-02 11:06:31 +01:00
Arkadiusz Fal
1efaed4541 Use fastlane fork with tvos certs fix 2024-02-02 11:06:31 +01:00
Arkadiusz Fal
3e96001511 Bump build number to 175 2024-02-02 11:06:31 +01:00
Arkadiusz Fal
6e8fb4a6db Update CHANGELOG 2024-02-02 11:06:31 +01:00
Arkadiusz Fal
446ee0ac8e Update dependencies 2024-02-02 11:06:31 +01:00
Arkadiusz Fal
f6a89c7daf Bump version number to 1.5.2 2024-02-02 11:06:31 +01:00
Karel van Klink
ad5dc8a871
Translated using Weblate (Dutch)
Currently translated at 41.8% (225 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nl/
2024-01-20 14:02:05 +01:00
Mohammed Al Otaibi
afaeb754ca
Translated using Weblate (Arabic)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-01-19 07:00:27 +01:00
OneiMoment
21fd92aea4
Translated using Weblate (Arabic)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-01-18 06:13:06 +01:00
jonnysemon
02e5749fc9
Translated using Weblate (Arabic)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-01-18 06:13:06 +01:00
OneiMoment
d44f80bd53
Translated using Weblate (Arabic)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-01-15 13:06:27 +00:00
cdguy
5c87916785
Translated using Weblate (Turkish)
Currently translated at 62.3% (335 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2024-01-15 13:06:26 +00:00
Arkadiusz Fal
043443fb89
Merge pull request #600 from yattee/chore/update-packages
Chore/update packages
2024-01-14 13:14:10 +01:00
Arkadiusz Fal
1bc44afde6 Update dependencies 2024-01-14 12:56:06 +01:00
Arkadiusz Fal
d80101d779 Update CHANGELOG 2024-01-14 12:53:52 +01:00
Arkadiusz Fal
39925c390a Bump build number to 174 2024-01-14 12:53:52 +01:00
Arkadiusz Fal
9c51f24d3f Bump build number to 173 2024-01-13 10:29:48 +01:00
Arkadiusz Fal
c231546b5c Revert "Update packages"
This reverts commit 4d953b0871a253715adcc8d31ed0b48fc8cce1dd.

Dependecies issue:

[09:16:19]: ▸ local binary target 'Libshaderc_combined' does not contain expected binary artifact named 'Libshaderc_combined'local binary target 'Libplacebo' does not contain expected binary artifact named 'Libplacebo'2024-01-13 09:16:19.223 xcodebuild[5250:23030] Writing error result bundle to /var/folders/mm/pltwc2yj1jx192t6dzy9zsrr0000gn/T/ResultBundle_2024-13-01_09-16-0019.xcresult
[09:16:20]: ▸ fatalErrorxcodebuild: error: Could not resolve package dependencies:
[09:16:20]: ▸   local binary target 'Libshaderc_combined' does not contain expected binary artifact named 'Libshaderc_combined'
[09:16:20]: ▸   local binary target 'Libplacebo' does not contain expected binary artifact named 'Libplacebo'
[09:16:20]: ▸   fatalError
2024-01-13 10:17:45 +01:00
Arkadiusz Fal
58c43acb2b Update CHANGELOG 2024-01-13 10:12:14 +01:00
Arkadiusz Fal
4d953b0871 Update packages 2024-01-13 10:12:14 +01:00
Arkadiusz Fal
101fee9a37 Revert "Enable external distribution"
This reverts commit 51e5aeec13539e93b0972e5a24b501b36ee06ece.
2024-01-13 10:12:14 +01:00
Arkadiusz Fal
a44bbe4cec
Merge pull request #599 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-01-13 10:11:58 +01:00
OneiMoment
3f0eec3c54
Translated using Weblate (Arabic)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-01-13 00:06:25 +01:00
mrtag-oss
ea0f52ebe0
Translated using Weblate (Russian)
Currently translated at 99.6% (535 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2024-01-11 19:06:37 +01:00
OneiMoment
a82bdd2a00
Translated using Weblate (Arabic)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2024-01-11 19:06:37 +01:00
Arkadiusz Fal
b2b8565635 Update fastlane only 2024-01-09 18:14:58 +01:00
Arkadiusz Fal
a061c1c040 Revert "Update packages"
This reverts commit 06aafc17197f14adfa6f9782bf256643d330fab8.
2024-01-09 18:14:24 +01:00
Arkadiusz Fal
0182faceae Bump build number to 172 2024-01-09 17:03:07 +01:00
Arkadiusz Fal
3b4e594fcf Update CHANGELOG 2024-01-09 17:02:55 +01:00
Arkadiusz Fal
51e5aeec13 Enable external distribution 2024-01-09 17:02:55 +01:00
Arkadiusz Fal
06aafc1719 Update packages 2024-01-09 17:02:54 +01:00
Arkadiusz Fal
f468fa6340
Merge pull request #587 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-01-09 16:55:41 +01:00
M K
eb85e3b731
Translated using Weblate (Dutch)
Currently translated at 10.2% (55 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nl/
2024-01-09 16:55:22 +01:00
M K
935f5cc75e
Added translation using Weblate (Dutch) 2024-01-09 16:55:22 +01:00
ssantos
bdbd23d66b
Translated using Weblate (Portuguese)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt/
2024-01-09 16:55:22 +01:00
maboroshin
e4796b08b6
Translated using Weblate (Japanese)
Currently translated at 99.2% (533 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2024-01-09 16:55:22 +01:00
Ophiushi
19bc9197ec
Translated using Weblate (French)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2024-01-09 16:55:22 +01:00
mere
c513753f59
Translated using Weblate (Romanian)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2024-01-09 16:55:22 +01:00
joaooliva
81b5ae1fa9
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2024-01-09 16:55:22 +01:00
gallegonovato
eddab1980a
Translated using Weblate (Spanish)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2024-01-09 16:55:22 +01:00
Arkadiusz Fal
7ff52294a8
Merge pull request #597 from stonerl/regex-for-chapters
more robust regex for chapters from description
2024-01-09 16:55:16 +01:00
Arkadiusz Fal
5ae18a5170
Merge pull request #596 from stonerl/update-defaults
Update defaults to 7.3.1
2024-01-09 16:55:07 +01:00
Toni Förster
ecba91f35d
more robust regex for chapters from description
This should reduce the number of falsely matched chapters, e.g., when time-code-like numbers appear in the middle of the text, like 16:9 or sports results.

It also checks for chapters that have an end time and omits the end time code from the title.

Track lists in music videos are now also properly displayed as chapters.
2024-01-01 16:04:40 +01:00
Toni Förster
36ecf63b6c
defaults move from .observe() to .updates()
https://github.com/sindresorhus/Defaults/releases/tag/v7.0.0
2023-12-27 17:27:24 +01:00
Toni Förster
0671b6ef9f
update Defaults package to 7.3.1 2023-12-27 17:00:18 +01:00
Arkadiusz Fal
39f6319043 Bump build number to 171 2023-12-09 22:08:21 +01:00
Arkadiusz Fal
6a2dc9164e Update CHANGELOG 2023-12-09 22:08:20 +01:00
Arkadiusz Fal
37d3f43596 Add channel view help labels 2023-12-09 22:08:20 +01:00
Arkadiusz Fal
aef0ba6ffd Fix displaying account username 2023-12-09 22:08:20 +01:00
Arkadiusz Fal
1ae12cfa21 Add localizations for fa, es, tr, ru 2023-12-09 22:08:20 +01:00
Arkadiusz Fal
8475669aab Update packages 2023-12-09 22:08:20 +01:00
Arkadiusz Fal
6fec76fcb3
Merge pull request #584 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-12-09 22:08:13 +01:00
Arkadiusz Fal
08b2e7ceac
Translated using Weblate (Polish)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-12-09 22:08:00 +01:00
Anonymous
60972f0c7b
Translated using Weblate (English)
Currently translated at 100.0% (537 of 537 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-12-09 22:08:00 +01:00
Arkadiusz Fal
a49db76588
Merge pull request #564 from stonerl/collapsible-chapters
make chapters collapsible and highlight current chapter
2023-12-09 21:51:24 +01:00
Arkadiusz Fal
bd0c86060b
Merge pull request #583 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-12-09 21:34:14 +01:00
Arkadiusz Fal
9849f31e1f
Translated using Weblate (Polish)
Currently translated at 100.0% (535 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-12-09 21:33:58 +01:00
Arkadiusz Fal
00ac222af6
Merge pull request #568 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-12-09 21:25:54 +01:00
Vijay
732a8d7385
Translated using Weblate (Hindi)
Currently translated at 65.7% (352 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/
2023-12-09 21:03:19 +01:00
Vijay
a009ad7d53
Translated using Weblate (Hindi)
Currently translated at 63.5% (340 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/
2023-12-09 21:03:19 +01:00
Kaambiz
91c7c9fc8e
Translated using Weblate (Persian)
Currently translated at 78.1% (418 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fa/
2023-12-09 21:03:19 +01:00
Alper Zengintaş
e836f87c88
Translated using Weblate (Turkish)
Currently translated at 49.9% (267 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2023-12-09 21:03:19 +01:00
Kaambiz
1e1a23acd0
Added translation using Weblate (Persian) 2023-12-09 21:03:19 +01:00
Sloom
d8d728a48f
Translated using Weblate (Arabic (Najdi))
Currently translated at 0.3% (2 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ars/
2023-12-09 21:03:19 +01:00
maboroshin
067e8a79bf
Translated using Weblate (Japanese)
Currently translated at 99.6% (533 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-12-09 21:03:19 +01:00
joaooliva
576f40360d
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (535 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-12-09 21:03:19 +01:00
gallegonovato
c679a52903
Translated using Weblate (Spanish)
Currently translated at 100.0% (535 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-12-09 21:03:19 +01:00
Alper Zengintaş
82f109290d
Translated using Weblate (Turkish)
Currently translated at 46.7% (250 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2023-12-09 21:03:19 +01:00
Ophiushi
765006b185
Translated using Weblate (French)
Currently translated at 100.0% (535 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-12-09 21:03:19 +01:00
Anonymous
86cddb06e4
Translated using Weblate (English)
Currently translated at 100.0% (535 of 535 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-12-09 21:03:19 +01:00
Sloom
012b5156b7
Added translation using Weblate (Arabic (Najdi)) 2023-12-09 21:03:19 +01:00
Arkadiusz Fal
d6c04540e9
Merge pull request #569 from stonerl/no-180-rotation
disable portraitUpsideDown on iPhones
2023-12-09 21:03:14 +01:00
Arkadiusz Fal
3144b52b55
Merge pull request #578 from stonerl/description-fix
macOS: disable hit testing when not expanded
2023-12-09 21:01:30 +01:00
Toni Förster
b04385ceae
macOS: disable hit testing when not expanded
fixes overspilling of text when clicking on collapsed descriptions
2023-12-05 00:36:12 +01:00
Toni Förster
721a97dc41
no need for NotificationCenter 2023-12-05 00:07:36 +01:00
Toni Förster
4ca8adc4dd
move onReceive to the chapter header in VideoDetails 2023-12-04 23:00:20 +01:00
Toni Förster
d361ef01d4
move updating the time to PlayerModel
this makes the chapter view much much smoother
2023-12-04 21:58:49 +01:00
Toni Förster
0d9c27319d
make time updates work for .mpv and .avp
- time update notifications work for both backends
- only init mpv timers when mpv is the active backend
- move notification extension to playerbackend
2023-12-04 14:47:26 +01:00
Toni Förster
600b8d198b
add option to expand vertical chapters by default 2023-12-04 01:04:39 +01:00
Toni Förster
d65224320e
chapters can only be clicked if expanded or horizontal 2023-12-04 00:39:29 +01:00
Toni Förster
586cea7d44
subscribe chapters to currentTime notification 2023-12-04 00:07:39 +01:00
Toni Förster
aa5d6733b2
remove superfluous vars and chaptermodel 2023-12-03 20:04:57 +01:00
Toni Förster
982dca1846
highlight current chapter when clicked on it 2023-11-29 00:31:53 +01:00
Toni Förster
7de702ad23
change color to appRed for chapter 2023-11-28 23:06:37 +01:00
Toni Förster
13ef96cd02
separate tvOS View 2023-11-28 20:05:04 +01:00
Toni Förster
4ec6f35c5d
highlight current chapter 2023-11-28 16:45:36 +01:00
Toni Förster
7f19f7aa47
disable portraitUpsideDown on iPhones 2023-11-27 18:05:17 +01:00
Toni Förster
fb5cd0f681
vertical chapters are full width and max 3 lines 2023-11-27 14:18:43 +01:00
Toni Förster
3feafc153c
remove toggle for vertical chapters 2023-11-27 13:34:18 +01:00
Toni Förster
8f08674527
vertical chapters with images are always collapsed 2023-11-27 12:13:51 +01:00
Toni Förster
a7baaeb485
iOS click on collapsed chapters expands the view 2023-11-26 21:24:52 +01:00
Toni Förster
b0d81cdefd
iOS - chapters header is a button now 2023-11-26 21:24:52 +01:00
Toni Förster
a33a1d7658
formatting 2023-11-26 21:24:51 +01:00
Toni Förster
e436dec4ba
make chapters collapsible
Chapters are now collapsible by default only the first two chapters are shown. The second will be shown opaque to indicate more chapters.
2023-11-26 21:24:50 +01:00
Arkadiusz Fal
eb1dfe69cd Bump build number to 170 2023-11-26 19:09:45 +01:00
Arkadiusz Fal
9f5720d393 Update CHANGELOG 2023-11-26 19:09:26 +01:00
Arkadiusz Fal
675db6f651 Update packages 2023-11-26 18:59:10 +01:00
Arkadiusz Fal
fb5e86c2cb
Merge pull request #559 from stonerl/collapsable-details
make description collapsible
2023-11-26 18:57:27 +01:00
Toni Förster
d384a4c520
iOS click on collapsed description text expands it 2023-11-26 15:42:47 +01:00
Toni Förster
6994271eca
iOS - the description header is a button now 2023-11-26 15:42:47 +01:00
Toni Förster
ed5fa8e4aa
no preview shows nothing now 2023-11-26 15:42:47 +01:00
Toni Förster
cbdf295d67
fix macOS layout 2023-11-26 15:42:47 +01:00
Toni Förster
5d78946320
add stepper for previewed line (broken on macOS) 2023-11-26 15:42:47 +01:00
Toni Förster
84fdc22861
formatting 2023-11-26 15:42:46 +01:00
Toni Förster
a57645f824
make description collapsible 2023-11-26 15:42:46 +01:00
Arkadiusz Fal
e78f40c555
Merge pull request #560 from stonerl/clickable-links
make links in description clickable
2023-11-26 12:28:58 +01:00
Arkadiusz Fal
23f5fc9575
Merge pull request #562 from stonerl/honor-aspect-ratio
honour the aspect ratio when resizing
2023-11-26 12:27:02 +01:00
Toni Förster
fc7a7b085f
fixed styling 2023-11-26 10:33:58 +01:00
Arkadiusz Fal
7ffa34b0f9
Merge pull request #554 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-11-25 22:26:26 +01:00
maboroshin
177e28121b
Translated using Weblate (Japanese)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-11-25 22:26:19 +01:00
gallegonovato
68a35b8804
Translated using Weblate (Spanish)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:19 +01:00
ooosssay
9ec9a680a6
Translated using Weblate (Spanish)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:19 +01:00
gallegonovato
7629931747
Translated using Weblate (Spanish)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:19 +01:00
ooosssay
afb22e6c25
Translated using Weblate (Spanish)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:19 +01:00
gallegonovato
052fc86388
Translated using Weblate (Spanish)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:19 +01:00
ooosssay
4de51f29c8
Translated using Weblate (Spanish)
Currently translated at 98.4% (525 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:19 +01:00
gallegonovato
d1a1f4da38
Translated using Weblate (Spanish)
Currently translated at 98.4% (525 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:18 +01:00
ooosssay
d1153ed97d
Translated using Weblate (Spanish)
Currently translated at 97.5% (520 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:18 +01:00
gallegonovato
1c356560d5
Translated using Weblate (Spanish)
Currently translated at 97.5% (520 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:18 +01:00
gallegonovato
bbd18d921b
Translated using Weblate (Spanish)
Currently translated at 91.5% (488 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:18 +01:00
gallegonovato
9e4860d97c
Translated using Weblate (Spanish)
Currently translated at 88.7% (473 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:18 +01:00
ooosssay
50d42f721f
Translated using Weblate (Spanish)
Currently translated at 88.7% (473 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:18 +01:00
gallegonovato
9bfccb49e3
Translated using Weblate (Spanish)
Currently translated at 86.3% (460 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:18 +01:00
Sergio Varela
919126f9b0
Translated using Weblate (Spanish)
Currently translated at 86.1% (459 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-11-25 22:26:18 +01:00
Toni Förster
d00903569f
Translated using Weblate (German)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-11-25 22:26:18 +01:00
Toni Förster
25e7b0d3e1
Translated using Weblate (German)
Currently translated at 99.8% (532 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-11-25 22:26:18 +01:00
Arkadiusz Fal
2aafe33417
Merge pull request #565 from stonerl/private-instances
support for private instances
2023-11-25 22:26:13 +01:00
Toni Förster
e525f36824
formatting 2023-11-22 10:24:41 +01:00
Toni Förster
5a5f5a8696
code formatting 2023-11-22 10:21:21 +01:00
Toni Förster
45d2968d9e
support for private instances
closes #552

and some formatting
2023-11-21 18:14:17 +01:00
Toni Förster
c3e1465f31
fix broken fullscreen on macOS 2023-11-21 17:58:44 +01:00
Toni Förster
df47ffb013
change default minWidth with hidden sidebar 2023-11-21 17:49:29 +01:00
Toni Förster
8900f96ce7
honour the aspect ratio when resizing
The `defaultMinimumHeightLeft? has been adjusted to make the radio of the video view 16:9 in its initial state. Also on macOS when resizing the window, the aspect ratio of the view now correlates with the video.
2023-11-21 13:47:51 +01:00
Toni Förster
ed69780d52
removed superfluous braces 2023-11-21 01:13:01 +01:00
Toni Förster
691a3305e7
make links in description clickable
Adds clickable links to macOS. I it is a naive approach, but it works.

Probably closes #51
2023-11-21 00:58:40 +01:00
Arkadiusz Fal
987f6dcac8 Bump build number to 169 2023-11-04 11:34:45 +01:00
Arkadiusz Fal
1113a94d67 Update CHANGELOG 2023-11-04 11:34:25 +01:00
Arkadiusz Fal
8c6fc7d561 Update dependencies 2023-11-04 11:33:01 +01:00
Arkadiusz Fal
fb84927fc8 Bump build number to 168 2023-10-24 09:17:22 +02:00
Arkadiusz Fal
c452e66999 Update CHANGELOG 2023-10-24 09:17:22 +02:00
Arkadiusz Fal
7ab91e08f4 Update dependencies 2023-10-24 09:16:37 +02:00
Arkadiusz Fal
bdd18dba4e
Merge pull request #543 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-10-24 09:15:59 +02:00
maboroshin
026654d3a2
Translated using Weblate (Japanese)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-10-22 19:40:21 +02:00
mere
e9729bc9b3
Translated using Weblate (Romanian)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-10-22 19:40:21 +02:00
Arkadiusz Fal
a04159969e Use AF for Piped token request 2023-10-22 19:39:34 +02:00
Arkadiusz Fal
3624d186bc Bump build number to 167 2023-10-16 15:53:58 +02:00
Arkadiusz Fal
31ea4860cf Bump version number to 1.5.1 2023-10-16 15:53:41 +02:00
Arkadiusz Fal
5a074d7607 Bump build number to 166 2023-10-15 14:10:05 +02:00
Arkadiusz Fal
9d8b20148b Update CHANGELOG 2023-10-15 14:09:37 +02:00
Arkadiusz Fal
0ffb9d0606 Fix date parsing for piped videos 2023-10-15 14:08:38 +02:00
Arkadiusz Fal
8258d8d5b4 Bring back iOS 14 and macOS 11 2023-10-15 14:08:08 +02:00
Arkadiusz Fal
94915b0496 Revert "Drop iOS 14 and macOS 11 support"
This reverts commit dcef7f47ff5194ccf6635c3f626fef1daee05514.
2023-10-15 13:35:23 +02:00
Arkadiusz Fal
053b4a22b8 Fix style 2023-10-15 13:33:46 +02:00
Arkadiusz Fal
e6a9f39477 Bump build number to 165 2023-10-15 00:46:59 +02:00
Arkadiusz Fal
ff4f80b7ef Update CHANGELOG 2023-10-15 00:46:59 +02:00
Arkadiusz Fal
7e1218ce13 Update packages 2023-10-15 00:46:59 +02:00
Arkadiusz Fal
4c707271c3 Add setting to disable chapters and related, add info section in
player settings
2023-10-15 00:46:59 +02:00
Arkadiusz Fal
04e56638ce Fix possible crash 2023-10-15 00:46:59 +02:00
Arkadiusz Fal
674269c4c1 Fix issue with tvOS screensaver 2023-10-15 00:46:58 +02:00
Arkadiusz Fal
2b4627c3d6 Fix lint 2023-10-15 00:46:58 +02:00
Arkadiusz Fal
4260c6d6b5
Merge pull request #539 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-10-15 00:46:13 +02:00
maboroshin
4057021cb9
Translated using Weblate (Japanese)
Currently translated at 99.4% (530 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-10-10 15:04:11 +00:00
gallegonovato
151a99c2a3
Translated using Weblate (Spanish)
Currently translated at 84.2% (449 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-10-08 12:03:32 +00:00
Radical
bd606a4cf8
Translated using Weblate (Norwegian Bokmål)
Currently translated at 59.6% (318 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/
2023-10-08 12:03:31 +00:00
Ophiushi
4dbf2551d9
Translated using Weblate (French)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-09-28 19:02:38 +02:00
Arkadiusz Fal
8fefbccc83 Bump build number to 164 2023-09-26 18:19:49 +02:00
Arkadiusz Fal
65bc1e4efe Update CHANGELOG 2023-09-26 18:19:27 +02:00
Arkadiusz Fal
9a257ec897 Fix quality profile form 2023-09-26 18:19:09 +02:00
Arkadiusz Fal
dd0a966a9f Bump build number to 163 2023-09-26 17:57:19 +02:00
Arkadiusz Fal
cec0327293 Update CHANGELOG 2023-09-26 17:47:18 +02:00
Arkadiusz Fal
5b2c94758b Set window min width on macOS 2023-09-26 17:47:00 +02:00
Arkadiusz Fal
2dd1d2ac56 Fix possible crash 2023-09-26 17:44:01 +02:00
Arkadiusz Fal
3be7a5a69f Fix issue with creating quality profile 2023-09-26 17:44:01 +02:00
Arkadiusz Fal
3e6ea2633e Remove unnecessary frameworks 2023-09-26 17:44:01 +02:00
Arkadiusz Fal
2ef692edb8 Migrate to SwiftUIIntrospect 2023-09-26 17:44:01 +02:00
Arkadiusz Fal
3edfa5dfe7 Set focus on search textfield on macOS
Fix #268
2023-09-26 17:44:01 +02:00
Arkadiusz Fal
8976ef04f6 Localization fix 2023-09-26 17:44:00 +02:00
Arkadiusz Fal
cf81aedb60
Merge pull request #538 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-09-26 17:43:48 +02:00
joaooliva
78a225cde4
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-09-26 17:43:26 +02:00
Arkadiusz Fal
efdbbbc1f4
Translated using Weblate (Polish)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-09-26 17:43:26 +02:00
Anonymous
f82056a358
Translated using Weblate (English)
Currently translated at 100.0% (533 of 533 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-09-26 17:43:26 +02:00
Arkadiusz Fal
983b9c4ddc Bump build number to 162 2023-09-25 21:39:12 +02:00
Arkadiusz Fal
7a96312a83 Update CHANGELOG 2023-09-25 21:38:54 +02:00
Arkadiusz Fal
9d49f3dd68 Add podcasts and releases tabs for channels in Invidious 2023-09-25 21:37:30 +02:00
Arkadiusz Fal
a1e8a50aaf Bump build number to 161 2023-09-24 17:23:48 +02:00
Arkadiusz Fal
35d895d562 Update CHANGELOG 2023-09-24 17:23:48 +02:00
Arkadiusz Fal
be2d21384d Fix selection of formats in quality profile 2023-09-24 17:23:03 +02:00
Arkadiusz Fal
74917a482e Fix url parsing 2023-09-24 11:49:26 +02:00
Arkadiusz Fal
e5f49fda5e Fix tab selection bugfix 2023-09-24 11:14:24 +02:00
Arkadiusz Fal
e556dac871 Fix search suggestions whitespace 2023-09-24 11:14:11 +02:00
Arkadiusz Fal
a71a9f92b4 Bump build number to 160 2023-09-23 22:42:27 +02:00
Arkadiusz Fal
0b1d4bb762 Update CHANGELOG 2023-09-23 22:42:20 +02:00
Arkadiusz Fal
4a83cc6127 Disable automatic external testers distribution 2023-09-23 22:42:08 +02:00
Arkadiusz Fal
de250cc335 Bump build number to 159 2023-09-23 22:27:29 +02:00
Arkadiusz Fal
bf2a4f2977 Update CHANGELOG 2023-09-23 22:27:29 +02:00
Arkadiusz Fal
d8fa0e6aeb Bump build number to 158 2023-09-23 22:06:17 +02:00
Arkadiusz Fal
c23f5fcf83 Update CHANGELOG 2023-09-23 22:05:57 +02:00
Arkadiusz Fal
40ac137461 Fix sidebar navigation 2023-09-23 21:59:38 +02:00
Arkadiusz Fal
d47ecb2723 Fix parsing playlist urls (with list parameter) 2023-09-23 21:59:38 +02:00
Arkadiusz Fal
f25005536c Fix close button in Playback settings on macOS 2023-09-23 21:59:38 +02:00
Arkadiusz Fal
9db5fb0de7 Handle core-idle property 2023-09-23 21:59:38 +02:00
Arkadiusz Fal
b0db04c119 Remove Vendor 2023-09-23 21:59:37 +02:00
Arkadiusz Fal
13d5bd39af mpvkit implementation fixes 2023-09-23 21:59:37 +02:00
Arkadiusz Fal
4cd03f35f7 Update project settings for Xcode 15 2023-09-23 17:14:21 +02:00
Arkadiusz Fal
12a8582e89 Fix style issues, remove unused code 2023-09-23 17:14:21 +02:00
Arkadiusz Fal
dfda1181e2 Fix swiftformat issues 2023-09-23 17:14:21 +02:00
Arkadiusz Fal
2330924fef Update packages 2023-09-23 17:14:21 +02:00
Arkadiusz Fal
1980e8a10e Minor image fixes 2023-09-23 17:14:21 +02:00
Arkadiusz Fal
19d11a3ad9 Add button to add public instance to custom list 2023-09-23 17:14:21 +02:00
Arkadiusz Fal
548908b26f
Merge pull request #530 from dnicolson/fix-manifest-keyboard-type
Fix manifest keyboard type
2023-09-23 17:13:54 +02:00
Arkadiusz Fal
1be62cc7ad
Merge pull request #533 from dnicolson/fix-tvos-settings-icon-alignment
Fix tvOS settings icon alignment
2023-09-23 17:10:31 +02:00
Arkadiusz Fal
5fa69aadea
Merge pull request #532 from dnicolson/fix-ios-contact-label-alignment
Fix iOS contact label alignment
2023-09-23 17:10:25 +02:00
Arkadiusz Fal
bbb16204d8
Merge pull request #531 from dnicolson/fix-tvos-settings-table
Fix tvOS settings table padding
2023-09-23 17:10:13 +02:00
Arkadiusz Fal
b74207923c
Merge pull request #521 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-09-23 16:41:48 +02:00
MacCZ
afde6c2ce2
Translated using Weblate (Czech)
Currently translated at 97.9% (519 of 530 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/cs/
2023-09-23 16:41:13 +02:00
mrtag-oss
f640bfc0c0
Translated using Weblate (Russian)
Currently translated at 100.0% (530 of 530 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ru/
2023-09-23 16:41:13 +02:00
mrtag-oss
832cbef61f
Added translation using Weblate (Russian) 2023-09-23 16:41:13 +02:00
maboroshin
3b0fa31787
Translated using Weblate (Japanese)
Currently translated at 99.6% (528 of 530 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-09-23 16:41:13 +02:00
mere
bac7347e43
Translated using Weblate (Romanian)
Currently translated at 100.0% (530 of 530 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-09-23 16:41:13 +02:00
joaooliva
f2bd1010ba
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (530 of 530 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-09-23 16:41:13 +02:00
Ophiushi
14b03b7eb5
Translated using Weblate (French)
Currently translated at 100.0% (530 of 530 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-09-23 16:41:13 +02:00
Bharathi
a0ebe72ede
Translated using Weblate (German)
Currently translated at 100.0% (530 of 530 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-09-23 16:41:13 +02:00
Arkadiusz Fal
0f25e33213
Merge pull request #507 from amaumene/use-mpvkit
switch mpv to mpvkit
2023-09-23 16:41:06 +02:00
Arkadiusz Fal
757afd39da
Merge branch 'main' into use-mpvkit 2023-09-23 16:17:33 +02:00
Dave Nicolson
f935cc9cd8 Fix iOS contact label alignment 2023-09-10 20:09:16 +02:00
Dave Nicolson
57038c590e Fix tvOS settings icon alignment 2023-09-10 20:09:00 +02:00
Dave Nicolson
bd961ebbf0 Fix tvOS settings table padding 2023-09-09 18:20:44 +02:00
Dave Nicolson
0822c7b93a Fix username keyboard input 2023-09-09 18:18:54 +02:00
Dave Nicolson
85cb42fc8d Fix manifest keyboard type 2023-09-09 18:18:54 +02:00
Arkadiusz Fal
2f10dc8368 Bump build number to 157 2023-07-24 20:07:09 +02:00
Arkadiusz Fal
e0b1b78553 Update CHANGELOG 2023-07-24 20:06:52 +02:00
Arkadiusz Fal
62089109ef Update packages 2023-07-24 20:00:31 +02:00
Arkadiusz Fal
fe31e4ffd1 Fix URL handling on macOS 2023-07-24 19:56:59 +02:00
Arkadiusz Fal
58ba6f5fe7 Fix #519 2023-07-24 19:45:31 +02:00
Arkadiusz Fal
44045b9e19 Don't observe favorites settings change 2023-07-24 19:45:31 +02:00
Arkadiusz Fal
4cabfb7733 Fix scrubbing area 2023-07-24 19:45:31 +02:00
Arkadiusz Fal
52dad5942e Details channel avatar performance improvement 2023-07-24 19:45:31 +02:00
Arkadiusz Fal
98d26c37ff Minor style change 2023-07-24 19:45:30 +02:00
Arkadiusz Fal
99ffaed5fc Honor "Keep channels with unwatched videos on top of subscriptions list"
setting in sidebar
2023-07-24 19:45:30 +02:00
Arkadiusz Fal
3c9e04d243 Channels performance improvements
Add settings:
Show channel avatars in channels lists
Show channel avatars in videos lists

Fix #508
2023-07-24 19:45:30 +02:00
Arkadiusz Fal
37a96a01db Fix #515 2023-07-24 19:45:30 +02:00
Arkadiusz Fal
a246e716fc Update packages 2023-07-24 19:45:30 +02:00
Arkadiusz Fal
dcef7f47ff Drop iOS 14 and macOS 11 support 2023-07-24 19:45:30 +02:00
Arkadiusz Fal
a1c9d3aaa9 Update packages 2023-07-24 19:45:30 +02:00
Arkadiusz Fal
e827b97cd5 Fix lint issues 2023-07-24 19:45:30 +02:00
Arkadiusz Fal
b12933e61d Update packages 2023-07-24 19:45:30 +02:00
Arkadiusz Fal
5d6b2b4398 Bump build number to 156 2023-07-24 19:45:30 +02:00
Arkadiusz Fal
291133c384 Bump version number to 1.5 2023-07-24 19:45:30 +02:00
Arkadiusz Fal
28dffea9c2
Merge pull request #504 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-07-24 19:44:47 +02:00
Arkadiusz Fal
70e2fb994a
Translated using Weblate (Polish)
Currently translated at 100.0% (530 of 530 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-07-24 19:44:16 +02:00
Anonymous
17d897e227
Translated using Weblate (English)
Currently translated at 100.0% (530 of 530 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-07-24 19:44:15 +02:00
maboroshin
e38e76adaf
Translated using Weblate (Japanese)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-07-18 19:06:46 +02:00
Gamom Yang
a55d9115c5
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/zh_Hans/
2023-07-06 15:52:34 +02:00
Gamom Yang
cf1bafa991
Translated using Weblate (Chinese (Simplified))
Currently translated at 84.2% (445 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/zh_Hans/
2023-07-05 14:52:58 +02:00
ssantos
2784f14f36
Translated using Weblate (Portuguese)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt/
2023-06-28 22:52:00 +02:00
maboroshin
dff24f9e4f
Translated using Weblate (Japanese)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-06-26 06:40:24 +02:00
amaumene
e7c1f8116d switch mpv to mpvkit 2023-06-26 13:18:28 +09:00
Allan Nordhøy
5fabd851d6
Translated using Weblate (Norwegian Bokmål)
Currently translated at 60.0% (317 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/
2023-06-21 14:51:40 +02:00
Arkadiusz Fal
e555b74396
Merge pull request #500 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-06-17 13:40:52 +02:00
maboroshin
1304af1407
Translated using Weblate (Japanese)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-06-16 19:52:00 +02:00
joaooliva
a190d07fba
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-06-16 19:52:00 +02:00
Ophiushi
043292cf67
Translated using Weblate (French)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-06-16 19:51:59 +02:00
Bharathi
359058dbfe
Translated using Weblate (German)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-06-16 19:51:59 +02:00
Arkadiusz Fal
02ebad0712
Merge pull request #494 from PabloMz85/patch-1
Update Localizable.strings
2023-06-13 10:24:14 +02:00
PabloMz85
0e7b2e6fac
Update Localizable.strings 2023-06-12 11:37:29 +02:00
Arkadiusz Fal
16b25df3bc Fix method and property access 2023-06-09 18:03:42 +02:00
Arkadiusz Fal
a66e59a282 Bump build number to 155 2023-06-09 17:50:56 +02:00
Arkadiusz Fal
aeeedf3d63 Update CHANGELOG 2023-06-09 17:50:30 +02:00
Arkadiusz Fal
3d35a60c7a Performance improvements 2023-06-09 17:46:31 +02:00
Arkadiusz Fal
8ffdd4d51f Fix crash 2023-06-09 17:45:51 +02:00
Arkadiusz Fal
f871c7aaf5 Bump build number to 154 2023-06-08 12:27:26 +02:00
Arkadiusz Fal
32af2b385b Update CHANGELOG 2023-06-08 12:25:12 +02:00
Arkadiusz Fal
f78545baf9 Fix issue with AVPlayer rate restore 2023-06-08 12:25:12 +02:00
Arkadiusz Fal
d95bcc4065 Fix #492 2023-06-08 12:17:16 +02:00
Arkadiusz Fal
1efd9e2b90 Fix hiding overlays 2023-06-08 12:11:44 +02:00
Arkadiusz Fal
59e5fcb37d Fix properties access 2023-06-07 23:51:17 +02:00
Arkadiusz Fal
47d68d7948 Update packages 2023-06-07 23:51:09 +02:00
Arkadiusz Fal
3e22c1ebde Bump build number to 153 2023-06-07 23:38:24 +02:00
Arkadiusz Fal
ede5d85693 Update CHANGELOG 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
4d5390ce2d Fixed video player background 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
91290d4736 Fix #486 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
7e7225c59f Fix build on tvOS 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
7b9bbd8974 Fix #473 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
c36dc67a72 Fix issue with AVPlayer rate 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
71b25afa28 Use method to find instance 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
2c5eb18bc9 Fix crash 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
56c2e552f7 Revert "Add loading status to vertical cells"
This reverts commit c6cff4dee4749198143558417e734bb32bcac832.
2023-06-07 23:37:27 +02:00
Arkadiusz Fal
5ee869c02c Fix lists padding in favorites 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
6f91eedf4c Update watch time on close item 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
0328656a44 Fix AVPlayer layout 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
8d11a92f97 Fix switching to AVPlayer in fullscreen 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
f3a8a0977c Do not reload channels cache if not needed 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
3bbc2df431
Merge pull request #484 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-06-07 21:35:50 +02:00
maboroshin
5faa5f0a48
Translated using Weblate (Japanese)
Currently translated at 98.8% (522 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-06-07 11:52:11 +02:00
maboroshin
ca8586ce7f
Translated using Weblate (Japanese)
Currently translated at 98.6% (521 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-06-05 13:51:46 +02:00
joaooliva
8efa68e65c
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-06-03 18:49:46 +02:00
Ophiushi
04c15ed59a
Translated using Weblate (French)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-06-03 18:49:46 +02:00
Bharathi
75772533fd
Translated using Weblate (German)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-06-03 18:49:45 +02:00
mere
3ca0f4cf2d
Translated using Weblate (Romanian)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-06-01 08:48:29 +02:00
Arkadiusz Fal
5f745afecb
Translated using Weblate (Polish)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-30 10:28:57 +02:00
Anonymous
e8c8c6b5b4
Translated using Weblate (English)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-30 10:28:57 +02:00
Arkadiusz Fal
0585120bd8
Translated using Weblate (English)
Currently translated at 100.0% (527 of 527 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-30 10:26:30 +02:00
Anonymous
1f43e11b8e
Translated using Weblate (English)
Currently translated at 100.0% (527 of 527 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-30 10:26:26 +02:00
Arkadiusz Fal
792a2c1c6c Fix method access 2023-05-29 16:50:40 +02:00
Arkadiusz Fal
12ef9e091c Bump build number to 152 2023-05-29 16:42:20 +02:00
Arkadiusz Fal
101ecb6892 Update CHANGELOG 2023-05-29 16:42:20 +02:00
Arkadiusz Fal
be2e4acedd Simplify channels view 2023-05-29 16:42:20 +02:00
Arkadiusz Fal
c6cff4dee4 Add loading status to vertical cells 2023-05-29 16:42:19 +02:00
Arkadiusz Fal
eaeaa45422 Fix #479 2023-05-29 16:42:19 +02:00
Arkadiusz Fal
6ddf1113bf Fix fullscreen exit 2023-05-29 16:42:19 +02:00
Arkadiusz Fal
15f3e11a78 Simplify playlists view 2023-05-29 16:42:19 +02:00
Arkadiusz Fal
713570dfd6 Fix navigatable in favorite item view 2023-05-29 16:13:13 +02:00
Arkadiusz Fal
50eb0be1d7 Change subscriptions picker labels to text 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
3adbea1897 Fix #443 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
80a644eb7a Fix Invidious trending categories 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
b1637c5ef1 Fix buttons on tvOS 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
166482601d Fix home settings buttons colors 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
1d61dec8eb Revert "Minor improvements"
This reverts commit 24241d348553b22a28220c556ec2436a25676a4d.
2023-05-29 16:13:12 +02:00
Arkadiusz Fal
ca7195caba Add remove item to search recents items 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
947f216fac Fix closing video on error 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
a054d343a9 Fix music mode in AVPlayer 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
48263ae7db Add tap on search to focus search field on iOS 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
562df2d9ba Add advanced setting "Show video context menu options to force selected backend" 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
e5f137a2d2 Add setting "Keep channels with unwatched videos on top of subscriptions list" 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
6856506834
Merge pull request #478 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-29 16:12:54 +02:00
maboroshin
a604382a3d
Translated using Weblate (Japanese)
Currently translated at 99.4% (521 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-05-29 09:48:10 +02:00
Bharathi
0bbbf0907d
Translated using Weblate (German)
Currently translated at 100.0% (524 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-05-29 09:48:09 +02:00
mere
921987be5d
Translated using Weblate (Romanian)
Currently translated at 100.0% (524 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-05-27 06:50:18 +02:00
joaooliva
b47156ba5e
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (524 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-05-27 06:50:18 +02:00
Ophiushi
5ab06d0e09
Translated using Weblate (French)
Currently translated at 100.0% (524 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-27 06:50:17 +02:00
Arkadiusz Fal
af632d7943 Fix property access 2023-05-25 19:03:59 +02:00
Arkadiusz Fal
56993de1c2 Bump build number to 151 2023-05-25 18:55:40 +02:00
Arkadiusz Fal
ca61f0d8e5 Update packages 2023-05-25 18:55:26 +02:00
Arkadiusz Fal
6e89538623 Update CHANGELOG 2023-05-25 18:54:21 +02:00
Arkadiusz Fal
d167ff575d Hide buttons in channels menu on list view 2023-05-25 18:54:12 +02:00
Arkadiusz Fal
c7d6253739 Localizations fixes 2023-05-25 18:53:17 +02:00
Arkadiusz Fal
24241d3485 Minor improvements 2023-05-25 18:36:04 +02:00
Arkadiusz Fal
5cfcffc885 Show loading video 2023-05-25 18:36:04 +02:00
Arkadiusz Fal
9d2e6f117d Fix url handling 2023-05-25 18:36:03 +02:00
Arkadiusz Fal
7c24a86a6a Fix updating watch on AVPlayer 2023-05-25 18:36:03 +02:00
Arkadiusz Fal
4d70c8a3c3 Change subscriptions view picker 2023-05-25 18:36:03 +02:00
Arkadiusz Fal
59f48c739a Add "Startup section" setting
Fix #103
2023-05-25 18:36:03 +02:00
Arkadiusz Fal
ae144ea82f Minor player fix 2023-05-25 18:36:03 +02:00
Arkadiusz Fal
2583be9401 Fix music mode controls 2023-05-25 18:36:03 +02:00
Arkadiusz Fal
0061bd8c20 Home Settings 2023-05-25 18:36:03 +02:00
Arkadiusz Fal
12afb31c03 Fix issue with navigation links and lists 2023-05-25 18:36:03 +02:00
Arkadiusz Fal
35867ba14a Home changes 2023-05-25 18:36:03 +02:00
Arkadiusz Fal
50e1491990 Refactor hide shorts 2023-05-25 18:36:03 +02:00
Arkadiusz Fal
1e23809359 Improve history view performance 2023-05-25 18:36:03 +02:00
Arkadiusz Fal
eed9330c0c Add buttons for hiding watched videos
Fix #448
2023-05-25 18:36:02 +02:00
Arkadiusz Fal
1e2d6cf72f Move "Show toggle watch status button" to History settings 2023-05-25 18:36:02 +02:00
Arkadiusz Fal
ac7dad2ab8 Fix #450 2023-05-25 18:36:02 +02:00
Arkadiusz Fal
e1f03bc025 Remove "Rotate to portrait when exiting fullscreen" setting
Now it is automatically decided depending on device type
2023-05-25 18:36:02 +02:00
Arkadiusz Fal
0dee8310ce Fix #468 2023-05-25 18:36:02 +02:00
Arkadiusz Fal
b16eae3d88 Fix #467 2023-05-25 18:36:02 +02:00
Arkadiusz Fal
02a29e5d07 Fix #446 2023-05-25 18:36:02 +02:00
Arkadiusz Fal
22bbf731e9 Fix #466 2023-05-25 18:36:02 +02:00
Arkadiusz Fal
c0053cf837 Fix PiP close on Mac 2023-05-25 18:36:02 +02:00
Arkadiusz Fal
16fb7087e3 Fix playlists view on iOS 2023-05-25 18:36:02 +02:00
Arkadiusz Fal
8f48da93d8
Merge pull request #475 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-25 18:35:24 +02:00
Arkadiusz Fal
f3eac03b58
Translated using Weblate (Polish)
Currently translated at 100.0% (524 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-25 18:35:11 +02:00
Anonymous
86b8c7384c
Translated using Weblate (English)
Currently translated at 100.0% (524 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-25 18:35:10 +02:00
Arkadiusz Fal
1502d02184
Merge pull request #472 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-25 18:30:30 +02:00
maboroshin
a0dc280f22
Translated using Weblate (Japanese)
Currently translated at 97.7% (510 of 522 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-05-25 18:29:52 +02:00
Francesco
91ec2f39b0
Translated using Weblate (Italian)
Currently translated at 98.0% (512 of 522 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/
2023-05-25 18:29:51 +02:00
Robert Kleinschuster
1d85f92087
Translated using Weblate (German)
Currently translated at 98.0% (512 of 522 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-05-25 18:29:50 +02:00
Arkadiusz Fal
ce2f9cee99
Translated using Weblate (Polish)
Currently translated at 100.0% (522 of 522 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-25 18:29:49 +02:00
Anonymous
63fe52695c
Translated using Weblate (English)
Currently translated at 100.0% (522 of 522 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-25 18:29:49 +02:00
Lionel Vallet
9524991c5f
Translated using Weblate (French)
Currently translated at 100.0% (512 of 512 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-23 08:49:32 +02:00
Bharathi
78f2c681a7
Translated using Weblate (German)
Currently translated at 100.0% (512 of 512 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-05-23 08:49:32 +02:00
mere
8f8abe7bb1
Translated using Weblate (Romanian)
Currently translated at 100.0% (512 of 512 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-05-22 05:45:25 +02:00
joaooliva
0a6beabae8
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (512 of 512 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-05-22 05:45:25 +02:00
Ophiushi
873bbf90bb
Translated using Weblate (French)
Currently translated at 100.0% (512 of 512 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-22 05:45:24 +02:00
Arkadiusz Fal
450a4b42f7 Fix property access 2023-05-21 19:37:22 +02:00
Arkadiusz Fal
db4b3115b1 Bump build number to 150 2023-05-21 19:27:30 +02:00
Arkadiusz Fal
fba465a22a Update CHANGELOG 2023-05-21 19:27:30 +02:00
Arkadiusz Fal
d996069a20 Rotation fixes 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
d7a2564617 Localization fix 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
d5f8e35430 Fix channel videos horizontal layout 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
8f9de6d1be Disable mac beta lane in release workflow 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
0fc6f7fdb7 Remove padding from search suggestions 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
d5f88a73f8 Fix Add to playlist actionable 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
97af5a6e0c Fix opening channels and playlists 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
4c5ef920b4 Fix drag gesture 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
a55683e6bf Fix orientation 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
739ca007e8 Fix tests 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
7d7bd40a89 Add option "Close player on end of video"
Fix #442
2023-05-21 19:13:42 +02:00
Arkadiusz Fal
c6798be167 Show stream opening status with AVPlayer 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
2b7ccc4b03 Orientation fixes 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
08ce572b9e Fix actions buttons text 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
1cc66fdc10 Fix various issues 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
34a05433d5 Fix issue with streams list duplicates 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
5383cf0e90 AVPlayer system controls on iOS 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
a4fdd50388 Update packages 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
f67b1d4feb Improve orientation and safe area handling
Fix #369
Fix #382
2023-05-21 19:13:42 +02:00
Arkadiusz Fal
b53b5eac56 Localization fix 2023-05-21 19:13:19 +02:00
Arkadiusz Fal
6c5b8ef3ec Bump build number to 149 2023-05-21 19:13:18 +02:00
Arkadiusz Fal
226da4d2be Bump version number to 1.4.5 2023-05-21 19:13:18 +02:00
Arkadiusz Fal
49ffffae53 Fix crash 2023-05-21 19:13:18 +02:00
Arkadiusz Fal
848a43ce7f
Merge pull request #464 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-21 19:12:56 +02:00
Arkadiusz Fal
6ca0e82feb
Translated using Weblate (Polish)
Currently translated at 100.0% (512 of 512 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-21 19:12:46 +02:00
Anonymous
f7f53c6417
Translated using Weblate (English)
Currently translated at 100.0% (512 of 512 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-21 19:12:45 +02:00
Arkadiusz Fal
55a0b2dee6
Merge pull request #462 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-21 18:41:07 +02:00
Lionel Vallet
9f0700d2bf
Translated using Weblate (French)
Currently translated at 99.0% (503 of 508 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-21 18:40:36 +02:00
Arkadiusz Fal
ab0c2e7b84
Translated using Weblate (Polish)
Currently translated at 100.0% (508 of 508 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-21 18:40:35 +02:00
Anonymous
d603ef7431
Translated using Weblate (English)
Currently translated at 100.0% (508 of 508 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-21 18:40:34 +02:00
mere
281e0510cd
Translated using Weblate (Romanian)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-05-21 11:04:56 +02:00
joaooliva
837c9a3f75
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-05-21 11:04:55 +02:00
Ophiushi
bcec9d09ab
Translated using Weblate (French)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-21 11:04:55 +02:00
Lionel Vallet
82a09d1584
Translated using Weblate (French)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-21 11:04:54 +02:00
Bharathi
dae667fa8a
Translated using Weblate (German)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-05-21 11:04:54 +02:00
Arkadiusz Fal
0f802684f2
Translated using Weblate (Polish)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-21 11:04:53 +02:00
Anonymous
59b49c2e2f
Translated using Weblate (English)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-21 11:04:53 +02:00
Arkadiusz Fal
3a8d6aed76 Bump build number to 148 2023-05-19 10:54:02 +02:00
Arkadiusz Fal
2952e10359 Update CHANGELOG 2023-05-19 10:54:02 +02:00
Arkadiusz Fal
59f84ec129
Merge pull request #460 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-19 10:53:07 +02:00
Dan
a76dae6656
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (500 of 500 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2023-05-19 10:52:56 +02:00
Arkadiusz Fal
a208ef9147 Bump build number to 147 2023-05-19 09:59:31 +02:00
Arkadiusz Fal
7a998d2d69 Update CHANGELOG 2023-05-19 09:59:31 +02:00
Arkadiusz Fal
f3659904dc Fix keyboard issue with account/instance form on iOS 2023-05-19 09:55:25 +02:00
Arkadiusz Fal
72246448f1 Localizations fixes 2023-05-19 09:55:04 +02:00
Arkadiusz Fal
6617ad5fc6 Fix player width on macOS 2023-05-19 09:54:35 +02:00
Arkadiusz Fal
65b3eb60d9
Merge pull request #459 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-19 09:54:19 +02:00
Arkadiusz Fal
0174d2f8a0
Translated using Weblate (Polish)
Currently translated at 100.0% (500 of 500 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-19 09:54:00 +02:00
Anonymous
8f340586a6
Translated using Weblate (English)
Currently translated at 100.0% (500 of 500 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-19 09:54:00 +02:00
Anonymous
23e07baa7a
Translated using Weblate (English)
Currently translated at 100.0% (499 of 499 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-19 09:48:46 +02:00
Arkadiusz Fal
e0e0352238 Bump build number to 146 2023-05-18 11:34:44 +02:00
Arkadiusz Fal
543a7c0da6 Update CHANGELOG 2023-05-18 11:34:44 +02:00
Arkadiusz Fal
c78a0dc8c3 Fix playback settings sheet height 2023-05-18 11:32:45 +02:00
Arkadiusz Fal
0051b3ab74 Minor change 2023-05-18 11:32:45 +02:00
Arkadiusz Fal
1f0a2d25e9 Localization fix 2023-05-18 11:32:45 +02:00
Arkadiusz Fal
5d8e8483d1 Minor performance improvement 2023-05-18 11:32:45 +02:00
Arkadiusz Fal
7972498f2c
Merge pull request #454 from mikhailokarpenko/localization/ukrainian-translation
cover more ukrainian translation
2023-05-18 11:31:14 +02:00
Arkadiusz Fal
703ac90a33
Merge pull request #455 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-18 11:30:26 +02:00
maboroshin
265c4cd95c
Translated using Weblate (Japanese)
Currently translated at 98.9% (491 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-05-15 00:47:45 +02:00
ButterflyOfFire
b7929465a7
Translated using Weblate (Arabic)
Currently translated at 86.8% (431 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2023-05-15 00:47:45 +02:00
Francesco
669f7d5aa6
Translated using Weblate (Italian)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/
2023-05-15 00:47:44 +02:00
Ophiushi
3136d6328d
Translated using Weblate (French)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-15 00:47:44 +02:00
Mike
32b19d8cd5 cover more ukrainian translation 2023-05-14 12:12:42 -04:00
Arkadiusz Fal
2efb3ec334 Bump build number to 145 2023-05-07 22:30:38 +02:00
Arkadiusz Fal
16c580b1f3 Update CHANGELOG 2023-05-07 22:06:51 +02:00
Arkadiusz Fal
5cef7d40ff Bump build number to 144 2023-05-07 22:06:51 +02:00
Arkadiusz Fal
7b9aa8ce99 Fix settings height on macOS 2023-05-07 22:06:51 +02:00
Arkadiusz Fal
a83657b8c6 Fix crash 2023-05-07 22:06:51 +02:00
Arkadiusz Fal
287bd25360 Switch seek duration +/- buttons
Fix #438
2023-05-07 22:06:51 +02:00
Arkadiusz Fal
7c45f3286b Update packages 2023-05-07 22:06:51 +02:00
Arkadiusz Fal
a9e3e81567 Remove unused package 2023-05-07 22:06:50 +02:00
Arkadiusz Fal
f45001da78 Codestyle fixes 2023-05-07 22:06:50 +02:00
Arkadiusz Fal
3779b7ed1f Add scrollbars in video details and button to scroll comments to top
Added player settings to disable scroll to top button

Fix #439
2023-05-07 22:06:50 +02:00
Arkadiusz Fal
55517fd44d Bump build number to 143 2023-05-07 22:04:22 +02:00
Arkadiusz Fal
77c40226eb Bump version number to 1.4.4 2023-05-07 22:04:22 +02:00
Arkadiusz Fal
5424d5168a
Merge pull request #437 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-07 13:40:07 +02:00
tototof
2c7fce011f
Translated using Weblate (French)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-06 14:49:33 +02:00
Ophiushi
6911fb8e08
Translated using Weblate (French)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-06 14:49:33 +02:00
Leonardo Barone
a73a030d92
Translated using Weblate (Italian)
Currently translated at 94.3% (468 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/
2023-05-02 16:48:08 +02:00
Bharathi
930cefc29e
Translated using Weblate (German)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-04-29 19:49:16 +02:00
Arkadiusz Fal
02d9b34fb0
Merge pull request #435 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-04-26 13:53:40 +02:00
Louis Vignier
0a8d9dfceb
Translated using Weblate (French)
Currently translated at 84.2% (418 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-04-25 15:50:34 +02:00
Arkadiusz Fal
84db321b70 Update CHANGELOG 2023-04-24 13:00:57 +02:00
Arkadiusz Fal
10c1fbd503 Bump build number to 142 2023-04-24 12:58:24 +02:00
Arkadiusz Fal
c7b64c973d Fix player overlay opacity 2023-04-24 12:57:31 +02:00
Arkadiusz Fal
40097de1fd Fix localizations 2023-04-24 12:57:20 +02:00
Arkadiusz Fal
8eac32078b Fix placeholders 2023-04-24 12:57:06 +02:00
Arkadiusz Fal
8271feb77a Fix actions actionable 2023-04-24 12:16:45 +02:00
Arkadiusz Fal
77fde219e0 Fix chapters layout 2023-04-24 12:16:45 +02:00
Arkadiusz Fal
383bb32215
Merge pull request #434 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-04-24 12:16:28 +02:00
joaooliva
6a4f031cca
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-04-23 23:03:50 +02:00
Arkadiusz Fal
1e65f6d807 Bump build number to 141 2023-04-23 14:11:59 +02:00
Arkadiusz Fal
b4d5322ac6 Update CHANGELOG 2023-04-23 14:11:39 +02:00
Arkadiusz Fal
a5bfabed0c Fix clipped controls
Fix #431
2023-04-23 14:10:44 +02:00
Arkadiusz Fal
f6569db418 Fix crash 2023-04-23 14:10:44 +02:00
Arkadiusz Fal
9cc9c74f97
Merge pull request #433 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-04-23 14:10:39 +02:00
mere
5dbc211c95
Translated using Weblate (Romanian)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-04-23 14:10:20 +02:00
joaooliva
03b27280f4
Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.5% (494 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-04-23 14:10:20 +02:00
Arkadiusz Fal
913ac37991
Translated using Weblate (Polish)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-04-23 14:10:20 +02:00
Arkadiusz Fal
7cfbe5ae5a Add missing bundle platform 2023-04-22 23:47:56 +02:00
Arkadiusz Fal
bf80c4024c Bump build number to 140 2023-04-22 23:39:44 +02:00
Arkadiusz Fal
e70808c463 Update fastlane 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
ce68c0f5b4 Update CHANGELOG 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
8e829ed3b1 Localizations fixes 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
5c0cf7452c Remove unused code 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
2d02d9b472 Fix possible crashes 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
72a98314c1 Fix accounts switcher padding on tvOS 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
ea997ffdb9 Fix #425 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
83dfdd6c0e tvOS filters for all views
Vertical list for trending, popular, playlists, search

Fix #413, #415
2023-04-22 23:39:43 +02:00
Arkadiusz Fal
6596a440a5 New chapters layout 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
d52ccf2ce6 Add video description expanding 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
b19918e219 Disable offset animation on hiding player 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
2fe211edb4 Add setting for toggle video watch status 2023-04-22 23:39:43 +02:00
Arkadiusz Fal
f852782f5e Fix handling watch statuses 2023-04-22 23:39:42 +02:00
Arkadiusz Fal
c8feeca41f Add playback mode to playback settings 2023-04-22 23:39:42 +02:00
Arkadiusz Fal
9a594b4a8d Account name handling fix 2023-04-22 23:39:42 +02:00
Arkadiusz Fal
c48301c788 Performance fix 2023-04-22 23:39:42 +02:00
Arkadiusz Fal
9936d9dd9e Fix details reload 2023-04-22 23:39:42 +02:00
Arkadiusz Fal
8f9fb7ba82 New actions buttons 2023-04-22 23:39:42 +02:00
Arkadiusz Fal
a7763c5802 Fix controls buttons settings 2023-04-22 23:10:28 +02:00
Arkadiusz Fal
160ea86298 Fix url opening 2023-04-22 23:10:28 +02:00
Arkadiusz Fal
a9e9fa3a6d Code style changes 2023-04-22 23:10:28 +02:00
Arkadiusz Fal
afa0049333 Improve placeholders 2023-04-22 23:10:27 +02:00
Arkadiusz Fal
28f346dee2 Remove Watch Next 2023-04-22 23:10:27 +02:00
Arkadiusz Fal
67690bc435 Video details changes and channel sheet 2023-04-22 23:10:27 +02:00
Arkadiusz Fal
5db74a3997 Code style fixes 2023-04-22 23:10:27 +02:00
Arkadiusz Fal
8ef016d792 Minor performance improvement 2023-04-22 23:10:27 +02:00
Arkadiusz Fal
b59baa6fab Update packages 2023-04-22 23:10:27 +02:00
Arkadiusz Fal
3657d732d9
Merge pull request #430 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-04-22 23:09:29 +02:00
Arkadiusz Fal
309e4a3281
Translated using Weblate (Polish)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-04-22 23:08:38 +02:00
Anonymous
f6e5486412
Translated using Weblate (English)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-04-22 23:08:38 +02:00
Anonymous
d58a68cd66
Translated using Weblate (English)
Currently translated at 100.0% (476 of 476 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-04-22 22:48:23 +02:00
Arkadiusz Fal
faa7d82b8f
Merge pull request #418 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-04-22 09:47:22 +02:00
jonnysemon
3dd9ff837e
Translated using Weblate (Arabic)
Currently translated at 90.6% (429 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2023-04-06 16:48:37 +02:00
oguska
73a62ea76e
Translated using Weblate (Turkish)
Currently translated at 50.1% (237 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2023-04-01 12:37:58 +02:00
Lachtan
7ce37fd5dd
Translated using Weblate (Czech)
Currently translated at 100.0% (473 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/cs/
2023-03-30 21:39:47 +02:00
Ophiushi
25ca69f17d
Translated using Weblate (French)
Currently translated at 83.7% (396 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-03-30 21:39:47 +02:00
maboroshin
578c5a8a61
Translated using Weblate (Japanese)
Currently translated at 97.8% (463 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-03-13 11:37:38 +01:00
ssantos
c3e81d1b67
Translated using Weblate (Portuguese)
Currently translated at 100.0% (473 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt/
2023-03-13 11:37:38 +01:00
Arkadiusz Fal
a65db8555b Bump build number to 139 2023-03-12 22:56:23 +01:00
Arkadiusz Fal
afc1a25c26 Update changelog 2023-03-12 22:56:23 +01:00
Arkadiusz Fal
4851db4879 Fix loading channels data in Favorites 2023-03-12 22:52:54 +01:00
Arkadiusz Fal
912e1d1a23 Add Japanese localization 2023-03-12 22:46:36 +01:00
maboroshin
acc880fd47 Translated using Weblate (Japanese)
Currently translated at 97.6% (462 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-03-12 22:45:10 +01:00
maboroshin
30e587ea97 Added translation using Weblate (Japanese) 2023-03-12 22:45:10 +01:00
mere
d723b2d6f1 Translated using Weblate (Romanian)
Currently translated at 100.0% (473 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-03-12 22:45:10 +01:00
joaooliva
f7c0f8dd34 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (473 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-03-12 22:45:10 +01:00
mere
5f53515bdd Translated using Weblate (Romanian)
Currently translated at 98.3% (465 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-03-12 22:45:10 +01:00
Bharathi
ad4920e0f2 Translated using Weblate (German)
Currently translated at 100.0% (473 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-03-12 22:45:10 +01:00
Arkadiusz Fal
e467b21c9d Translated using Weblate (Polish)
Currently translated at 100.0% (473 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-03-01 23:39:45 +01:00
Anonymous
f94a00a7bb Translated using Weblate (English)
Currently translated at 100.0% (473 of 473 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-03-01 23:39:45 +01:00
Arkadiusz Fal
f7b35925b4 Change syntax to overcome compiler issue 2023-02-28 22:13:52 +01:00
Arkadiusz Fal
5bac92fdbf Bump build number to 138 2023-02-28 21:31:58 +01:00
Arkadiusz Fal
bf2c9a9e43 Update changelog 2023-02-28 21:31:26 +01:00
Arkadiusz Fal
f8d79bb08c Add browsing setting for unwatched feed
Fix #383
2023-02-28 21:27:47 +01:00
Arkadiusz Fal
b9ad5bc633 Hide share button when it should not be available 2023-02-28 21:04:42 +01:00
Arkadiusz Fal
f1e132a909 Add channel tabs and pagination
Fix #135
2023-02-28 21:04:42 +01:00
Arkadiusz Fal
d58026bcef Fix favorite channel button on tvOS 2023-02-26 19:14:06 +01:00
Anonymous
4697aa9696 Translated using Weblate (Swedish)
Currently translated at 24.1% (105 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/sv/
2023-02-26 18:55:38 +01:00
Anonymous
dbf3537f22 Translated using Weblate (Arabic)
Currently translated at 97.4% (424 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2023-02-26 18:55:38 +01:00
Anonymous
474e280faa Translated using Weblate (Czech)
Currently translated at 98.8% (430 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/cs/
2023-02-26 18:55:38 +01:00
Anonymous
04fe18ac4c Translated using Weblate (Spanish)
Currently translated at 26.2% (114 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-02-26 18:55:38 +01:00
Anonymous
66665db344 Translated using Weblate (Turkish)
Currently translated at 52.8% (230 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2023-02-26 18:55:38 +01:00
Arkadiusz Fal
38612eae52 Translated using Weblate (Norwegian Bokmål)
Currently translated at 69.1% (301 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/
2023-02-26 18:55:38 +01:00
Anonymous
80c1f63e35 Translated using Weblate (Norwegian Bokmål)
Currently translated at 69.1% (301 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/
2023-02-26 18:55:38 +01:00
Arkadiusz Fal
f4fb31e9e2 Translated using Weblate (Hindi)
Currently translated at 78.1% (340 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/
2023-02-26 18:55:38 +01:00
Anonymous
44ff68b3c1 Translated using Weblate (Hindi)
Currently translated at 78.1% (340 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/
2023-02-26 18:55:38 +01:00
Anonymous
737f762bfb Translated using Weblate (French)
Currently translated at 90.8% (395 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-02-26 18:55:38 +01:00
Arkadiusz Fal
2844838485 Update workflows 2023-02-26 18:54:12 +01:00
Arkadiusz Fal
f39accdf7e Update workflows 2023-02-26 18:42:43 +01:00
github-actions[bot]
75ac11c60d Bump build number to 137 2023-02-25 21:29:30 +00:00
Arkadiusz Fal
0d3138b36e Update release workflow 2023-02-25 22:18:24 +01:00
Arkadiusz Fal
582f07388e Update changelog 2023-02-25 17:21:23 +01:00
Arkadiusz Fal
2b18f0cffa Add hiding short videos 2023-02-25 17:18:35 +01:00
Arkadiusz Fal
ef401168ec Use only tab navigation on iPhone 2023-02-25 16:46:09 +01:00
Arkadiusz Fal
0c7af0351b Add bump build action 2023-02-25 14:51:50 +01:00
Arkadiusz Fal
0995e3ee2f Add Arabic, Portugese and Portugese (Brazil) localizations 2023-02-25 14:51:50 +01:00
Arkadiusz Fal
cbd95ebc58 Update xcodeproj 2023-02-25 14:51:50 +01:00
Arkadiusz Fal
528863bf1b Update release workflow 2023-02-25 14:51:50 +01:00
Arkadiusz Fal
c931aa09a2 Update packages 2023-02-25 14:51:50 +01:00
Arkadiusz Fal
089b4a03b3 Bump build number to 136 2023-02-24 18:23:26 +01:00
Arkadiusz Fal
1d0615c9e1 Change controls tap behavior 2023-02-24 18:23:26 +01:00
Arkadiusz Fal
8ca499756a Minor fixes 2023-02-24 18:23:26 +01:00
jonnysemon
35d20763fb Translated using Weblate (Arabic)
Currently translated at 97.4% (424 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2023-02-24 18:19:12 +01:00
tototof
0f9efc54ee Translated using Weblate (French)
Currently translated at 90.8% (395 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-02-24 18:19:12 +01:00
ssantos
df9dfbc55e Translated using Weblate (Portuguese)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt/
2023-02-24 18:19:12 +01:00
ssantos
281e7cf645 Added translation using Weblate (Portuguese) 2023-02-24 18:19:12 +01:00
Arkadiusz Fal
de153ab9ac Add workflow for building and TestFlight 2023-02-24 18:17:44 +01:00
Arkadiusz Fal
ddcc134d75 Update fastlane config 2023-02-24 18:17:44 +01:00
Eric Rudz
8c2157fd0e Translated using Weblate (French)
Currently translated at 90.8% (395 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-02-19 14:21:49 +01:00
zoulnix
315949daef Translated using Weblate (Swedish)
Currently translated at 24.1% (105 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/sv/
2023-02-19 14:21:49 +01:00
zoulnix
ec202461e5 Translated using Weblate (Swedish)
Currently translated at 18.1% (79 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/sv/
2023-02-19 14:21:49 +01:00
zoulnix
7f38197ce2 Added translation using Weblate (Swedish) 2023-02-19 14:21:49 +01:00
Arkadiusz Fal
48d14c623c Fix build issue 2023-02-19 14:19:37 +01:00
Arkadiusz Fal
3d45aa357e Dismiss keyboard interactively 2023-02-06 21:52:21 +01:00
Arkadiusz Fal
cfd85a018e Fix Invidious channel videos 2023-02-06 21:42:16 +01:00
Arkadiusz Fal
2b238c9b7b Fix Xcode warning 2023-02-05 17:06:20 +01:00
Arkadiusz Fal
0eda2c0485 Change mpv qos 2023-02-05 17:06:20 +01:00
Arkadiusz Fal
71a3d54201 Always use WebImage with Piped 2023-02-05 14:24:17 +01:00
Arkadiusz Fal
04b4659cf7 Minor fixes 2023-02-05 14:24:17 +01:00
Arkadiusz Fal
a763e1d097 Fix Favorites performance 2023-02-05 14:24:17 +01:00
Arkadiusz Fal
3661d06080 Hide unavailable tools in local video details 2023-02-05 14:24:17 +01:00
Arkadiusz Fal
3c971f582f Add shortcut to watch next hide 2023-02-05 14:24:17 +01:00
Arkadiusz Fal
ff5893f8db Fix refreshing network state 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
5de52e55f6 Fix banner content shape 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
52f96c3837 Fix thumnbnail placeholders 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
f86fd37c50 Add scroll to dismiss keyboard 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
809bcd183a New playback settings sheet 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
c01ff56854 Watch next improvements, clear queue buttons 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
18cbbd3c90 Fix crashes 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
6594d5ba95 Remove initializer of player 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
a73ea78edf Fix button 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
53092e48cf Fix possible crash 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
3988e0ca9f Change icon 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
69e9869fe9 Disable gradient on iOS 14 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
5b482323a0 Add stepper and scroll dismiss keyboard in settings 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
79df3c6e8b Fix gradient height 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
78940a005b Accounts loading improvements 2023-02-05 14:24:16 +01:00
Arkadiusz Fal
a32f417776 Throttle controls bar gestures 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
c432aa3b9a Minor fixes 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
37d8873424 Hide player on cancelling error of loading 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
c9dda5efb7 Allow to remove video not fully opened 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
859efb2dbe Fix button animation 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
32e98b6410 Fix player 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
ce9c3fbd74 Fix gradient 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
41ac15b204 Relax logging 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
f058b0c30c Add Catalan language 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
16e365ce6c Minor improvements 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
d80929bd88 Player controls settings 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
b7f0ec4bfb Back to previous date format 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
8631389b8f Seek settings 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
5d4983d6d2 Actions buttons settings 2023-02-05 14:24:15 +01:00
Arkadiusz Fal
636e8205fe Watch Next menu improvements 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
2ce903b6c3 Settings for new features 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
3de18da7a7 Controls gradient 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
bdb1f032a9 Home queue 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
8ab42322fc Refactor context menu 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
441f3c9e80 Don't show not available details 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
9bde57e4eb New details page button 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
467f50715c Fix channel padding 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
39fc23c5dc Watch Next behavior and settings 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
b90c856e21 Fix banner indicator position 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
af85c67163 Minor fixes 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
ba1ec7c559 Player layout changes 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
328119f0e0 Player view layout changes 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
eca685ae29 Watch next view 2023-02-05 14:24:14 +01:00
Arkadiusz Fal
fcf527fa87 Player bar visibility modes and settings 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
8e5bafba58 Documents navigation 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
cf0572a94b Instance and account form and validation improvements 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
9f3a94137c Fix thumbnail view 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
23b6edfcc5 Fix avc/av1 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
894b9bdcd7 Fix favorite subscriptions visibility 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
485c4315ac Add channel description 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
adc32d5ae1 Add playing indicator 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
5054f828de Use CachedAsyncImage if possible 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
26d3fba0f3 Feed count model 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
4acf9284f4 Revert "Feed calculation logic change"
This reverts commit 2624b7dd0e07674f3639fb18618a5e11b3e9f53f.
2023-02-05 14:24:13 +01:00
Arkadiusz Fal
4c0fae19ee Feed calculation logic change 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
ee9e8bc064 Fix possible crash 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
f39b440b21 Refactor 2023-02-05 14:24:13 +01:00
Arkadiusz Fal
b621eba236 Use menu for add to playlists 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
6b30e804c0 Don't close video on error alert until dismissed 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
fb22fb762c Hide thumbnail in playlist if not available 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
b8f693e213 Caching for favorites 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
a2c69e0fed Fix favorite button in playlists 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
960e7bdaf1 More channel cache 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
4330856c5e Less obnoxious error handling 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
a9c8053474 Rename button 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
d19446d37d Minor layout fixes 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
f1551141e3 Fix cells on tvOS 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
88926edb98 Minor label change 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
4a7d6ace24 Add button for toggling watch status 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
afe7d72352 Playlist submenu and play buttons 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
f090fc9fea Add to playlist menu 2023-02-05 14:24:12 +01:00
Arkadiusz Fal
a5e5604d84 Fix images size in sidebar 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
a7b489d081 Remove progress view from video banner 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
404e2e6768 Fix updating watch history 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
12005e63e1 Fix channel videos cache 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
cb6eaa180e Fix history view 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
f6d3d5504a Improve recents thumbnails urls 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
67e32f4da1 Fix channel links 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
a156ef6a3f Add action to mark channel feed as watched/unwatched 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
b55c6f8619 Fix handling feed 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
2812ea0301 Fix settings window on Mac 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
71c666ebfe Fix placeholders 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
2028446d03 Fix calculating feed badge 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
037d3a0481 Add channels thumbnails to cells and list 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
3b31f21c81 Channels caching 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
d9622cf24c Add play unwatched on macOS 2023-02-05 14:24:11 +01:00
Arkadiusz Fal
17b82945b3 Fix cache resources in APIs 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
488b0d2e9b Fix favorite button in playlist 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
1a1ef7e76c Video listing improvement 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
4fc2f964b2 Change date parsing from Invidious 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
2a7c51abda Don't perform feed operations if not signed in 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
14b894a2e9 Fix unneeded binding 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
0517b4feea Fix flicker in feed 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
e390bbcf09 Hide channel on list item if inside channel view 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
05b4da9e25 Bump build number 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
e3b1e19645 Disable PeerTube 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
93c9298c07 Fix badge backport for tvOS 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
b3ddf4a153 Show badge for channels subscriptions 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
d5626b877c Don't observe thumbnails model in thumbnails view 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
e4d583a263 Cache settings 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
8f2b570163 Fix account button on macOS 2023-02-05 14:24:10 +01:00
Arkadiusz Fal
1cd2dbe5f7 Add list views for channels, playlists and placeholders 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
d38a507be5 Video banner layout fixes 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
57b2276f36 Improve video lists 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
8c1d900a63 Unwatched videos in subscriptions 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
02b30394ed Fix parsing Piped comments 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
1c746bc8e0 Use cache for loading player queue and history 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
33a131d2ca Fix instance long description 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
b351b31e05 UI improvements 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
a35d697ebe Cache fixes 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
25da312966 Listing styles 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
c2d16774f7 Menus improvements 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
09ff16d464 Favorited playlists belong to account 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
115f9fea67 Fix playlists in favorites 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
d02bb23e57 New account selection menu 2023-02-05 14:24:09 +01:00
Arkadiusz Fal
4c143f6d88 Search navigation improvements 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
d4ec360581 Channel playlists cache 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
38593ed488 Show channel avatars in sidebar 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
bc42a2fa88 User playlists cache 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
33bd052fdc Fix placeholder cells 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
899e66b204 Channel view and other small improvements 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
006423682b Playlists fixes 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
385dbbbef2 Typo fix 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
3c546f96df Limit number of stored feed items 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
f6a261662c Favorites improvements 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
784fc8cfc6 Search improvements 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
e3daa738ce Fix thumbnails 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
5e0f13cace Subscribed channels list in tab navigation 2023-02-05 14:24:08 +01:00
Arkadiusz Fal
7ba743afbc Use ImageManager of SDWebImageSwiftUI 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
92999118fd Detail badges improvement 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
74d65f6ab8 Browser player bar as overlay 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
7df397b662 Playlists menu for iOS 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
e9c219a76e Subscribed channels cache 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
971beddc8d Feed cache 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
eae04c9382 Sign in required fixes 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
2e3454a18f Search and trending menus for iOS 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
64146b26c2 Videos cache model 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
0c960c2461 Peertube fixes 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
faf2469e04 Initial PeerTube Support 2023-02-05 14:24:07 +01:00
Arkadiusz Fal
72ea17b257 Bump build and version number 2023-02-05 14:24:07 +01:00
Jorge Torres
c0ef66fa1f Translated using Weblate (Spanish)
Currently translated at 26.2% (114 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2023-02-05 14:23:33 +01:00
Mostafa Abd El-Fattah
c791e26ab6 Translated using Weblate (Arabic)
Currently translated at 97.0% (422 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2023-01-08 19:17:01 +01:00
Ahmed Eissa
af15fa31b6 Translated using Weblate (Arabic)
Currently translated at 89.4% (389 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2023-01-08 19:17:01 +01:00
metezd
5c5b7c5512 Translated using Weblate (Turkish)
Currently translated at 52.8% (230 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2023-01-08 19:17:01 +01:00
Ahmed Eissa
67672c76e5 Added translation using Weblate (Arabic) 2023-01-08 19:17:01 +01:00
Nidi
afb832ec7b Translated using Weblate (Azerbaijani)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/
2023-01-08 19:17:01 +01:00
Arkadiusz Fal
298652a82a
Update README.md 2023-01-08 16:07:13 +01:00
Arkadiusz Fal
7248b50600
Create FUNDING.yml 2022-12-26 11:26:04 +01:00
Oleksii Filonenko
46639c4e16 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2022-12-20 19:08:43 +01:00
metezd
b9946add67 Translated using Weblate (Turkish)
Currently translated at 49.6% (216 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2022-12-20 19:08:43 +01:00
Mattia Borda
8c4b275ead Translated using Weblate (Italian)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/
2022-12-20 19:08:43 +01:00
Austrapede
17268b867c Translated using Weblate (Catalan)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ca/
2022-12-20 19:08:43 +01:00
Gamom Yang
2c7508db22 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/zh_Hans/
2022-12-20 19:08:43 +01:00
joaooliva
c09893389f Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2022-12-20 19:08:43 +01:00
Mattia Borda
0d876cfadd Translated using Weblate (Italian)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/
2022-12-20 19:08:43 +01:00
Bharathi
b00fab116f Translated using Weblate (German)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-12-20 19:08:43 +01:00
Austrapede
d3803c76bf Added translation using Weblate (Catalan) 2022-12-20 19:08:43 +01:00
Arkadiusz Fal
5cac035e89 Bump build number 2022-12-05 11:04:25 +01:00
Arkadiusz Fal
e5bfcfadb4 Fix displaying subscriptions label in channel 2022-12-05 10:13:20 +01:00
Arkadiusz Fal
6394d129de Bump build number 2022-12-05 10:13:20 +01:00
Arkadiusz Fal
7bb978d02c Localization fixes 2022-12-05 10:13:20 +01:00
Arkadiusz Fal
1d64c84137 Add Chinese (Simplified) language 2022-12-05 10:13:20 +01:00
Arkadiusz Fal
f1664d026c Fix bookmarks 2022-12-05 10:13:20 +01:00
Arkadiusz Fal
220aef65ce Improve channel content type picker 2022-12-05 10:13:20 +01:00
Arkadiusz Fal
82cd08d44e Fix updating video watches 2022-12-05 10:13:20 +01:00
Arkadiusz Fal
210f28da37 Fix Invidious accounts validation 2022-12-05 10:13:19 +01:00
Arkadiusz Fal
79473951d7 Persist opened video details page 2022-12-05 10:13:19 +01:00
Arkadiusz Fal
5625067456 Unify channel and playlist close buttons 2022-12-05 10:13:19 +01:00
Arkadiusz Fal
7a2dcc3cf1 Reset sponsorblock segments for local videos 2022-12-05 10:13:19 +01:00
Arkadiusz Fal
33abe4d487 Channel pages 2022-12-05 10:13:19 +01:00
Arkadiusz Fal
909f035399 Minor controls changes 2022-12-05 10:13:19 +01:00
Arkadiusz Fal
779bf190f4 Fix opening URLs via share sheet 2022-12-05 10:13:19 +01:00
Arkadiusz Fal
e8515d63e3 Don't try save bookmarks for remote URLs 2022-12-05 10:13:19 +01:00
Arkadiusz Fal
53620c4d35 Fix default alignment swiftlint offense 2022-12-05 10:13:19 +01:00
Arkadiusz Fal
84e50ddd25 Fix watches 2022-12-05 10:13:19 +01:00
Arkadiusz Fal
0d333b5583 Replace environment objects with observed objects 2022-12-05 10:13:19 +01:00
mere
23fa0968c6 Translated using Weblate (Romanian)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2022-12-05 10:12:19 +01:00
metezd
9fb72a3fa4 Translated using Weblate (Turkish)
Currently translated at 31.4% (137 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2022-12-05 10:12:19 +01:00
Bharathi
5b72182ad7 Translated using Weblate (German)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-12-05 10:12:19 +01:00
Arkadiusz Fal
031a424dc2 Translated using Weblate (Polish)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-12-05 10:12:19 +01:00
Anonymous
71e217c6b5 Translated using Weblate (English)
Currently translated at 100.0% (435 of 435 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-12-05 10:12:19 +01:00
Arkadiusz Fal
2d27459c41 Update README 2022-12-05 00:36:17 +01:00
Arkadiusz Fal
fbfe319b36 Update templates 2022-12-05 00:32:08 +01:00
Bharathi
8b5a3c2af1 Create config.yml
adding the issues template config.yml
2022-12-05 00:31:20 +01:00
Bharathi
922da341f0 Update feature-request.yaml 2022-12-05 00:31:20 +01:00
Bharathi
c9a60b0d02 Rename feature_request.yaml to feature-request.yaml 2022-12-05 00:31:20 +01:00
Bharathi
b293f68d44 Create feature_request.yaml
adding updated feature request template
2022-12-05 00:31:20 +01:00
Bharathi
f2b4f1680f Create bug-report.yaml
adding updated bug report
2022-12-05 00:31:19 +01:00
Arkadiusz Fal
d4819e17dc Bump build number 2022-12-04 13:32:30 +01:00
Arkadiusz Fal
54334d2ce5 Fix button on macOS 2022-12-04 13:32:30 +01:00
Arkadiusz Fal
548abd63a6 Bump build number 2022-12-04 13:32:30 +01:00
Arkadiusz Fal
da5e582b59 Localization fix 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
45705e0b29 Update project settings 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
201ae0fde1 Fix tool active position 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
57e0356c8e Minor recent documents fixes 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
8d53d7fe79 Don't show directories in recent documents 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
5ded159dec Minor fixes 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
9949303166 Update watched date at every watch 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
b16ded4537 Minor fixes 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
35c553cd05 Fix calculating progress on banner 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
cda6dc8b9b Add close item action to AVPlayer on tvOS 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
8054c9f44a Minor fixes 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
5d7bc809cb Fix browser settings labels 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
708c6fc112 Fix buttons on tvOS 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
36ccda23a1 Fix handling player closing on tvOS 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
88cc4db1a3 Bump build number 2022-12-04 13:32:29 +01:00
Arkadiusz Fal
a14b0b12fa Minor style fixes 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
99769380fc Localization and platform availability fixes 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
780b7ab130 Localizations fixes 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
be2af82300 Change playback rates for MPV 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
bc1571a746 Add recent documents to home on iOS 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
8ec06b0d59 Add setting for player action buttons label style 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
59eda3af26 Don't show empty channel button 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
a1ade66594 Fix dislikes counter 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
2dbc50dc71 Fix url parsing 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
a629bec1ff Minor layout fixes 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
414b6210c0 Fix toolbar tabs 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
c31a5ee2e0 Fix redrawing video description 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
3b01fe34c3 Minor fixes 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
fa15a18374 Fix possible crash 2022-12-04 13:32:28 +01:00
Arkadiusz Fal
09de2cbbab Add overlay model visibility methods 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
daaf2417b7 Add Czech language 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
66c9fa773c Fix visible tvOS sections 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
78ff495927 Add clear history button to home 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
8727fb1e30 Bump build number 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
b92d71d79b Fix likes 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
ae24dea4d6 Fix urlbookmarks 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
de7eff389f Fix issue with toolbar tools switching 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
09aabcf1e5 Preload history videos on home appear 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
dba8fa59b2 Bump version number 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
c409673ff1 Update fastlane 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
48a1ba46bc Bump build number 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
876f470b89 Fix opening files 2022-12-04 13:32:27 +01:00
Arkadiusz Fal
f66451db35 Fix tvOS build 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
041a28e7a0 Video details toolbar and inspector settings 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
7cc3cd950b Video details layout fixes 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
b6067a3f67 Show video properties 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
e58afcbf3f Inspector and other layouts fixes 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
4ee1383f3a New video details 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
9bea3b623d Fix retry video buttons 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
0f8c82bbca Add close button to open videos view on Mac 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
bd08cf02ad Hide mouse cursor with controls in fullscreen on Mac 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
4206b25f88 Add Romanian language 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
b45ceaec59 Add retry button to video load error alert 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
815d0b3ae8 Cancel loading assets on switching backend 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
fe9b7c03ca Fix orientation issues 2022-12-04 13:32:26 +01:00
Arkadiusz Fal
0049a68839 Revert "Remove "Honor system orientation lock""
This reverts commit 2d51f6adff3de607bf9a09d430d6826b9d7bba61.
2022-12-04 13:32:25 +01:00
Arkadiusz Fal
ac99971d03 Fix progress view on banner 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
256b1671c1 Reorder locations settings 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
61d235780d Various minor fixes 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
4657af2f3d Documents tab with file sharing 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
ccded28468 Improve bookmarks loading 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
eb9924bd4a Layout fixes 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
eb1e440ed2 Fix environment objects 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
ab5afbc9a4 Add Edit favorites 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
2c004b81fe Close item if could not be opened 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
ebc9ea0031 Fix loading message 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
4a6350f3fe Minor trending layout fixes 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
51bd46b3ae Home settings 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
ef1f95a3ad Fix ignoring loaded video details 2022-12-04 13:32:25 +01:00
Arkadiusz Fal
fa0523d3c6 Improve sections visibility 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
4c2d473caa Reorganize locations settings 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
611bcde238 Fix #338 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
5d033cbdfc Use anonymous account after adding first instance 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
80d8d66986 Local videos fixes 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
4cb9e24796 Fix opening file stream label 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
9be29f581b Fix queue row opening 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
730ba1ea2e Improve open videos layouts 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
6ca6a40aa1 Fix urls handling from pasteboard 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
8978bf3f33 Layout fixes 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
5eafbb1151 Remove "Show history in player" 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
99e20f54a2 Attempt to fix orientation lock on iOS 16 2022-12-04 13:32:24 +01:00
Arkadiusz Fal
a3ba1cb6df Improve opening videos 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
38454720de Add share extension, rework bookmarks model 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
973596f56c Remove Safari extension 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
ca8e6e7a1a Remove imported types 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
786906bd0d Fix sidebar 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
c821285664 Fix crashes when using mpv 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
01d8e28d7c Add more playback rates for MPV 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
a44cce462a Preserve playback rate between restarts 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
04a5224ab0 Minor fixes 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
21b61982ad tvOS layout fixes 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
d779ec7215 Remove demo app 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
67d2c33771 Hide buttons on tvOS 2022-12-04 13:32:23 +01:00
Arkadiusz Fal
cb81a687bc Improve EOF handling with MPV 2022-12-04 13:32:22 +01:00
Arkadiusz Fal
aeb7bac886 Minor open videos improvements 2022-12-04 13:32:22 +01:00
Arkadiusz Fal
50479d6b2f Fix share actions 2022-12-04 13:32:22 +01:00
Arkadiusz Fal
6be5342183 Fix possible crash 2022-12-04 13:32:22 +01:00
Arkadiusz Fal
9eb8228fcf Add open from clipboard 2022-12-04 13:32:22 +01:00
Arkadiusz Fal
4a26ef2839 Fix clear history, add clear cache 2022-12-04 13:32:22 +01:00
Arkadiusz Fal
4d94126abd Load images with low priority 2022-12-04 13:32:22 +01:00
Arkadiusz Fal
402d1a2f79 Opening videos by URL and local files 2022-12-04 13:32:22 +01:00
Arkadiusz Fal
34f7621f36 Update packages 2022-12-04 13:32:22 +01:00
Arkadiusz Fal
31dfa747be Add Ukrainian language 2022-12-04 13:32:22 +01:00
Arkadiusz Fal
dbb7134eb7 Rename Favorites to Home
Fix #329
2022-12-04 13:32:22 +01:00
Arkadiusz Fal
e4588478c9 Bump build number 2022-12-04 13:32:21 +01:00
Arkadiusz Fal
0c3c8e53fd Translated using Weblate (Polish)
Currently translated at 100.0% (434 of 434 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-12-04 13:32:08 +01:00
Anonymous
f70450693a Translated using Weblate (English)
Currently translated at 100.0% (434 of 434 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-12-04 13:31:12 +01:00
Lachtan
d28cfe8d0c Translated using Weblate (Czech)
Currently translated at 99.5% (431 of 433 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/cs/
2022-12-04 13:25:19 +01:00
Arkadiusz Fal
a1d061b03a Translated using Weblate (Polish)
Currently translated at 100.0% (433 of 433 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-12-04 13:25:19 +01:00
Anonymous
3cfd0be2a7 Translated using Weblate (English)
Currently translated at 100.0% (433 of 433 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-12-04 13:25:19 +01:00
Gamom Yang
1c93501798 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (431 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/zh_Hans/
2022-12-04 13:25:19 +01:00
metezd
4fc7d787c9 Translated using Weblate (Turkish)
Currently translated at 14.8% (64 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2022-12-04 13:25:19 +01:00
Justin Reyes
05eb7e22f7 Translated using Weblate (Spanish)
Currently translated at 24.1% (104 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2022-12-04 13:25:19 +01:00
joaooliva
21ddbc1167 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (431 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2022-12-04 13:25:19 +01:00
Mattia Borda
ce79c495ce Translated using Weblate (Italian)
Currently translated at 100.0% (431 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/
2022-11-20 18:53:20 +01:00
mere
b8cbc4b281 Translated using Weblate (Romanian)
Currently translated at 100.0% (431 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2022-11-20 14:41:01 +01:00
_AMD_
a5662ea976 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (431 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2022-11-20 14:41:01 +01:00
Léo L
b0248ab818 Translated using Weblate (French)
Currently translated at 84.9% (366 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2022-11-20 14:41:01 +01:00
Bharathi
94334d5b48 Translated using Weblate (German)
Currently translated at 100.0% (431 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-11-20 14:41:01 +01:00
Arkadiusz Fal
5556e6ee16 Translated using Weblate (Polish)
Currently translated at 100.0% (431 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-11-20 14:41:01 +01:00
Anonymous
9997d7ae93 Translated using Weblate (English)
Currently translated at 100.0% (431 of 431 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-11-20 14:41:01 +01:00
mere
ef1eb5765e Translated using Weblate (Romanian)
Currently translated at 99.5% (425 of 427 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2022-11-19 15:17:25 +01:00
Bharathi
a01667d552 Translated using Weblate (German)
Currently translated at 99.5% (425 of 427 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-11-19 15:17:25 +01:00
Arkadiusz Fal
9bc7165237 Translated using Weblate (Polish)
Currently translated at 100.0% (427 of 427 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-11-19 15:17:25 +01:00
Anonymous
386eb237c6 Translated using Weblate (English)
Currently translated at 100.0% (427 of 427 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-11-19 15:17:25 +01:00
Bharathi
e67d6494ec Translated using Weblate (German)
Currently translated at 99.0% (421 of 425 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-11-19 00:54:08 +01:00
Arkadiusz Fal
33ea69b8dc Translated using Weblate (Polish)
Currently translated at 100.0% (425 of 425 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-11-19 00:54:08 +01:00
Anonymous
70c8a9d8d9 Translated using Weblate (English)
Currently translated at 100.0% (425 of 425 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-11-19 00:54:08 +01:00
Bharathi
9ebc29ea29 Translated using Weblate (German)
Currently translated at 90.8% (377 of 415 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-11-19 00:15:22 +01:00
Bharathi
da3a8b070a Translated using Weblate (German)
Currently translated at 90.6% (376 of 415 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-11-19 00:15:22 +01:00
Arkadiusz Fal
e7a878bfc9 Translated using Weblate (Polish)
Currently translated at 100.0% (415 of 415 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-11-19 00:15:22 +01:00
Anonymous
87d2547ca2 Translated using Weblate (English)
Currently translated at 100.0% (415 of 415 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-11-19 00:15:22 +01:00
Arkadiusz Fal
926afd5fa6 Translated using Weblate (English)
Currently translated at 100.0% (373 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-11-19 00:15:22 +01:00
Anonymous
696b40338c Translated using Weblate (English)
Currently translated at 100.0% (373 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-11-19 00:15:22 +01:00
Arkadiusz Fal
b18602c1ce Translated using Weblate (English)
Currently translated at 100.0% (371 of 371 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-11-19 00:15:22 +01:00
Arkadiusz Fal
53eafbdb8d Translated using Weblate (English)
Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-11-19 00:15:22 +01:00
Anonymous
2379b028f1 Translated using Weblate (English)
Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-11-19 00:15:22 +01:00
Gamom Yang
f027048e8f Translated using Weblate (Chinese (Simplified))
Currently translated at 54.9% (200 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/zh_Hans/
2022-11-18 23:46:33 +01:00
Net
95da791ea5 Translated using Weblate (Azerbaijani)
Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/
2022-11-18 23:46:33 +01:00
Gamom Yang
27fc8783f0 Added translation using Weblate (Chinese (Simplified)) 2022-11-18 23:46:33 +01:00
Lachtan
2e4896d558 Translated using Weblate (Czech)
Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/cs/
2022-11-15 20:44:07 +01:00
mere
fdaf8c8f66 Translated using Weblate (Romanian)
Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2022-11-15 20:44:07 +01:00
Lachtan
e04d3f9f72 Added translation using Weblate (Czech) 2022-11-15 20:44:07 +01:00
mere
f2ed09acc0 Translated using Weblate (Romanian)
Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2022-11-13 13:48:36 +01:00
mere
2cbbb75b4e Added translation using Weblate (Romanian) 2022-11-13 13:48:36 +01:00
Weblate (bot)
f2740419eb
Translations update from Hosted Weblate (#339)
* Translated using Weblate (Turkish)

Currently translated at 19.2% (72 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/

* Translated using Weblate (German)

Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 83.1% (311 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/

* Translated using Weblate (Hindi)

Currently translated at 93.5% (350 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/

* Translated using Weblate (Italian)

Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/

* Translated using Weblate (French)

Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/

* Translated using Weblate (Polish)

Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/

* Translated using Weblate (Spanish)

Currently translated at 28.6% (107 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/

* Translated using Weblate (English)

Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/

* Translated using Weblate (Turkish)

Currently translated at 19.0% (71 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/

* Translated using Weblate (German)

Currently translated at 100.0% (373 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 83.1% (310 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/

* Translated using Weblate (Hindi)

Currently translated at 93.5% (349 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/

* Translated using Weblate (Italian)

Currently translated at 100.0% (373 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/

* Translated using Weblate (French)

Currently translated at 100.0% (373 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/

* Translated using Weblate (Polish)

Currently translated at 100.0% (373 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (373 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/

* Translated using Weblate (Spanish)

Currently translated at 28.4% (106 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (373 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (373 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/

* Translated using Weblate (English)

Currently translated at 100.0% (373 of 373 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/

* Translated using Weblate (Turkish)

Currently translated at 18.8% (70 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/

* Translated using Weblate (German)

Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/

* Translated using Weblate (Spanish)

Currently translated at 28.2% (105 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/

* Translated using Weblate (French)

Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/

* Translated using Weblate (Hindi)

Currently translated at 93.5% (348 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 83.0% (309 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/

* Translated using Weblate (Polish)

Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/

* Translated using Weblate (English)

Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/

* Translated using Weblate (Turkish)

Currently translated at 18.3% (68 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/

* Translated using Weblate (German)

Currently translated at 100.0% (370 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (370 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (370 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (370 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (370 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/

* Translated using Weblate (French)

Currently translated at 100.0% (370 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/

* Translated using Weblate (Hindi)

Currently translated at 93.5% (346 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 82.9% (307 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/

* Translated using Weblate (Polish)

Currently translated at 100.0% (370 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/

* Translated using Weblate (English)

Currently translated at 100.0% (371 of 371 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/

* Translated using Weblate (Turkish)

Currently translated at 18.1% (67 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/

* Translated using Weblate (German)

Currently translated at 100.0% (369 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (369 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/

* Translated using Weblate (Spanish)

Currently translated at 27.9% (103 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (369 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (369 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (369 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/

* Translated using Weblate (French)

Currently translated at 100.0% (369 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/

* Translated using Weblate (Hindi)

Currently translated at 93.4% (345 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 82.9% (306 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/

* Translated using Weblate (Polish)

Currently translated at 100.0% (369 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/

* Translated using Weblate (English)

Currently translated at 100.0% (370 of 370 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/

* Translated using Weblate (Turkish)

Currently translated at 17.9% (66 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/

* Translated using Weblate (German)

Currently translated at 100.0% (368 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (368 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/

* Translated using Weblate (Spanish)

Currently translated at 27.7% (102 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (368 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (368 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (368 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/

* Translated using Weblate (French)

Currently translated at 100.0% (368 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/

* Translated using Weblate (Hindi)

Currently translated at 93.4% (344 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 82.8% (305 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/

* Translated using Weblate (Polish)

Currently translated at 100.0% (368 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/

* Translated using Weblate (English)

Currently translated at 100.0% (369 of 369 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/

* Translated using Weblate (Turkish)

Currently translated at 17.7% (65 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/

* Translated using Weblate (German)

Currently translated at 100.0% (367 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (367 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (367 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (367 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (367 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/

* Translated using Weblate (French)

Currently translated at 100.0% (367 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/

* Translated using Weblate (Hindi)

Currently translated at 93.4% (343 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 82.8% (304 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/

* Translated using Weblate (Polish)

Currently translated at 100.0% (367 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/

* Translated using Weblate (English)

Currently translated at 100.0% (368 of 368 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/

* Translated using Weblate (Turkish)

Currently translated at 17.4% (64 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/

* Translated using Weblate (German)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/

* Translated using Weblate (French)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/

* Translated using Weblate (Hindi)

Currently translated at 93.4% (342 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 82.7% (303 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/

* Translated using Weblate (Polish)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/

* Translated using Weblate (Italian)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/

* Translated using Weblate (English)

Currently translated at 100.0% (367 of 367 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/

* Translated using Weblate (Turkish)

Currently translated at 17.4% (64 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/

* Translated using Weblate (German)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/

* Translated using Weblate (French)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/

* Translated using Weblate (Hindi)

Currently translated at 93.4% (342 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 82.7% (303 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/

* Translated using Weblate (Polish)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/

* Translated using Weblate (Italian)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/

* Translated using Weblate (English)

Currently translated at 100.0% (366 of 366 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/

* Translated using Weblate (Turkish)

Currently translated at 17.0% (62 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/

* Translated using Weblate (German)

Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/

* Translated using Weblate (French)

Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/

* Translated using Weblate (Hindi)

Currently translated at 93.4% (340 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 82.6% (301 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/

* Translated using Weblate (Polish)

Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/

* Translated using Weblate (Italian)

Currently translated at 100.0% (364 of 364 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/

* Translated using Weblate (English)

Currently translated at 100.0% (365 of 365 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/

Co-authored-by: Arkadiusz Fal <arek@arekf.net>
2022-11-09 21:36:42 +01:00
Mario
93e9ce4a0c Translated using Weblate (Spanish)
Currently translated at 28.6% (107 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2022-11-09 21:21:59 +01:00
musiclover382
a00e3243fb Translated using Weblate (Italian)
Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/
2022-11-09 21:21:59 +01:00
Arthegor
b6834d2878 Translated using Weblate (French)
Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2022-11-09 21:21:59 +01:00
joaooliva
1018a9ea53 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2022-11-09 21:21:59 +01:00
joaooliva
fe064ab5e7 Added translation using Weblate (Portuguese (Brazil)) 2022-11-09 21:21:59 +01:00
Dmytro
1ff07acfed Translated using Weblate (Ukrainian)
Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2022-11-09 21:21:59 +01:00
_AMD_
f968a548cb Translated using Weblate (Ukrainian)
Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2022-11-09 21:21:59 +01:00
_AMD_
6fa3d07b9e Translated using Weblate (Ukrainian)
Currently translated at 98.6% (369 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2022-11-09 21:21:59 +01:00
Dmytro
298560d2a0 Translated using Weblate (Ukrainian)
Currently translated at 98.1% (367 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2022-11-09 21:21:59 +01:00
_AMD_
1b6e929677 Translated using Weblate (Ukrainian)
Currently translated at 98.1% (367 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2022-11-09 21:21:59 +01:00
_AMD_
f07a65840c Added translation using Weblate (Ukrainian) 2022-11-09 21:21:59 +01:00
Mario
79f461501d Translated using Weblate (Spanish)
Currently translated at 19.2% (72 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/es/
2022-11-09 21:21:59 +01:00
metezd
77316434dd Translated using Weblate (Turkish)
Currently translated at 19.2% (72 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2022-11-09 21:21:59 +01:00
Jesus H. Christ
9645fb81ed Translated using Weblate (Turkish)
Currently translated at 19.2% (72 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2022-11-09 21:21:59 +01:00
Arthegor
d568ca21fd Translated using Weblate (French)
Currently translated at 74.5% (279 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2022-11-09 21:21:59 +01:00
Mario
e0a1f8a633 Added translation using Weblate (Spanish) 2022-11-09 21:21:59 +01:00
Arkadiusz Fal
0433d2e875 Update README 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
70bee1a1ef Remove donations link
On dearest App Store Review Team request
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1fe01808a4 Add demo instance, remove public manifest 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8ab6a0fa89 Add Azerbaijani and Italian locales 2022-10-27 18:03:57 +02:00
Nizami
ffb1df1e39 Translated using Weblate (Turkish)
Currently translated at 1.8% (7 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/tr/
2022-10-27 18:03:57 +02:00
Nizami
05b4679d68 Translated using Weblate (Azerbaijani)
Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/
2022-10-27 18:03:57 +02:00
musiclover382
69155447d0 Translated using Weblate (Italian)
Currently translated at 50.8% (190 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/
2022-10-27 18:03:57 +02:00
Nizami
e3cd2c5d45 Translated using Weblate (Azerbaijani)
Currently translated at 84.7% (317 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/
2022-10-27 18:03:57 +02:00
Nizami
8d439eaded Added translation using Weblate (Turkish) 2022-10-27 18:03:57 +02:00
Nizami
a9411ac020 Translated using Weblate (Azerbaijani)
Currently translated at 0.2% (1 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/az/
2022-10-27 18:03:57 +02:00
Nizami
96946d2312 Added translation using Weblate (Azerbaijani) 2022-10-27 18:03:57 +02:00
Bharathi
43bf64dc82 Translated using Weblate (German)
Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2188939710 Deleted translation using Weblate (Belarusian) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1a19416b5a Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0652af65a4 Add translations info to help 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fb1358cfc3 Localizations fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ba9460483f Translated using Weblate (Polish)
Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Anonymous
56d895d5ae Translated using Weblate (English)
Currently translated at 100.0% (374 of 374 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ad01f763aa Translated using Weblate (Polish)
Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5e8c29e77b Translated using Weblate (English)
Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Anonymous
4672c158b1 Translated using Weblate (English)
Currently translated at 100.0% (372 of 372 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Anonymous
0d72670329 Translated using Weblate (English)
Currently translated at 100.0% (352 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Weblate (bot)
53ddea9295 Translations update from Hosted Weblate (#324)
Remove unneeded strings
2022-10-27 18:03:57 +02:00
musiclover382
dc039f76d7 Translated using Weblate (Italian)
Currently translated at 33.2% (117 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/
2022-10-27 18:03:57 +02:00
musiclover382
ba33c8bcb7 Added translation using Weblate (Italian) 2022-10-27 18:03:57 +02:00
chadify
f0658f0b84 Translated using Weblate (Hindi)
Currently translated at 100.0% (352 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/
2022-10-27 18:03:57 +02:00
chadify
5d0a08d8aa Translated using Weblate (Hindi)
Currently translated at 100.0% (352 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/
2022-10-27 18:03:57 +02:00
Léo L
3609fb162f Translated using Weblate (French)
Currently translated at 66.7% (235 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0bfba57291 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8fa3286676 Add Hindi translation 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bd55bbd9be Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5f09626098 Add missing translation strings 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b32a892c21 Add French and Norwegian languages 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a086a0f440 Use Swift 5.7 if-let style 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8f96a7c5e0 Change default cache settings values 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
804d2592d5 Fix settings window height on macOS 2022-10-27 18:03:57 +02:00
chadify
b2b8353e69 Translated using Weblate (Hindi)
Currently translated at 100.0% (352 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/
2022-10-27 18:03:57 +02:00
Allan Nordhøy
acf05ee6e1 Translated using Weblate (Norwegian Bokmål)
Currently translated at 88.3% (311 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/
2022-10-27 18:03:57 +02:00
Allan Nordhøy
9cf1a9a855 Translated using Weblate (Norwegian Bokmål)
Currently translated at 88.3% (311 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/
2022-10-27 18:03:57 +02:00
chadify
ba97cae733 Translated using Weblate (Hindi)
Currently translated at 56.8% (200 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/
2022-10-27 18:03:57 +02:00
Bharathi
fa4f8e2845 Translated using Weblate (German)
Currently translated at 100.0% (352 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
403fd87dda Translated using Weblate (Polish)
Currently translated at 100.0% (352 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Anonymous
b528ea89b2 Translated using Weblate (English)
Currently translated at 100.0% (352 of 352 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
J. Lavoie
b00b6569de Translated using Weblate (French)
Currently translated at 58.4% (204 of 349 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2022-10-27 18:03:57 +02:00
Bharathi
8bf0143ace Translated using Weblate (German)
Currently translated at 100.0% (349 of 349 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
66fd4bed80 Translated using Weblate (Polish)
Currently translated at 100.0% (349 of 349 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Anonymous
1047e0061e Translated using Weblate (English)
Currently translated at 100.0% (349 of 349 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Allan Nordhøy
b76fff6f71 Translated using Weblate (Norwegian Bokmål)
Currently translated at 88.5% (308 of 348 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
154a7e0c90 Translated using Weblate (Polish)
Currently translated at 100.0% (348 of 348 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
37c0b4fef6 Translated using Weblate (English)
Currently translated at 100.0% (348 of 348 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Anonymous
6aad992f5d Translated using Weblate (English)
Currently translated at 100.0% (348 of 348 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2fb1886f8c Fix localizations 2022-10-27 18:03:57 +02:00
Allan Nordhøy
b7af3987f7 Translated using Weblate (Norwegian Bokmål)
Currently translated at 65.1% (226 of 347 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
063a251d1c Translated using Weblate (Polish)
Currently translated at 100.0% (347 of 347 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Anonymous
02e5b2340c Translated using Weblate (English)
Currently translated at 100.0% (347 of 347 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Allan Nordhøy
a11ff24c73 Translated using Weblate (Norwegian Bokmål)
Currently translated at 64.7% (224 of 346 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
37ad2ff3b4 Translated using Weblate (Polish)
Currently translated at 100.0% (346 of 346 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Anonymous
07dd5522e1 Translated using Weblate (English)
Currently translated at 100.0% (346 of 346 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
32c0af7bd6 Fix localizations 2022-10-27 18:03:57 +02:00
Allan Nordhøy
ddee3c4dab Translated using Weblate (Norwegian Bokmål)
Currently translated at 60.8% (210 of 345 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nb_NO/
2022-10-27 18:03:57 +02:00
Bharathi
8653e4a0f3 Translated using Weblate (German)
Currently translated at 79.1% (273 of 345 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
92d858604b Translated using Weblate (Polish)
Currently translated at 100.0% (345 of 345 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Anonymous
f8a27087ff Translated using Weblate (English)
Currently translated at 100.0% (345 of 345 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Allan Nordhøy
3dea43bf0f Added translation using Weblate (Norwegian Bokmål) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4082936475 Fix localization 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7573b0321c More localization fixes 2022-10-27 18:03:57 +02:00
Bharathi
c86e9d95d1 Translated using Weblate (German)
Currently translated at 63.5% (218 of 343 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c02afcc8d0 Translated using Weblate (Polish)
Currently translated at 100.0% (343 of 343 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Anonymous
e2f261a61c Translated using Weblate (English)
Currently translated at 100.0% (343 of 343 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4a9dff3261 Translated using Weblate (English)
Currently translated at 100.0% (337 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e74c6a2a5b Translated using Weblate (German)
Currently translated at 59.9% (202 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1dbda63d4d Translated using Weblate (French)
Currently translated at 60.5% (204 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f08c3e97c9 Translated using Weblate (Polish)
Currently translated at 100.0% (337 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
chadify
86d365de5a Translated using Weblate (Hindi)
Currently translated at 2.0% (7 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/hi/
2022-10-27 18:03:57 +02:00
J. Lavoie
3051fa1c73 Translated using Weblate (French)
Currently translated at 60.5% (204 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2022-10-27 18:03:57 +02:00
J. Lavoie
50a3b45a98 Translated using Weblate (German)
Currently translated at 59.9% (202 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Bharathi
c878b3008d Translated using Weblate (German)
Currently translated at 59.9% (202 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
chadify
dc31c27dad Added translation using Weblate (Hindi) 2022-10-27 18:03:57 +02:00
J. Lavoie
a8465a34c1 Translated using Weblate (French)
Currently translated at 12.4% (42 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2022-10-27 18:03:57 +02:00
Userx Uberspace
382ee1773f Translated using Weblate (German)
Currently translated at 41.8% (141 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Bharathi
2519b4f24d Translated using Weblate (German)
Currently translated at 41.8% (141 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8a74d39728 Translated using Weblate (Polish)
Currently translated at 100.0% (337 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Anonymous
fca92ba699 Translated using Weblate (English)
Currently translated at 100.0% (337 of 337 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3e30c8cffd Added translation using Weblate (Belarusian) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4214328e45 Add translations info to README 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c673b45369 Minor strings changes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6d15945c88 Update Fastlane 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
adebf28f72 Translated using Weblate (Polish)
Currently translated at 100.0% (336 of 336 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Anonymous
9b3bc3da90 Translated using Weblate (English)
Currently translated at 100.0% (336 of 336 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1b6d1d7b6a Add German language 2022-10-27 18:03:57 +02:00
Bharathi
05c4ceee3e Translated using Weblate (German)
Currently translated at 35.0% (114 of 325 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Bharathi
18d3e5509e Translated using Weblate (German)
Currently translated at 35.0% (114 of 325 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a4ee779990 Translated using Weblate (German)
Currently translated at 35.0% (114 of 325 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d6c5c35533 Translated using Weblate (Polish)
Currently translated at 100.0% (325 of 325 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3fa9cf9e3c Added translation using Weblate (French) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3b745f0c02 Added translation using Weblate (German) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b66e177114 Localizations 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2d51f6adff Remove "Honor system orientation lock"
Fix #309
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3273032ffb Fix #305 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a7a3e0827d Revert "Improve setting audio session"
This reverts commit eedc8f32d1fd26bad24078425aab0ee7db5d3406.

Fix #307
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
cc60011cc6 Update SDWebImageSwiftUI 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ccc32c305c Extract variable 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
800e306487 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d01b0f8275 Allow to add videos only to user created playlists 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8c2ffa1b99 Fix issue with reloading playlists 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9676aa9f79 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1c926f276b Use WebImage fix 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b94dc08b68 Revert "Add CachedAsyncImage"
This reverts commit 50c77325f69bc60b1f47a7633a359c6ef0b742f9.
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b320ffb540 Revert "Fix images layout"
This reverts commit ee6e2370d22fb6d9013a4a05b51b4c6d155a3068.
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6ec59cf442 Add tvOS option to disable captions 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
01601176dd Add option "Keep last played video in the queue after restart" 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f7fb8174cd Fix full screen layout of player 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
dffcfc8b06 Pull to refresh fixes, added alert for playlist error 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
14b0316724 Add back Shuffle All and fix Play All 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1744210615 Fix optional use 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
07b6649f26 Use lazy views in TV navigation 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
179455bbc0 Add refreshable views 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4f8ac1eadb Minor fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
748dd02034 Remove padding from timeline 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f3f8fc175b Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
38ad9523c7 Don't show buffering 100% osd 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d69794cbd8 Fix recents order 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7af53a04ce Fixed controls bar model 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b5e8fb9024 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
eebca5ca59 Fix tvOS controls overlay buttons 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f7dd88a6cb Fix images layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
79512d4cbf Extract playback stats view 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5b72ad931b Improve rotation 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d48fc71660 Don't hide related on load 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f607e6e276 Model improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7b48041165 Fix opening player sheet 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
df96c2dba0 Fix updating playing status 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
db98124de5 Model improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
02617a7c42 Shared seek model 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6643208579 Change aspect ratio handling with MPV 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
418dae9dbe Add CachedAsyncImage 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b8380b2528 Minor fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f5016cc961 Model improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
dfe3fb81ea Fix reloading account token 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c24b561c13 Remove controls thumb 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0d3ccc00ce Model improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b220f212df Revert "Fix drawing state issues"
This reverts commit 97149df71cbf769c15ba6534953ad0f1deb79da0.
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
cf30ca7d31 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e9df94256c Fix gesture state 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ba07c410f5 Fix respecting restart playing videos setting 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3a05462965 Music mode controls layout improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a2b9d4d938 Allow to delete history even when it is disabled 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c3ae1d80cd Decrease loading delay 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
14cb1958d8 Fix drawing state issues 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a3940b4271 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d5ffc6554b Fix presenting player default value 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7a16b1fd96 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
53fbae29a0 Fix player initializer 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ac8cb98cdd Fix stream picker 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
66506c6ad1 Limit available controls layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9419d504dc Add gesture sensitivity setting 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e444dc3c79 Improve seek gesture 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d5f8ad4eec Fix updating now playing info with mpv on tvOS
Other minor tvOS fixes
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
945dbfe00b Regular TV layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
44600831a6 Refresh views on entering foreground 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0f7d826a3e Controls layouts, gestures and settings 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5b785cc9c2 Fix timeline view time padding 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1cf37e5a00 Improve setting audio session 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
60f38a80aa Improve PiP
Fix #186
Fix #196
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
99881ccc23 Minor timeline fix 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fc7052a7fa Fix orientation on iOS 16 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
653cca07fe Change channel close button 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5c038b2aa7 Add playback mode menu in AVPlayer on tvOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6deaec90f8 Fix cells on tvOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2f2fd67860 Add username/password login and keychain manager
Fix #224
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
08ed810b9e Animations improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5f50797b54 Controls updates fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
711208d9b7 Fix animation of overlays 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9ca905e5c7 Performance improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
642354fc8e Fix parsing timestamps with Piped 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
83f033f4dd Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c843250219 Fix crash on tvOS on opening related 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
56e04c5c29 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7bee3b161f Fix channel view subscription icon 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
eedb3d0471 Add shorts url parsing 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a235267796 Change default value for showing account username 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
868883dc45 Add "Always use AVPlayer for live videos" option 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e4c6fd8c9e Update packages 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9dd8352740 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a22d953a6c Fix updating UI on main thread 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
256bbd52a7 Extend available streams formats list to AVPlayer 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7b2dd75f2d Fix details overlay layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bcd06dfda5 Channel layout improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
24ed872560 Fix parsing channel url 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bb43e1c377 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6bbcdd8425 Fix playlists layout on tvOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e139d98bd7 Add clear history button (fix #205) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4ea44a5267 Fixed issue with portrait lock on launch (fix #274) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7cbb80847a Minor quality profiles UI improvements (fix #272) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
59632e8330 Add support for Invidious Saved Playlists (fix #259) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a51de0d084 Fix settings overlay layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d5b4b6baac Force seek only on HLS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ae9b23b9e7 Bring AVPlayer back to tvOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
48e616b301 AVPlayer background music mode 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b5f8a0fba2 Minor performance improvement 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c3e2e5c258 Fix updating system controls playback status on macOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6c6ba19df4 PiP improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ace8c6e3ff Fix default quality profiles 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
97fc8fa4b7 Add tappable description links and timestamps in iOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
eeda7a5c6e Don't switch to MPV after closing PiP 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fa76f726c5 Show asset loading error from AVPlayer 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b169fc91b3 Fix setting quality profile resolution on edit 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
acf962b4a0 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ff497cb09b Search performance improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fda7839527 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a222c722c3 Fix applying video player size on iOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bda794d5ff Fix remove instance/account button on macOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a96756d220 Unify wording 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a6f2ff9f52 Add default profiles and option to reset to defaults 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3999c19a6e Improve quality profile formats description 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
10fb417d87 Add proxy video setting to macOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3a508b98ab Fix player instance setting (fix #260) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
30889619fd Use altool to upload builds 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7607cf24b2 Fix getting build number in Fastlane 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3526559859 Remove build increment from Fastlane 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2add20ade0 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
eefd55a405 Update packages 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7636e78df4 Add account error alerts 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
aac760a870 Fix player size when orientation is locked 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2edab536a6 Minor PiP improvement 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
561ead0c5a Use system constant for internal battery power source name on macOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c5738ee14a tvOS buttons hint 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
dcfd24e376 Minor changes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d2cecf164d Remove best resolution setting 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
409b229dc8 Fix quality profile form 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4dd9005508 Allow swipe down to open controls settings on tvOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a776b06fc6 Clear system controls after closing item 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
356f8c7af1 Fix settings overlay layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
62769016fc Add playback mode button controls update 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ac9abaec5a Quality profiles 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
57d8698f86 Fix applying aspect ratio 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ce4f07e788 Add disable for multiselect row 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1f74aaa923 Use unified video context menu 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
117b00f7ee Fix search recents on tvOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
60a52bc12e tvOS layout improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9a958113c4 Minor performance improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c51b9dd8e8 Code style change 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
661de78ab3 Close overlays on exiting full screen 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
cf14ff51c5 Fix managing screen saver on macOS
Fix #245
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2086c6f679 Fix parsing video links
Fix alert on macOS
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2c81808125 Remove custom PiP placeholder 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e8dbf46e14 Remove unused gesture 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f828943982 Code style change 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e609e90165 Improve AVPlayer performance
Fix updating aspect ratio

Fix #170
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a347474437 Hide keyboard on navigation 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5c9d17f265 Fix typo 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f90fdb48c8 Layout fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bcc1d5aeaa Improve player transitions 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fb40f42c6c Improve controls overlays performance 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
33d52aeed3 Channel/playlist view transition improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ddf306d5c1 Fix crash on scrubbing 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a29024698e Disable cells animation on redraw 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
def67409cc Hide channel name in channel view (fix #229) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5a5590cf0e Disable Siesta logging in release 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e472d28265 Fix possible crash 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
50424c8dbf Fix applying safe area insets on iOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bc4b5fefe8 Lower network state update frequency 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0365369dcd Add setting "Rotate to portrait when exiting fullscreen" 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8e2c30bf00 Add setting "Close player when closing video" 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d0e3839040 Fix player on tvOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ccd9c4eb2a Minor fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d7735cf59d Fix pickers on iOS 16 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0797030a97 Minor controls overlay changes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5f5bd37bd4 Improve fullscreen animation 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8318b4c5fb Improve translucency effect 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2fd96e3a44 Fix drawing borders 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
152a79d44f Improve PiP close animation 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a0088e5404 Add server error message details (fix #221) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
cbae8628c9 Update packages 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fdec8ddaa3 Search performance improvements (fix #209) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e6baaa519a Live streams fix (fix #174, #175) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
827c64719c Add option to disable proxying video streams for Invidious 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6ce9ed3063 Fix replaying item 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
93243592a7 Fix Xcode build settings 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6352bbd7c7 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
aaf051a010 Fix player gesture 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4ec194c438 Fix settings layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
36cacc2258 Fix player sidebar background 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2de6df9899 Better UI for autoplay 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fda4013907 Improve placeholders 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fa0784e5d9 Fix details overlay height 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0716602e3f Minor fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1329bef2f9 Aspect ratio fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b6c65f2325 Add setting for controls center buttons 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
06d71a9bf0 Minor project change 2022-10-27 18:03:57 +02:00
Jan Weiß
ccdfe17985 Added unversioned TeamID infrastructure.
This is described in detail in the file "Shared.xcconfig".
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9445139546 Minor fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fc175be76b Lock orientation button 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5bfc1a3206 Minor fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ebe3ba9ed5 Playback modes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f0b8e7f655 Details panels in controls 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
db46289813 Update player gesture 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2cb849ad8e Improve orientation observing 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1e21c50b5d Aspect ratio improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5f858bc6d4 Orientation improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6c71cd72b1 Player layout fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
06b7bc79e8 Update build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c10e0e128e Unify overlay animation with other controls animation 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
36363628d4 Add MPV logs export 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e0a862fb36 Update README 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d8a699a4eb Enable mpv drawing in simulator 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
09ce5f02c3 Update build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f63a0f27fd Fix offenses 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ef47478748 Remove Catalyst version 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d81b8abe93 Minor loading UI improvement 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
17aeef9af7 Close overlay with tap outside 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e56ab3804e CC support with Invidious and MPV 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3718311a93 Update README 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
88b7f6b7f2 Update mpv for iOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
62f18e8506 Improve switching between public and private locations 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
78c50bef45 Fix version footer on tvOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
795b19e07b Add chat links in settings 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
11ef8c4dfc Fix handling EOF with MPV (#201) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9cb6ff64ff Switch to private account on selecting to not use public location (#212) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
02a27b4f95 Fix advanced settings layout (#213) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1bc2196264 Update settings menu order 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8d9c01677a Welcome screen fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fa8f5d1bf7 Update MPV headers 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
41fb021b64 Add MPV cache advanced settings 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
394980fe73 Minor controls bar improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7c4ee9bf35 Add Invidious comments support 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4fcf57d755 Locations manifest, reorganized instances settings 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6f62f14adf Fix browser controls bar on macOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
11984400fd Add back Safari Extension 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
51188cfc8e Add "Mark as watched" video context menu item (#193) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f10aa1abd9 Add "Pause when entering background" option (#198) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
12e427bcb8 Fix playlists parsing with Invidious 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
117c4fc9b6 Fix #190 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
485e1b060e Fix browser toolbar layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f8fe2961ca Fix #203 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
25ef0a4383 Fix #194 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8358dd50aa Video loading errors reporting 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
dc346f6c25 Replace repeating timer implementation 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
283d89b5a9 Fix another crash 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
029f19ba0b Fix crash 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a02eb2b168 Fix crash on unloadable history items 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
125b8cc89a Minor macOS performance improvement 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b7c3eb0248 Fix playlists on Piped 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
97e9889682 Minor tvOS controls and remote improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1645c81e00 Fix duplicated toolbar on macOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c66e9a6f35 Add translucency to tab bar on iOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7224b5e75f Fix toggle play command for tvOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a124a0c927 Update MPV and its dependencies
Add simulator libraries for Apple Silicon and Intel
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ab4456e663 Update build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f2c79a941e Update artwork of AVPlayer using URLSession 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bed7e759a8 Update build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ba57ecb965 Disable hardware decoding on Intel 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e67f85c9d1 Fix browsing settings height 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5889e4f92b Remove comments placement setting 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
575c2dd2ba Fix video details offset 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c960a82885 Decrease size of fulls creen video title and author 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4df919616d Fix setting PiP delegate 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f490102c31 Fix browser toolbar layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8d3bbb34d6 Update artwork using URLSession 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d8bd3ef50c Disable RefreshControl on iOS 16
Causes crashes
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5bfdc2c79b Upgrade packages 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
033249fa75 Fix setting frontend URL 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
539869cdbb Fix toggling player view 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bc0ed93e87 Fix updating controls buttons status 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
abdb6d6512 Fix showing restore segment button 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
69f0fd295a Fix loading playlists contents 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d670a76635 Add settings to tvOS tab menu (fix #119) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d330771fef Minor tvOS layout fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
993e602ca3 Fix channels URL parsing 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
adb1f72684 Add setting for thumbnails quality 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8ebadd4758 Add share actions to video context menu 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
72ec6094bc Add playlists actions to related view 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b6a157eda9 Details improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fe94d35af0 Improve queue details loading 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0f48a14e96 Fix sign in required view text layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
42f0ff80f8 Fix controls overlay 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bc63b5c9fd Minor controls bar changes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
69209ff771 Fix toggling full screen details 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
17787fa69c Add setting for closing player after playing last item (fix #98) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c940fb3198 Minor UI changes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7b09805b81 Improve network state updates 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c88b410936 Improve sharing 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f3f8466a95 Improve URL handling 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
321c265a11 Player controls UI changes
WIP on controls

Chapters

working

Add previews variable

Add lists ids

WIP
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9c98cf9558 Disable MPV drawing in simulator 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c0f395b2df Hide home indicator 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
13208a4444 Fix crash while managing playlists 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f367fe8728 Improve data parsers 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
12b18b5d59 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4e0b4469ab Fix extracting FPS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c135dd291c Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8341966159 Fix stream short quality 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ab03c2dcaf Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fd7bc16283 Update package 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d29ef4609d Silence warnings 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c9ef70b593 Remove Defaults workaround 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b88adca781 Add buffering and cache length to stats 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c35015e73f Allow HTTP calls 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e6e18de106 Improve streams extraction 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
15cdde17a6 Improve performance and add statistics for MPV 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
81b8cbd5f1 Add dropped frames counter 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
80659713f6 Fix crash on share link 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0c6ddc3dae Improve MPV performance 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ed71231df3 Improve Invidious thumbnails URL handling 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
aa9e52fa01 Fix video cell details layout 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fa0302d901 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b9e7b287d8 Add upstream fix for Invidious thumbnails
On John Ternus request (@llsc12)
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
312587ee05 Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bd7267e7f4 Update project settings 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
59b8a5532d Player will close with swipe animation when threshold is reached 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7a6698897a Minor controls improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
cb89c4d53d Fix orientation lock when closing player 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
574df016f3 Fix #163 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f1e06a0c1d Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
8e4961431a Add encryption key 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c2301862f2 Fix #162 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c330e8b3c6 Update packages 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
739b2667c6 Update build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
59978ecebe Remove Sparkle update framework 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fd3b3e604a Allow player swipe gesture when in fullscreen 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d659063897 Update watch history using background context 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
dbffa4ae08 Fix setting video mode on MPV 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
7cd6928e8c Minor controls fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0ecc271e70 Minor layout fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1855a57946 Fix mpv initialization on macOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
92b420869d Remove old unneeded patch 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
35fd392307 Add initial version of music mode 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e387db1f56 Temporary fix for SDWebImage/SDWebImage#3352 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
dfeeaeb36f Add model to fixtures environment objects 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ef26f96f66 Minor SponsorBlock fix 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0ed48bb5f9 Pause on hiding player by default only on tvOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
208ba623e5 Improve MPV loading commands 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c0c9967bfd Don't draw player when in background 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ba79a3b664 Improve animations 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
48ba498183 Load audio and video together with one command with MPV 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
26ef360b2a Bump build number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4f1520a6ce Minor fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ea2b6f9932 Add close video button to browser controls 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ab8549e103 Minor improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
31c5ed1643 Navigation improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a2e23fe72c New playlist navigation 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0ad350a6b5 New channel navigation 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c5af865ffe Add buttons to next video and restart video (fix #106)
Previous video requires rebuilding queue a little, maybe in the future
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d07b82a21b Don't push MPV to play HLS on changing backends
It takes longer to load than WEBM
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9abba2d19c Add Open in PiP option (fix #137) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3b1f6b21f3 Fix playing video from start when history disabled 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d4a5545db6 Minor improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
584571fb71 Player animation improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
71de78113d Fix orientation (#121) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c48d478f64 Minor improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
78d7693128 Player overlaying other views and swipe gesture (fix #44, #130) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
687949fbd5 Remove some default Favorites 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3d015f2298 More controls improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e3cf77e928 Minor player controls improvements (fix #94) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
45d591537c Update build and version for TestFlight 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
5703ed8915 Bump build and version number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
825c312b1c Fix browser controls play button disabled state 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
613f874c42 PiP improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bf46c36eca Don't skip segments that start before 4 seconds
To minimize buffering
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bdc83a7e7c Fix #126 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c5852601e4 Improve stream control on macOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0b274f92bd Prefer VP9/WEBM over H.264/MP4 (fix #128) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
acf9af936a Add PiP for iOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
a93b9a2fe5 Minor improvements to controls 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
3e029f631c Fix rate button 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b3d471c8a0 Lint 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ae365e814c Add resolution 8K 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6376e573b1 Run play action async 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
52df6bf76e Fix using Watch history in player queue 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
51b5c5709a Throttle SponsorBlock seek 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c4fd6aca24 Add rate change selector 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d58ec0684c Restore last played item into queue only if it's not in there yet 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
0e1192bb03 Improve subscriptions count
Piped API now includes it in the streams response, no need for separate
query
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
31374630e8 Add resolutions for 50fps and 48fps (fix #120) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b203b5205a Add hd2160p60fps resolution (fix #118) 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e42d574b46 Fix player size handling 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
e60349ded1 Minor fix 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
dfbbc96c85 Bump build and version number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
f29ef8907f Fix #86 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
57d00053d8 Improve EOF handling 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
512899235e Try to patch #78
Issue appears when app switches layout from tab to sidebar navigation
2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9fc4638298 Limit formats available to AVPlayer 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9edcf66557 Fullscreen handling changes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
76df80578d Remove redunant update of player size 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
c5add05c7e Fix project settings 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6466452e45 Improve keyboard shortcuts 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6fb2c4adc5 Minor fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fadffca69f Fix optional 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
6d32122e1c Bump build and version number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
cf3fa3871b Controls fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
fa91367b3d tvOS fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
2e7cbda5dc Close fullscreen and restore portrait on closing player 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
61054862d2 Improve streams quality settings 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
83db550c18 Add tvOS mpv libraries 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
cf20d3344a Fix player window on Mac 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
67723f4624 Minor improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
590aa00148 Bump version number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
180bace38c Add toggle for dislikes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b6a7f3886d Bump version number 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
bd8966735f Minor fixes 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
b22d75742e Add ReturnYoutubeDislike API 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
79118ff7e2 Fixes for MPV in macOS 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
d32b38c352 Fix EOF handler 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
4081c65c34 Minor improvements 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
1d24705c92 Add hide player button cancel action 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
ae861bce65 Prevent multiple seeks 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
70ea098378 Add Now Playing info center updates 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
31a28a7cbd Hello, mpv! 🎉 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
9868a2ef01 Reorganize toolbars placement 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
249c7ca7fa Update version and iOS Info.plist for Testflight 2022-10-27 18:03:57 +02:00
Arkadiusz Fal
cd32bae24b Add Fastlane config 2022-08-15 16:27:38 +02:00
Arkadiusz Fal
17e56dc69a Bump build number 2022-08-15 16:27:03 +02:00
Arkadiusz Fal
ad715fa367 Fix #246 2022-08-15 14:51:19 +02:00
Arkadiusz Fal
560d7c4b9a Update packages 2022-08-05 09:23:58 +02:00
Arkadiusz Fal
7a88f28170 Update packages 2022-07-11 15:31:46 +02:00
Arkadiusz Fal
419e8991c9 Fix parsing Piped streams 2022-07-11 15:30:32 +02:00
Jan Weiß
720bdde728 Set project indentation default to spaces. 2022-07-11 14:39:47 +02:00
Jan Weiß
965a757031 Added unversioned TeamID infrastructure.
This is described in detail in the file "Shared.xcconfig".
2022-07-11 14:39:47 +02:00
Arkadiusz Fal
811196ae55 Update README 2022-07-04 19:57:12 +02:00
Arkadiusz Fal
5b0f6397d6 Minor style change 2022-06-15 01:09:16 +02:00
Arkadiusz Fal
d5362519d6 Update build number 2022-06-15 01:04:50 +02:00
Arkadiusz Fal
e9ee0f5ee9 Update packages 2022-06-15 01:04:32 +02:00
Arkadiusz Fal
0a9fb75c0c Update README 2022-06-15 00:42:47 +02:00
Arkadiusz Fal
eb33b65f3d Fix #161 2022-06-14 23:24:17 +02:00
Arkadiusz Fal
1c71520d6f Fix #166 2022-06-14 18:12:42 +02:00
Arkadiusz Fal
7a7e265ba1 Update build and version numbers 2022-05-27 22:59:10 +02:00
Arkadiusz Fal
c086112a49 Update README 2022-05-25 23:11:38 +02:00
Arkadiusz Fal
0802fe0029 Fix #133 2022-05-25 09:23:34 +02:00
Arkadiusz Fal
40813c2859 Update build number for Testflight 2022-05-25 09:14:27 +02:00
Arkadiusz Fal
b1238869a6 Update README 2022-05-22 23:27:59 +02:00
Arkadiusz Fal
453a5fa71b Update README 2022-05-22 23:23:05 +02:00
Arkadiusz Fal
86be252bd5 Bump build and version number 2022-05-22 18:45:19 +02:00
Bharathi
20f96fb9d6 Update README.md
Change in Piped "User Playlists" availability
2022-05-22 18:16:01 +02:00
Arkadiusz Fal
e539fb0067 Remaining playlists fixes 2022-05-22 18:08:14 +02:00
Arkadiusz Fal
03d5eefab0 Reload playlist on adding video
In case video was added to the saame playlist
2022-05-22 00:36:25 +02:00
Arkadiusz Fal
0bc4a677d4 Create/delete Piped playlists and add/remove videos to Piped playlists 2022-05-22 00:30:10 +02:00
Arkadiusz Fal
b374f82da4 Update package 2022-05-21 23:01:04 +02:00
Arkadiusz Fal
b70697e1be Improve subscriptions count
Piped API now includes it in the streams response, no need for separate
query
2022-04-16 20:05:20 +02:00
Arkadiusz Fal
db5765a84b Disable placeholder channel link 2022-04-16 20:03:25 +02:00
Arkadiusz Fal
8d36f57271 Preliminary support for Piped playlist (listing playlists and videos) 2022-04-10 17:07:10 +02:00
Arkadiusz Fal
836057578f Minor changes 2022-04-02 14:34:06 +02:00
Arkadiusz Fal
e39f4373bb Fix crashes (#69, #71) 2022-03-31 20:39:02 +02:00
Arkadiusz Fal
1490437537 Update README 2022-03-28 21:30:31 +02:00
Arkadiusz Fal
4f1b52826d Fix #109 2022-03-28 21:26:52 +02:00
Arkadiusz Fal
15e62468bb Update README 2022-03-27 23:13:53 +02:00
Arkadiusz Fal
1380036c44 Bump build and version number 2022-03-27 22:02:07 +02:00
Arkadiusz Fal
c893e5dc38 Fix menu commands 2022-03-27 22:02:07 +02:00
Arkadiusz Fal
8b4838dca5 Fix placeholders on tvOS 2022-03-27 20:31:56 +02:00
Arkadiusz Fal
1c520831d1 Improve placeholders 2022-03-27 20:27:59 +02:00
Arkadiusz Fal
8770bfb56d Fix #87 2022-03-27 13:26:38 +02:00
Arkadiusz Fal
ae4796a4c5 Add placeholders 2022-03-27 13:26:38 +02:00
Arkadiusz Fal
70b55ec2b2 Further subscribe buttons improvements 2022-03-26 19:01:38 +01:00
Arkadiusz Fal
c14a4a153d Fix #72 2022-03-26 15:22:29 +01:00
Arkadiusz Fal
c8fa972a61 Hide player on video end only on tvOS 2022-03-26 15:12:06 +01:00
Arkadiusz Fal
cc7bb83e74 Fix #84 2022-03-26 14:37:55 +01:00
Arkadiusz Fal
6a65123876 Hide subscribe button when not logged in 2022-03-26 14:07:00 +01:00
Arkadiusz Fal
aa42551c7c Fix #81 2022-03-26 13:50:01 +01:00
Arkadiusz Fal
9d8a2607ab Fix parsing subscriptions published date 2022-03-24 14:13:51 +01:00
Arkadiusz Fal
b4a0835a43 Fix #80 2022-03-24 14:04:31 +01:00
Arkadiusz Fal
066e048022 Add Defaults workaround 2022-03-24 14:03:38 +01:00
Arkadiusz Fal
d825cd8b20 Update README 2022-03-20 23:29:26 +01:00
Arkadiusz Fal
bb988764b4 Bump version number 2022-02-26 11:10:32 +01:00
Arkadiusz Fal
f7789c73d5 Fix opening playlists when recents is not saved (fix #57) 2022-02-26 11:10:29 +01:00
Ryan Stentz
1085bf0e9a fix issue #57 2022-02-26 11:07:50 +01:00
Arkadiusz Fal
5f263efeb2 Bump build and version number 2022-01-24 22:34:24 +01:00
Arkadiusz Fal
a98b4eac83 Fix selecting best quality stream (fix #54) 2022-01-24 22:23:10 +01:00
Arkadiusz Fal
975b8fe5c3 Fix displaying settings/account buttons when only search is visible (fix #56) 2022-01-24 22:22:47 +01:00
Arkadiusz Fal
33e86710a8 Bump build number 2022-01-20 23:14:42 +01:00
Arkadiusz Fal
96f4e819a7 Update README 2022-01-20 23:14:11 +01:00
Arkadiusz Fal
8ab97ddbaf Fix search closing when entering new query after opening recent 2022-01-20 23:14:11 +01:00
Arkadiusz Fal
df72bf99ba Fix displaying searches in favorites 2022-01-13 21:16:25 +01:00
Arkadiusz Fal
db4a817164 Bump build number 2022-01-09 16:52:12 +01:00
Arkadiusz Fal
ce8a8cbef3 Fix selecting video details tab on sidebar visibility change 2022-01-09 16:38:17 +01:00
Arkadiusz Fal
a04827cc56 Fix restoring queue 2022-01-09 16:38:05 +01:00
Arkadiusz Fal
534f356471 Fix search suggestion prefix 2022-01-09 15:47:48 +01:00
Arkadiusz Fal
5050ad5d02 Fix menu command for Popular 2022-01-09 15:47:24 +01:00
Arkadiusz Fal
ca38133b1d Fix opening channel from video details 2022-01-09 15:47:00 +01:00
Arkadiusz Fal
a9ccd6b0f2 Bump build number 2022-01-07 22:19:13 +01:00
Arkadiusz Fal
76273a4724 Add option to rotate to landscape on entering fullscreen with button 2022-01-07 22:19:11 +01:00
Arkadiusz Fal
8370714b61 Fix hiding history in Now Playing view in tvOS 2022-01-07 20:11:56 +01:00
Arkadiusz Fal
d096fdb344 Slightly more compact thumbnails badges 2022-01-07 20:06:18 +01:00
Arkadiusz Fal
5b12dbcb1e Pause before dismissing player on tvOS 2022-01-07 19:48:03 +01:00
Arkadiusz Fal
d1ed896166 Add SponsorBlock categories details 2022-01-07 19:46:47 +01:00
Arkadiusz Fal
3630cd404d Fix refresh buttons opacity 2022-01-07 12:12:56 +01:00
Arkadiusz Fal
c698595517 Bump build number 2022-01-07 00:11:48 +01:00
Arkadiusz Fal
f2063be4a3 Update packages 2022-01-07 00:08:22 +01:00
Arkadiusz Fal
9304bf6158 Add refresh buttons keyboard shortcuts 2022-01-07 00:00:40 +01:00
Arkadiusz Fal
3495ecf693 Show recent channels/playlists in search in tab navigation 2022-01-06 18:21:14 +01:00
Arkadiusz Fal
f29dc792c2 Fix player controls progress bar warning 2022-01-06 17:47:07 +01:00
Arkadiusz Fal
792db567ed Fix manage object context in tvOS info view controllers 2022-01-06 17:06:03 +01:00
Arkadiusz Fal
e159bb772c Improve macOS Big Sur blur effect 2022-01-06 17:00:58 +01:00
Arkadiusz Fal
8a74938b98 Improve windows handling on macOS 2022-01-06 16:35:45 +01:00
Arkadiusz Fal
3baa7a6893 Redesigned settings (fixes #47) 2022-01-06 16:02:53 +01:00
Arkadiusz Fal
520d69f37a Backport blur effect for iOS 14/macOS Big Sur 2022-01-06 15:58:16 +01:00
Arkadiusz Fal
4e88f2baf8 Add setting for disabling thumbnails rounding 2022-01-06 15:57:28 +01:00
Arkadiusz Fal
b5d187c52f Add help link for adding Invidious account 2022-01-06 15:56:03 +01:00
Arkadiusz Fal
c1e219e46e Fix player controls progress bar 2022-01-06 15:55:34 +01:00
Arkadiusz Fal
7317aec1ed Minor layout improvements 2022-01-06 11:13:53 +01:00
Arkadiusz Fal
3e8ac15c66 Improve playlists toolbar layout on iOS 2022-01-05 17:26:25 +01:00
Arkadiusz Fal
363424fa74 Add pull to refresh for Subscriptions, Popular and Trending (fixes #31) 2022-01-05 17:25:57 +01:00
Arkadiusz Fal
1db4a3197d Add infinite scroll for comments 2022-01-05 17:12:32 +01:00
Arkadiusz Fal
ac755d0ee6 Fix tvOS player dismiss animation 2022-01-05 17:08:48 +01:00
Arkadiusz Fal
16a3a4728d Fix watched at string 2022-01-05 17:08:29 +01:00
Arkadiusz Fal
ea6363ba65 Add infinite scroll for search (fixes #5) 2022-01-05 11:44:53 +01:00
Arkadiusz Fal
3326088081 Improve search suggestion button area 2022-01-04 23:34:09 +01:00
Arkadiusz Fal
5498e2c4ab Bump build number 2022-01-02 22:38:56 +01:00
Arkadiusz Fal
00778b585f Add iOS options for handling landscape fullscreen (fixes #38) 2022-01-02 22:38:56 +01:00
Arkadiusz Fal
d6e75295e1 Add iOS option to lock portrait mode in browsing 2022-01-02 20:50:59 +01:00
Arkadiusz Fal
aec7480353 Add Play/Shuffle All buttons to playlists context menu 2022-01-02 20:50:59 +01:00
Arkadiusz Fal
e29982454b Add options for history: badge color and reset watched status on playing 2022-01-02 20:50:59 +01:00
Arkadiusz Fal
117057dd0e Add option to show/hide history of videos in player queue view 2022-01-02 20:50:59 +01:00
Arkadiusz Fal
9ede4b9b1f Add option to show/hide username in account picker button 2022-01-02 20:50:58 +01:00
Arkadiusz Fal
f0d1b74e34 Add Toggle Sidebar button for macOS 2022-01-02 20:46:02 +01:00
Arkadiusz Fal
2a75d0a1d4 Improve search suggestions layout, add separate button for search/append 2022-01-02 20:46:02 +01:00
Arkadiusz Fal
04df9551ba Add Play/Shuffle All for playlists (fixes #39)
Add Remove All from queue button on tvOS
2022-01-02 20:46:02 +01:00
Arkadiusz Fal
ba21583a95 Add Check for Updates button in macOS settings 2022-01-02 20:46:00 +01:00
Arkadiusz Fal
149607efbc Fix reporting player item duration to Now Playing 2021-12-29 20:20:09 +01:00
Arkadiusz Fal
89957e3b56 Better UI handling for loading video details (fixes #46) 2021-12-29 19:55:41 +01:00
Arkadiusz Fal
0af2db2fd7 Fix keywords background color 2021-12-29 19:40:25 +01:00
Arkadiusz Fal
ab174c73fd Extract progress view, show video details loading 2021-12-29 19:39:38 +01:00
Arkadiusz Fal
e4f3914ff8 Bump build number 2021-12-26 23:35:47 +01:00
Arkadiusz Fal
ac1c6685a1 Improve history, resume videos, mark watched videos (fixes #42) 2021-12-26 23:35:44 +01:00
Arkadiusz Fal
adcebb77a5 Fix video details buttons alignment 2021-12-26 21:27:46 +01:00
Arkadiusz Fal
32862ab446 Fix marking live videos from Piped 2021-12-26 20:14:45 +01:00
Arkadiusz Fal
e06febd2e3 Fix playback time formatting 2021-12-26 20:07:59 +01:00
Arkadiusz Fal
f257632354 Open PiP on iPad on going home screen (iOS 14.2+) 2021-12-26 20:07:25 +01:00
Arkadiusz Fal
19d57ff55c Retry loading thumbnails 2021-12-24 20:21:11 +01:00
Arkadiusz Fal
91fa4ea2ff Extract open URL action 2021-12-24 20:20:05 +01:00
Arkadiusz Fal
18d6000976 Fix skipping intro (should not happen when changing stream) 2021-12-20 00:39:45 +01:00
Arkadiusz Fal
ea90f650d8 Remove unused code, minor style changes 2021-12-20 00:36:12 +01:00
Arkadiusz Fal
0a5cb5b542 Fix video context menu channel subscription button (fixes #41) 2021-12-19 23:27:20 +01:00
Arkadiusz Fal
f132ba9683 Bump build number 2021-12-19 18:22:13 +01:00
Arkadiusz Fal
efce339234 Add context menu to close current video from player bar 2021-12-19 18:21:10 +01:00
Arkadiusz Fal
f89c5ff055 Improve player queue rows buttons labels 2021-12-19 18:18:33 +01:00
Arkadiusz Fal
9b2209c9b5 Update default list of favorites 2021-12-19 18:18:01 +01:00
Arkadiusz Fal
61a4951831 Layout and PiP improvements, new settings
- player is now a separate window on macOS
- add setting to disable pause when player is closed (fixes #40)
- add PiP settings:
  * Close PiP when starting playing other video
  * Close PiP when player is opened
  * Close PiP and open player when application
    enters foreground (iOS/tvOS) (fixes #37)
- new player placeholder when in PiP, context menu with exit option
2021-12-19 18:17:04 +01:00
Arkadiusz Fal
cef0b2594a Better loading and handling streams 2021-12-19 17:56:47 +01:00
Arkadiusz Fal
1fbb0cfa80 Remove favorites drag opacity effect on iOS (fixes #43)
No workaround for how to handle drag and drop effect on opening
context menu
2021-12-19 17:32:28 +01:00
Arkadiusz Fal
984e9e7b16 Fix visibility of likes/dislikes 2021-12-19 17:15:27 +01:00
Arkadiusz Fal
4793fc9a38 Fix visibility of Subscriptions tab navigation item on tvOS 2021-12-19 17:08:48 +01:00
Arkadiusz Fal
b6e1f8148c Bump version number 2021-12-17 21:24:21 +01:00
Arkadiusz Fal
23e2e216db Start playing after video intro instead of seeking from beginning 2021-12-17 21:02:15 +01:00
Arkadiusz Fal
d7058b46d3 Fix updating player item duration for live streams 2021-12-17 21:01:18 +01:00
Arkadiusz Fal
c4ca5eb4c7 Show channel thumbnail in player 2021-12-17 21:01:05 +01:00
Arkadiusz Fal
02e66e4520 Fix tab navigation environment objects 2021-12-17 20:58:24 +01:00
Arkadiusz Fal
de09f9dd52 SponsorBlock segments loading improvement 2021-12-17 20:55:52 +01:00
Arkadiusz Fal
4fab7c2c16 Fix channel view in tab navigation 2021-12-17 20:53:53 +01:00
Arkadiusz Fal
f609ed1ed4 Fix unsubscribing from channel 2021-12-17 20:53:24 +01:00
Arkadiusz Fal
201e91a3cc Show errors when handling playlists 2021-12-17 20:53:05 +01:00
Arkadiusz Fal
923f0c0356 More uniform comments UI 2021-12-17 20:46:49 +01:00
Arkadiusz Fal
008cd1553d Comments UI fixes 2021-12-17 18:22:46 +01:00
Arkadiusz Fal
8d49934fe8 Encapsulate open channel action 2021-12-17 17:34:55 +01:00
Arkadiusz Fal
a4c43d9a3a Fix subscriptions/playlists reload on account change 2021-12-14 23:50:19 +01:00
Arkadiusz Fal
310ed3b12b Update README 2021-12-10 23:34:11 +01:00
Arkadiusz Fal
fe33cc5e3a Bump build number 2021-12-08 00:29:00 +01:00
Arkadiusz Fal
7e7b4e89b5 Add Sparkle update framework for macOS 2021-12-08 00:27:38 +01:00
Arkadiusz Fal
d88292662f Minor README updates 2021-12-08 00:08:13 +01:00
Arkadiusz Fal
21b04e21c4 Remove unused file 2021-12-08 00:07:23 +01:00
Arkadiusz Fal
a44a61b017 Remove redundant query for replies when collapsed and expanded 2021-12-08 00:06:59 +01:00
Arkadiusz Fal
1b090fcd51 Bump build number 2021-12-06 19:15:05 +01:00
Arkadiusz Fal
12eb4401b5 Update README 2021-12-06 19:13:57 +01:00
Arkadiusz Fal
170f2ee94e Fix reloading favorites view 2021-12-06 19:13:49 +01:00
Arkadiusz Fal
fe56739211 Fix crash on dismissing channel playlist on iOS 2021-12-06 19:13:37 +01:00
Arkadiusz Fal
759a942426 Fix search field on macOS 2021-12-06 19:13:20 +01:00
Arkadiusz Fal
8d9bbf647a Fix disabling comments on tvOS 2021-12-06 19:12:59 +01:00
Arkadiusz Fal
eeb7b1f151 Improve search suggestions 2021-12-06 19:12:33 +01:00
Arkadiusz Fal
62bff9283c Faster replacing player item 2021-12-06 19:12:02 +01:00
Arkadiusz Fal
3624c9619a Add setting for displaying comments in separate tab or below description 2021-12-06 19:11:19 +01:00
Arkadiusz Fal
f7fc2369e3 Bump build number 2021-12-05 18:31:35 +01:00
Arkadiusz Fal
82ea8733ec Fix crash when video thumbnail cannot be loaded (fixes #28) 2021-12-05 18:31:35 +01:00
Arkadiusz Fal
1f495562fc Comments improvements
* Show text when there is no comments or comments are disabled
* Show progress indicator for loading comments/replies
* Improve layout of icons and text spacing
2021-12-05 18:31:33 +01:00
Arkadiusz Fal
37b99c59e1 Fix disabling comments 2021-12-05 18:12:13 +01:00
Arkadiusz Fal
7f9b53bd1f Fix login with Invidious accounts 2021-12-05 18:10:10 +01:00
Arkadiusz Fal
941e6a909d Set full screen views background color based on color scheme on tvOS (fixes #30) 2021-12-05 18:09:25 +01:00
Arkadiusz Fal
5143c4f8ce Bump build number 2021-12-04 20:57:11 +01:00
Arkadiusz Fal
19a3f08336 Comments (fixes #4) 2021-12-04 20:57:09 +01:00
Arkadiusz Fal
eb537676e6 Update README 2021-12-02 21:35:55 +01:00
Arkadiusz Fal
e97daa1944 Minor UI fixes 2021-12-02 21:35:42 +01:00
Arkadiusz Fal
bd59b8e2c3 Improve favorite button 2021-12-02 21:35:25 +01:00
Arkadiusz Fal
19b146c6ad Close current video (fixes #15) 2021-12-02 21:19:10 +01:00
Arkadiusz Fal
dd995105b5 Minor UI fixes for macOS Big Sur 2021-12-02 20:33:32 +01:00
Arkadiusz Fal
c4b5c7ce41 Fix scrolling of favorites on macOS Big Sur 2021-12-02 20:33:32 +01:00
Arkadiusz Fal
cc2bf90218 Bump build number 2021-12-02 00:18:51 +01:00
Arkadiusz Fal
45c917160e Display build number next to version 2021-12-02 00:17:19 +01:00
Arkadiusz Fal
9f5e9ea237 Add context menu to related items for queuing 2021-12-02 00:15:36 +01:00
Arkadiusz Fal
1c61ad37a9 Fix search on tvOS 2021-12-02 00:13:41 +01:00
Arkadiusz Fal
06f7391ad9 Add setting for saving recents (fixes #14) 2021-12-02 00:12:15 +01:00
Arkadiusz Fal
e61d1dfe2e Add settings for selecting visible sections (fixes #16) 2021-12-02 00:10:21 +01:00
Arkadiusz Fal
ff83abd103 Fix crash on opening player in iOS 14 (fixes #20) 2021-12-02 00:08:48 +01:00
Arkadiusz Fal
500c55689b Bump build number 2021-12-01 00:01:08 +01:00
Arkadiusz Fal
f60f7a0455 Improve playback bar font colors 2021-11-30 23:59:24 +01:00
Arkadiusz Fal
52ab162a6c Fix crash caused by tab navigation 2021-11-30 23:58:46 +01:00
Arkadiusz Fal
c6f077dcd3 Remove unused code 2021-11-30 23:58:27 +01:00
Arkadiusz Fal
b5ffa5b267 Fix sheets and covers on iOS 14 2021-11-30 23:58:11 +01:00
Arkadiusz Fal
5ef89ac9f4 iOS 14/macOS Big Sur Support 2021-11-30 19:01:08 +01:00
Arkadiusz Fal
696751e07c Remove alpha channel from iOS icons 2021-11-30 19:01:08 +01:00
Arkadiusz Fal
e88219ce88 Bump version 2021-11-16 22:12:40 +01:00
Arkadiusz Fal
adf4157ff3 Update README 2021-11-16 21:12:47 +01:00
Arkadiusz Fal
c78dd4a35e Enable text selection for video description 2021-11-15 19:28:21 +01:00
Arkadiusz Fal
8d2694df33
Merge pull request #2 from yattee/feature/piped-accounts
Add support for logging in with Piped accounts, viewing feed and managing subscriptions.
2021-11-15 19:21:01 +01:00
Arkadiusz Fal
0e3effd512 Add support for Piped accounts and subscriptions 2021-11-15 18:58:45 +01:00
Arkadiusz Fal
a70d4f3b38 Fix share URLs 2021-11-13 16:45:47 +01:00
Arkadiusz Fal
6328bfbfab Remove development team 2021-11-12 21:54:55 +01:00
Arkadiusz Fal
184992ea32 Bump packages 2021-11-12 21:54:03 +01:00
Arkadiusz Fal
dd8d6b6c4a Fix removing instance 2021-11-12 21:46:15 +01:00
485 changed files with 56727 additions and 7024 deletions

5
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# These are supported funding model platforms
github: yattee
patreon: arekf
custom: https://github.com/yattee/yattee/wiki/Donations

74
.github/ISSUE_TEMPLATE/bug-report.yaml vendored Normal file
View File

@ -0,0 +1,74 @@
name: Bug Report
description: Report a bug or unexpected behavior
labels: "bug"
body:
- type: markdown
attributes:
value: |
**Before You Submit Your Issue**
- This is not a right place to ask questions. Use [Discussions](https://github.com/yattee/yattee/discussions) or contact directly via [Discord](https://yattee.stream/discord) or [Matrix Channel](https://matrix.to/#/#Yattee:matrix.org)
- type: checkboxes
attributes:
label: Guidelines
description: Please ensure you've completed the following
options:
- label: I have searched the [issue tracker](https://github.com/yattee/yattee/issues) and I haven't found bug report like this
required: true
- type: textarea
attributes:
label: Current Behavior
description: Describe what happened and the steps you have done to encounter the problem
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: Describe what you expected to happen
validations:
required: true
- type: input
attributes:
label: Device & OS Version
description: What device and operating system version are you running? You can find it in device settings
placeholder: e.g. iPhone, iOS 16.1
validations:
required: true
- type: input
attributes:
label: App Build
description: What build of the application are you running? You can find this in the bottom of the Settings page
placeholder: e.g. 112
validations:
required: true
- type: textarea
attributes:
label: App Settings
description: If the issue is related to some non-default configuration, please provide the settings you have changed
validations:
required: false
- type: textarea
attributes:
label: Crash log
description: If you are reporing a crash, attach crash log here
placeholder: |
1. Go to 'Settings'
2. Click on 'Privacy'
3. Scroll down and open 'Analytics & Improvements'
4. Go to 'Analytics Data'
4. See crash log
validations:
required: false
- type: textarea
attributes:
label: Screenshots, Videos and other files
description: Submit screenshots, video of the issue or other files that can help to understand the problem better. If you are reporting issue with playback in MPV, attach [logs from MPV](https://github.com/yattee/yattee/wiki/Getting-MPV-logs)
validations:
required: false

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1 @@
blank_issues_enabled: true

View File

@ -0,0 +1,39 @@
name: Feature request
description: Suggest an new feature or an idea to improve the existing one
labels: "enhancement"
body:
- type: markdown
attributes:
value: |
**Before You Submit Your Issue**
- This is not a right place to ask questions. Use [Discussions](https://github.com/yattee/yattee/discussions) or contact directly via [Discord](https://yattee.stream/discord) or [Matrix Channel](https://matrix.to/#/#Yattee:matrix.org)
- type: checkboxes
attributes:
label: Guidelines
description: Please ensure you've completed the following
options:
- label: I have searched the [issue tracker](https://github.com/yattee/yattee/issues) and I haven't found feature request like this
required: true
- type: dropdown
attributes:
label: Type
description: Please select a type that fits this request. Choose multiple options, if applicable
multiple: true
options:
- New feature
- Improvement to existing feature
- Usability improvement
- Visual improvement
- Other
validations:
required: true
- type: textarea
attributes:
label: Describe the feature
description: Provide a description of the feature that you're requesting to have implemented. You can attach screenshots or mockups to help to understand it better
placeholder: The feature should work like this...
validations:
required: true

34
.github/workflows/bump-build.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Bump build number
on:
workflow_dispatch:
env:
APP_NAME: Yattee
jobs:
bump_build:
name: Bump build number
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Configure git
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
bundler-cache: true
- uses: maierj/fastlane-action@v3.0.0
with:
lane: bump_build
- run: echo "BUILD_NUMBER=$(cat Yattee.xcodeproj/project.pbxproj | grep -m 1 CURRENT_PROJECT_VERSION | cut -d' ' -f3 | sed 's/;//g')" >> $GITHUB_ENV
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GIT_AUTHORIZATION }}
branch: actions/bump-build-to-${{ env.BUILD_NUMBER }}
base: main
title: Bump build number to ${{ env.BUILD_NUMBER }}

102
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,102 @@
name: Build and release to TestFlight and GitHub
on:
workflow_dispatch:
env:
APP_NAME: Yattee
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
ITC_TEAM_ID: ${{ secrets.ITC_TEAM_ID }}
TEAM_ID: ${{ secrets.TEAM_ID }}
DEVELOPER_KEY_ID: ${{ secrets.DEVELOPER_KEY_ID }}
DEVELOPER_KEY_ISSUER_ID: ${{ secrets.DEVELOPER_KEY_ISSUER_ID }}
DEVELOPER_KEY_CONTENT: ${{ secrets.DEVELOPER_KEY_CONTENT }}
TEMP_KEYCHAIN_USER: ${{ secrets.TEMP_KEYCHAIN_USER }}
TEMP_KEYCHAIN_PASSWORD: ${{ secrets.TEMP_KEYCHAIN_PASSWORD }}
DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
GIT_AUTHORIZATION: ${{ secrets.GIT_AUTHORIZATION }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
CERTIFICATES_GIT_URL: ${{ secrets.CERTIFICATES_GIT_URL }}
TESTFLIGHT_EXTERNAL_GROUPS: ${{ secrets.TESTFLIGHT_EXTERNAL_GROUPS }}
jobs:
testflight:
strategy:
matrix:
# disabled mac beta lane
# lane: ['mac beta', 'ios beta', 'tvos beta']
lane: ['ios beta', 'tvos beta']
name: Releasing ${{ matrix.lane }} version to TestFlight
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
bundler-cache: true
- name: Replace signing certificate to AppStore
run: |
sed -i '' 's/match Development/match AppStore/' Yattee.xcodeproj/project.pbxproj
sed -i '' 's/iPhone Developer/iPhone Distribution/' Yattee.xcodeproj/project.pbxproj
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- uses: maierj/fastlane-action@v3.0.0
with:
lane: ${{ matrix.lane }}
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.lane }} build
path: fastlane/builds/**/*.ipa
if-no-files-found: ignore
mac_notarized:
name: Build and notarize macOS app
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
bundler-cache: true
- name: Replace signing certificate to Direct with Developer ID
run: |
sed -i '' 's/match AppStore/match Direct/' Yattee.xcodeproj/project.pbxproj
sed -i '' 's/3rd Party Mac Developer Application/Developer ID Application/' Yattee.xcodeproj/project.pbxproj
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- uses: maierj/fastlane-action@v3.0.0
with:
lane: mac build_and_notarize
- run: |
echo "BUILD_NUMBER=$(cat Yattee.xcodeproj/project.pbxproj | grep -m 1 CURRENT_PROJECT_VERSION | cut -d' ' -f3 | sed 's/;//g')" >> $GITHUB_ENV
echo "VERSION_NUMBER=$(cat Yattee.xcodeproj/project.pbxproj | grep -m 1 MARKETING_VERSION | cut -d' ' -f3 | sed 's/;//g')" >> $GITHUB_ENV
- run: |
echo "APP_PATH=fastlane/builds/${{ env.VERSION_NUMBER }}-${{ env.BUILD_NUMBER }}/macOS/Yattee.app" >> $GITHUB_ENV
echo "ZIP_PATH=fastlane/builds/${{ env.VERSION_NUMBER }}-${{ env.BUILD_NUMBER }}/macOS/Yattee-${{ env.VERSION_NUMBER }}-macOS.zip" >> $GITHUB_ENV
- name: ZIP build
run: /usr/bin/ditto -c -k --keepParent ${{ env.APP_PATH }} ${{ env.ZIP_PATH }}
- uses: actions/upload-artifact@v4
with:
name: mac notarized build
path: ${{ env.ZIP_PATH }}
if-no-files-found: error
release:
needs: ['testflight', 'mac_notarized']
name: Create GitHub release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "BUILD_NUMBER=$(cat Yattee.xcodeproj/project.pbxproj | grep -m 1 CURRENT_PROJECT_VERSION | cut -d' ' -f3 | sed 's/;//g')" >> $GITHUB_ENV
- run: echo "VERSION_NUMBER=$(cat Yattee.xcodeproj/project.pbxproj | grep -m 1 MARKETING_VERSION | cut -d' ' -f3 | sed 's/;//g')" >> $GITHUB_ENV
- uses: actions/download-artifact@v4
with:
path: artifacts
- uses: ncipollo/release-action@v1
with:
artifacts: artifacts/**/*.ipa,artifacts/**/*.zip
commit: main
tag: ${{ env.VERSION_NUMBER }}-${{ env.BUILD_NUMBER }}
prerelease: true
bodyFile: CHANGELOG.md

10
.gitignore vendored
View File

@ -81,6 +81,9 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
fastlane/builds
fastlane/.env
fastlane/*.p8
# Code Injection
#
@ -91,3 +94,10 @@ iOSInjectionProject/
# SwiftLint Remote Config Cache
.swiftlint/RemoteConfigCache
# User-specific xcconfig files
Xcode-config/DEVELOPMENT_TEAM.xcconfig
# Bundler
.bundle/
Vendor/bundle/

View File

@ -1,12 +1,14 @@
parent_config: https://raw.githubusercontent.com/sindresorhus/swiftlint-config/main/.swiftlint.yml
disabled_rules:
- conditional_returns_on_newline
- identifier_name
- opening_brace
- number_separator
- multiline_arguments
- implicit_return
excluded:
- Vendor
- Tests Apple TV
- Tests iOS
- Tests macOS

13
Backports/Backport.swift Normal file
View File

@ -0,0 +1,13 @@
import SwiftUI
public struct Backport<Content> {
public let content: Content
public init(_ content: Content) {
self.content = content
}
}
extension View {
var backport: Backport<Self> { Backport(self) }
}

View File

@ -0,0 +1,15 @@
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func badge(_ count: Text?) -> some View {
#if os(tvOS)
content
#else
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
content.badge(count)
} else {
content
}
#endif
}
}

View File

@ -0,0 +1,15 @@
import Foundation
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func listRowSeparator(_ visible: Bool) -> some View {
if #available(iOS 15, macOS 13, *) {
content
#if !os(tvOS)
.listRowSeparator(visible ? .visible : .hidden)
#endif
} else {
content
}
}
}

View File

@ -0,0 +1,11 @@
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func persistentSystemOverlays(_ visible: Bool) -> some View {
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) {
content.persistentSystemOverlays(visible ? .visible : .hidden)
} else {
content
}
}
}

View File

@ -0,0 +1,11 @@
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func refreshable(action: @Sendable @escaping () async -> Void) -> some View {
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
content.refreshable(action: action)
} else {
content
}
}
}

View File

@ -0,0 +1,12 @@
import Foundation
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func scrollContentBackground(_ visibility: Bool) -> some View {
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) {
content.scrollContentBackground(visibility ? .visible : .hidden)
} else {
content
}
}
}

View File

@ -0,0 +1,20 @@
import Foundation
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func scrollDismissesKeyboardImmediately() -> some View {
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) {
content.scrollDismissesKeyboard(.immediately)
} else {
content
}
}
@ViewBuilder func scrollDismissesKeyboardInteractively() -> some View {
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) {
content.scrollDismissesKeyboard(.interactively)
} else {
content
}
}
}

View File

@ -0,0 +1,11 @@
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func tint(_ color: Color?) -> some View {
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
content.tint(color)
} else {
content.foregroundColor(color)
}
}
}

View File

@ -0,0 +1,21 @@
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func toolbarBackground(_ color: Color) -> some View {
if #available(iOS 16, *) {
content
.toolbarBackground(color, for: .navigationBar)
} else {
content
}
}
@ViewBuilder func toolbarBackgroundVisibility(_ visible: Bool) -> some View {
if #available(iOS 16, *) {
content
.toolbarBackground(visible ? .visible : .hidden, for: .navigationBar)
} else {
content
}
}
}

View File

@ -0,0 +1,12 @@
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func toolbarColorScheme(_ colorScheme: ColorScheme) -> some View {
if #available(iOS 16, *) {
content
.toolbarColorScheme(colorScheme, for: .navigationBar)
} else {
content
}
}
}

View File

@ -0,0 +1,97 @@
/*
Copyright © 2020 Apple Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import SwiftUI
#if os(iOS)
public struct VisualEffectBlur<Content: View>: View {
/// Defaults to .systemMaterial
var blurStyle: UIBlurEffect.Style
/// Defaults to nil
var vibrancyStyle: UIVibrancyEffectStyle?
var content: Content
public init(blurStyle: UIBlurEffect.Style = .systemMaterial, vibrancyStyle: UIVibrancyEffectStyle? = nil, @ViewBuilder content: () -> Content) {
self.blurStyle = blurStyle
self.vibrancyStyle = vibrancyStyle
self.content = content()
}
public var body: some View {
Representable(blurStyle: blurStyle, vibrancyStyle: vibrancyStyle, content: ZStack { content })
.accessibility(hidden: Content.self == EmptyView.self)
}
}
// MARK: - Representable
extension VisualEffectBlur {
struct Representable<Content: View>: UIViewRepresentable {
var blurStyle: UIBlurEffect.Style
var vibrancyStyle: UIVibrancyEffectStyle?
var content: Content
func makeUIView(context: Context) -> UIVisualEffectView {
context.coordinator.blurView
}
func updateUIView(_: UIVisualEffectView, context: Context) {
context.coordinator.update(content: content, blurStyle: blurStyle, vibrancyStyle: vibrancyStyle)
}
func makeCoordinator() -> Coordinator {
Coordinator(content: content)
}
}
}
// MARK: - Coordinator
extension VisualEffectBlur.Representable {
class Coordinator {
let blurView = UIVisualEffectView()
let vibrancyView = UIVisualEffectView()
let hostingController: UIHostingController<Content>
init(content: Content) {
hostingController = UIHostingController(rootView: content)
hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostingController.view.backgroundColor = nil
blurView.contentView.addSubview(vibrancyView)
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
vibrancyView.contentView.addSubview(hostingController.view)
vibrancyView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
func update(content: Content, blurStyle: UIBlurEffect.Style, vibrancyStyle: UIVibrancyEffectStyle?) {
hostingController.rootView = content
let blurEffect = UIBlurEffect(style: blurStyle)
blurView.effect = blurEffect
if let vibrancyStyle {
vibrancyView.effect = UIVibrancyEffect(blurEffect: blurEffect, style: vibrancyStyle)
} else {
vibrancyView.effect = nil
}
hostingController.view.setNeedsDisplay()
}
}
}
extension VisualEffectBlur where Content == EmptyView {
init(blurStyle: UIBlurEffect.Style = .systemMaterial) {
self.init(blurStyle: blurStyle, vibrancyStyle: nil) {
EmptyView()
}
}
}
#endif

View File

@ -0,0 +1,83 @@
/*
Copyright © 2020 Apple Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import SwiftUI
#if os(macOS)
public struct VisualEffectBlur: View {
private var material: NSVisualEffectView.Material
private var blendingMode: NSVisualEffectView.BlendingMode
private var state: NSVisualEffectView.State
public init(
material: NSVisualEffectView.Material = .headerView,
blendingMode: NSVisualEffectView.BlendingMode = .withinWindow,
state: NSVisualEffectView.State = .followsWindowActiveState
) {
self.material = material
self.blendingMode = blendingMode
self.state = state
}
public var body: some View {
Representable(
material: material,
blendingMode: blendingMode,
state: state
).accessibility(hidden: true)
}
}
// MARK: - Representable
extension VisualEffectBlur {
struct Representable: NSViewRepresentable {
var material: NSVisualEffectView.Material
var blendingMode: NSVisualEffectView.BlendingMode
var state: NSVisualEffectView.State
func makeNSView(context: Context) -> NSVisualEffectView {
context.coordinator.visualEffectView
}
func updateNSView(_: NSVisualEffectView, context: Context) {
context.coordinator.update(material: material)
context.coordinator.update(blendingMode: blendingMode)
context.coordinator.update(state: state)
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
}
class Coordinator {
let visualEffectView = NSVisualEffectView()
init() {
visualEffectView.blendingMode = .withinWindow
}
func update(material: NSVisualEffectView.Material) {
visualEffectView.material = material
}
func update(blendingMode: NSVisualEffectView.BlendingMode) {
visualEffectView.blendingMode = blendingMode
}
func update(state: NSVisualEffectView.State) {
visualEffectView.state = state
}
}
}
#endif

123
CHANGELOG.md Normal file
View File

@ -0,0 +1,123 @@
## Build 199
## What's Changed
* Add support for invidious companion by @lifo9 in https://github.com/yattee/yattee/pull/863
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/851
## Previous builds
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
* Added Settings Import/Export
* Export all settings, instances and accounts
* Import selected elements from the file
* Include unencrypted passwords in the export or provide them during the import
* Import via URL for tvOS
* Added Controls setting "Action button labels" icon or icon and text
* Added Advanced setting for MPV: "deinterlace"
* Add help text to all header buttons (by @rickykresslein)
* History Setting: hide the recent activity in the sidebar or limit the number of items shown (by @rickykresslein)
* Fix issues with empty comments (by @stonerl)
* Improved Invidious comments (by @stonerl)
* Allow import of accounts to manually added (not imported) instances
* Add import export of missing settings
* macOS: Fix settings windows layout
* Fix seek OSD layout on tvOS, revert OSD position
* Allow users to disable fullscreen swipe gesture by @stonerl in https://github.com/yattee/yattee/pull/814
* Proper audio interrupt and route change handling by @stonerl in https://github.com/yattee/yattee/pull/815
* Improved subtitle handling by @stonerl in https://github.com/yattee/yattee/pull/817
* Improvements to MPVGLView by @stonerl in https://github.com/yattee/yattee/pull/818
* Add drag gestures to video details by @stonerl in https://github.com/yattee/yattee/pull/820
* Fix uneven playback when using MPV and not syncing refreshrate by @blennster in https://github.com/yattee/yattee/pull/833
* Norwegian Language by @mmaalo in https://github.com/yattee/yattee/pull/834
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/836
* Update MPVKit to v0.39.0 by @stonerl in https://github.com/yattee/yattee/pull/824
* Update SwiftUI-Introspect by @stonerl in https://github.com/yattee/yattee/pull/813
* Orientation/Fullscreen fixes and cleanup by @stonerl in https://github.com/yattee/yattee/pull/806
* More robust resolution handling by @stonerl in https://github.com/yattee/yattee/pull/807
* MPV: improved A/V sync by @stonerl in https://github.com/yattee/yattee/pull/805
* Retry loading video before presenting error by @stonerl in https://github.com/yattee/yattee/pull/810
* Refactor Search by @stonerl in https://github.com/yattee/yattee/pull/809
* iOS: Simplified fullscreen and orientation by @stonerl in https://github.com/yattee/yattee/pull/786
* macOS: only apply player shortcuts when window is active by @stonerl in https://github.com/yattee/yattee/pull/802
* player controls: add background opacity selection by @stonerl in https://github.com/yattee/yattee/pull/799
* add missing Shorts resolutions by @stonerl in https://github.com/yattee/yattee/pull/797
* use -O1 on macOS by @stonerl in https://github.com/yattee/yattee/pull/801
* Gestures: swipe up toggles fullscreen by @stonerl in https://github.com/yattee/yattee/pull/778
* dont open video when dismissing context menu by @stonerl in https://github.com/yattee/yattee/pull/780
* mpv: remove video layer when entering background by @stonerl in https://github.com/yattee/yattee/pull/793
* hi-res invidious logos by @stonerl in https://github.com/yattee/yattee/pull/791
* enable -O3 by @stonerl in https://github.com/yattee/yattee/pull/794
* Better audio ducking by @stonerl in https://github.com/yattee/yattee/pull/779
* fix picture in picture by @stonerl in https://github.com/yattee/yattee/pull/789
* Invidious: propper HTTP basic auth support by @stonerl in https://github.com/yattee/yattee/pull/762
* Apply correct orientation by @stonerl in https://github.com/yattee/yattee/pull/770
* Circular Invidious logo by @stonerl in https://github.com/yattee/yattee/pull/769
* Video Thumbnails: retry 3 times fetching from URL by @stonerl in https://github.com/yattee/yattee/pull/768
* Make thumbnail fill the view in music mode by @stonerl in https://github.com/yattee/yattee/pull/766
* Changes to defaults by @stonerl in https://github.com/yattee/yattee/pull/767
* Fixed fullscreen handling for backgrounding by @stonerl in https://github.com/yattee/yattee/pull/772
* Update now playing info when using system controls Partial fix for 503 by @stonerl in https://github.com/yattee/yattee/pull/765
* Stop making videos with unknown length shorts. by @derspyy in https://github.com/yattee/yattee/pull/849
* Add Hungarian to locales list
* Fix crash on HLS live playback by @stonerl in https://github.com/yattee/yattee/pull/775
* Fix mpv crashing on macOS by @stonerl in https://github.com/yattee/yattee/pull/754
* Refreshed icons for iOS and macOS by @stonerl in https://github.com/yattee/yattee/pull/752
* Add new MPVKit repo by @stonerl in https://github.com/yattee/yattee/pull/753
* Add Chinese (Simplified) - zh-Hans to LanguageCodes by @stonerl in https://github.com/yattee/yattee/pull/757
* Color changes to VideoActions by @stonerl in https://github.com/yattee/yattee/pull/759
* Hide VideoActions Bar when no buttons is visible by @stonerl in https://github.com/yattee/yattee/pull/760
* Improved stream resolution handling by @stonerl in https://github.com/yattee/yattee/pull/747
* Fix some potential crashes by @stonerl in https://github.com/yattee/yattee/pull/748
* Fix regression and improve curentChapter handling by @stonerl in https://github.com/yattee/yattee/pull/749
* Refined chapter font scaling by @stonerl in https://github.com/yattee/yattee/pull/750
* Improved thumbnail handling by @stonerl in https://github.com/yattee/yattee/pull/740
* iOS: make timestamps in comments touchable by @stonerl in https://github.com/yattee/yattee/pull/741
* Improvements to opening channels from Videos by @stonerl in https://github.com/yattee/yattee/pull/742
* Allow hiding comments by @stonerl in https://github.com/yattee/yattee/pull/744
* Add option to exit fullscreen on end by @stonerl in https://github.com/yattee/yattee/pull/570
* Only updateWatch status while video is playing by @stonerl in https://github.com/yattee/yattee/pull/745
* Xcode 16 - update recommended settings by @stonerl in https://github.com/yattee/yattee/pull/737
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/724
* tvOS: Allow account picker by long pressing channels button in subscriptions view by @patelhiren in https://github.com/yattee/yattee/pull/704
* tvOS: Refined Subscriptions View by @patelhiren in https://github.com/yattee/yattee/pull/697
* More responsive UI when Favorites are used. by @stonerl in https://github.com/yattee/yattee/pull/695
* Improved conditional proxying by @stonerl in https://github.com/yattee/yattee/pull/696
* Don't show related in sidebar when disabled in settings by @stonerl in https://github.com/yattee/yattee/pull/635
* Handle audio session interrupts by other media by @stonerl in https://github.com/yattee/yattee/pull/640
* Only show Queue header in sidebar view by @stonerl in https://github.com/yattee/yattee/pull/642
* SponsorBlock Improvements by @stonerl in https://github.com/yattee/yattee/pull/639
* Chapter title on jump by @stonerl in https://github.com/yattee/yattee/pull/655
* Restart finished video by @stonerl in https://github.com/yattee/yattee/pull/646
* SponsorBlock jump to end instead of pausing by @stonerl in https://github.com/yattee/yattee/pull/648
* Call correct class of SDImageAWebPCoder by @stonerl in https://github.com/yattee/yattee/pull/664
* Fix handling and displaying captions by @stonerl in https://github.com/yattee/yattee/pull/636
* Advanced settings: make number fields .numPad by @stonerl in https://github.com/yattee/yattee/pull/661
* Preserve time on stream change by @stonerl in https://github.com/yattee/yattee/pull/651
* Switch to previous backend when leaving PiP by @stonerl in https://github.com/yattee/yattee/pull/641
* Handle deep links by @timonus in https://github.com/yattee/yattee/pull/645
* Music Mode: don't bindPlayerToLayer when entering foreground by @stonerl in https://github.com/yattee/yattee/pull/644
* Allow user to disable thumbnails and jump to current chapter in horizontal view by @stonerl in https://github.com/yattee/yattee/pull/665
* Rework qualitiy settings by @stonerl in https://github.com/yattee/yattee/pull/650
* HLS: set target bitrate / AVPlayer: higher resolution by @stonerl in https://github.com/yattee/yattee/pull/667
* Fix #619: Remove ports from shared YouTube links by @0x000C in https://github.com/yattee/yattee/pull/627
* XCode enable IDEPreferLogStreaming by @stonerl in https://github.com/yattee/yattee/pull/638
* Conditional proxying by @stonerl in https://github.com/yattee/yattee/pull/662
* HomeView: Changes to Favourites and History Widget by @stonerl in https://github.com/yattee/yattee/pull/672
* Snappy UI - Offloading non UI task to background threads by @stonerl in https://github.com/yattee/yattee/pull/671
* Fix PiP Mode Not Working Using MPV by @stonerl in https://github.com/yattee/yattee/pull/676
* Fix thumbnails failing to load on tvOS by @patelhiren in https://github.com/yattee/yattee/pull/688
* speed up sorting for Stream by @stonerl in https://github.com/yattee/yattee/pull/681
* faster chapter extraction by @stonerl in https://github.com/yattee/yattee/pull/682
* Invidious: add images to chapters by @stonerl in https://github.com/yattee/yattee/pull/685
* Improved Captions handling by @stonerl in https://github.com/yattee/yattee/pull/684
* Add User-Agent to request by @stonerl in https://github.com/yattee/yattee/pull/680
* MPV: speed up playback start by @stonerl in https://github.com/yattee/yattee/pull/689
* Advanced Settings: cache-pause-initial by @stonerl in https://github.com/yattee/yattee/pull/679
* Changed description for Format reordering by @stonerl in https://github.com/yattee/yattee/pull/677
* Add Chinese (Traditional) localization (by @rexcsk)
* Localization fixes
* Updated localizations
* Upgraded dependencies
* Fixed reported crash
* Other minor changes and improvements
**Big thanks to the current, past and future project contributors!**

View File

@ -0,0 +1,11 @@
import AVKit
extension AVPlayerViewController {
func enterFullScreen(animated: Bool) {
perform(NSSelectorFromString("enterFullScreenAnimated:completionHandler:"), with: animated, with: nil)
}
func exitFullScreen(animated: Bool) {
perform(NSSelectorFromString("exitFullScreenAnimated:completionHandler:"), with: animated, with: nil)
}
}

View File

@ -0,0 +1,15 @@
import SwiftUI
extension Color {
#if os(macOS)
static let background = Color(NSColor.windowBackgroundColor)
static let secondaryBackground = Color(NSColor.controlBackgroundColor)
#elseif os(iOS)
static let background = Color(UIColor.systemBackground)
static let secondaryBackground = Color(UIColor.secondarySystemBackground)
#else
static func background(scheme: ColorScheme) -> Color {
scheme == .dark ? .black : .init(white: 0.8)
}
#endif
}

View File

@ -0,0 +1,15 @@
import SwiftUI
extension ShapeStyle where Self == Color {
static var debug: Color {
#if DEBUG
return Color(
red: .random(in: 0 ... 1),
green: .random(in: 0 ... 1),
blue: .random(in: 0 ... 1)
)
#else
return Color(.clear)
#endif
}
}

View File

@ -0,0 +1,7 @@
import Foundation
extension Comparable {
func clamped(to limits: ClosedRange<Self>) -> Self {
min(max(self, limits.lowerBound), limits.upperBound)
}
}

View File

@ -1,17 +1,28 @@
import Foundation
extension Double {
func formattedAsPlaybackTime() -> String? {
guard !isZero else {
func formattedAsPlaybackTime(allowZero: Bool = false, forceHours: Bool = false) -> String? {
guard allowZero || !isZero, isFinite else {
return nil
}
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = self >= (60 * 60) ? [.hour, .minute, .second] : [.minute, .second]
formatter.allowedUnits = self >= (60 * 60) || forceHours ? [.hour, .minute, .second] : [.minute, .second]
formatter.zeroFormattingBehavior = [.pad]
return formatter.string(from: self)
}
func formattedAsRelativeTime() -> String? {
let date = Date(timeIntervalSince1970: self)
let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .named
formatter.unitsStyle = .short
formatter.formattingContext = .standalone
return formatter.localizedString(for: date, relativeTo: Date())
}
}

View File

@ -0,0 +1,14 @@
import CoreData
extension NSManagedObjectContext {
/// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given managed object context up to date.
///
/// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute.
/// - Throws: An error if anything went wrong executing the batch deletion.
func executeAndMergeChanges(_ batchDeleteRequest: NSBatchDeleteRequest) throws {
batchDeleteRequest.resultType = .resultTypeObjectIDs
let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
}
}

View File

@ -0,0 +1,7 @@
extension NSObject {
class func swizzle(origSelector: Selector, withSelector: Selector, forClass: AnyClass) {
let originalMethod = class_getInstanceMethod(forClass, origSelector)
let swizzledMethod = class_getInstanceMethod(forClass, withSelector)
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}

View File

@ -0,0 +1,8 @@
import AppKit
extension NSTextField {
override open var focusRingType: NSFocusRingType {
get { .none }
set {}
}
}

View File

@ -0,0 +1,8 @@
import Foundation
extension Sequence where Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
var seen: Set<Iterator.Element> = []
return filter { seen.insert($0).inserted }
}
}

View File

@ -0,0 +1,35 @@
import Foundation
extension String {
func replacingFirstOccurrence(of target: String, with replacement: String) -> String {
guard let range = range(of: target) else {
return self
}
return replacingCharacters(in: range, with: replacement)
}
func replacingMatches(regex: String, replacementStringClosure: (String) -> String?) -> String {
guard let regex = try? NSRegularExpression(pattern: regex) else {
return self
}
let results = regex.matches(in: self, range: NSRange(startIndex..., in: self))
var outputText = self
for match in results.reversed() {
for rangeIndex in (1 ..< match.numberOfRanges).reversed() {
let matchingGroup: String = (self as NSString).substring(with: match.range(at: rangeIndex))
let rangeBounds = match.range(at: rangeIndex)
guard let range = Range(rangeBounds, in: self) else {
continue
}
let replacement = replacementStringClosure(matchingGroup) ?? matchingGroup
outputText = outputText.replacingOccurrences(of: matchingGroup, with: replacement, range: range)
}
}
return outputText
}
}

View File

@ -0,0 +1,7 @@
import Foundation
extension String {
func localized(_ comment: String = "") -> Self {
NSLocalizedString(self, tableName: "Localizable", bundle: .main, comment: comment)
}
}

View File

@ -0,0 +1,14 @@
import Foundation
extension String {
var replacingHTMLEntities: String {
do {
return try NSAttributedString(data: Data(utf8), options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
], documentAttributes: nil).string
} catch {
return self
}
}
}

View File

@ -0,0 +1,28 @@
import Foundation
import UIKit
extension UIDevice {
/// A Boolean value indicating whether the device has cellular data capabilities (true) or not (false).
var hasCellularCapabilites: Bool {
var addrs: UnsafeMutablePointer<ifaddrs>?
var cursor: UnsafeMutablePointer<ifaddrs>?
defer { freeifaddrs(addrs) }
guard getifaddrs(&addrs) == 0 else { return false }
cursor = addrs
while cursor != nil {
guard
let utf8String = cursor?.pointee.ifa_name,
let name = NSString(utf8String: utf8String),
name == "pdp_ip0"
else {
cursor = cursor?.pointee.ifa_next
continue
}
return true
}
return false
}
}

View File

@ -0,0 +1,15 @@
import UIKit
extension UIViewController {
@objc var swizzle_prefersHomeIndicatorAutoHidden: Bool {
true
}
public class func swizzleHomeIndicatorProperty() {
swizzle(
origSelector: #selector(getter: UIViewController.prefersHomeIndicatorAutoHidden),
withSelector: #selector(getter: UIViewController.swizzle_prefersHomeIndicatorAutoHidden),
forClass: UIViewController.self
)
}
}

View File

@ -0,0 +1,18 @@
import Foundation
extension URL {
func byReplacingYatteeProtocol(with urlProtocol: String = "https") -> URL! {
var urlAbsoluteString = absoluteString
guard urlAbsoluteString.hasPrefix(Strings.yatteeProtocol) else {
return self
}
urlAbsoluteString = String(urlAbsoluteString.dropFirst(Strings.yatteeProtocol.count))
if absoluteString.contains("://") {
return URL(string: urlAbsoluteString)
}
return URL(string: "\(urlProtocol)://\(urlAbsoluteString)")
}
}

View File

@ -10,7 +10,30 @@ extension View {
verticalEdgeBorder(.bottom, height: height, color: color)
}
func borderLeading(width: Double, color: Color = Color(white: 0.7, opacity: 1)) -> some View {
horizontalEdgeBorder(.leading, width: width, color: color)
}
func borderTrailing(width: Double, color: Color = Color(white: 0.7, opacity: 1)) -> some View {
horizontalEdgeBorder(.trailing, width: width, color: color)
}
private func verticalEdgeBorder(_ edge: Alignment, height: Double, color: Color) -> some View {
overlay(Rectangle().frame(width: nil, height: height, alignment: .top).foregroundColor(color), alignment: edge)
overlay(
Rectangle()
.frame(width: nil, height: height, alignment: .top)
.foregroundColor(color)
.ignoresSafeArea(.all, edges: .horizontal),
alignment: edge
)
}
private func horizontalEdgeBorder(_ edge: Alignment, width: Double, color: Color) -> some View {
overlay(
Rectangle()
.frame(width: width, height: nil, alignment: .leading)
.foregroundColor(color),
alignment: edge
)
}
}

View File

@ -3,6 +3,7 @@ import Foundation
extension ChannelPlaylist {
static var fixture: ChannelPlaylist {
ChannelPlaylist(
id: "fixture-channel-playlist",
title: "Playlist with a very long title that will not fit easily in the screen",
thumbnailURL: URL(string: "https://i.ytimg.com/vi/hT_nvWreIhg/hqdefault.jpg?sqp=-oaymwEWCKgBEF5IWvKriqkDCQgBFQAAiEIYAQ==&rs=AOn4CLAAD21_-Bo6Td1z3cV-UFyoi1flEg")!,
channel: Video.fixture.channel,

View File

@ -0,0 +1,18 @@
import Foundation
extension Comment {
static var fixture: Comment {
Comment(
id: UUID().uuidString,
author: "The Author",
authorAvatarURL: "https://pipedproxy-ams-2.kavin.rocks/Si7ZhtmpX84wj6MoJYLs8kwALw2Hm53wzbrPamoU-z3qvCKs2X3zPNYKMSJEvPDLUHzbvTfLcg=s176-c-k-c0x00ffffff-no-rw?host=yt3.ggpht.com",
time: "2 months ago",
pinned: true,
hearted: true,
likeCount: 30032,
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut luctus feugiat mi, suscipit pharetra lectus dapibus vel. Vivamus orci erat, sagittis sit amet dui vel, feugiat cursus ante. Pellentesque eget orci tortor. Suspendisse pulvinar orci tortor, eu scelerisque neque consequat nec. Aliquam sit amet turpis et nunc placerat finibus eget sit amet justo. Nullam tincidunt ornare neque. Donec ornare, arcu at elementum pulvinar, urna elit pharetra diam, vel ultrices lacus diam at lorem. Sed vel maximus dolor. Morbi massa est, interdum quis justo sit amet, dapibus bibendum tellus. Integer at purus nec neque tincidunt convallis sit amet eu odio. Duis et ante vitae sem tincidunt facilisis sit amet ac mauris. Quisque varius non nisi vel placerat. Nulla orci metus, imperdiet ac accumsan sed, pellentesque eget nisl. Praesent a suscipit lacus, ut finibus orci. Nulla ut eros commodo, fermentum purus at, porta leo. In finibus luctus nulla, eget posuere eros mollis vel. ",
repliesPage: "some url",
channel: .init(app: .invidious, id: "", name: "")
)
}
}

View File

@ -2,6 +2,6 @@ import Foundation
extension Instance {
static var fixture: Instance {
Instance(app: .invidious, name: "Home", apiURL: "https://invidious.home.net")
Instance(app: .invidious, name: "Home", apiURLString: "https://invidious.home.net")
}
}

View File

@ -10,7 +10,7 @@ extension Thumbnail {
}
private static var fixturesHost: String {
"https://invidious.home.arekf.net"
"https://invidious.snopyta.org"
}
private static func fixtureUrl(videoId: String, quality: Thumbnail.Quality) -> URL {

View File

@ -1,13 +1,18 @@
import Foundation
extension Video {
static var fixtureID: Video.ID = "video-fixture"
static var fixtureChannelID: Channel.ID = "channel-fixture"
static var fixture: Video {
let id = "D2sxamzaHkM"
let bannerURL = "https://yt3.ggpht.com/SQiRareBDrV2Z6A30HSD0iUABOGysanmKLtaJq7lJ_ME-MtoLb3O61QdlJfH2KhSOA0eKPr_=w2560-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj"
let thumbnailURL = "https://yt3.ggpht.com/ytc/AKedOLR-pT_JEsz_hcaA4Gjx8DHcqJ8mS42aTRqcVy6P7w=s88-c-k-c0x00ffffff-no-rj-mo"
let chapterImageURL = URL(string: "https://pipedproxy.kavin.rocks/vi/rr2XfL_df3o/hqdefault_29633.jpg?sqp=-oaymwEcCNACELwBSFXyq4qpAw4IARUAAIhCGAFwAcABBg%3D%3D&rs=AOn4CLDFDm9D5SvsIA7D3v5n5KZahLs_UA&host=i.ytimg.com")!
return Video(
videoID: UUID().uuidString,
title: "Relaxing Piano Music that will make you feel amazingly good",
app: .invidious,
videoID: fixtureID,
title: "Relaxing Piano Music to feel good",
author: "Fancy Videotuber",
length: 582,
published: "7 years ago",
@ -15,19 +20,69 @@ extension Video {
description: "Some relaxing live piano music",
genre: "Music",
channel: Channel(
id: "AbCdEFgHI",
app: .invidious,
id: fixtureChannelID,
name: "The Channel",
bannerURL: URL(string: bannerURL)!,
thumbnailURL: URL(string: thumbnailURL)!,
description: "The best channel that ever existed.\nThe best channel that ever existed. The best channel that ever existed. The best channel that ever existed. The best channel that ever existed. ",
subscriptionsCount: 2300,
totalViews: 3_260_378_817,
videos: []
),
thumbnails: Thumbnail.fixturesForAllQualities(videoId: id),
thumbnails: [],
live: false,
upcoming: false,
publishedAt: Date.now,
publishedAt: Date(),
likes: 37333,
dislikes: 30,
keywords: ["very", "cool", "video", "msfs 2020", "757", "747", "A380", "737-900", "MOD", "Zibo", "MD80", "MD11", "Rotate", "Laminar", "787", "A350", "MSFS", "MS2020", "Microsoft Flight Simulator", "Microsoft", "Flight", "Simulator", "SIM", "World", "Ortho", "Flying", "Boeing MAX"]
keywords: ["very", "cool", "video", "msfs 2020", "757", "747", "A380", "737-900", "MOD", "Zibo", "MD80", "MD11", "Rotate", "Laminar", "787", "A350", "MSFS", "MS2020", "Microsoft Flight Simulator", "Microsoft", "Flight", "Simulator", "SIM", "World", "Ortho", "Flying", "Boeing MAX"],
related: [.otherFixture],
chapters: [
.init(title: "A good chapter name", image: chapterImageURL, start: 20),
.init(title: "Other fine but incredibly too long chapter name, I don't know what else to say", image: chapterImageURL, start: 30),
.init(title: "Short", image: chapterImageURL, start: 60)
]
)
}
static var otherFixture: Video {
let bannerURL = "https://yt3.ggpht.com/SQiRareBDrV2Z6A30HSD0iUABOGysanmKLtaJq7lJ_ME-MtoLb3O61QdlJfH2KhSOA0eKPr_=w2560-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj"
let thumbnailURL = "https://yt3.ggpht.com/ytc/AKedOLR-pT_JEsz_hcaA4Gjx8DHcqJ8mS42aTRqcVy6P7w=s88-c-k-c0x00ffffff-no-rj-mo"
let chapterImageURL = URL(string: "https://pipedproxy.kavin.rocks/vi/rr2XfL_df3o/hqdefault_29633.jpg?sqp=-oaymwEcCNACELwBSFXyq4qpAw4IARUAAIhCGAFwAcABBg%3D%3D&rs=AOn4CLDFDm9D5SvsIA7D3v5n5KZahLs_UA&host=i.ytimg.com")!
return Video(
app: .invidious,
videoID: fixtureID + fixtureID,
title: "Relaxing Piano Music to feel good",
author: "Fancy Videotuber",
length: 582,
published: "7 years ago",
views: 21534,
description: "Some relaxing live piano music",
genre: "Music",
channel: Channel(
app: .invidious,
id: fixtureChannelID + fixtureChannelID,
name: "The Channel",
bannerURL: URL(string: bannerURL)!,
thumbnailURL: URL(string: thumbnailURL)!,
subscriptionsCount: 2300,
totalViews: 3_260_378_817,
videos: []
),
thumbnails: [],
live: false,
upcoming: false,
publishedAt: Date(),
likes: 37333,
dislikes: 30,
keywords: ["very", "cool", "video", "msfs 2020", "757", "747", "A380", "737-900", "MOD", "Zibo", "MD80", "MD11", "Rotate", "Laminar", "787", "A350", "MSFS", "MS2020", "Microsoft Flight Simulator", "Microsoft", "Flight", "Simulator", "SIM", "World", "Ortho", "Flying", "Boeing MAX"],
chapters: [
.init(title: "A good chapter name", image: chapterImageURL, start: 20),
.init(title: "Other fine but incredibly too long chapter name, I don't know what else to say", image: chapterImageURL, start: 30),
.init(title: "Short", image: chapterImageURL, start: 60)
]
)
}

View File

@ -4,44 +4,6 @@ import SwiftUI
struct FixtureEnvironmentObjectsModifier: ViewModifier {
func body(content: Content) -> some View {
content
.environmentObject(AccountsModel())
.environmentObject(InstancesModel())
.environmentObject(invidious)
.environmentObject(NavigationModel())
.environmentObject(PipedAPI())
.environmentObject(player)
.environmentObject(PlaylistsModel())
.environmentObject(RecentsModel())
.environmentObject(SearchModel())
.environmentObject(subscriptions)
.environmentObject(ThumbnailsModel())
}
private var invidious: InvidiousAPI {
let api = InvidiousAPI()
api.validInstance = true
api.signedIn = true
return api
}
private var player: PlayerModel {
let player = PlayerModel()
player.currentItem = PlayerQueueItem(Video.fixture)
player.queue = Video.allFixtures.map { PlayerQueueItem($0) }
player.history = player.queue
return player
}
private var subscriptions: SubscriptionsModel {
let subscriptions = SubscriptionsModel()
subscriptions.channels = Video.allFixtures.map { $0.channel }
return subscriptions
}
}

6
Gemfile Normal file
View File

@ -0,0 +1,6 @@
source "https://rubygems.org"
gem 'fastlane'
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

230
Gemfile.lock Normal file
View File

@ -0,0 +1,230 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.7)
base64
nkf
rexml
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.2)
aws-partitions (1.1072.0)
aws-sdk-core (3.220.2)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.99.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.182.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.11.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.6.20240107)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.112.0)
faraday (1.10.4)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.1.0)
multipart-post (~> 2.0)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
fastlane (2.227.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored (~> 1.2)
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-env (>= 1.6.0, < 2.0.0)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (>= 0.1.1, < 1.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.5)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.0)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.3)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.8.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.5.0)
google-cloud-storage (1.47.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.31.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.8.1)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.8)
domain_name (~> 0.5)
httpclient (2.9.0)
mutex_m
jmespath (1.6.2)
json (2.10.2)
jwt (2.10.1)
base64
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
naturally (2.2.1)
nkf (0.2.0)
optparse (0.6.0)
os (1.1.4)
plist (3.7.2)
public_suffix (6.0.1)
rake (13.2.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.4.1)
rouge (3.28.0)
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
security (0.1.5)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
CFPropertyList
naturally
sysrandom (1.0.5)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unicode-display_width (2.6.0)
word_wrap (1.0.0)
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
xcpretty (0.4.0)
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
arm64-darwin-21
arm64-darwin-23
arm64-darwin-24
x86_64-darwin-19
x86_64-darwin-20
x86_64-darwin-21
x86_64-linux
DEPENDENCIES
fastlane
BUNDLED WITH
2.3.6

View File

@ -5,47 +5,83 @@ struct Account: Defaults.Serializable, Hashable, Identifiable {
static var bridge = AccountsBridge()
let id: String
let instanceID: String
var name: String?
let url: String
let sid: String
var app: VideosApp?
let instanceID: String?
var name: String
let urlString: String
var username: String
var password: String?
let anonymous: Bool
let country: String?
let region: String?
init(
id: String? = nil,
app: VideosApp? = nil,
instanceID: String? = nil,
name: String? = nil,
url: String? = nil,
sid: String? = nil,
anonymous: Bool = false
urlString: String? = nil,
username: String? = nil,
password: String? = nil,
anonymous: Bool = false,
country: String? = nil,
region: String? = nil
) {
self.anonymous = anonymous
self.id = id ?? (anonymous ? "anonymous-\(instanceID!)" : UUID().uuidString)
self.instanceID = instanceID ?? UUID().uuidString
self.name = name
self.url = url ?? ""
self.sid = sid ?? ""
self.id = id ?? (anonymous ? "anonymous-\(instanceID ?? urlString ?? UUID().uuidString)" : UUID().uuidString)
self.instanceID = instanceID
self.name = name ?? ""
self.urlString = urlString ?? ""
self.username = username ?? ""
self.password = password ?? ""
self.country = country
self.region = region
self.app = app ?? instance.app
}
var instance: Instance {
Defaults[.instances].first { $0.id == instanceID }!
var url: URL! {
URL(string: urlString)
}
var anonymizedSID: String {
guard sid.count > 3 else {
return ""
}
var token: String? {
KeychainModel.shared.getAccountKey(self, "token")
}
let index = sid.index(sid.startIndex, offsetBy: 4)
return String(sid[..<index])
var credentials: (String?, String?) {
AccountsModel.getCredentials(self)
}
var instance: Instance! {
InstancesModel.shared.find(instanceID) ?? Instance(app: app ?? .invidious, name: urlString, apiURLString: urlString)
}
var isPublic: Bool {
instanceID.isNil
}
var isPublicAddedToCustom: Bool {
InstancesModel.shared.findByURLString(urlString) != nil
}
var description: String {
(name != nil && name!.isEmpty) ? "Unnamed (\(anonymizedSID))" : name!
guard !isPublic else {
return name
}
let (username, _) = credentials
return username ?? name
}
var urlHost: String {
URLComponents(url: url, resolvingAgainstBaseURL: false)?.host ?? ""
}
func hash(into hasher: inout Hasher) {
hasher.combine(sid)
hasher.combine(username)
}
var feedCacheKey: String {
"feed-\(id)"
}
}

View File

@ -1,11 +1,12 @@
import Alamofire
import Foundation
import Siesta
import SwiftUI
final class AccountValidator: Service {
let app: Binding<VideosApp>
let app: Binding<VideosApp?>
let url: String
let account: Account?
let account: Account!
var formObjectID: Binding<String>
var isValid: Binding<Bool>
@ -13,8 +14,10 @@ final class AccountValidator: Service {
var isValidating: Binding<Bool>
var error: Binding<String?>?
private var appsToValidateInstance = VideosApp.allCases
init(
app: Binding<VideosApp>,
app: Binding<VideosApp?>,
url: String,
account: Account? = nil,
id: Binding<String>,
@ -41,87 +44,215 @@ final class AccountValidator: Service {
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
}
configure("/api/v1/auth/feed", requestMethods: [.get]) {
guard self.account != nil else {
return
}
configure("/login", requestMethods: [.post]) {
$0.headers["Content-Type"] = "application/json"
}
}
$0.headers["Cookie"] = self.cookieHeader
func instanceValidationResource(_ app: VideosApp) -> Resource {
switch app {
case .invidious:
return resource("/api/v1/videos/dQw4w9WgXcQ")
case .piped:
return resource("/streams/dQw4w9WgXcQ")
case .peerTube:
// TODO: fixme
return resource("")
case .local:
return resource("")
}
}
func validateInstance() {
reset()
neverGonnaGiveYouUp
guard let app = appsToValidateInstance.popLast() else { return }
tryValidatingUsing(app)
}
func tryValidatingUsing(_ app: VideosApp) {
instanceValidationResource(app)
.load()
.onSuccess { response in
guard self.url == self.formObjectID.wrappedValue else {
return
}
guard !response.json.isEmpty else {
if app == .piped {
if response.text.contains("property=\"og:title\" content=\"Piped\"") {
self.isValid.wrappedValue = false
self.isValidated.wrappedValue = true
self.isValidating.wrappedValue = false
self.error?.wrappedValue = "Trying to use Piped front-end URL, you need to use URL for Piped API instead"
return
}
}
guard let nextApp = self.appsToValidateInstance.popLast() else {
self.isValid.wrappedValue = false
self.isValidated.wrappedValue = true
self.isValidating.wrappedValue = false
return
}
self.tryValidatingUsing(nextApp)
return
}
let json = response.json.dictionaryValue
let author = self.app.wrappedValue == .invidious ? json["author"] : json["uploader"]
let author = app == .invidious ? json["author"] : json["uploader"]
if author == "Rick Astley" {
self.app.wrappedValue = app
self.isValid.wrappedValue = true
self.error?.wrappedValue = nil
} else {
self.isValid.wrappedValue = false
}
self.isValidated.wrappedValue = true
self.isValidating.wrappedValue = false
}
.onFailure { error in
guard self.url == self.formObjectID.wrappedValue else {
return
}
self.isValid.wrappedValue = false
self.error?.wrappedValue = error.userMessage
}
.onCompletion { _ in
self.isValidated.wrappedValue = true
self.isValidating.wrappedValue = false
if self.appsToValidateInstance.isEmpty {
self.isValidating.wrappedValue = false
self.isValidated.wrappedValue = true
self.isValid.wrappedValue = false
self.error?.wrappedValue = error.userMessage
} else {
guard let app = self.appsToValidateInstance.popLast() else { return }
self.tryValidatingUsing(app)
}
}
}
func validateAccount() {
reset()
switch app.wrappedValue {
case .invidious:
validateInvidiousAccount()
case .piped:
validatePipedAccount()
default:
setValidationResult(false)
}
}
func validateInvidiousAccount() {
reset()
guard let username = account?.username,
let password = account?.password
else {
setValidationResult(false)
feed
.load()
.onSuccess { _ in
guard self.account!.sid == self.formObjectID.wrappedValue else {
return
}
AF
.request(login.url, method: .post, parameters: ["email": username, "password": password], encoding: URLEncoding.default)
.redirect(using: .doNotFollow)
.response { response in
guard let headers = response.response?.headers,
let cookies = headers["Set-Cookie"]
else {
self.setValidationResult(false)
return
}
self.isValid.wrappedValue = true
}
.onFailure { _ in
guard self.account!.sid == self.formObjectID.wrappedValue else {
let sidRegex = #"SID=(?<sid>[^;]*);"#
guard let sidRegex = try? NSRegularExpression(pattern: sidRegex),
let match = sidRegex.matches(in: cookies, range: NSRange(cookies.startIndex..., in: cookies)).first
else {
self.setValidationResult(false)
return
}
self.isValid.wrappedValue = false
}
.onCompletion { _ in
self.isValidated.wrappedValue = true
self.isValidating.wrappedValue = false
let matchRange = match.range(withName: "sid")
if let substringRange = Range(matchRange, in: cookies) {
let sid = String(cookies[substringRange])
if !sid.isEmpty {
self.setValidationResult(true)
}
} else {
self.setValidationResult(false)
}
}
}
func validatePipedAccount() {
guard let request = accountRequest else {
setValidationResult(false)
return
}
request.onSuccess { response in
guard self.account!.username == self.formObjectID.wrappedValue else {
return
}
switch self.app.wrappedValue {
case .invidious:
self.isValid.wrappedValue = true
case .piped:
let error = response.json.dictionaryValue["error"]?.string
let token = response.json.dictionaryValue["token"]?.string
self.isValid.wrappedValue = error?.isEmpty ?? !(token?.isEmpty ?? true)
self.error!.wrappedValue = error
default:
return
}
}
.onFailure { _ in
guard self.account!.username == self.formObjectID.wrappedValue else {
return
}
self.isValid.wrappedValue = false
}
.onCompletion { _ in
self.isValidated.wrappedValue = true
self.isValidating.wrappedValue = false
}
}
func setValidationResult(_ result: Bool) {
isValid.wrappedValue = result
isValidated.wrappedValue = true
isValidating.wrappedValue = false
}
var accountRequest: Siesta.Request? {
switch app.wrappedValue {
case .invidious:
guard let password = account.password else { return nil }
return login.request(.post, urlEncoded: ["email": account.username, "password": password])
case .piped:
return login.request(.post, json: ["username": account.username, "password": account.password])
default:
return nil
}
}
func reset() {
appsToValidateInstance = VideosApp.allCases
app.wrappedValue = nil
isValid.wrappedValue = false
isValidated.wrappedValue = false
isValidating.wrappedValue = false
error?.wrappedValue = nil
}
var cookieHeader: String {
"SID=\(account!.sid)"
}
var feed: Resource {
resource("/api/v1/auth/feed")
var login: Resource {
resource("/login")
}
var videoResourceBasePath: String {

View File

@ -6,32 +6,51 @@ struct AccountsBridge: Defaults.Bridge {
typealias Serializable = [String: String]
func serialize(_ value: Value?) -> Serializable? {
guard let value = value else {
guard let value else {
return nil
}
// Parse the urlString to check for embedded username and password
var sanitizedUrlString = value.urlString
if var urlComponents = URLComponents(string: value.urlString) {
if let user = urlComponents.user, let password = urlComponents.password {
// Sanitize the embedded username and password
let sanitizedUser = user.addingPercentEncoding(withAllowedCharacters: .urlUserAllowed) ?? user
let sanitizedPassword = password.addingPercentEncoding(withAllowedCharacters: .urlPasswordAllowed) ?? password
// Update the URL components with sanitized credentials
urlComponents.user = sanitizedUser
urlComponents.password = sanitizedPassword
// Reconstruct the sanitized URL
sanitizedUrlString = urlComponents.string ?? value.urlString
}
}
return [
"id": value.id,
"instanceID": value.instanceID,
"name": value.name ?? "",
"apiURL": value.url,
"sid": value.sid
"instanceID": value.instanceID ?? "",
"name": value.name,
"apiURL": sanitizedUrlString,
"username": value.username,
"password": value.password ?? ""
]
}
func deserialize(_ object: Serializable?) -> Value? {
guard
let object = object,
let object,
let id = object["id"],
let instanceID = object["instanceID"],
let url = object["apiURL"],
let sid = object["sid"]
let username = object["username"]
else {
return nil
}
let name = object["name"] ?? ""
let password = object["password"]
return Account(id: id, instanceID: instanceID, name: name, url: url, sid: sid)
return Account(id: id, instanceID: instanceID, name: name, urlString: url, username: username, password: password)
}
}

View File

@ -3,10 +3,15 @@ import Defaults
import Foundation
final class AccountsModel: ObservableObject {
static let shared = AccountsModel()
@Published private(set) var current: Account!
@Published private var invidious = InvidiousAPI()
@Published private var piped = PipedAPI()
@Published private var peerTube = PeerTubeAPI()
@Published var publicAccount: Account?
private var cancellables = [AnyCancellable]()
@ -19,15 +24,26 @@ final class AccountsModel: ObservableObject {
return nil
}
return AccountsModel.find(id)
return Self.find(id)
}
var any: Account? {
lastUsed ?? all.randomElement()
}
var app: VideosApp {
current?.instance.app ?? .invidious
current?.instance?.app ?? .local
}
var api: VideosAPI {
app == .piped ? piped : invidious
var api: VideosAPI! {
switch app {
case .piped:
return piped
case .invidious:
return invidious
default:
return peerTube
}
}
var isEmpty: Bool {
@ -35,7 +51,7 @@ final class AccountsModel: ObservableObject {
}
var signedIn: Bool {
!isEmpty && !current.anonymous
!isEmpty && !current.anonymous && api.signedIn
}
init() {
@ -48,6 +64,19 @@ final class AccountsModel: ObservableObject {
)
}
func find(_ id: Account.ID) -> Account? {
all.first { $0.id == id }
}
func configureAccount() {
if let account = lastUsed ??
InstancesModel.shared.lastUsed?.anonymousAccount ??
InstancesModel.shared.all.first?.anonymousAccount
{
setCurrent(account)
}
}
func setCurrent(_ account: Account! = nil) {
guard account != current else {
return
@ -56,34 +85,63 @@ final class AccountsModel: ObservableObject {
current = account
guard !account.isNil else {
current = nil
return
}
switch account.instance.app {
case .local:
return
case .invidious:
invidious.setAccount(account)
case .piped:
piped.setAccount(account)
case .peerTube:
peerTube.setAccount(account)
}
Defaults[.lastAccountID] = account.anonymous ? nil : account.id
Defaults[.lastInstanceID] = account.instanceID
Defaults[.lastAccountIsPublic] = account.isPublic
if !account.isPublic {
Defaults[.lastAccountID] = account.anonymous ? nil : account.id
Defaults[.lastInstanceID] = account.instanceID
}
}
static func find(_ id: Account.ID) -> Account? {
Defaults[.accounts].first { $0.id == id }
}
static func add(instance: Instance, name: String, sid: String) -> Account {
let account = Account(instanceID: instance.id, name: name, url: instance.apiURL, sid: sid)
static func add(instance: Instance, id: String? = UUID().uuidString, name: String, username: String, password: String) -> Account {
let account = Account(id: id, instanceID: instance.id, name: name, urlString: instance.apiURLString)
Defaults[.accounts].append(account)
setCredentials(account, username: username, password: password)
return account
}
static func remove(_ account: Account) {
if let accountIndex = Defaults[.accounts].firstIndex(where: { $0.id == account.id }) {
let account = Defaults[.accounts][accountIndex]
KeychainModel.shared.removeAccountKeys(account)
Defaults[.accounts].remove(at: accountIndex)
}
}
static func setToken(_ account: Account, _ token: String) {
KeychainModel.shared.updateAccountKey(account, "token", token)
}
static func setCredentials(_ account: Account, username: String, password: String) {
KeychainModel.shared.updateAccountKey(account, "username", username)
KeychainModel.shared.updateAccountKey(account, "password", password)
}
static func getCredentials(_ account: Account) -> (String?, String?) {
(
KeychainModel.shared.getAccountKey(account, "username"),
KeychainModel.shared.getAccountKey(account, "password")
)
}
}

View File

@ -7,23 +7,35 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
let app: VideosApp
let id: String
let name: String
let apiURL: String
let apiURLString: String
var frontendURL: String?
var proxiesVideos: Bool
var invidiousCompanion: Bool
init(app: VideosApp, id: String? = nil, name: String, apiURL: String, frontendURL: String? = nil) {
init(app: VideosApp, id: String? = nil, name: String? = nil, apiURLString: String, frontendURL: String? = nil, proxiesVideos: Bool = false, invidiousCompanion: Bool = false) {
self.app = app
self.id = id ?? UUID().uuidString
self.name = name
self.apiURL = apiURL
self.name = name ?? app.rawValue
self.apiURLString = apiURLString
self.frontendURL = frontendURL
self.proxiesVideos = proxiesVideos
self.invidiousCompanion = invidiousCompanion
}
var anonymous: VideosAPI {
var apiURL: URL! {
URL(string: apiURLString)
}
var anonymous: VideosAPI! {
switch app {
case .invidious:
return InvidiousAPI(account: anonymousAccount)
case .piped:
return PipedAPI(account: anonymousAccount)
case .peerTube:
return PeerTubeAPI(account: anonymousAccount)
case .local:
return nil
}
}
@ -32,23 +44,23 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
}
var longDescription: String {
name.isEmpty ? "\(app.name) - \(apiURL)" : "\(app.name) - \(name) (\(apiURL))"
name.isEmpty ? "\(app.name) - \(apiURLString)" : "\(app.name) - \(name) (\(apiURLString))"
}
var shortDescription: String {
name.isEmpty ? apiURL : name
name.isEmpty ? apiURLString : name
}
var anonymousAccount: Account {
Account(instanceID: id, name: "Anonymous", url: apiURL, anonymous: true)
Account(instanceID: id, name: "Anonymous".localized(), urlString: apiURLString, anonymous: true)
}
var urlComponents: URLComponents {
URLComponents(string: apiURL)!
URLComponents(url: apiURL, resolvingAgainstBaseURL: false)!
}
var frontendHost: String? {
guard let url = app == .invidious ? apiURL : frontendURL else {
guard let url = app == .invidious ? apiURLString : frontendURL else {
return nil
}
@ -58,4 +70,8 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
func hash(into hasher: inout Hasher) {
hasher.combine(apiURL)
}
var accounts: [Account] {
AccountsModel.shared.all.filter { $0.instanceID == id }
}
}

View File

@ -6,7 +6,7 @@ struct InstancesBridge: Defaults.Bridge {
typealias Serializable = [String: String]
func serialize(_ value: Value?) -> Serializable? {
guard let value = value else {
guard let value else {
return nil
}
@ -14,14 +14,16 @@ struct InstancesBridge: Defaults.Bridge {
"app": value.app.rawValue,
"id": value.id,
"name": value.name,
"apiURL": value.apiURL,
"frontendURL": value.frontendURL ?? ""
"apiURL": value.apiURLString,
"frontendURL": value.frontendURL ?? "",
"proxiesVideos": value.proxiesVideos ? "true" : "false",
"invidiousCompanion": value.invidiousCompanion ? "true" : "false"
]
}
func deserialize(_ object: Serializable?) -> Value? {
guard
let object = object,
let object,
let app = VideosApp(rawValue: object["app"] ?? ""),
let id = object["id"],
let apiURL = object["apiURL"]
@ -30,8 +32,10 @@ struct InstancesBridge: Defaults.Bridge {
}
let name = object["name"] ?? ""
let frontendURL: String? = object["frontendURL"]!.isEmpty ? nil : object["frontendURL"]
return Instance(app: app, id: id, name: name, apiURL: apiURL, frontendURL: frontendURL)
let proxiesVideos = object["proxiesVideos"] == "true"
let invidiousCompanion = object["invidiousCompanion"] == "true"
return Instance(app: app, id: id, name: name, apiURLString: apiURL, frontendURL: frontendURL, proxiesVideos: proxiesVideos, invidiousCompanion: invidiousCompanion)
}
}

View File

@ -2,19 +2,29 @@ import Defaults
import Foundation
final class InstancesModel: ObservableObject {
static var all: [Instance] {
static var shared = InstancesModel()
var all: [Instance] {
Defaults[.instances]
}
var forPlayer: Instance? {
guard let id = Defaults[.playerInstanceID] else {
return nil
}
return Self.shared.find(id)
}
var lastUsed: Instance? {
guard let id = Defaults[.lastInstanceID] else {
return nil
}
return InstancesModel.find(id)
return Self.shared.find(id)
}
static func find(_ id: Instance.ID?) -> Instance? {
func find(_ id: Instance.ID?) -> Instance? {
guard id != nil else {
return nil
}
@ -22,20 +32,34 @@ final class InstancesModel: ObservableObject {
return Defaults[.instances].first { $0.id == id }
}
static func accounts(_ id: Instance.ID?) -> [Account] {
func findByURLString(_ urlString: String?) -> Instance? {
guard let urlString else { return nil }
return Defaults[.instances].first { $0.apiURLString == urlString }
}
func accounts(_ id: Instance.ID?) -> [Account] {
Defaults[.accounts].filter { $0.instanceID == id }
}
static func add(app: VideosApp, name: String, url: String) -> Instance {
func add(id: String? = UUID().uuidString, app: VideosApp, name: String, url: String) -> Instance {
let instance = Instance(
app: app, id: UUID().uuidString, name: name, apiURL: standardizedURL(url)
app: app, id: id, name: name, apiURLString: standardizedURL(url)
)
Defaults[.instances].append(instance)
return instance
}
static func setFrontendURL(_ instance: Instance, _ url: String) {
func insert(id: String? = UUID().uuidString, app: VideosApp, name: String, url: String) -> Instance {
if let instance = Defaults[.instances].first(where: { $0.apiURL.absoluteString == standardizedURL(url) }) {
return instance
}
return add(id: id, app: app, name: name, url: url)
}
func setFrontendURL(_ instance: Instance, _ url: String) {
if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) {
var instance = Defaults[.instances][index]
instance.frontendURL = standardizedURL(url)
@ -44,19 +68,40 @@ final class InstancesModel: ObservableObject {
}
}
static func remove(_ instance: Instance) {
let accounts = InstancesModel.accounts(instance.id)
func setProxiesVideos(_ instance: Instance, _ proxiesVideos: Bool) {
guard let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) else {
return
}
var instance = Defaults[.instances][index]
instance.proxiesVideos = proxiesVideos
Defaults[.instances][index] = instance
}
func setInvidiousCompanion(_ instance: Instance, _ invidiousCompanion: Bool) {
guard let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) else {
return
}
var instance = Defaults[.instances][index]
instance.invidiousCompanion = invidiousCompanion
Defaults[.instances][index] = instance
}
func remove(_ instance: Instance) {
let accounts = accounts(instance.id)
if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) {
Defaults[.instances].remove(at: index)
accounts.forEach { AccountsModel.remove($0) }
}
}
static func standardizedURL(_ url: String) -> String {
if url.last == "/" {
func standardizedURL(_ url: String) -> String {
if url.count > 7, url.last == "/" {
return String(url.dropLast())
} else {
return url
}
return url
}
}

View File

@ -1,3 +1,4 @@
import Alamofire
import AVKit
import Defaults
import Foundation
@ -9,8 +10,15 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
@Published var account: Account!
@Published var validInstance = true
@Published var signedIn = false
static func withAnonymousAccountForInstanceURL(_ url: URL) -> InvidiousAPI {
.init(account: Instance(app: .invidious, apiURLString: url.absoluteString).anonymousAccount)
}
var signedIn: Bool {
guard let account else { return false }
return !account.anonymous && !(account.token?.isEmpty ?? true)
}
init(account: Account? = nil) {
super.init()
@ -26,52 +34,15 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
func setAccount(_ account: Account) {
self.account = account
validInstance = false
signedIn = false
configure()
validate()
}
func validate() {
validateInstance()
validateSID()
}
func validateInstance() {
guard !validInstance else {
return
}
home?
.load()
.onSuccess { _ in
self.validInstance = true
}
.onFailure { _ in
self.validInstance = false
}
}
func validateSID() {
guard !signedIn else {
return
}
feed?
.load()
.onSuccess { _ in
self.signedIn = true
}
.onFailure { _ in
self.signedIn = false
}
}
func configure() {
invalidateConfiguration()
configure {
if !self.account.sid.isEmpty {
$0.headers["Cookie"] = self.cookieHeader
if let cookie = self.cookieHeader {
$0.headers["Cookie"] = cookie
}
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
}
@ -81,95 +52,193 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
}
configureTransformer(pathPattern("popular"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
content.json.arrayValue.map(InvidiousAPI.extractVideo)
content.json.arrayValue.map(self.extractVideo)
}
configureTransformer(pathPattern("trending"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
content.json.arrayValue.map(InvidiousAPI.extractVideo)
content.json.arrayValue.map(self.extractVideo)
}
configureTransformer(pathPattern("search"), requestMethods: [.get]) { (content: Entity<JSON>) -> [ContentItem] in
content.json.arrayValue.map {
let type = $0.dictionaryValue["type"]?.stringValue
configureTransformer(pathPattern("search"), requestMethods: [.get]) { (content: Entity<JSON>) -> SearchPage in
let results = content.json.arrayValue.compactMap { json -> ContentItem? in
let type = json.dictionaryValue["type"]?.string
if type == "channel" {
return ContentItem(channel: InvidiousAPI.extractChannel(from: $0))
} else if type == "playlist" {
return ContentItem(playlist: InvidiousAPI.extractChannelPlaylist(from: $0))
return ContentItem(channel: self.extractChannel(from: json))
}
return ContentItem(video: InvidiousAPI.extractVideo(from: $0))
if type == "playlist" {
return ContentItem(playlist: self.extractChannelPlaylist(from: json))
}
if type == "video" {
return ContentItem(video: self.extractVideo(from: json))
}
return nil
}
return SearchPage(results: results, last: results.isEmpty)
}
configureTransformer(pathPattern("search/suggestions"), requestMethods: [.get]) { (content: Entity<JSON>) -> [String] in
if let suggestions = content.json.dictionaryValue["suggestions"] {
return suggestions.arrayValue.map(String.init)
return suggestions.arrayValue.map(\.stringValue).map(\.replacingHTMLEntities)
}
return []
}
configureTransformer(pathPattern("auth/playlists"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Playlist] in
content.json.arrayValue.map(Playlist.init)
content.json.arrayValue.map(self.extractPlaylist)
}
configureTransformer(pathPattern("auth/playlists/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> Playlist in
Playlist(content.json)
self.extractPlaylist(from: content.json)
}
configureTransformer(pathPattern("auth/playlists"), requestMethods: [.post, .patch]) { (content: Entity<Data>) -> Playlist in
// hacky, to verify if possible to get it in easier way
Playlist(JSON(parseJSON: String(data: content.content, encoding: .utf8)!))
self.extractPlaylist(from: JSON(parseJSON: String(data: content.content, encoding: .utf8)!))
}
configureTransformer(pathPattern("auth/feed"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
if let feedVideos = content.json.dictionaryValue["videos"] {
return feedVideos.arrayValue.map(InvidiousAPI.extractVideo)
return feedVideos.arrayValue.map(self.extractVideo)
}
return []
}
configureTransformer(pathPattern("auth/subscriptions"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Channel] in
content.json.arrayValue.map(InvidiousAPI.extractChannel)
content.json.arrayValue.map(self.extractChannel)
}
configureTransformer(pathPattern("channels/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> Channel in
InvidiousAPI.extractChannel(from: content.json)
configureTransformer(pathPattern("channels/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPage in
self.extractChannelPage(from: content.json, forceNotLast: true)
}
configureTransformer(pathPattern("channels/*/videos"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPage in
self.extractChannelPage(from: content.json)
}
configureTransformer(pathPattern("channels/*/latest"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
content.json.arrayValue.map(InvidiousAPI.extractVideo)
content.json.dictionaryValue["videos"]?.arrayValue.map(self.extractVideo) ?? []
}
for type in ["latest", "playlists", "streams", "shorts", "channels", "videos", "releases", "podcasts"] {
configureTransformer(pathPattern("channels/*/\(type)"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPage in
self.extractChannelPage(from: content.json)
}
}
configureTransformer(pathPattern("playlists/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPlaylist in
InvidiousAPI.extractChannelPlaylist(from: content.json)
self.extractChannelPlaylist(from: content.json)
}
configureTransformer(pathPattern("videos/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> Video in
InvidiousAPI.extractVideo(from: content.json)
self.extractVideo(from: content.json)
}
configureTransformer(pathPattern("comments/*")) { (content: Entity<JSON>) -> CommentsPage in
let details = content.json.dictionaryValue
let comments = details["comments"]?.arrayValue.compactMap { self.extractComment(from: $0) } ?? []
let nextPage = details["continuation"]?.string
let disabled = !details["error"].isNil
return CommentsPage(comments: comments, nextPage: nextPage, disabled: disabled)
}
if account.token.isNil || account.token!.isEmpty {
updateToken()
} else {
FeedModel.shared.onAccountChange()
SubscribedChannelsModel.shared.onAccountChange()
PlaylistsModel.shared.onAccountChange()
}
}
func updateToken(force: Bool = false) {
let (username, password) = AccountsModel.getCredentials(account)
guard !account.anonymous,
(account.token?.isEmpty ?? true) || force
else {
return
}
guard let username,
let password,
!username.isEmpty,
!password.isEmpty
else {
NavigationModel.shared.presentAlert(
title: "Account Error",
message: "Remove and add your account again in Settings."
)
return
}
let presentTokenUpdateFailedAlert: (AFDataResponse<Data?>?, String?) -> Void = { response, message in
NavigationModel.shared.presentAlert(
title: "Account Error",
message: message ?? "\(response?.response?.statusCode ?? -1) - \(response?.error?.errorDescription ?? "unknown")\nIf this issue persists, try removing and adding your account again in Settings."
)
}
AF
.request(login.url, method: .post, parameters: ["email": username, "password": password], encoding: URLEncoding.default)
.redirect(using: .doNotFollow)
.response { response in
guard let headers = response.response?.headers,
let cookies = headers["Set-Cookie"]
else {
presentTokenUpdateFailedAlert(response, nil)
return
}
let sidRegex = #"SID=(?<sid>[^;]*);"#
guard let sidRegex = try? NSRegularExpression(pattern: sidRegex),
let match = sidRegex.matches(in: cookies, range: NSRange(cookies.startIndex..., in: cookies)).first
else {
presentTokenUpdateFailedAlert(nil, String(format: "Could not extract SID from received cookies: %@".localized(), cookies))
return
}
let matchRange = match.range(withName: "sid")
if let substringRange = Range(matchRange, in: cookies) {
let sid = String(cookies[substringRange])
AccountsModel.setToken(self.account, sid)
self.objectWillChange.send()
} else {
presentTokenUpdateFailedAlert(nil, String(format: "Could not extract SID from received cookies: %@".localized(), cookies))
}
self.configure()
}
}
var login: Resource {
resource(baseURL: account.url, path: "login")
}
private func pathPattern(_ path: String) -> String {
"**\(InvidiousAPI.basePath)/\(path)"
"**\(Self.basePath)/\(path)"
}
private func basePathAppending(_ path: String) -> String {
"\(InvidiousAPI.basePath)/\(path)"
"\(Self.basePath)/\(path)"
}
private var cookieHeader: String {
"SID=\(account.sid)"
private var cookieHeader: String? {
guard let token = account?.token, !token.isEmpty else { return nil }
return "SID=\(token)"
}
var popular: Resource? {
resource(baseURL: account.url, path: "\(InvidiousAPI.basePath)/popular")
resource(baseURL: account.url, path: "\(Self.basePath)/popular")
}
func trending(country: Country, category: TrendingCategory?) -> Resource {
resource(baseURL: account.url, path: "\(InvidiousAPI.basePath)/trending")
.withParam("type", category?.name)
resource(baseURL: account.url, path: "\(Self.basePath)/trending")
.withParam("type", category?.type)
.withParam("region", country.rawValue)
}
@ -177,20 +246,53 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
resource(baseURL: account.url, path: "/feed/subscriptions")
}
func feed(_ page: Int?) -> Resource? {
resourceWithAuthCheck(baseURL: account.url, path: "\(Self.basePath)/auth/feed")
.withParam("page", String(page ?? 1))
}
var feed: Resource? {
resource(baseURL: account.url, path: "\(InvidiousAPI.basePath)/auth/feed")
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/feed"))
}
var subscriptions: Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
}
func channelSubscription(_ id: String) -> Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions")).child(id)
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
.child(channelID)
.request(.post)
.onCompletion { _ in onCompletion() }
}
func channel(_ id: String) -> Resource {
resource(baseURL: account.url, path: basePathAppending("channels/\(id)"))
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void) {
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
.child(channelID)
.request(.delete)
.onCompletion { _ in onCompletion() }
}
func channel(_ id: String, contentType: Channel.ContentType, data _: String?, page: String?) -> Resource {
if page.isNil, contentType == .videos {
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)"))
}
var resource = resource(baseURL: account.url, path: basePathAppending("channels/\(id)/\(contentType.invidiousID)"))
if let page, !page.isEmpty {
resource = resource.withParam("continuation", page)
}
return resource
}
func channelByName(_: String) -> Resource? {
nil
}
func channelByUsername(_: String) -> Resource? {
nil
}
func channelVideos(_ id: String) -> Resource {
@ -202,11 +304,15 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
}
var playlists: Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/playlists"))
if account.isNil || account.anonymous {
return nil
}
return resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/playlists"))
}
func playlist(_ id: String) -> Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/playlists/\(id)"))
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/playlists/\(id)"))
}
func playlistVideos(_ id: String) -> Resource? {
@ -217,11 +323,71 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
playlist(playlistID)?.child("videos").child(videoID)
}
func addVideoToPlaylist(
_ videoID: String,
_ playlistID: String,
onFailure: @escaping (RequestError) -> Void = { _ in },
onSuccess: @escaping () -> Void = {}
) {
let resource = playlistVideos(playlistID)
let body = ["videoId": videoID]
resource?
.request(.post, json: body)
.onSuccess { _ in onSuccess() }
.onFailure(onFailure)
}
func removeVideoFromPlaylist(
_ index: String,
_ playlistID: String,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping () -> Void
) {
let resource = playlistVideo(playlistID, index)
resource?
.request(.delete)
.onSuccess { _ in onSuccess() }
.onFailure(onFailure)
}
func playlistForm(
_ name: String,
_ visibility: String,
playlist: Playlist?,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping (Playlist?) -> Void
) {
let body = ["title": name, "privacy": visibility]
let resource = !playlist.isNil ? self.playlist(playlist!.id) : playlists
resource?
.request(!playlist.isNil ? .patch : .post, json: body)
.onSuccess { response in
if let modifiedPlaylist: Playlist = response.typedContent() {
onSuccess(modifiedPlaylist)
}
}
.onFailure(onFailure)
}
func deletePlaylist(
_ playlist: Playlist,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping () -> Void
) {
self.playlist(playlist.id)?
.request(.delete)
.onSuccess { _ in onSuccess() }
.onFailure(onFailure)
}
func channelPlaylist(_ id: String) -> Resource? {
resource(baseURL: account.url, path: basePathAppending("playlists/\(id)"))
}
func search(_ query: SearchQuery) -> Resource {
func search(_ query: SearchQuery, page: String?) -> Resource {
var resource = resource(baseURL: account.url, path: basePathAppending("search"))
.withParam("q", searchQuery(query.query))
.withParam("sort_by", query.sortBy.parameter)
@ -235,6 +401,10 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
resource = resource.withParam("duration", duration.rawValue)
}
if let page {
resource = resource.withParam("page", page)
}
return resource
}
@ -243,6 +413,13 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
.withParam("q", query.lowercased())
}
func comments(_ id: Video.ID, page: String?) -> Resource? {
let resource = resource(baseURL: account.url, path: basePathAppending("comments/\(id)"))
guard let page else { return resource }
return resource.withParam("continuation", page)
}
private func searchQuery(_ query: String) -> String {
var searchQuery = query
@ -263,11 +440,14 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
}
static func proxiedAsset(instance: Instance, asset: AVURLAsset) -> AVURLAsset? {
guard let instanceURLComponents = URLComponents(string: instance.apiURL),
guard let instanceURLComponents = URLComponents(url: instance.apiURL, resolvingAgainstBaseURL: false),
var urlComponents = URLComponents(url: asset.url, resolvingAgainstBaseURL: false) else { return nil }
urlComponents.scheme = instanceURLComponents.scheme
urlComponents.host = instanceURLComponents.host
urlComponents.user = instanceURLComponents.user
urlComponents.password = instanceURLComponents.password
urlComponents.port = instanceURLComponents.port
guard let url = urlComponents.url else {
return nil
@ -276,13 +456,15 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
return AVURLAsset(url: url)
}
static func extractVideo(from json: JSON) -> Video {
func extractVideo(from json: JSON) -> Video {
let indexID: String?
var id: Video.ID
var published = json["publishedText"].stringValue
var publishedAt: Date?
if let publishedInterval = json["published"].double {
publishedAt = Date(timeIntervalSince1970: publishedInterval)
published = ""
}
let videoID = json["videoId"].stringValue
@ -295,99 +477,363 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
id = videoID
}
let description = json["description"].stringValue
let length = json["lengthSeconds"].doubleValue
return Video(
instanceID: account.instanceID,
app: .invidious,
instanceURL: account.instance.apiURL,
id: id,
videoID: videoID,
title: json["title"].stringValue,
author: json["author"].stringValue,
length: json["lengthSeconds"].doubleValue,
published: json["publishedText"].stringValue,
length: length,
published: published,
views: json["viewCount"].intValue,
description: json["description"].stringValue,
description: description,
genre: json["genre"].stringValue,
channel: extractChannel(from: json),
thumbnails: extractThumbnails(from: json),
indexID: indexID,
live: json["liveNow"].boolValue,
upcoming: json["isUpcoming"].boolValue,
short: length <= Video.shortLength && length != 0.0,
publishedAt: publishedAt,
likes: json["likeCount"].int,
dislikes: json["dislikeCount"].int,
keywords: json["keywords"].arrayValue.map { $0.stringValue },
keywords: json["keywords"].arrayValue.compactMap { $0.string },
streams: extractStreams(from: json),
related: extractRelated(from: json)
related: extractRelated(from: json),
chapters: createChapters(from: description, thumbnails: json),
captions: extractCaptions(from: json)
)
}
static func extractChannel(from json: JSON) -> Channel {
let thumbnailURL = "https:\(json["authorThumbnails"].arrayValue.first?.dictionaryValue["url"]?.stringValue ?? "")"
func extractChannel(from json: JSON) -> Channel {
var thumbnailURL = json["authorThumbnails"].arrayValue.last?.dictionaryValue["url"]?.string ?? ""
// append protocol to unproxied thumbnail URL if it's missing
if thumbnailURL.count > 2,
String(thumbnailURL[..<thumbnailURL.index(thumbnailURL.startIndex, offsetBy: 2)]) == "//",
let accountUrlComponents = URLComponents(string: account.urlString)
{
thumbnailURL = "\(accountUrlComponents.scheme ?? "https"):\(thumbnailURL)"
}
let tabs = json["tabs"].arrayValue.compactMap { name in
if let name = name.string, let type = Channel.ContentType.from(name) {
return Channel.Tab(contentType: type, data: "")
}
return nil
}
return Channel(
app: .invidious,
id: json["authorId"].stringValue,
name: json["author"].stringValue,
bannerURL: json["authorBanners"].arrayValue.first?.dictionaryValue["url"]?.url,
thumbnailURL: URL(string: thumbnailURL),
description: json["description"].stringValue,
subscriptionsCount: json["subCount"].int,
subscriptionsText: json["subCountText"].string,
videos: json.dictionaryValue["latestVideos"]?.arrayValue.map(InvidiousAPI.extractVideo) ?? []
totalViews: json["totalViews"].int,
videos: json.dictionaryValue["latestVideos"]?.arrayValue.map(extractVideo) ?? [],
tabs: tabs
)
}
static func extractChannelPlaylist(from json: JSON) -> ChannelPlaylist {
func extractChannelPlaylist(from json: JSON) -> ChannelPlaylist {
let details = json.dictionaryValue
return ChannelPlaylist(
id: details["playlistId"]!.stringValue,
title: details["title"]!.stringValue,
id: details["playlistId"]?.string ?? details["mixId"]?.string ?? UUID().uuidString,
title: details["title"]?.stringValue ?? "",
thumbnailURL: details["playlistThumbnail"]?.url,
channel: extractChannel(from: json),
videos: details["videos"]?.arrayValue.compactMap(InvidiousAPI.extractVideo) ?? []
videos: details["videos"]?.arrayValue.compactMap(extractVideo) ?? [],
videosCount: details["videoCount"]?.int
)
}
private static func extractThumbnails(from details: JSON) -> [Thumbnail] {
details["videoThumbnails"].arrayValue.map { json in
Thumbnail(url: json["url"].url!, quality: .init(rawValue: json["quality"].string!)!)
// Determines if the request requires Basic Auth credentials to be removed
private func needsBasicAuthRemoval(for path: String) -> Bool {
return path.hasPrefix("\(Self.basePath)/auth/")
}
// Creates a resource URL with consideration for removing Basic Auth credentials
private func createResourceURL(baseURL: URL, path: String) -> URL {
var resourceURL = baseURL
// Remove Basic Auth credentials if required
if needsBasicAuthRemoval(for: path), var urlComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) {
urlComponents.user = nil
urlComponents.password = nil
resourceURL = urlComponents.url ?? baseURL
}
return resourceURL.appendingPathComponent(path)
}
func resourceWithAuthCheck(baseURL: URL, path: String) -> Resource {
let sanitizedURL = createResourceURL(baseURL: baseURL, path: path)
return super.resource(absoluteURL: sanitizedURL)
}
private func extractThumbnails(from details: JSON) -> [Thumbnail] {
details["videoThumbnails"].arrayValue.compactMap { json in
guard let url = json["url"].url,
var components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let quality = json["quality"].string,
let accountUrlComponents = URLComponents(string: account.urlString)
else {
return nil
}
// Some instances are not configured properly and return thumbnail links
// with an incorrect scheme or a missing port.
components.scheme = accountUrlComponents.scheme
components.port = accountUrlComponents.port
// If basic HTTP authentication is used,
// the username and password need to be prepended to the URL.
components.user = accountUrlComponents.user
components.password = accountUrlComponents.password
guard let thumbnailUrl = components.url else {
return nil
}
print("Final thumbnail URL: \(thumbnailUrl)")
return Thumbnail(url: thumbnailUrl, quality: .init(rawValue: quality)!)
}
}
private static func extractStreams(from json: JSON) -> [Stream] {
extractFormatStreams(from: json["formatStreams"].arrayValue) +
extractAdaptiveFormats(from: json["adaptiveFormats"].arrayValue)
private func createChapters(from description: String, thumbnails: JSON) -> [Chapter] {
var chapters = extractChapters(from: description)
if !chapters.isEmpty {
let thumbnailsData = extractThumbnails(from: thumbnails)
let thumbnailURL = thumbnailsData.first { $0.quality == .medium }?.url
for chapter in chapters.indices {
if let url = thumbnailURL {
chapters[chapter].image = url
}
}
}
return chapters
}
private static func extractFormatStreams(from streams: [JSON]) -> [Stream] {
streams.map {
SingleAssetStream(
avAsset: AVURLAsset(url: $0["url"].url!),
resolution: Stream.Resolution.from(resolution: $0["resolution"].stringValue),
private static var contentItemsKeys = ["items", "videos", "latestVideos", "playlists", "relatedChannels"]
private func extractChannelPage(from json: JSON, forceNotLast: Bool = false) -> ChannelPage {
let nextPage = json.dictionaryValue["continuation"]?.string
var contentItems = [ContentItem]()
if let key = Self.contentItemsKeys.first(where: { json.dictionaryValue.keys.contains($0) }),
let items = json.dictionaryValue[key]
{
contentItems = extractContentItems(from: items)
}
var last = false
if !forceNotLast {
last = nextPage?.isEmpty ?? true
}
return ChannelPage(
results: contentItems,
channel: extractChannel(from: json),
nextPage: nextPage,
last: last
)
}
private func extractStreams(from json: JSON) -> [Stream] {
let hls = extractHLSStreams(from: json)
if json["liveNow"].boolValue {
return hls
}
let videoId = json["videoId"].stringValue
return extractFormatStreams(from: json["formatStreams"].arrayValue, videoId: videoId) +
extractAdaptiveFormats(from: json["adaptiveFormats"].arrayValue, videoId: videoId) +
hls
}
private func extractFormatStreams(from streams: [JSON], videoId: String?) -> [Stream] {
streams.compactMap { stream in
guard let streamURL = stream["url"].url else {
return nil
}
let finalURL: URL
if let videoId, let itag = stream["itag"].string, account.instance.invidiousCompanion {
let companionURLString = "\(account.instance.apiURLString)/latest_version?id=\(videoId)&itag=\(itag)"
finalURL = URL(string: companionURLString) ?? streamURL
} else {
finalURL = streamURL
}
return SingleAssetStream(
instance: account.instance,
avAsset: AVURLAsset(url: finalURL),
resolution: Stream.Resolution.from(resolution: stream["resolution"].string ?? ""),
kind: .stream,
encoding: $0["encoding"].stringValue
encoding: stream["encoding"].string ?? ""
)
}
}
private static func extractAdaptiveFormats(from streams: [JSON]) -> [Stream] {
let audioAssetURL = streams.first { $0["type"].stringValue.starts(with: "audio/mp4") }
guard audioAssetURL != nil else {
return []
private func extractAdaptiveFormats(from streams: [JSON], videoId: String?) -> [Stream] {
let audioStreams = streams
.filter { $0["type"].stringValue.starts(with: "audio/mp4") }
.sorted {
$0.dictionaryValue["bitrate"]?.int ?? 0 >
$1.dictionaryValue["bitrate"]?.int ?? 0
}
guard let audioStream = audioStreams.first else {
return .init()
}
let videoAssetsURLs = streams.filter { $0["type"].stringValue.starts(with: "video/mp4") && $0["encoding"].stringValue == "h264" }
let videoStreams = streams.filter { $0["type"].stringValue.starts(with: "video/") }
return videoAssetsURLs.map {
Stream(
audioAsset: AVURLAsset(url: audioAssetURL!["url"].url!),
videoAsset: AVURLAsset(url: $0["url"].url!),
resolution: Stream.Resolution.from(resolution: $0["resolution"].stringValue),
return videoStreams.compactMap { videoStream in
guard let audioAssetURL = audioStream["url"].url,
let videoAssetURL = videoStream["url"].url,
let audioItag = audioStream["itag"].string,
let videoItag = videoStream["itag"].string
else {
return nil
}
let finalAudioURL: URL
let finalVideoURL: URL
if let videoId, account.instance.invidiousCompanion {
let audioCompanionURLString = "\(account.instance.apiURLString)/latest_version?id=\(videoId)&itag=\(audioItag)"
let videoCompanionURLString = "\(account.instance.apiURLString)/latest_version?id=\(videoId)&itag=\(videoItag)"
finalAudioURL = URL(string: audioCompanionURLString) ?? audioAssetURL
finalVideoURL = URL(string: videoCompanionURLString) ?? videoAssetURL
} else {
finalAudioURL = audioAssetURL
finalVideoURL = videoAssetURL
}
return Stream(
instance: account.instance,
audioAsset: AVURLAsset(url: finalAudioURL),
videoAsset: AVURLAsset(url: finalVideoURL),
resolution: Stream.Resolution.from(resolution: videoStream["resolution"].stringValue),
kind: .adaptive,
encoding: $0["encoding"].stringValue
encoding: videoStream["encoding"].string,
videoFormat: videoStream["type"].string,
bitrate: videoStream["bitrate"].int,
requestRange: videoStream["init"].string ?? videoStream["index"].string
)
}
}
private static func extractRelated(from content: JSON) -> [Video] {
private func extractHLSStreams(from content: JSON) -> [Stream] {
if let hlsURL = content.dictionaryValue["hlsUrl"]?.url {
return [Stream(instance: account.instance, hlsURL: hlsURL)]
}
return []
}
private func extractRelated(from content: JSON) -> [Video] {
content
.dictionaryValue["recommendedVideos"]?
.arrayValue
.compactMap(extractVideo(from:)) ?? []
}
private func extractPlaylist(from content: JSON) -> Playlist {
let id = content["playlistId"].stringValue
return Playlist(
id: id,
title: content["title"].stringValue,
visibility: content["isListed"].boolValue ? .public : .private,
editable: id.starts(with: "IV"),
updated: content["updated"].doubleValue,
videos: content["videos"].arrayValue.map { extractVideo(from: $0) }
)
}
private func extractComment(from content: JSON) -> Comment? {
let details = content.dictionaryValue
let author = details["author"]?.string ?? ""
let channelId = details["authorId"]?.string ?? UUID().uuidString
let authorAvatarURL = details["authorThumbnails"]?.arrayValue.last?.dictionaryValue["url"]?.string ?? ""
let htmlContent = details["contentHtml"]?.string ?? ""
let decodedContent = decodeHtml(htmlContent)
return Comment(
id: UUID().uuidString,
author: author,
authorAvatarURL: authorAvatarURL,
time: details["publishedText"]?.string ?? "",
pinned: false,
hearted: false,
likeCount: details["likeCount"]?.int ?? 0,
text: decodedContent,
repliesPage: details["replies"]?.dictionaryValue["continuation"]?.string,
channel: Channel(app: .invidious, id: channelId, name: author)
)
}
private func decodeHtml(_ htmlEncodedString: String) -> String {
if let data = htmlEncodedString.data(using: .utf8) {
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
]
if let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) {
return attributedString.string
}
}
return htmlEncodedString
}
private func extractCaptions(from content: JSON) -> [Captions] {
content["captions"].arrayValue.compactMap { details in
guard let url = URL(string: details["url"].stringValue, relativeTo: account.url) else { return nil }
return Captions(
label: details["label"].stringValue,
code: details["language_code"].stringValue,
url: url
)
}
}
private func extractContentItems(from json: JSON) -> [ContentItem] {
json.arrayValue.compactMap { extractContentItem(from: $0) }
}
private func extractContentItem(from json: JSON) -> ContentItem? {
let type = json.dictionaryValue["type"]?.string
if type == "channel" {
return ContentItem(channel: extractChannel(from: json))
}
if type == "playlist" {
return ContentItem(playlist: extractChannelPlaylist(from: json))
}
if type == "video" {
return ContentItem(video: extractVideo(from: json))
}
return nil
}
}
extension Channel.ContentType {
var invidiousID: String {
switch self {
case .livestreams:
return "streams"
default:
return rawValue
}
}
}

View File

@ -0,0 +1,594 @@
import Alamofire
import AVKit
import Defaults
import Foundation
import Siesta
import SwiftyJSON
final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
static let basePath = "/api/v1"
@Published var account: Account!
@Published var validInstance = true
var signedIn: Bool {
guard let account else { return false }
return !account.anonymous && !(account.token?.isEmpty ?? true)
}
static func withAnonymousAccountForInstanceURL(_ url: URL) -> PeerTubeAPI {
.init(account: Instance(app: .peerTube, apiURLString: url.absoluteString).anonymousAccount)
}
init(account: Account? = nil) {
super.init()
guard !account.isNil else {
self.account = .init(name: "Empty")
return
}
setAccount(account!)
}
func setAccount(_ account: Account) {
self.account = account
validInstance = account.anonymous
configure()
if !account.anonymous {
validate()
}
}
func validate() {
validateInstance()
validateSID()
}
func validateInstance() {
guard !validInstance else {
return
}
home?
.load()
.onSuccess { _ in
self.validInstance = true
}
.onFailure { _ in
self.validInstance = false
}
}
func validateSID() {
guard signedIn, !(account.token?.isEmpty ?? true) else {
return
}
feed(1)?
.load()
.onFailure { _ in
self.updateToken(force: true)
}
}
func configure() {
invalidateConfiguration()
configure {
if let cookie = self.cookieHeader {
$0.headers["Cookie"] = cookie
}
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
}
configure("**", requestMethods: [.post]) {
$0.pipeline[.parsing].removeTransformers()
}
configureTransformer(pathPattern("popular"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
content.json.arrayValue.map(self.extractVideo)
}
configureTransformer(pathPattern("videos"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
content.json.dictionaryValue["data"]?.arrayValue.map(self.extractVideo) ?? []
}
configureTransformer(pathPattern("search/videos"), requestMethods: [.get]) { (content: Entity<JSON>) -> SearchPage in
let results = content.json.dictionaryValue["data"]?.arrayValue.compactMap { json -> ContentItem in .init(video: self.extractVideo(from: json)) } ?? []
return SearchPage(results: results, last: results.isEmpty)
}
configureTransformer(pathPattern("search/suggestions"), requestMethods: [.get]) { (content: Entity<JSON>) -> [String] in
if let suggestions = content.json.dictionaryValue["suggestions"] {
return suggestions.arrayValue.map(String.init)
}
return []
}
configureTransformer(pathPattern("auth/playlists"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Playlist] in
content.json.arrayValue.map(self.extractPlaylist)
}
configureTransformer(pathPattern("auth/playlists/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> Playlist in
self.extractPlaylist(from: content.json)
}
configureTransformer(pathPattern("auth/playlists"), requestMethods: [.post, .patch]) { (content: Entity<Data>) -> Playlist in
self.extractPlaylist(from: JSON(parseJSON: String(data: content.content, encoding: .utf8)!))
}
configureTransformer(pathPattern("auth/feed"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
if let feedVideos = content.json.dictionaryValue["videos"] {
return feedVideos.arrayValue.map(self.extractVideo)
}
return []
}
configureTransformer(pathPattern("auth/subscriptions"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Channel] in
content.json.arrayValue.map(self.extractChannel)
}
configureTransformer(pathPattern("channels/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> Channel in
self.extractChannel(from: content.json)
}
configureTransformer(pathPattern("channels/*/latest"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
content.json.arrayValue.map(self.extractVideo)
}
configureTransformer(pathPattern("channels/*/playlists"), requestMethods: [.get]) { (content: Entity<JSON>) -> [ContentItem] in
let playlists = (content.json.dictionaryValue["playlists"]?.arrayValue ?? []).compactMap { self.extractChannelPlaylist(from: $0) }
return ContentItem.array(of: playlists)
}
configureTransformer(pathPattern("playlists/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPlaylist in
self.extractChannelPlaylist(from: content.json)
}
configureTransformer(pathPattern("videos/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> Video in
self.extractVideo(from: content.json)
}
configureTransformer(pathPattern("comments/*")) { (content: Entity<JSON>) -> CommentsPage in
let details = content.json.dictionaryValue
let comments = details["comments"]?.arrayValue.compactMap { self.extractComment(from: $0) } ?? []
let nextPage = details["continuation"]?.string
let disabled = !details["error"].isNil
return CommentsPage(comments: comments, nextPage: nextPage, disabled: disabled)
}
updateToken()
}
func updateToken(force: Bool = false) {
let (username, password) = AccountsModel.getCredentials(account)
guard !account.anonymous,
(account.token?.isEmpty ?? true) || force
else {
return
}
guard let username,
let password,
!username.isEmpty,
!password.isEmpty
else {
NavigationModel.shared.presentAlert(
title: "Account Error",
message: "Remove and add your account again in Settings."
)
return
}
let presentTokenUpdateFailedAlert: (AFDataResponse<Data?>?, String?) -> Void = { response, message in
NavigationModel.shared.presentAlert(
title: "Account Error",
message: message ?? "\(response?.response?.statusCode ?? -1) - \(response?.error?.errorDescription ?? "unknown")\nIf this issue persists, try removing and adding your account again in Settings."
)
}
AF
.request(login.url, method: .post, parameters: ["email": username, "password": password], encoding: URLEncoding.default)
.redirect(using: .doNotFollow)
.response { response in
guard let headers = response.response?.headers,
let cookies = headers["Set-Cookie"]
else {
presentTokenUpdateFailedAlert(response, nil)
return
}
let sidRegex = #"SID=(?<sid>[^;]*);"#
guard let sidRegex = try? NSRegularExpression(pattern: sidRegex),
let match = sidRegex.matches(in: cookies, range: NSRange(cookies.startIndex..., in: cookies)).first
else {
presentTokenUpdateFailedAlert(nil, String(format: "Could not extract SID from received cookies: %@".localized(), cookies))
return
}
let matchRange = match.range(withName: "sid")
if let substringRange = Range(matchRange, in: cookies) {
let sid = String(cookies[substringRange])
AccountsModel.setToken(self.account, sid)
self.objectWillChange.send()
} else {
presentTokenUpdateFailedAlert(nil, String(format: "Could not extract SID from received cookies: %@".localized(), cookies))
}
self.configure()
}
}
var login: Resource {
resource(baseURL: account.url, path: "login")
}
private func pathPattern(_ path: String) -> String {
"**\(Self.basePath)/\(path)"
}
private func basePathAppending(_ path: String) -> String {
"\(Self.basePath)/\(path)"
}
private var cookieHeader: String? {
guard let token = account?.token, !token.isEmpty else { return nil }
return "SID=\(token)"
}
var popular: Resource? {
resource(baseURL: account.url, path: "\(Self.basePath)/popular")
}
func trending(country _: Country, category _: TrendingCategory?) -> Resource {
resource(baseURL: account.url, path: "\(Self.basePath)/videos")
.withParam("isLocal", "true")
// .withParam("type", category?.name)
// .withParam("region", country.rawValue)
}
var home: Resource? {
resource(baseURL: account.url, path: "/feed/subscriptions")
}
func feed(_ page: Int?) -> Resource? {
resource(baseURL: account.url, path: "\(Self.basePath)/auth/feed")
.withParam("page", String(page ?? 1))
}
var subscriptions: Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
}
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
.child(channelID)
.request(.post)
.onCompletion { _ in onCompletion() }
}
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void) {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
.child(channelID)
.request(.delete)
.onCompletion { _ in onCompletion() }
}
func channel(_ id: String, contentType: Channel.ContentType, data _: String?, page _: String?) -> Resource {
if contentType == .playlists {
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)/playlists"))
}
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)"))
}
func channelByName(_: String) -> Resource? {
nil
}
func channelByUsername(_: String) -> Resource? {
nil
}
func channelVideos(_ id: String) -> Resource {
resource(baseURL: account.url, path: basePathAppending("channels/\(id)/latest"))
}
func video(_ id: String) -> Resource {
resource(baseURL: account.url, path: basePathAppending("videos/\(id)"))
}
var playlists: Resource? {
if account.isNil || account.anonymous {
return nil
}
return resource(baseURL: account.url, path: basePathAppending("auth/playlists"))
}
func playlist(_ id: String) -> Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/playlists/\(id)"))
}
func playlistVideos(_ id: String) -> Resource? {
playlist(id)?.child("videos")
}
func playlistVideo(_ playlistID: String, _ videoID: String) -> Resource? {
playlist(playlistID)?.child("videos").child(videoID)
}
func addVideoToPlaylist(
_ videoID: String,
_ playlistID: String,
onFailure: @escaping (RequestError) -> Void = { _ in },
onSuccess: @escaping () -> Void = {}
) {
let resource = playlistVideos(playlistID)
let body = ["videoId": videoID]
resource?
.request(.post, json: body)
.onSuccess { _ in onSuccess() }
.onFailure(onFailure)
}
func removeVideoFromPlaylist(
_ index: String,
_ playlistID: String,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping () -> Void
) {
let resource = playlistVideo(playlistID, index)
resource?
.request(.delete)
.onSuccess { _ in onSuccess() }
.onFailure(onFailure)
}
func playlistForm(
_ name: String,
_ visibility: String,
playlist: Playlist?,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping (Playlist?) -> Void
) {
let body = ["title": name, "privacy": visibility]
let resource = !playlist.isNil ? self.playlist(playlist!.id) : playlists
resource?
.request(!playlist.isNil ? .patch : .post, json: body)
.onSuccess { response in
if let modifiedPlaylist: Playlist = response.typedContent() {
onSuccess(modifiedPlaylist)
}
}
.onFailure(onFailure)
}
func deletePlaylist(
_ playlist: Playlist,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping () -> Void
) {
self.playlist(playlist.id)?
.request(.delete)
.onSuccess { _ in onSuccess() }
.onFailure(onFailure)
}
func channelPlaylist(_ id: String) -> Resource? {
resource(baseURL: account.url, path: basePathAppending("playlists/\(id)"))
}
func search(_ query: SearchQuery, page _: String?) -> Resource {
resource(baseURL: account.url, path: basePathAppending("search/videos"))
.withParam("search", query.query)
// .withParam("sort_by", query.sortBy.parameter)
// .withParam("type", "all")
//
// if let date = query.date, date != .any {
// resource = resource.withParam("date", date.rawValue)
// }
//
// if let duration = query.duration, duration != .any {
// resource = resource.withParam("duration", duration.rawValue)
// }
//
// if let page {
// resource = resource.withParam("page", page)
// }
// return resource
}
func searchSuggestions(query: String) -> Resource {
resource(baseURL: account.url, path: basePathAppending("search/suggestions"))
.withParam("q", query.lowercased())
}
func comments(_ id: Video.ID, page: String?) -> Resource? {
let resource = resource(baseURL: account.url, path: basePathAppending("comments/\(id)"))
guard let page else { return resource }
return resource.withParam("continuation", page)
}
static func proxiedAsset(instance: Instance, asset: AVURLAsset) -> AVURLAsset? {
guard let instanceURLComponents = URLComponents(string: instance.apiURLString),
var urlComponents = URLComponents(url: asset.url, resolvingAgainstBaseURL: false) else { return nil }
urlComponents.scheme = instanceURLComponents.scheme
urlComponents.host = instanceURLComponents.host
guard let url = urlComponents.url else {
return nil
}
return AVURLAsset(url: url)
}
func extractVideo(from json: JSON) -> Video {
let id = json["uuid"].stringValue
let url = json["url"].url
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let publishedAt = dateFormatter.date(from: json["publishedAt"].stringValue)
return Video(
instanceID: account.instanceID,
app: .peerTube,
instanceURL: account.instance.apiURL,
id: id,
videoID: id,
videoURL: url,
title: json["name"].stringValue,
author: json["channel"].dictionaryValue["name"]?.stringValue ?? "",
length: json["duration"].doubleValue,
views: json["views"].intValue,
description: json["description"].stringValue,
channel: extractChannel(from: json["channel"]),
thumbnails: extractThumbnails(from: json),
live: json["isLive"].boolValue,
publishedAt: publishedAt,
likes: json["likes"].int,
dislikes: json["dislikes"].int,
streams: extractStreams(from: json)
// related: extractRelated(from: json),
// chapters: extractChapters(from: description),
// captions: extractCaptions(from: json)
)
}
func extractChannel(from json: JSON) -> Channel {
Channel(
app: .peerTube,
id: json["id"].stringValue,
name: json["name"].stringValue
)
}
func extractChannelPlaylist(from json: JSON) -> ChannelPlaylist {
let details = json.dictionaryValue
return ChannelPlaylist(
id: details["playlistId"]?.string ?? details["mixId"]?.string ?? UUID().uuidString,
title: details["title"]?.stringValue ?? "",
thumbnailURL: details["playlistThumbnail"]?.url,
channel: extractChannel(from: json),
videos: details["videos"]?.arrayValue.compactMap(extractVideo) ?? [],
videosCount: details["videoCount"]?.int
)
}
private func extractThumbnails(from details: JSON) -> [Thumbnail] {
if let thumbnailPath = details["thumbnailPath"].string {
return [Thumbnail(url: URL(string: thumbnailPath, relativeTo: account.url)!, quality: .medium)]
}
return []
}
private func extractStreams(from json: JSON) -> [Stream] {
let hls = extractHLSStreams(from: json)
if json["isLive"].boolValue {
return hls
}
return extractFormatStreams(from: json) +
extractAdaptiveFormats(from: json) +
hls
}
private func extractFormatStreams(from json: JSON) -> [Stream] {
var streams = [Stream]()
if let fileURL = json.dictionaryValue["streamingPlaylists"]?.arrayValue.first?
.dictionaryValue["files"]?.arrayValue.first?
.dictionaryValue["fileUrl"]?.url
{
let resolution = Stream.Resolution.predefined(.hd720p30)
streams.append(SingleAssetStream(instance: account.instance, avAsset: AVURLAsset(url: fileURL), resolution: resolution, kind: .stream))
}
return streams
}
private func extractAdaptiveFormats(from json: JSON) -> [Stream] {
json.dictionaryValue["files"]?.arrayValue.compactMap { file in
if let resolution = file.dictionaryValue["resolution"]?.dictionaryValue["label"]?.stringValue, let url = file.dictionaryValue["fileUrl"]?.url {
return SingleAssetStream(instance: account.instance, avAsset: AVURLAsset(url: url), resolution: Stream.Resolution.from(resolution: resolution), kind: .adaptive, videoFormat: "mp4")
}
return nil
} ?? []
}
private func extractHLSStreams(from content: JSON) -> [Stream] {
if let hlsURL = content.dictionaryValue["streamingPlaylists"]?.arrayValue.first?.dictionaryValue["playlistUrl"]?.url {
return [Stream(instance: account.instance, hlsURL: hlsURL)]
}
return []
}
private func extractRelated(from content: JSON) -> [Video] {
content
.dictionaryValue["recommendedVideos"]?
.arrayValue
.compactMap(extractVideo(from:)) ?? []
}
private func extractPlaylist(from content: JSON) -> Playlist {
let id = content["playlistId"].stringValue
return Playlist(
id: id,
title: content["title"].stringValue,
visibility: content["isListed"].boolValue ? .public : .private,
editable: id.starts(with: "IV"),
updated: content["updated"].doubleValue,
videos: content["videos"].arrayValue.map { extractVideo(from: $0) }
)
}
private func extractComment(from content: JSON) -> Comment? {
let details = content.dictionaryValue
let author = details["author"]?.string ?? ""
let channelId = details["authorId"]?.string ?? UUID().uuidString
let authorAvatarURL = details["authorThumbnails"]?.arrayValue.last?.dictionaryValue["url"]?.string ?? ""
return Comment(
id: UUID().uuidString,
author: author,
authorAvatarURL: authorAvatarURL,
time: details["publishedText"]?.string ?? "",
pinned: false,
hearted: false,
likeCount: details["likeCount"]?.int ?? 0,
text: details["content"]?.string ?? "",
repliesPage: details["replies"]?.dictionaryValue["continuation"]?.string,
channel: Channel(app: .peerTube, id: channelId, name: author)
)
}
private func extractCaptions(from content: JSON) -> [Captions] {
content["captions"].arrayValue.compactMap { _ in
nil
// let baseURL = account.url
// guard let url = URL(string: baseURL + details["url"].stringValue) else { return nil }
//
// return Captions(
// label: details["label"].stringValue,
// code: details["language_code"].stringValue,
// url: url
// )
}
}
}

View File

@ -1,13 +1,18 @@
import Alamofire
import AVFoundation
import Foundation
import Siesta
import SwiftyJSON
final class PipedAPI: Service, ObservableObject, VideosAPI {
static var disallowedVideoCodecs = ["av01"]
static var authorizedEndpoints = ["subscriptions", "subscribe", "unsubscribe", "user/playlists"]
static var contentItemsKeys = ["items", "content", "relatedStreams"]
@Published var account: Account!
var anonymousAccount: Account {
.init(instanceID: account.instance.id, name: "Anonymous", url: account.instance.apiURL)
static func withAnonymousAccountForInstanceURL(_ url: URL) -> PipedAPI {
.init(account: Instance(app: .piped, apiURLString: url.absoluteString).anonymousAccount)
}
init(account: Account? = nil) {
@ -27,41 +32,200 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
}
func configure() {
invalidateConfiguration()
configure {
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
}
configureTransformer(pathPattern("channel/*")) { (content: Entity<JSON>) -> Channel? in
PipedAPI.extractChannel(from: content.json)
configure(whenURLMatches: { url in self.needsAuthorization(url) }) {
$0.headers["Authorization"] = self.account.token
}
configureTransformer(pathPattern("channel/*")) { (content: Entity<JSON>) -> ChannelPage in
let nextPage = content.json.dictionaryValue["nextpage"]?.string
let channel = self.extractChannel(from: content.json)
return ChannelPage(
results: self.extractContentItems(from: self.contentItemsDictionary(from: content.json)),
channel: channel,
nextPage: nextPage,
last: nextPage.isNil
)
}
configureTransformer(pathPattern("/nextpage/channel/*")) { (content: Entity<JSON>) -> ChannelPage in
let nextPage = content.json.dictionaryValue["nextpage"]?.string
return ChannelPage(
results: self.extractContentItems(from: self.contentItemsDictionary(from: content.json)),
channel: self.extractChannel(from: content.json),
nextPage: nextPage,
last: nextPage.isNil
)
}
configureTransformer(pathPattern("channels/tabs*")) { (content: Entity<JSON>) -> [ContentItem] in
(content.json.dictionaryValue["content"]?.arrayValue ?? []).compactMap { self.extractContentItem(from: $0) }
}
configureTransformer(pathPattern("c/*")) { (content: Entity<JSON>) -> Channel? in
self.extractChannel(from: content.json)
}
configureTransformer(pathPattern("user/*")) { (content: Entity<JSON>) -> Channel? in
self.extractChannel(from: content.json)
}
configureTransformer(pathPattern("playlists/*")) { (content: Entity<JSON>) -> ChannelPlaylist? in
PipedAPI.extractChannelPlaylist(from: content.json)
self.extractChannelPlaylist(from: content.json)
}
configureTransformer(pathPattern("user/playlists/create")) { (_: Entity<JSON>) in }
configureTransformer(pathPattern("user/playlists/delete")) { (_: Entity<JSON>) in }
configureTransformer(pathPattern("user/playlists/add")) { (_: Entity<JSON>) in }
configureTransformer(pathPattern("user/playlists/remove")) { (_: Entity<JSON>) in }
configureTransformer(pathPattern("streams/*")) { (content: Entity<JSON>) -> Video? in
PipedAPI.extractVideo(from: content.json)
self.extractVideo(from: content.json)
}
configureTransformer(pathPattern("trending")) { (content: Entity<JSON>) -> [Video] in
PipedAPI.extractVideos(from: content.json)
self.extractVideos(from: content.json)
}
configureTransformer(pathPattern("search")) { (content: Entity<JSON>) -> [ContentItem] in
PipedAPI.extractContentItems(from: content.json.dictionaryValue["items"]!)
configureTransformer(pathPattern("search")) { (content: Entity<JSON>) -> SearchPage in
let nextPage = content.json.dictionaryValue["nextpage"]?.string
return SearchPage(
results: self.extractContentItems(from: content.json.dictionaryValue["items"]!),
nextPage: nextPage,
last: nextPage == "null"
)
}
configureTransformer(pathPattern("suggestions")) { (content: Entity<JSON>) -> [String] in
content.json.arrayValue.map(String.init)
}
configureTransformer(pathPattern("subscriptions")) { (content: Entity<JSON>) -> [Channel] in
content.json.arrayValue.compactMap { self.extractChannel(from: $0) }
}
configureTransformer(pathPattern("feed")) { (content: Entity<JSON>) -> [Video] in
content.json.arrayValue.compactMap { self.extractVideo(from: $0) }
}
configureTransformer(pathPattern("comments/*")) { (content: Entity<JSON>?) -> CommentsPage in
guard let details = content?.json.dictionaryValue else {
return CommentsPage(comments: [], nextPage: nil, disabled: true)
}
let comments = details["comments"]?.arrayValue.compactMap { self.extractComment(from: $0) } ?? []
let nextPage = details["nextpage"]?.string
let disabled = details["disabled"]?.bool ?? false
return CommentsPage(comments: comments, nextPage: nextPage, disabled: disabled)
}
configureTransformer(pathPattern("user/playlists")) { (content: Entity<JSON>) -> [Playlist] in
content.json.arrayValue.compactMap { self.extractUserPlaylist(from: $0) }
}
if account.token.isNil || account.token!.isEmpty {
updateToken()
} else {
FeedModel.shared.onAccountChange()
SubscribedChannelsModel.shared.onAccountChange()
PlaylistsModel.shared.onAccountChange()
}
}
func channel(_ id: String) -> Resource {
resource(baseURL: account.url, path: "channel/\(id)")
func needsAuthorization(_ url: URL) -> Bool {
Self.authorizedEndpoints.contains { url.absoluteString.contains($0) }
}
func updateToken() {
let (username, password) = AccountsModel.getCredentials(account)
guard !account.anonymous,
let username,
let password
else {
return
}
AF.request(
login.url,
method: .post,
parameters: ["username": username, "password": password],
encoding: JSONEncoding.default
)
.responseDecodable(of: JSON.self) { [weak self] response in
guard let self else {
return
}
switch response.result {
case let .success(value):
let json = JSON(value)
let token = json.dictionaryValue["token"]?.string ?? ""
if let error = json.dictionaryValue["error"]?.string {
NavigationModel.shared.presentAlert(
title: "Account Error",
message: error
)
} else if !token.isEmpty {
AccountsModel.setToken(self.account, token)
self.objectWillChange.send()
} else {
NavigationModel.shared.presentAlert(
title: "Account Error",
message: "Could not update your token."
)
}
self.configure()
case let .failure(error):
NavigationModel.shared.presentAlert(
title: "Account Error",
message: error.localizedDescription
)
}
}
}
var login: Resource {
resource(baseURL: account.url, path: "login")
}
func channel(_ id: String, contentType: Channel.ContentType, data: String?, page: String?) -> Resource {
let path = page.isNil ? "channel" : "nextpage/channel"
var channel: Siesta.Resource
if contentType == .videos || data.isNil {
channel = resource(baseURL: account.url, path: "\(path)/\(id)")
} else {
channel = resource(baseURL: account.url, path: "channels/tabs")
.withParam("data", data)
}
if let page, !page.isEmpty {
channel = channel.withParam("nextpage", page)
}
return channel
}
func channelByName(_ name: String) -> Resource? {
resource(baseURL: account.url, path: "c/\(name)")
}
func channelByUsername(_ username: String) -> Resource? {
resource(baseURL: account.url, path: "user/\(username)")
}
func channelVideos(_ id: String) -> Resource {
channel(id)
channel(id, contentType: .videos)
}
func channelPlaylist(_ id: String) -> Resource? {
@ -73,10 +237,18 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
.withParam("region", country.rawValue)
}
func search(_ query: SearchQuery) -> Resource {
resource(baseURL: account.instance.apiURL, path: "search")
func search(_ query: SearchQuery, page: String?) -> Resource {
let path = page.isNil ? "search" : "nextpage/search"
let resource = resource(baseURL: account.instance.apiURL, path: path)
.withParam("q", query.query)
.withParam("filter", "")
.withParam("filter", "all")
if page.isNil {
return resource
}
return resource.withParam("nextpage", page)
}
func searchSuggestions(query: String) -> Resource {
@ -88,31 +260,135 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
resource(baseURL: account.instance.apiURL, path: "streams/\(id)")
}
var signedIn: Bool { false }
var signedIn: Bool {
guard let account else {
return false
}
return !account.anonymous && !(account.token?.isEmpty ?? true)
}
var subscriptions: Resource? {
resource(baseURL: account.instance.apiURL, path: "subscriptions")
}
func feed(_: Int?) -> Resource? {
resource(baseURL: account.instance.apiURL, path: "feed")
.withParam("authToken", account.token)
}
var subscriptions: Resource? { nil }
var feed: Resource? { nil }
var home: Resource? { nil }
var popular: Resource? { nil }
var playlists: Resource? { nil }
var playlists: Resource? {
resource(baseURL: account.instance.apiURL, path: "user/playlists")
}
func channelSubscription(_: String) -> Resource? { nil }
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
resource(baseURL: account.instance.apiURL, path: "subscribe")
.request(.post, json: ["channelId": channelID])
.onCompletion { _ in onCompletion() }
}
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
resource(baseURL: account.instance.apiURL, path: "unsubscribe")
.request(.post, json: ["channelId": channelID])
.onCompletion { _ in onCompletion() }
}
func playlist(_ id: String) -> Resource? {
channelPlaylist(id)
}
func playlist(_: String) -> Resource? { nil }
func playlistVideo(_: String, _: String) -> Resource? { nil }
func playlistVideos(_: String) -> Resource? { nil }
func addVideoToPlaylist(
_ videoID: String,
_ playlistID: String,
onFailure: @escaping (RequestError) -> Void = { _ in },
onSuccess: @escaping () -> Void = {}
) {
let resource = resource(baseURL: account.instance.apiURL, path: "user/playlists/add")
let body = ["videoId": videoID, "playlistId": playlistID]
resource
.request(.post, json: body)
.onSuccess { _ in onSuccess() }
.onFailure(onFailure)
}
func removeVideoFromPlaylist(
_ index: String,
_ playlistID: String,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping () -> Void
) {
let resource = resource(baseURL: account.instance.apiURL, path: "user/playlists/remove")
let body: [String: Any] = ["index": Int(index)!, "playlistId": playlistID]
resource
.request(.post, json: body)
.onSuccess { _ in onSuccess() }
.onFailure(onFailure)
}
func playlistForm(
_ name: String,
_: String,
playlist: Playlist?,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping (Playlist?) -> Void
) {
let body = ["name": name]
let resource = playlist.isNil ? resource(baseURL: account.instance.apiURL, path: "user/playlists/create") : nil
resource?
.request(.post, json: body)
.onSuccess { response in
if let modifiedPlaylist: Playlist = response.typedContent() {
onSuccess(modifiedPlaylist)
} else {
onSuccess(nil)
}
}
.onFailure(onFailure)
}
func deletePlaylist(
_ playlist: Playlist,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping () -> Void
) {
let resource = resource(baseURL: account.instance.apiURL, path: "user/playlists/delete")
let body = ["playlistId": playlist.id]
resource
.request(.post, json: body)
.onSuccess { _ in onSuccess() }
.onFailure(onFailure)
}
func comments(_ id: Video.ID, page: String?) -> Resource? {
let path = page.isNil ? "comments/\(id)" : "nextpage/comments/\(id)"
let resource = resource(baseURL: account.url, path: path)
if page.isNil {
return resource
}
return resource.withParam("nextpage", page)
}
private func pathPattern(_ path: String) -> String {
"**\(path)"
}
private static func extractContentItem(from content: JSON) -> ContentItem? {
private func extractContentItem(from content: JSON) -> ContentItem? {
let details = content.dictionaryValue
let url: String! = details["url"]?.string
let contentType: ContentItem.ContentType
if !url.isNil {
if let url = details["url"]?.string {
if url.contains("/playlist") {
contentType = .playlist
} else if url.contains("/channel") {
@ -126,184 +402,358 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
switch contentType {
case .video:
if let video = PipedAPI.extractVideo(from: content) {
if let video = extractVideo(from: content) {
return ContentItem(video: video)
}
case .playlist:
if let playlist = PipedAPI.extractChannelPlaylist(from: content) {
if let playlist = extractChannelPlaylist(from: content) {
return ContentItem(playlist: playlist)
}
case .channel:
if let channel = PipedAPI.extractChannel(from: content) {
if let channel = extractChannel(from: content) {
return ContentItem(channel: channel)
}
default:
return nil
}
return nil
}
private static func extractContentItems(from content: JSON) -> [ContentItem] {
content.arrayValue.compactMap { PipedAPI.extractContentItem(from: $0) }
private func extractContentItems(from content: JSON) -> [ContentItem] {
content.arrayValue.compactMap { extractContentItem(from: $0) }
}
private static func extractChannel(from content: JSON) -> Channel? {
private func extractChannel(from content: JSON) -> Channel? {
let attributes = content.dictionaryValue
guard let id = attributes["id"]?.stringValue ??
(attributes["url"] ?? attributes["uploaderUrl"])?.stringValue.components(separatedBy: "/").last
guard let id = attributes["id"]?.string ??
(attributes["url"] ?? attributes["uploaderUrl"])?.string?.components(separatedBy: "/").last
else {
return nil
}
let subscriptionsCount = attributes["subscriberCount"]?.intValue ?? attributes["subscribers"]?.intValue
let subscriptionsCount = attributes["subscriberCount"]?.int ?? attributes["subscribers"]?.int
var videos = [Video]()
if let relatedStreams = attributes["relatedStreams"] {
videos = PipedAPI.extractVideos(from: relatedStreams)
videos = extractVideos(from: relatedStreams)
}
let name = attributes["name"]?.string ??
attributes["uploaderName"]?.string ??
attributes["uploader"]?.string ?? ""
let thumbnailURL = attributes["avatarUrl"]?.url ??
attributes["uploaderAvatar"]?.url ??
attributes["avatar"]?.url ??
attributes["thumbnail"]?.url
let tabs = attributes["tabs"]?.arrayValue.compactMap { tab in
let name = tab["name"].string
let data = tab["data"].string
if let name, let data, let type = Channel.ContentType(rawValue: name) {
return Channel.Tab(contentType: type, data: data)
}
return nil
} ?? [Channel.Tab]()
return Channel(
app: .piped,
id: id,
name: attributes["name"]!.stringValue,
thumbnailURL: attributes["thumbnail"]?.url,
name: name,
bannerURL: attributes["bannerUrl"]?.url,
thumbnailURL: thumbnailURL,
subscriptionsCount: subscriptionsCount,
videos: videos
verified: attributes["verified"]?.bool,
videos: videos,
tabs: tabs
)
}
static func extractChannelPlaylist(from json: JSON) -> ChannelPlaylist? {
func extractChannelPlaylist(from json: JSON) -> ChannelPlaylist? {
let details = json.dictionaryValue
let id = details["url"]?.stringValue.components(separatedBy: "?list=").last ?? UUID().uuidString
let id = details["url"]?.stringValue.components(separatedBy: "?list=").last
let thumbnailURL = details["thumbnail"]?.url ?? details["thumbnailUrl"]?.url
var videos = [Video]()
if let relatedStreams = details["relatedStreams"] {
videos = PipedAPI.extractVideos(from: relatedStreams)
videos = extractVideos(from: relatedStreams)
}
return ChannelPlaylist(
id: id,
title: details["name"]!.stringValue,
id: id ?? UUID().uuidString,
title: details["name"]?.string ?? "",
thumbnailURL: thumbnailURL,
channel: extractChannel(from: json)!,
channel: extractChannel(from: json),
videos: videos,
videosCount: details["videos"]?.int
)
}
private static func extractVideo(from content: JSON) -> Video? {
let details = content.dictionaryValue
let url = details["url"]?.string
static func nonProxiedAsset(asset: AVURLAsset, completion: @escaping (AVURLAsset?) -> Void) {
guard var urlComponents = URLComponents(url: asset.url, resolvingAgainstBaseURL: false) else {
completion(asset)
return
}
if !url.isNil {
guard url!.contains("/watch") else {
guard let hostItem = urlComponents.queryItems?.first(where: { $0.name == "host" }),
let hostValue = hostItem.value
else {
completion(asset)
return
}
urlComponents.host = hostValue
guard let newUrl = urlComponents.url else {
completion(asset)
return
}
completion(AVURLAsset(url: newUrl))
}
// Overload used for hlsURLS
static func nonProxiedAsset(url: URL, completion: @escaping (AVURLAsset?) -> Void) {
let asset = AVURLAsset(url: url)
nonProxiedAsset(asset: asset, completion: completion)
}
private func extractVideo(from content: JSON) -> Video? {
let details = content.dictionaryValue
if let url = details["url"]?.string {
guard url.contains("/watch") else {
return nil
}
}
let channelId = details["uploaderUrl"]!.stringValue.components(separatedBy: "/").last!
let channelId = details["uploaderUrl"]?.string?.components(separatedBy: "/").last ?? "unknown"
let thumbnails: [Thumbnail] = Thumbnail.Quality.allCases.compactMap {
if let url = PipedAPI.buildThumbnailURL(from: content, quality: $0) {
if let url = buildThumbnailURL(from: content, quality: $0) {
return Thumbnail(url: url, quality: $0)
}
return nil
}
let author = details["uploaderName"]?.stringValue ?? details["uploader"]!.stringValue
let author = details["uploaderName"]?.string ?? details["uploader"]?.string ?? ""
let authorThumbnailURL = details["avatarUrl"]?.url ?? details["uploaderAvatar"]?.url ?? details["avatar"]?.url
let subscriptionsCount = details["uploaderSubscriberCount"]?.int
let uploaded = details["uploaded"]?.double
var published = (uploaded.isNil || uploaded == -1) ? nil : (uploaded! / 1000).formattedAsRelativeTime()
var publishedAt: Date?
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withInternetDateTime]
if published.isNil,
let date = details["uploadDate"]?.string,
let formattedDate = dateFormatter.date(from: date)
{
publishedAt = formattedDate
} else {
published = (details["uploadedDate"] ?? details["uploadDate"])?.string ?? ""
}
let live = details["livestream"]?.bool ?? (details["duration"]?.int == -1)
let description = extractDescription(from: content) ?? ""
var chapters = extractChapters(from: content)
if chapters.isEmpty, !description.isEmpty {
chapters = extractChapters(from: description)
}
let length = details["duration"]?.double ?? 0
return Video(
videoID: PipedAPI.extractID(from: content),
title: details["title"]!.stringValue,
instanceID: account.instanceID,
app: .piped,
instanceURL: account.instance.apiURL,
videoID: extractID(from: content),
title: details["title"]?.string ?? "",
author: author,
length: details["duration"]!.doubleValue,
published: details["uploadedDate"]?.stringValue ?? details["uploadDate"]!.stringValue,
views: details["views"]!.intValue,
description: PipedAPI.extractDescription(from: content),
channel: Channel(id: channelId, name: author),
length: length,
published: published ?? "",
views: details["views"]?.int ?? 0,
description: description,
channel: Channel(app: .piped, id: channelId, name: author, thumbnailURL: authorThumbnailURL, subscriptionsCount: subscriptionsCount),
thumbnails: thumbnails,
live: live,
short: details["isShort"]?.bool ?? (length <= Video.shortLength),
publishedAt: publishedAt,
likes: details["likes"]?.int,
dislikes: details["dislikes"]?.int,
streams: extractStreams(from: content),
related: extractRelated(from: content)
related: extractRelated(from: content),
chapters: extractChapters(from: content)
)
}
private static func extractID(from content: JSON) -> Video.ID {
content.dictionaryValue["url"]?.stringValue.components(separatedBy: "?v=").last ??
extractThumbnailURL(from: content)!.relativeString.components(separatedBy: "/")[4]
private func extractID(from content: JSON) -> Video.ID {
content.dictionaryValue["url"]?.string?.components(separatedBy: "?v=").last ??
extractThumbnailURL(from: content)?.relativeString.components(separatedBy: "/")[4] ?? ""
}
private static func extractThumbnailURL(from content: JSON) -> URL? {
content.dictionaryValue["thumbnail"]?.url! ?? content.dictionaryValue["thumbnailUrl"]!.url!
private func extractThumbnailURL(from content: JSON) -> URL? {
content.dictionaryValue["thumbnail"]?.url ?? content.dictionaryValue["thumbnailUrl"]?.url
}
private static func buildThumbnailURL(from content: JSON, quality: Thumbnail.Quality) -> URL? {
let thumbnailURL = extractThumbnailURL(from: content)
guard !thumbnailURL.isNil else {
private func buildThumbnailURL(from content: JSON, quality: Thumbnail.Quality) -> URL? {
guard let thumbnailURL = extractThumbnailURL(from: content) else {
return nil
}
return URL(string: thumbnailURL!
.absoluteString
.replacingOccurrences(of: "hqdefault", with: quality.filename)
.replacingOccurrences(of: "maxresdefault", with: quality.filename)
)!
return URL(
string: thumbnailURL
.absoluteString
.replacingOccurrences(of: "hqdefault", with: quality.filename)
.replacingOccurrences(of: "maxresdefault", with: quality.filename)
)
}
private static func extractDescription(from content: JSON) -> String? {
guard var description = content.dictionaryValue["description"]?.string else {
return nil
}
private func extractUserPlaylist(from json: JSON) -> Playlist? {
let id = json["id"].string ?? ""
let title = json["name"].string ?? ""
let visibility = Playlist.Visibility.private
description = description.replacingOccurrences(
return Playlist(id: id, title: title, visibility: visibility)
}
private func extractDescription(from content: JSON) -> String? {
guard let description = content.dictionaryValue["description"]?.string else { return nil }
return replaceHTML(description)
}
private func replaceHTML(_ string: String) -> String {
var string = string.replacingOccurrences(
of: "<br/>|<br />|<br>",
with: "\n",
options: .regularExpression,
range: nil
)
description = description.replacingOccurrences(
of: "<[^>]+>",
with: "",
options: .regularExpression,
range: nil
)
let linkRegex = #"(<a\s+(?:[^>]*?\s+)?href=\"[^"]*\">[^<]*<\/a>)"#
let hrefRegex = #"href=\"([^"]*)\">"#
guard let hrefRegex = try? NSRegularExpression(pattern: hrefRegex) else { return string }
string = string.replacingMatches(regex: linkRegex) { matchingGroup in
let results = hrefRegex.matches(in: matchingGroup, range: NSRange(matchingGroup.startIndex..., in: matchingGroup))
return description
if let result = results.first {
if let swiftRange = Range(result.range(at: 1), in: matchingGroup) {
return String(matchingGroup[swiftRange])
}
}
return matchingGroup
}
string = string
.replacingOccurrences(of: "&amp;", with: "&")
.replacingOccurrences(of: "&nbsp;", with: " ")
.replacingOccurrences(
of: "<[^>]+>",
with: "",
options: .regularExpression,
range: nil
)
return string
}
private static func extractVideos(from content: JSON) -> [Video] {
private func extractVideos(from content: JSON) -> [Video] {
content.arrayValue.compactMap(extractVideo(from:))
}
private static func extractStreams(from content: JSON) -> [Stream] {
private func extractStreams(from content: JSON) -> [Stream] {
var streams = [Stream]()
if let hlsURL = content.dictionaryValue["hls"]?.url {
streams.append(Stream(hlsURL: hlsURL))
streams.append(Stream(instance: account.instance, hlsURL: hlsURL))
}
guard let audioStream = PipedAPI.compatibleAudioStreams(from: content).first else {
let audioStreams = content
.dictionaryValue["audioStreams"]?
.arrayValue
.filter { $0.dictionaryValue["format"]?.string == "M4A" }
.filter { stream in
let type = stream.dictionaryValue["audioTrackType"]?.string
return type == nil || type == "ORIGINAL"
}
.sorted {
$0.dictionaryValue["bitrate"]?.int ?? 0 >
$1.dictionaryValue["bitrate"]?.int ?? 0
} ?? []
guard let audioStream = audioStreams.first else {
return streams
}
let videoStreams = PipedAPI.compatibleVideoStream(from: content)
let videoStreams = content.dictionaryValue["videoStreams"]?.arrayValue ?? []
videoStreams.forEach { videoStream in
let audioAsset = AVURLAsset(url: audioStream.dictionaryValue["url"]!.url!)
let videoAsset = AVURLAsset(url: videoStream.dictionaryValue["url"]!.url!)
for videoStream in videoStreams {
let videoCodec = videoStream.dictionaryValue["codec"]?.string ?? ""
if Self.disallowedVideoCodecs.contains(where: videoCodec.contains) {
continue
}
let videoOnly = videoStream.dictionaryValue["videoOnly"]?.boolValue ?? true
let resolution = Stream.Resolution.from(resolution: videoStream.dictionaryValue["quality"]!.stringValue)
guard let audioAssetUrl = audioStream.dictionaryValue["url"]?.url,
let videoAssetUrl = videoStream.dictionaryValue["url"]?.url
else {
continue
}
let audioAsset = AVURLAsset(url: audioAssetUrl)
let videoAsset = AVURLAsset(url: videoAssetUrl)
let videoOnly = videoStream.dictionaryValue["videoOnly"]?.bool ?? true
let quality = videoStream.dictionaryValue["quality"]?.string ?? "unknown"
let qualityComponents = quality.components(separatedBy: "p")
let fps = qualityComponents.count > 1 ? Int(qualityComponents[1]) : 30
let resolution = Stream.Resolution.from(resolution: quality, fps: fps)
let videoFormat = videoStream.dictionaryValue["format"]?.string
let bitrate = videoStream.dictionaryValue["bitrate"]?.int
var requestRange: String?
if let initStart = videoStream.dictionaryValue["initStart"]?.int,
let initEnd = videoStream.dictionaryValue["initEnd"]?.int
{
requestRange = "\(initStart)-\(initEnd)"
} else if let indexStart = videoStream.dictionaryValue["indexStart"]?.int,
let indexEnd = videoStream.dictionaryValue["indexEnd"]?.int
{
requestRange = "\(indexStart)-\(indexEnd)"
} else {
requestRange = nil
}
if videoOnly {
streams.append(
Stream(audioAsset: audioAsset, videoAsset: videoAsset, resolution: resolution, kind: .adaptive)
Stream(
instance: account.instance,
audioAsset: audioAsset,
videoAsset: videoAsset,
resolution: resolution,
kind: .adaptive,
videoFormat: videoFormat,
bitrate: bitrate,
requestRange: requestRange
)
)
} else {
streams.append(
SingleAssetStream(avAsset: videoAsset, resolution: resolution, kind: .stream)
SingleAssetStream(
instance: account.instance,
avAsset: videoAsset,
resolution: resolution,
kind: .stream
)
)
}
}
@ -311,27 +761,71 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
return streams
}
private static func extractRelated(from content: JSON) -> [Video] {
private func extractRelated(from content: JSON) -> [Video] {
content
.dictionaryValue["relatedStreams"]?
.arrayValue
.compactMap(extractVideo(from:)) ?? []
}
private static func compatibleAudioStreams(from content: JSON) -> [JSON] {
content
.dictionaryValue["audioStreams"]?
.arrayValue
.filter { $0.dictionaryValue["format"]?.stringValue == "M4A" }
.sorted {
$0.dictionaryValue["bitrate"]?.intValue ?? 0 > $1.dictionaryValue["bitrate"]?.intValue ?? 0
} ?? []
private func extractComment(from content: JSON) -> Comment? {
let details = content.dictionaryValue
let author = details["author"]?.string ?? ""
let commentorUrl = details["commentorUrl"]?.string
let channelId = commentorUrl?.components(separatedBy: "/")[2] ?? ""
let commentText = extractCommentText(from: details["commentText"]?.stringValue)
let commentId = details["commentId"]?.string ?? UUID().uuidString
// Sanity checks: return nil if required data is missing
if commentText.isEmpty || commentId.isEmpty || author.isEmpty {
return nil
}
return Comment(
id: commentId,
author: author,
authorAvatarURL: details["thumbnail"]?.string ?? "",
time: details["commentedTime"]?.string ?? "",
pinned: details["pinned"]?.bool ?? false,
hearted: details["hearted"]?.bool ?? false,
likeCount: details["likeCount"]?.int ?? 0,
text: commentText,
repliesPage: details["repliesPage"]?.string,
channel: Channel(app: .piped, id: channelId, name: author)
)
}
private static func compatibleVideoStream(from content: JSON) -> [JSON] {
content
.dictionaryValue["videoStreams"]?
.arrayValue
.filter { $0.dictionaryValue["format"] == "MPEG_4" } ?? []
private func extractCommentText(from string: String?) -> String {
guard let string, !string.isEmpty else { return "" }
return replaceHTML(string)
}
private func extractChapters(from content: JSON) -> [Chapter] {
guard let chapters = content.dictionaryValue["chapters"]?.array else {
return .init()
}
return chapters.compactMap { chapter in
guard let title = chapter["title"].string,
let image = chapter["image"].url,
let start = chapter["start"].double
else {
return nil
}
return Chapter(title: title, image: image, start: start)
}
}
private func contentItemsDictionary(from content: JSON) -> JSON {
if let key = Self.contentItemsKeys.first(where: { content.dictionaryValue.keys.contains($0) }),
let items = content.dictionaryValue[key]
{
return items
}
return .null
}
}

View File

@ -6,58 +6,121 @@ protocol VideosAPI {
var account: Account! { get }
var signedIn: Bool { get }
func channel(_ id: String) -> Resource
static func withAnonymousAccountForInstanceURL(_ url: URL) -> Self
func channel(_ id: String, contentType: Channel.ContentType, data: String?, page: String?) -> Resource
func channelByName(_ name: String) -> Resource?
func channelByUsername(_ username: String) -> Resource?
func channelVideos(_ id: String) -> Resource
func trending(country: Country, category: TrendingCategory?) -> Resource
func search(_ query: SearchQuery) -> Resource
func search(_ query: SearchQuery, page: String?) -> Resource
func searchSuggestions(query: String) -> Resource
func video(_ id: Video.ID) -> Resource
func feed(_ page: Int?) -> Resource?
var subscriptions: Resource? { get }
var feed: Resource? { get }
var home: Resource? { get }
var popular: Resource? { get }
var playlists: Resource? { get }
func channelSubscription(_ id: String) -> Resource?
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void)
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void)
func playlist(_ id: String) -> Resource?
func playlistVideo(_ playlistID: String, _ videoID: String) -> Resource?
func playlistVideos(_ id: String) -> Resource?
func addVideoToPlaylist(
_ videoID: String,
_ playlistID: String,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping () -> Void
)
func removeVideoFromPlaylist(
_ index: String,
_ playlistID: String,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping () -> Void
)
func playlistForm(
_ name: String,
_ visibility: String,
playlist: Playlist?,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping (Playlist?) -> Void
)
func deletePlaylist(
_ playlist: Playlist,
onFailure: @escaping (RequestError) -> Void,
onSuccess: @escaping () -> Void
)
func channelPlaylist(_ id: String) -> Resource?
func loadDetails(_ item: PlayerQueueItem, completionHandler: @escaping (PlayerQueueItem) -> Void)
func shareURL(_ item: ContentItem, frontendHost: String?, time: CMTime?) -> URL?
func loadDetails(
_ item: PlayerQueueItem,
failureHandler: ((RequestError) -> Void)?,
completionHandler: @escaping (PlayerQueueItem) -> Void
)
func shareURL(_ item: ContentItem, frontendURLString: String?, time: CMTime?) -> URL?
func comments(_ id: Video.ID, page: String?) -> Resource?
}
extension VideosAPI {
func loadDetails(_ item: PlayerQueueItem, completionHandler: @escaping (PlayerQueueItem) -> Void = { _ in }) {
func channel(_ id: String, contentType: Channel.ContentType, data: String? = nil, page: String? = nil) -> Resource {
channel(id, contentType: contentType, data: data, page: page)
}
func loadDetails(
_ item: PlayerQueueItem,
failureHandler: ((RequestError) -> Void)? = nil,
completionHandler: @escaping (PlayerQueueItem) -> Void = { _ in }
) {
guard (item.video?.streams ?? []).isEmpty else {
completionHandler(item)
return
}
video(item.videoID).load().onSuccess { response in
guard let video: Video = response.typedContent() else {
return
}
var newItem = item
newItem.video = video
completionHandler(newItem)
if let video = item.video, video.isLocal {
completionHandler(item)
return
}
video(item.videoID).load()
.onSuccess { response in
guard let video: Video = response.typedContent() else {
return
}
VideosCacheModel.shared.storeVideo(video)
var newItem = item
newItem.id = UUID()
newItem.video = video
completionHandler(newItem)
}
.onFailure { failureHandler?($0) }
}
func shareURL(_ item: ContentItem, frontendHost: String? = nil, time: CMTime? = nil) -> URL? {
guard let frontendHost = frontendHost ?? account.instance.frontendHost else {
return nil
func shareURL(_ item: ContentItem, frontendURLString: String? = nil, time: CMTime? = nil) -> URL? {
var urlComponents: URLComponents?
if let frontendURLString,
let frontendURL = URL(string: frontendURLString)
{
urlComponents = URLComponents(url: frontendURL, resolvingAgainstBaseURL: false)
} else if let instanceComponents = account?.instance?.urlComponents {
urlComponents = instanceComponents
}
var urlComponents = account.instance.urlComponents
urlComponents.host = frontendHost
guard var urlComponents else {
return nil
}
var queryItems = [URLQueryItem]()
@ -70,6 +133,8 @@ extension VideosAPI {
case .playlist:
urlComponents.path = "/playlist"
queryItems.append(.init(name: "list", value: item.playlist.id))
default:
return nil
}
if !time.isNil, time!.seconds.isFinite {
@ -80,6 +145,103 @@ extension VideosAPI {
urlComponents.queryItems = queryItems
}
return urlComponents.url!
return urlComponents.url
}
func extractChapters(from description: String) -> [Chapter] {
/*
The following chapter patterns are covered:
1) "start - end - title" / "start - end: Title" / "start - end title"
2) "start - title" / "start: title" / "start title" / "[start] - title" / "[start]: title" / "[start] title"
3) "index. title - start" / "index. title start"
4) "title: (start)"
5) "(start) title"
These represent:
- "start" and "end" are timestamps, defining the start and end of the individual chapter
- "title" is the name of the chapter
- "index" is the chapter's position in a list
The order of these patterns is important as it determines the priority. The patterns listed first have a higher priority.
In the case of multiple matches, the pattern with the highest priority will be chosen - lower number means higher priority.
*/
let patterns = [
"(?<=\\n|^)\\s*(?:►\\s*)?\\[?(?<start>(?:[0-9]+:){1,2}[0-9]+)\\]?(?:\\s*-\\s*)?(?<end>(?:[0-9]+:){1,2}[0-9]+)?(?:\\s*-\\s*|\\s*[:]\\s*)?(?<title>.*)(?=\\n|$)",
"(?<=\\n|^)\\s*(?:►\\s*)?\\[?(?<start>(?:[0-9]+:){1,2}[0-9]+)\\]?\\s*[-:]?\\s*(?<title>.+)(?=\\n|$)",
"(?<=\\n|^)(?<index>[0-9]+\\.\\s)(?<title>.+?)(?:\\s*-\\s*)?(?<start>(?:[0-9]+:){1,2}[0-9]+)(?=\\n|$)",
"(?<=\\n|^)(?<title>.+?):\\s*\\((?<start>(?:[0-9]+:){1,2}[0-9]+)\\)(?=\\n|$)",
"(?<=^|\\n)\\((?<start>(?:[0-9]+:){1,2}[0-9]+)\\)\\s*(?<title>.+?)(?=\\n|$)"
]
let extractChaptersGroup = DispatchGroup()
var capturedChapters: [Int: [Chapter]] = [:]
let lock = NSLock()
for (index, pattern) in patterns.enumerated() {
extractChaptersGroup.enter()
DispatchQueue.global().async {
if let chaptersRegularExpression = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) {
let chapterLines = chaptersRegularExpression.matches(in: description, range: NSRange(description.startIndex..., in: description))
let extractedChapters = chapterLines.compactMap { line -> Chapter? in
let titleRange = line.range(withName: "title")
let startRange = line.range(withName: "start")
guard let titleSubstringRange = Range(titleRange, in: description),
let startSubstringRange = Range(startRange, in: description)
else {
return nil
}
let titleCapture = String(description[titleSubstringRange]).trimmingCharacters(in: .whitespaces)
let startCapture = String(description[startSubstringRange])
let startComponents = startCapture.components(separatedBy: ":")
guard startComponents.count <= 3 else { return nil }
var hours: Double?
var minutes: Double?
var seconds: Double?
if startComponents.count == 3 {
hours = Double(startComponents[0])
minutes = Double(startComponents[1])
seconds = Double(startComponents[2])
} else if startComponents.count == 2 {
minutes = Double(startComponents[0])
seconds = Double(startComponents[1])
}
guard var startSeconds = seconds else { return nil }
startSeconds += (minutes ?? 0) * 60
startSeconds += (hours ?? 0) * 60 * 60
return Chapter(title: titleCapture, start: startSeconds)
}
if !extractedChapters.isEmpty {
lock.lock()
capturedChapters[index] = extractedChapters
lock.unlock()
}
}
extractChaptersGroup.leave()
}
}
extractChaptersGroup.wait()
// Now we sort the keys of the capturedChapters dictionary.
// These keys correspond to the priority of each pattern.
let sortedKeys = Array(capturedChapters.keys).sorted(by: <)
// Return first non-empty result in the order of patterns
for key in sortedKeys {
if let chapters = capturedChapters[key], !chapters.isEmpty {
return chapters
}
}
return []
}
}

View File

@ -1,14 +1,41 @@
import Foundation
enum VideosApp: String, CaseIterable {
case invidious, piped
enum AppType: String {
case local
case youTube
case peerTube
}
case local
case invidious
case piped
case peerTube
var name: String {
rawValue.capitalized
switch self {
case .peerTube:
return "PeerTube"
default:
return rawValue.capitalized
}
}
var appType: AppType {
switch self {
case .local:
return .local
case .invidious:
return .youTube
case .piped:
return .youTube
case .peerTube:
return .peerTube
}
}
var supportsAccounts: Bool {
self == .invidious
self != .local
}
var supportsPopular: Bool {
@ -19,19 +46,59 @@ enum VideosApp: String, CaseIterable {
self == .invidious
}
var supportsSearchSuggestions: Bool {
self != .peerTube
}
var supportsSubscriptions: Bool {
supportsAccounts
}
var paginatesSubscriptions: Bool {
self == .invidious
}
var supportsTrendingCategories: Bool {
self == .invidious
}
var supportsUserPlaylists: Bool {
self != .local
}
var userPlaylistsEndpointIncludesVideos: Bool {
self == .invidious
}
var userPlaylistsUseChannelPlaylistEndpoint: Bool {
self == .piped
}
var userPlaylistsHaveVisibility: Bool {
self == .invidious
}
var userPlaylistsAreEditable: Bool {
self == .invidious
}
var hasFrontendURL: Bool {
self == .piped
}
var searchUsesIndexedPages: Bool {
self == .invidious
}
var supportsOpeningChannelsByName: Bool {
self == .piped
}
var allowsDisablingVidoesProxying: Bool {
self == .invidious || self == .piped
}
var supportsOpeningVideosByID: Bool {
self != .local
}
}

View File

@ -0,0 +1,41 @@
import Cache
import Foundation
import Logging
import SwiftyJSON
struct BaseCacheModel {
static var shared = Self()
static let jsonToDataTransformer: (JSON) -> Data = { try! $0.rawData() }
static let jsonFromDataTransformer: (Data) -> JSON = { try! JSON(data: $0) }
static let jsonTransformer = Transformer(toData: jsonToDataTransformer, fromData: jsonFromDataTransformer)
static let imageCache = URLCache(memoryCapacity: 512 * 1000 * 1000, diskCapacity: 10 * 1000 * 1000 * 1000)
var models: [CacheModel] {
[
FeedCacheModel.shared,
VideosCacheModel.shared,
ChannelsCacheModel.shared,
PlaylistsCacheModel.shared,
ChannelPlaylistsCacheModel.shared,
SubscribedChannelsModel.shared
]
}
func clear() {
models.forEach { $0.clear() }
Self.imageCache.removeAllCachedResponses()
}
var totalSize: Int {
models.compactMap { $0.storage?.totalDiskStorageSize }.reduce(0, +) + Self.imageCache.currentDiskUsage
}
var totalSizeFormatted: String {
byteCountFormatter.string(fromByteCount: Int64(totalSize))
}
private var byteCountFormatter: ByteCountFormatter { .init() }
}

View File

@ -0,0 +1,17 @@
import Cache
import Foundation
import Logging
import SwiftyJSON
struct BookmarksCacheModel {
static var shared = Self()
let logger = Logger(label: "stream.yattee.cache")
static let bookmarksGroup = "group.stream.yattee.app.bookmarks"
let defaults = UserDefaults(suiteName: Self.bookmarksGroup)
func clear() {
guard let defaults else { return }
defaults.dictionaryRepresentation().keys.forEach(defaults.removeObject(forKey:))
}
}

View File

@ -0,0 +1,41 @@
import Cache
import Foundation
import SwiftyJSON
protocol CacheModel {
var storage: Storage<String, JSON>? { get }
func clear()
}
extension CacheModel {
func clear() {
try? storage?.removeAll()
}
func getFormattedDate(_ date: Date?) -> String {
guard let date else { return "unknown" }
let isSameDay = Calendar(identifier: .iso8601).isDate(date, inSameDayAs: Date())
let formatter = isSameDay ? dateFormatterForTimeOnly : dateFormatter
return formatter.string(from: date)
}
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}
var dateFormatterForTimeOnly: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .medium
return formatter
}
var iso8601DateFormatter: ISO8601DateFormatter { .init() }
}

View File

@ -0,0 +1,57 @@
import Cache
import Foundation
import Logging
import SwiftyJSON
struct ChannelPlaylistsCacheModel: CacheModel {
static let shared = Self()
let logger = Logger(label: "stream.yattee.cache.channel-playlists")
static let diskConfig = DiskConfig(name: "channel-playlists")
static let memoryConfig = MemoryConfig()
var storage = try? Storage<String, JSON>(
diskConfig: Self.diskConfig,
memoryConfig: Self.memoryConfig,
fileManager: FileManager.default,
transformer: BaseCacheModel.jsonTransformer
)
func storePlaylist(playlist: ChannelPlaylist) {
let date = iso8601DateFormatter.string(from: Date())
logger.info("STORE \(playlist.cacheKey) -- \(date)")
let feedTimeObject: JSON = ["date": date]
let playlistObject: JSON = ["playlist": playlist.json.object]
try? storage?.setObject(feedTimeObject, forKey: playlistTimeCacheKey(playlist.cacheKey))
try? storage?.setObject(playlistObject, forKey: playlist.cacheKey)
}
func retrievePlaylist(_ playlist: ChannelPlaylist) -> ChannelPlaylist? {
logger.info("RETRIEVE \(playlist.cacheKey)")
if let json = try? storage?.object(forKey: playlist.cacheKey).dictionaryValue["playlist"] {
return ChannelPlaylist.from(json)
}
return nil
}
func getPlaylistsTime(_ id: ChannelPlaylist.ID) -> Date? {
if let json = try? storage?.object(forKey: playlistTimeCacheKey(id)),
let string = json.dictionaryValue["date"]?.string,
let date = iso8601DateFormatter.date(from: string)
{
return date
}
return nil
}
func getFormattedPlaylistTime(_ id: ChannelPlaylist.ID) -> String {
getFormattedDate(getPlaylistsTime(id))
}
private func playlistTimeCacheKey(_ cacheKey: ChannelPlaylist.ID) -> String {
"\(cacheKey)-time"
}
}

View File

@ -0,0 +1,47 @@
import Cache
import Foundation
import Logging
import SwiftyJSON
struct ChannelsCacheModel: CacheModel {
static let shared = Self()
let logger = Logger(label: "stream.yattee.cache.channels")
static let diskConfig = DiskConfig(name: "channels")
static let memoryConfig = MemoryConfig()
let storage = try? Storage<String, JSON>(
diskConfig: Self.diskConfig,
memoryConfig: Self.memoryConfig,
fileManager: FileManager.default,
transformer: BaseCacheModel.jsonTransformer
)
func store(_ channel: Channel) {
guard channel.hasExtendedDetails else {
logger.debug("not caching \(channel.cacheKey)")
return
}
logger.info("caching \(channel.cacheKey)")
try? storage?.setObject(channel.json, forKey: channel.cacheKey)
}
func storeIfMissing(_ channel: Channel) {
guard let storage, !storage.objectExists(forKey: channel.cacheKey) else {
return
}
store(channel)
}
func retrieve(_ cacheKey: String) -> ChannelPage? {
logger.debug("retrieving cache for \(cacheKey)")
if let json = try? storage?.object(forKey: cacheKey) {
return ChannelPage(channel: Channel.from(json))
}
return nil
}
}

View File

@ -0,0 +1,67 @@
import Cache
import Defaults
import Foundation
import Logging
import SwiftyJSON
struct FeedCacheModel: CacheModel {
static let shared = Self()
let logger = Logger(label: "stream.yattee.cache.feed")
static let diskConfig = DiskConfig(name: "feed")
static let memoryConfig = MemoryConfig()
let storage = try? Storage<String, JSON>(
diskConfig: Self.diskConfig,
memoryConfig: Self.memoryConfig,
fileManager: FileManager.default,
transformer: BaseCacheModel.jsonTransformer
)
func storeFeed(account: Account, videos: [Video]) {
DispatchQueue.global(qos: .background).async {
let date = iso8601DateFormatter.string(from: Date())
logger.info("caching feed \(account.feedCacheKey) -- \(date)")
let feedTimeObject: JSON = ["date": date]
let videosObject: JSON = ["videos": videos.prefix(cacheLimit).map(\.json.object)]
try? storage?.setObject(feedTimeObject, forKey: feedTimeCacheKey(account.feedCacheKey))
try? storage?.setObject(videosObject, forKey: account.feedCacheKey)
}
}
func retrieveFeed(account: Account) -> [Video] {
logger.debug("retrieving cache for \(account.feedCacheKey)")
if let json = try? storage?.object(forKey: account.feedCacheKey),
let videos = json.dictionaryValue["videos"]
{
return videos.arrayValue.map { Video.from($0) }
}
return []
}
func getFeedTime(account: Account) -> Date? {
if let json = try? storage?.object(forKey: feedTimeCacheKey(account.feedCacheKey)),
let string = json.dictionaryValue["date"]?.string,
let date = iso8601DateFormatter.date(from: string)
{
return date
}
return nil
}
private var cacheLimit: Int {
let setting = Int(Defaults[.feedCacheSize]) ?? 0
if setting > 0 {
return setting
}
return 50
}
private func feedTimeCacheKey(_ feedCacheKey: String) -> String {
"\(feedCacheKey)-feedTime"
}
}

View File

@ -0,0 +1,64 @@
import Cache
import Foundation
import Logging
import SwiftyJSON
struct PlaylistsCacheModel: CacheModel {
static let shared = Self()
static let limit = 30
let logger = Logger(label: "stream.yattee.cache.playlists")
static let diskConfig = DiskConfig(name: "playlists")
static let memoryConfig = MemoryConfig()
let storage = try? Storage<String, JSON>(
diskConfig: Self.diskConfig,
memoryConfig: Self.memoryConfig,
fileManager: FileManager.default,
transformer: BaseCacheModel.jsonTransformer
)
func storePlaylist(account: Account, playlists: [Playlist]) {
let date = iso8601DateFormatter.string(from: Date())
logger.info("caching \(playlistCacheKey(account)) -- \(date)")
let feedTimeObject: JSON = ["date": date]
let playlistsObject: JSON = ["playlists": playlists.map(\.json.object)]
try? storage?.setObject(feedTimeObject, forKey: playlistTimeCacheKey(account))
try? storage?.setObject(playlistsObject, forKey: playlistCacheKey(account))
}
func retrievePlaylists(account: Account) -> [Playlist] {
logger.debug("retrieving cache for \(playlistCacheKey(account))")
if let json = try? storage?.object(forKey: playlistCacheKey(account)),
let playlists = json.dictionaryValue["playlists"]
{
return playlists.arrayValue.map { Playlist.from($0) }
}
return []
}
func getPlaylistsTime(account: Account) -> Date? {
if let json = try? storage?.object(forKey: playlistTimeCacheKey(account)),
let string = json.dictionaryValue["date"]?.string,
let date = iso8601DateFormatter.date(from: string)
{
return date
}
return nil
}
func getFormattedPlaylistTime(account: Account) -> String {
getFormattedDate(getPlaylistsTime(account: account))
}
private func playlistCacheKey(_ account: Account) -> String {
"playlists-\(account.id)"
}
private func playlistTimeCacheKey(_ account: Account) -> String {
"\(playlistCacheKey(account))-time"
}
}

View File

@ -0,0 +1,184 @@
import Cache
import Foundation
import Logging
import Siesta
import SwiftUI
import SwiftyJSON
final class SubscribedChannelsModel: ObservableObject, CacheModel {
static var shared = SubscribedChannelsModel()
let logger = Logger(label: "stream.yattee.cache.channels")
static let diskConfig = DiskConfig(name: "channels")
static let memoryConfig = MemoryConfig()
let storage = try? Storage<String, JSON>(
diskConfig: SubscribedChannelsModel.diskConfig,
memoryConfig: SubscribedChannelsModel.memoryConfig,
fileManager: FileManager.default,
transformer: BaseCacheModel.jsonTransformer
)
@Published var isLoading = false
@Published var channels = [Channel]()
@Published var error: RequestError?
var accounts: AccountsModel { .shared }
var unwatchedFeedCount: UnwatchedFeedCountModel { .shared }
var resource: Resource? {
accounts.api.subscriptions
}
var all: [Channel] {
channels.sorted { $0.name.lowercased() < $1.name.lowercased() }
}
var allByUnwatchedCount: [Channel] {
if let account = accounts.current {
return all.sorted { c1, c2 in
let c1HasUnwatched = (unwatchedFeedCount.unwatchedByChannel[account]?[c1.id] ?? -1) > 0
let c2HasUnwatched = (unwatchedFeedCount.unwatchedByChannel[account]?[c2.id] ?? -1) > 0
let nameIncreasing = c1.name.lowercased() < c2.name.lowercased()
return c1HasUnwatched ? (c2HasUnwatched ? nameIncreasing : true) : (c2HasUnwatched ? false : nameIncreasing)
}
}
return all
}
func subscribe(_ channelID: String, onSuccess: @escaping () -> Void = {}) {
accounts.api.subscribe(channelID) {
self.scheduleLoad(onSuccess: onSuccess)
}
}
func unsubscribe(_ channelID: String, onSuccess: @escaping () -> Void = {}) {
accounts.api.unsubscribe(channelID) {
self.scheduleLoad(onSuccess: onSuccess)
}
}
func isSubscribing(_ channelID: String) -> Bool {
channels.contains { $0.id == channelID }
}
func load(force: Bool = false, onSuccess: @escaping () -> Void = {}) {
guard accounts.app.supportsSubscriptions, !isLoading, accounts.signedIn, let account = accounts.current else {
channels = []
return
}
DispatchQueue.main.async { [weak self] in
guard let self else { return }
let request = force ? self.resource?.load() : self.resource?.loadIfNeeded()
guard request != nil else { return }
self.loadCachedChannels(account)
self.isLoading = true
request?
.onCompletion { [weak self] _ in
self?.isLoading = false
}
.onSuccess { resource in
self.error = nil
if let channels: [Channel] = resource.typedContent() {
self.channels = channels
self.storeChannels(account: account, channels: channels)
FeedModel.shared.calculateUnwatchedFeed()
onSuccess()
}
}
.onFailure { self.error = $0 }
}
}
func loadCachedChannels(_ account: Account) {
let cache = getChannels(account: account)
if !cache.isEmpty {
DispatchQueue.main.async {
self.channels = cache
}
}
}
func storeChannels(account: Account, channels: [Channel]) {
DispatchQueue.global(qos: .background).async {
let date = self.iso8601DateFormatter.string(from: Date())
self.logger.info("caching channels \(self.channelsDateCacheKey(account)) -- \(date)")
channels.forEach { ChannelsCacheModel.shared.storeIfMissing($0) }
let dateObject: JSON = ["date": date]
let channelsObject: JSON = ["channels": channels.map(\.json).map(\.object)]
try? self.storage?.setObject(dateObject, forKey: self.channelsDateCacheKey(account))
try? self.storage?.setObject(channelsObject, forKey: self.channelsCacheKey(account))
}
}
func getChannels(account: Account) -> [Channel] {
logger.info("getting channels \(channelsDateCacheKey(account))")
if let json = try? storage?.object(forKey: channelsCacheKey(account)),
let channels = json.dictionaryValue["channels"]
{
return channels.arrayValue.compactMap { json in
let channel = Channel.from(json)
if !channel.hasExtendedDetails,
let cache = ChannelsCacheModel.shared.retrieve(channel.cacheKey)
{
return cache.channel
}
return channel
}
}
return []
}
private func scheduleLoad(onSuccess: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.load(force: true, onSuccess: onSuccess)
}
}
private func channelsCacheKey(_ account: Account) -> String {
"channels-\(account.id)"
}
private func channelsDateCacheKey(_ account: Account) -> String {
"channels-\(account.id)-date"
}
func getChannelsTime(account: Account) -> Date? {
if let json = try? storage?.object(forKey: channelsDateCacheKey(account)),
let string = json.dictionaryValue["date"]?.string,
let date = iso8601DateFormatter.date(from: string)
{
return date
}
return nil
}
var channelsTime: Date? {
if let account = accounts.current {
return getChannelsTime(account: account)
}
return nil
}
var formattedCacheTime: String {
getFormattedDate(channelsTime)
}
func onAccountChange() {
channels = []
load(force: true)
}
}

View File

@ -0,0 +1,36 @@
import Cache
import Foundation
import Logging
import SwiftyJSON
struct VideosCacheModel: CacheModel {
static let shared = Self()
let logger = Logger(label: "stream.yattee.cache.videos")
static let diskConfig = DiskConfig(name: "videos")
static let memoryConfig = MemoryConfig()
let storage = try? Storage<String, JSON>(
diskConfig: Self.diskConfig,
memoryConfig: Self.memoryConfig,
fileManager: FileManager.default,
transformer: BaseCacheModel.jsonTransformer
)
func storeVideo(_ video: Video) {
logger.info("caching \(video.cacheKey)")
try? storage?.setObject(video.json, forKey: video.cacheKey)
ChannelsCacheModel.shared.storeIfMissing(video.channel)
}
func retrieveVideo(_ cacheKey: String) -> Video? {
logger.debug("retrieving cache for \(cacheKey)")
if let json = try? storage?.object(forKey: cacheKey) {
return Video.from(json)
}
return nil
}
}

12
Model/Captions.swift Normal file
View File

@ -0,0 +1,12 @@
import Foundation
struct Captions: Hashable, Identifiable {
var id = UUID().uuidString
let label: String
let code: String
let url: URL
var description: String {
"\(label) (\(code))"
}
}

View File

@ -4,39 +4,170 @@ import Foundation
import SwiftyJSON
struct Channel: Identifiable, Hashable {
enum ContentType: String, Identifiable, CaseIterable {
case videos
case playlists
case livestreams
case shorts
case channels
case releases
case podcasts
static func from(_ name: String) -> Self? {
let rawValueMatch = allCases.first { $0.rawValue == name }
guard rawValueMatch.isNil else { return rawValueMatch! }
if name == "streams" { return .livestreams }
return nil
}
var id: String {
rawValue
}
var description: String {
switch self {
case .livestreams:
return "Live Streams".localized()
default:
return rawValue.capitalized.localized()
}
}
var systemImage: String {
switch self {
case .videos:
return "video"
case .playlists:
return "list.and.film"
case .livestreams:
return "dot.radiowaves.left.and.right"
case .shorts:
return "1.square"
case .channels:
return "person.3"
case .releases:
return "square.stack"
case .podcasts:
return "radio"
}
}
var alwaysAvailable: Bool {
self == .videos || self == .playlists
}
}
struct Tab: Identifiable, Hashable {
var contentType: ContentType
var data: String
var id: String {
contentType.id
}
}
var app: VideosApp
var instanceID: Instance.ID?
var instanceURL: URL?
var id: String
var name: String
var bannerURL: URL?
var thumbnailURL: URL?
var description = ""
var subscriptionsCount: Int?
var subscriptionsText: String?
var totalViews: Int?
// swiftlint:disable discouraged_optional_boolean
var verified: Bool?
// swiftlint:enable discouraged_optional_boolean
var videos = [Video]()
var tabs = [Tab]()
private var subscriptionsCount: Int?
private var subscriptionsText: String?
init(
id: String,
name: String,
thumbnailURL: URL? = nil,
subscriptionsCount: Int? = nil,
subscriptionsText: String? = nil,
videos: [Video] = []
) {
self.id = id
self.name = name
self.thumbnailURL = thumbnailURL
self.subscriptionsCount = subscriptionsCount
self.subscriptionsText = subscriptionsText
self.videos = videos
var detailsLoaded: Bool {
!subscriptionsString.isNil
}
var subscriptionsString: String? {
if subscriptionsCount != nil, subscriptionsCount! > 0 {
return subscriptionsCount!.formattedAsAbbreviation()
if let subscriptionsCount, subscriptionsCount > 0 {
return subscriptionsCount.formattedAsAbbreviation()
}
return subscriptionsText
}
var totalViewsString: String? {
guard let totalViews, totalViews > 0 else { return nil }
return totalViews.formattedAsAbbreviation()
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
var contentItem: ContentItem {
ContentItem(channel: self)
}
func hasData(for contentType: ContentType) -> Bool {
tabs.contains { $0.contentType == contentType }
}
var cacheKey: String {
switch app {
case .local:
return id
case .invidious:
return "youtube-\(id)"
case .piped:
return "youtube-\(id)"
case .peerTube:
return "peertube-\(instanceURL?.absoluteString ?? "unknown-instance")-\(id)"
}
}
var hasExtendedDetails: Bool {
thumbnailURL != nil
}
var thumbnailURLOrCached: URL? {
thumbnailURL ?? ChannelsCacheModel.shared.retrieve(cacheKey)?.channel?.thumbnailURL
}
var json: JSON {
[
"app": app.rawValue,
"id": id,
"name": name,
"bannerURL": bannerURL?.absoluteString as Any,
"thumbnailURL": thumbnailURL?.absoluteString as Any,
"description": description,
"subscriptionsCount": subscriptionsCount as Any,
"subscriptionsText": subscriptionsText as Any,
"totalViews": totalViews as Any,
"verified": verified as Any,
"videos": videos.map(\.json.object)
]
}
static func from(_ json: JSON) -> Self {
.init(
app: VideosApp(rawValue: json["app"].stringValue) ?? .local,
id: json["id"].stringValue,
name: json["name"].stringValue,
bannerURL: json["bannerURL"].url,
thumbnailURL: json["thumbnailURL"].url,
description: json["description"].stringValue,
subscriptionsCount: json["subscriptionsCount"].int,
subscriptionsText: json["subscriptionsText"].string,
totalViews: json["totalViews"].int,
videos: json["videos"].arrayValue.map { Video.from($0) }
)
}
}

8
Model/ChannelPage.swift Normal file
View File

@ -0,0 +1,8 @@
import Foundation
struct ChannelPage {
var results = [ContentItem]()
var channel: Channel?
var nextPage: String?
var last = false
}

View File

@ -1,10 +1,37 @@
import Foundation
import SwiftyJSON
struct ChannelPlaylist: Identifiable {
var id: String = UUID().uuidString
var id: String
var title: String
var thumbnailURL: URL?
var channel: Channel?
var videos = [Video]()
var videosCount: Int?
var cacheKey: String {
"channelplaylists-\(id)"
}
var json: JSON {
[
"id": id,
"title": title,
"thumbnailURL": thumbnailURL?.absoluteString ?? "",
"channel": channel?.json.object ?? "",
"videos": videos.map(\.json.object),
"videosCount": String(videosCount ?? 0)
]
}
static func from(_ json: JSON) -> Self {
Self(
id: json["id"].stringValue,
title: json["title"].stringValue,
thumbnailURL: json["thumbnailURL"].url,
channel: Channel.from(json["channel"]),
videos: json["videos"].arrayValue.map { Video.from($0) },
videosCount: json["videosCount"].int
)
}
}

8
Model/Chapter.swift Normal file
View File

@ -0,0 +1,8 @@
import Foundation
struct Chapter: Identifiable, Equatable {
var id = UUID()
var title: String
var image: URL?
var start: Double
}

16
Model/Comment.swift Normal file
View File

@ -0,0 +1,16 @@
struct Comment: Identifiable, Equatable {
let id: String
let author: String
let authorAvatarURL: String
let time: String
let pinned: Bool
let hearted: Bool
var likeCount: Int
let text: String
let repliesPage: String?
let channel: Channel
var hasReplies: Bool {
!(repliesPage?.isEmpty ?? true)
}
}

107
Model/CommentsModel.swift Normal file
View File

@ -0,0 +1,107 @@
import Defaults
import Foundation
import SwiftyJSON
final class CommentsModel: ObservableObject {
static let shared = CommentsModel()
@Published var all = [Comment]()
@Published var nextPage: String?
@Published var firstPage = true
@Published var loaded = false
@Published var disabled = false
@Published var replies = [Comment]()
@Published var repliesPageID: String?
@Published var repliesLoaded = false
var player = PlayerModel.shared
var accounts = AccountsModel.shared
var instance: Instance? {
accounts.current?.instance
}
var nextPageAvailable: Bool {
!(nextPage?.isEmpty ?? true)
}
func loadIfNeeded() {
guard !loaded else { return }
load()
}
func load(page: String? = nil) {
guard let video = player.currentVideo else { return }
guard firstPage || nextPageAvailable else { return }
player
.playerAPI(video)?
.comments(video.videoID, page: page)?
.load()
.onSuccess { [weak self] response in
guard let self else { return }
if let commentsPage: CommentsPage = response.typedContent() {
self.all += commentsPage.comments
self.nextPage = commentsPage.nextPage
self.disabled = commentsPage.disabled
}
}
.onFailure { [weak self] _ in
self?.disabled = true
}
.onCompletion { [weak self] _ in
self?.loaded = true
}
}
func loadNextPageIfNeeded(current comment: Comment) {
let thresholdIndex = all.index(all.endIndex, offsetBy: -5)
if all.firstIndex(where: { $0 == comment }) == thresholdIndex {
loadNextPage()
}
}
func loadNextPage() {
guard nextPageAvailable else { return }
load(page: nextPage)
}
func loadReplies(page: String) {
guard !player.currentVideo.isNil else {
return
}
if page == repliesPageID {
return
}
replies = []
repliesPageID = page
repliesLoaded = false
accounts.api.comments(player.currentVideo!.videoID, page: page)?
.load()
.onSuccess { [weak self] response in
if let page: CommentsPage = response.typedContent() {
self?.replies = page.comments
self?.repliesLoaded = true
}
}
.onFailure { [weak self] _ in
self?.repliesLoaded = true
}
}
func reset() {
all = []
disabled = false
firstPage = true
nextPage = nil
loaded = false
replies = []
repliesLoaded = false
}
}

7
Model/CommentsPage.swift Normal file
View File

@ -0,0 +1,7 @@
import Foundation
struct CommentsPage {
var comments = [Comment]()
var nextPage: String?
var disabled = false
}

View File

@ -2,7 +2,7 @@ import Foundation
struct ContentItem: Identifiable {
enum ContentType: String {
case video, playlist, channel
case video, playlist, channel, placeholder
private var sortOrder: Int {
switch self {
@ -15,26 +15,51 @@ struct ContentItem: Identifiable {
}
}
static func < (lhs: ContentType, rhs: ContentType) -> Bool {
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.sortOrder < rhs.sortOrder
}
}
static var placeholders: [Self] {
(0 ..< 9).map { i in .init(id: String(i)) }
}
var video: Video!
var playlist: ChannelPlaylist!
var channel: Channel!
var id: String = UUID().uuidString
static func array(of videos: [Video]) -> [ContentItem] {
videos.map { ContentItem(video: $0) }
static func array(of videos: [Video]) -> [Self] {
videos.map { Self(video: $0) }
}
static func < (lhs: ContentItem, rhs: ContentItem) -> Bool {
static func array(of playlists: [ChannelPlaylist]) -> [Self] {
playlists.map { Self(playlist: $0) }
}
static func array(of channels: [Channel]) -> [Self] {
channels.map { Self(channel: $0) }
}
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.contentType < rhs.contentType
}
var contentType: ContentType {
video.isNil ? (channel.isNil ? .playlist : .channel) : .video
video.isNil ? (channel.isNil ? (playlist.isNil ? .placeholder : .playlist) : .channel) : .video
}
var cacheKey: String {
switch contentType {
case .video:
return video.cacheKey
case .playlist:
return playlist.cacheKey
case .channel:
return channel.cacheKey
case .placeholder:
return id
}
}
}

View File

@ -215,7 +215,7 @@ extension Country {
case .lk: return "Sri Lanka"
case .se: return "Sweden"
case .ch: return "Switzerland"
case .tw: return "Taiwan, Province of China[a]"
case .tw: return "Taiwan"
case .tz: return "Tanzania, United Republic of"
case .th: return "Thailand"
case .tn: return "Tunisia"
@ -234,6 +234,8 @@ extension Country {
}
}
// swiftlint:enable switch_case_on_newline
var flag: String {
let unicodeScalars = rawValue
.unicodeScalars
@ -272,7 +274,7 @@ extension Country {
private static func filteredCountries(_ predicate: (String) -> Bool) -> [Country] {
Country.allCases
.map { $0.name }
.map(\.name)
.filter(predicate)
.compactMap { string in Country.allCases.first { $0.name == string } }
}

166
Model/DocumentsModel.swift Normal file
View File

@ -0,0 +1,166 @@
import Foundation
final class DocumentsModel: ObservableObject {
static var shared = DocumentsModel()
@Published private(set) var refreshID = UUID()
typealias AreInIncreasingOrder = (URL, URL) -> Bool
private var fileManager: FileManager {
.default
}
var sortPredicates: [AreInIncreasingOrder] {
[
{ self.isDirectory($0) && !self.isDirectory($1) },
{ $0.lastPathComponent.caseInsensitiveCompare($1.lastPathComponent) == .orderedAscending }
]
}
func sortedDirectoryContents(_ directoryURL: URL) -> [URL] {
directoryContents(directoryURL).sorted { lhs, rhs in
for predicate in sortPredicates {
if !predicate(lhs, rhs), !predicate(rhs, lhs) {
continue
}
return predicate(lhs, rhs)
}
return false
}
}
func directoryContents(_ directoryURL: URL) -> [URL] {
contents(of: directoryURL)
}
var documentsDirectory: URL? {
if let url = try? fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) {
return standardizedURL(url)
}
return nil
}
func recentDocuments(_ limit: Int = 10) -> [URL] {
guard let documentsDirectory else { return [] }
return Array(
contents(of: documentsDirectory)
.filter { !isDirectory($0) }
.sorted {
((try? $0.resourceValues(forKeys: [.creationDateKey]).creationDate) ?? Date()) >
((try? $1.resourceValues(forKeys: [.creationDateKey]).creationDate) ?? Date())
}
.prefix(limit)
)
}
func isDocument(_ video: Video) -> Bool {
guard video.isLocal, let url = video.localStream?.localURL, let url = standardizedURL(url) else { return false }
return isDocument(url)
}
func isDocument(_ url: URL) -> Bool {
guard let url = standardizedURL(url), let documentsDirectory else { return false }
return url.absoluteString.starts(with: documentsDirectory.absoluteString)
}
func isDirectory(_ url: URL) -> Bool {
(try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
}
var creationDateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("YYMMddHHmm")
return formatter
}
func creationDate(_ video: Video) -> Date? {
guard video.isLocal, let url = video.localStream?.localURL, let url = standardizedURL(url) else { return nil }
return creationDate(url)
}
func creationDate(_ url: URL) -> Date? {
try? url.resourceValues(forKeys: [.creationDateKey]).creationDate
}
func formattedCreationDate(_ video: Video) -> String? {
guard video.isLocal, let url = video.localStream?.localURL, let url = standardizedURL(url) else { return nil }
return formattedCreationDate(url)
}
func formattedCreationDate(_ url: URL) -> String? {
if let date = try? url.resourceValues(forKeys: [.creationDateKey]).creationDate {
return creationDateFormatter.string(from: date)
}
return nil
}
var sizeFormatter: ByteCountFormatter {
let formatter = ByteCountFormatter()
formatter.allowedUnits = .useAll
formatter.countStyle = .file
formatter.includesUnit = true
formatter.isAdaptive = true
return formatter
}
func size(_ video: Video) -> Int? {
guard video.isLocal, let url = video.localStream?.localURL, let url = standardizedURL(url) else { return nil }
return size(url)
}
func size(_ url: URL) -> Int? {
try? url.resourceValues(forKeys: [.fileAllocatedSizeKey]).fileAllocatedSize
}
func formattedSize(_ video: Video) -> String? {
guard let size = size(video) else { return nil }
return sizeFormatter.string(fromByteCount: Int64(size))
}
func formattedSize(_ url: URL) -> String? {
guard let size = size(url) else { return nil }
return sizeFormatter.string(fromByteCount: Int64(size))
}
func removeDocument(_ url: URL) throws {
guard isDocument(url) else { return }
try fileManager.removeItem(at: url)
URLBookmarkModel.shared.removeBookmark(url)
refresh()
}
private func contents(of directory: URL) -> [URL] {
(try? fileManager.contentsOfDirectory(
at: directory,
includingPropertiesForKeys: [.creationDateKey, .fileAllocatedSizeKey, .isDirectoryKey],
options: [.includesDirectoriesPostOrder, .skipsHiddenFiles]
)) ?? []
}
func displayLabelForDocument(_ file: URL) -> String {
let components = file.absoluteString.components(separatedBy: "/Documents/")
if components.count == 2 {
let component = components[1]
return component.isEmpty ? "Documents" : component.removingPercentEncoding ?? component
}
return "Document"
}
func standardizedURL(_ url: URL) -> URL? {
let standardizedURL = NSString(string: url.absoluteString).standardizingPath
return URL(string: standardizedURL)
}
func refresh() {
refreshID = UUID()
}
}

View File

@ -3,16 +3,19 @@ import Foundation
struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
enum Section: Codable, Equatable, Defaults.Serializable {
case history
case subscriptions
case popular
case trending(String, String?)
case channel(String, String)
case playlist(String)
case channelPlaylist(String, String)
case channel(String, String, String)
case playlist(String, String)
case channelPlaylist(String, String, String)
case searchQuery(String, String, String, String)
var label: String {
switch self {
case .history:
return "History"
case .subscriptions:
return "Subscriptions"
case .popular:
@ -21,9 +24,9 @@ struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
let trendingCountry = Country(rawValue: country)!
let trendingCategory = category.isNil ? nil : TrendingCategory(rawValue: category!)
return "\(trendingCountry.flag) \(trendingCountry.id) \(trendingCategory?.name ?? "Trending")"
case let .channel(_, name):
case let .channel(_, _, name):
return name
case let .channelPlaylist(_, name):
case let .channelPlaylist(_, _, name):
return name
case let .searchQuery(text, date, duration, order):
var label = "Search: \"\(text)\""
@ -44,10 +47,14 @@ struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
}
}
static func == (lhs: FavoriteItem, rhs: FavoriteItem) -> Bool {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.section == rhs.section
}
var id = UUID().uuidString
var section: Section
var widgetSettingsKey: String {
"favorites-\(id)"
}
}

View File

@ -0,0 +1,134 @@
import Defaults
import Foundation
struct FavoritesModel {
static let shared = Self()
@Default(.showFavoritesInHome) var showFavoritesInHome
@Default(.favorites) var all
@Default(.widgetsSettings) var widgetsSettings
var isEnabled: Bool {
showFavoritesInHome
}
func contains(_ item: FavoriteItem) -> Bool {
all.contains { $0 == item }
}
func toggle(_ item: FavoriteItem) {
if contains(item) {
remove(item)
} else {
add(item)
}
}
func add(_ item: FavoriteItem) {
if contains(item) { return }
all.append(item)
}
func remove(_ item: FavoriteItem) {
if let index = all.firstIndex(where: { $0 == item }) {
all.remove(at: index)
}
}
func canMoveUp(_ item: FavoriteItem) -> Bool {
if let index = all.firstIndex(where: { $0 == item }) {
return index > all.startIndex
}
return false
}
func canMoveDown(_ item: FavoriteItem) -> Bool {
if let index = all.firstIndex(where: { $0 == item }) {
return index < all.endIndex - 1
}
return false
}
func moveUp(_ item: FavoriteItem) {
guard canMoveUp(item) else {
return
}
if let from = all.firstIndex(where: { $0 == item }) {
all.move(
fromOffsets: IndexSet(integer: from),
toOffset: from - 1
)
}
}
func moveDown(_ item: FavoriteItem) {
guard canMoveDown(item) else {
return
}
if let from = all.firstIndex(where: { $0 == item }) {
all.move(
fromOffsets: IndexSet(integer: from),
toOffset: from + 2
)
}
}
func addableItems() -> [FavoriteItem] {
let allItems = [
FavoriteItem(section: .subscriptions),
FavoriteItem(section: .popular),
FavoriteItem(section: .history)
]
return allItems.filter { item in !all.contains { $0.section == item.section } }
}
func listingStyle(_ item: FavoriteItem) -> WidgetListingStyle {
widgetSettings(item).listingStyle
}
func limit(_ item: FavoriteItem) -> Int {
min(WidgetSettings.maxLimit(listingStyle(item)), widgetSettings(item).limit)
}
func setListingStyle(_ style: WidgetListingStyle, _ item: FavoriteItem) {
if let index = widgetsSettings.firstIndex(where: { $0.id == item.widgetSettingsKey }) {
var settings = widgetsSettings[index]
settings.listingStyle = style
widgetsSettings[index] = settings
} else {
let settings = WidgetSettings(id: item.widgetSettingsKey, listingStyle: style)
widgetsSettings.append(settings)
}
}
func setLimit(_ limit: Int, _ item: FavoriteItem) {
if let index = widgetsSettings.firstIndex(where: { $0.id == item.widgetSettingsKey }) {
var settings = widgetsSettings[index]
let limit = min(max(1, limit), WidgetSettings.maxLimit(settings.listingStyle))
settings.limit = limit
widgetsSettings[index] = settings
} else {
var settings = WidgetSettings(id: item.widgetSettingsKey, limit: limit)
let limit = min(max(1, limit), WidgetSettings.maxLimit(settings.listingStyle))
settings.limit = limit
widgetsSettings.append(settings)
}
}
func widgetSettings(_ item: FavoriteItem) -> WidgetSettings {
widgetsSettings.first { $0.id == item.widgetSettingsKey } ?? WidgetSettings(id: item.widgetSettingsKey)
}
func updateWidgetSettings(_ settings: WidgetSettings) {
if let index = widgetsSettings.firstIndex(where: { $0.id == settings.id }) {
widgetsSettings[index] = settings
} else {
widgetsSettings.append(settings)
}
}
}

View File

@ -1,77 +0,0 @@
import Defaults
import Foundation
struct FavoritesModel {
static let shared = FavoritesModel()
@Default(.favorites) var all
func contains(_ item: FavoriteItem) -> Bool {
all.contains { $0 == item }
}
func toggle(_ item: FavoriteItem) {
contains(item) ? remove(item) : add(item)
}
func add(_ item: FavoriteItem) {
all.append(item)
}
func remove(_ item: FavoriteItem) {
if let index = all.firstIndex(where: { $0 == item }) {
all.remove(at: index)
}
}
func canMoveUp(_ item: FavoriteItem) -> Bool {
if let index = all.firstIndex(where: { $0 == item }) {
return index > all.startIndex
}
return false
}
func canMoveDown(_ item: FavoriteItem) -> Bool {
if let index = all.firstIndex(where: { $0 == item }) {
return index < all.endIndex - 1
}
return false
}
func moveUp(_ item: FavoriteItem) {
guard canMoveUp(item) else {
return
}
if let from = all.firstIndex(where: { $0 == item }) {
all.move(
fromOffsets: IndexSet(integer: from),
toOffset: from - 1
)
}
}
func moveDown(_ item: FavoriteItem) {
guard canMoveDown(item) else {
return
}
if let from = all.firstIndex(where: { $0 == item }) {
all.move(
fromOffsets: IndexSet(integer: from),
toOffset: from + 2
)
}
}
func addableItems() -> [FavoriteItem] {
let allItems = [
FavoriteItem(section: .subscriptions),
FavoriteItem(section: .popular)
]
return allItems.filter { item in !all.contains { $0.section == item.section } }
}
}

304
Model/FeedModel.swift Normal file
View File

@ -0,0 +1,304 @@
import Cache
import CoreData
import Defaults
import Foundation
import Siesta
import SwiftyJSON
final class FeedModel: ObservableObject, CacheModel {
static let shared = FeedModel()
@Published var isLoading = false
@Published var videos = [Video]()
@Published private var page = 1
@Published var watchedUUID = UUID()
private var feedCount = UnwatchedFeedCountModel.shared
private var cacheModel = FeedCacheModel.shared
private var accounts = AccountsModel.shared
var storage: Storage<String, JSON>?
@Published var error: RequestError?
private var backgroundContext = PersistenceController.shared.container.newBackgroundContext()
var feed: Resource? {
accounts.api.feed(page)
}
func loadResources(force: Bool = false, onCompletion: @escaping () -> Void = {}) {
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self else { return }
if force || self.videos.isEmpty {
self.loadCachedFeed()
}
if self.accounts.app == .invidious {
// Invidious for some reason won't refresh feed until homepage is loaded
DispatchQueue.main.async { [weak self] in
guard let self, let home = self.accounts.api.home else { return }
self.request(home, force: force)?
.onCompletion { _ in
self.loadFeed(force: force, onCompletion: onCompletion)
}
}
} else {
self.loadFeed(force: force, onCompletion: onCompletion)
}
}
}
func loadFeed(force: Bool = false, paginating: Bool = false, onCompletion: @escaping () -> Void = {}) {
DispatchQueue.main.async { [weak self] in
guard let self,
!self.isLoading,
let account = self.accounts.current
else {
self?.isLoading = false
onCompletion()
return
}
if paginating {
self.page += 1
} else {
self.page = 1
}
let feedBeforeLoad = self.feed
var request: Request?
if let feedBeforeLoad {
request = self.request(feedBeforeLoad, force: force)
}
if request != nil {
self.isLoading = true
}
request?
.onCompletion { _ in
self.isLoading = false
onCompletion()
}
.onSuccess { response in
self.error = nil
if let videos: [Video] = response.typedContent() {
if paginating {
self.videos.append(contentsOf: videos)
} else {
self.videos = videos
self.cacheModel.storeFeed(account: account, videos: self.videos)
self.calculateUnwatchedFeed()
}
}
}
.onFailure { self.error = $0 }
}
}
func reset() {
videos.removeAll()
page = 1
}
func loadNextPage() {
guard accounts.app.paginatesSubscriptions, !isLoading else { return }
loadFeed(force: true, paginating: true)
}
func onAccountChange() {
reset()
error = nil
loadResources(force: true)
calculateUnwatchedFeed()
}
func calculateUnwatchedFeed() {
guard let account = accounts.current, accounts.signedIn else { return }
let feed = cacheModel.retrieveFeed(account: account)
backgroundContext.perform { [weak self] in
guard let self else { return }
let watched = self.watchFetchRequestResult(feed, context: self.backgroundContext).filter(\.finished)
let unwatched = feed.filter { video in !watched.contains { $0.videoID == video.videoID } }
let unwatchedCount = max(0, feed.count - watched.count)
DispatchQueue.main.async { [weak self] in
guard let self else { return }
if unwatchedCount != self.feedCount.unwatched[account] {
self.feedCount.unwatched[account] = unwatchedCount
}
let byChannel = Dictionary(grouping: unwatched) { $0.channel.id }.mapValues(\.count)
self.feedCount.unwatchedByChannel[account] = byChannel
self.watchedUUID = UUID()
}
}
}
func markAllFeedAsWatched() {
let mark = { [weak self] in
guard let self else { return }
self.markVideos(self.videos, watched: true, watchedAt: Date(timeIntervalSince1970: 0))
}
if videos.isEmpty {
loadCachedFeed { mark() }
} else {
mark()
}
}
var canMarkAllFeedAsWatched: Bool {
guard let account = accounts.current, accounts.signedIn else { return false }
return (feedCount.unwatched[account] ?? 0) > 0
}
func canMarkChannelAsWatched(_ channelID: Channel.ID) -> Bool {
guard let account = accounts.current, accounts.signedIn else { return false }
return feedCount.unwatchedByChannel[account]?.keys.contains(channelID) ?? false
}
func markChannelAsWatched(_ channelID: Channel.ID) {
guard accounts.signedIn else { return }
let mark = { [weak self] in
guard let self else { return }
self.markVideos(self.videos.filter { $0.channel.id == channelID }, watched: true)
}
if videos.isEmpty {
loadCachedFeed { mark() }
} else {
mark()
}
}
func markChannelAsUnwatched(_ channelID: Channel.ID) {
guard accounts.signedIn else { return }
let mark = { [weak self] in
guard let self else { return }
self.markVideos(self.videos.filter { $0.channel.id == channelID }, watched: false)
}
if videos.isEmpty {
loadCachedFeed { mark() }
} else {
mark()
}
}
func markAllFeedAsUnwatched() {
guard accounts.current != nil else { return }
let mark = { [weak self] in
guard let self else { return }
self.markVideos(self.videos, watched: false)
}
if videos.isEmpty {
loadCachedFeed { mark() }
} else {
mark()
}
}
func markVideos(_ videos: [Video], watched: Bool, watchedAt: Date? = nil) {
guard accounts.signedIn, let account = accounts.current else { return }
backgroundContext.perform { [weak self] in
guard let self else { return }
if watched {
videos.forEach { Watch.markAsWatched(videoID: $0.videoID, account: account, duration: $0.length, watchedAt: watchedAt, context: self.backgroundContext) }
} else {
let watches = self.watchFetchRequestResult(videos, context: self.backgroundContext)
watches.forEach { self.backgroundContext.delete($0) }
}
try? self.backgroundContext.save()
self.calculateUnwatchedFeed()
WatchModel.shared.watchesChanged()
}
}
func playUnwatchedFeed() {
guard let account = accounts.current, accounts.signedIn else { return }
let videos = cacheModel.retrieveFeed(account: account)
guard !videos.isEmpty else { return }
let watches = watchFetchRequestResult(videos, context: backgroundContext)
let watchesIDs = watches.map(\.videoID)
let unwatched = videos.filter { video in
if Defaults[.hideShorts], video.short {
return false
}
if !watchesIDs.contains(video.videoID) {
return true
}
if let watch = watches.first(where: { $0.videoID == video.videoID }),
watch.finished
{
return false
}
return true
}
guard !unwatched.isEmpty else { return }
PlayerModel.shared.play(unwatched)
}
var canPlayUnwatchedFeed: Bool {
guard let account = accounts.current, accounts.signedIn else { return false }
return (feedCount.unwatched[account] ?? 0) > 0
}
var watchedId: String {
watchedUUID.uuidString
}
var feedTime: Date? {
if let account = accounts.current {
return cacheModel.getFeedTime(account: account)
}
return nil
}
var formattedFeedTime: String {
getFormattedDate(feedTime)
}
private func loadCachedFeed(_ onCompletion: @escaping () -> Void = {}) {
guard let account = accounts.current, accounts.signedIn else { return }
let cache = cacheModel.retrieveFeed(account: account)
if !cache.isEmpty {
DispatchQueue.main.async(qos: .userInteractive) { [weak self] in
self?.videos = cache
onCompletion()
}
}
}
private func request(_ resource: Resource, force: Bool = false) -> Request? {
if force {
return resource.load()
}
return resource.loadIfNeeded()
}
private func watchFetchRequestResult(_ videos: [Video], context: NSManagedObjectContext) -> [Watch] {
let watchFetchRequest = Watch.fetchRequest()
watchFetchRequest.predicate = NSPredicate(format: "videoID IN %@", videos.map(\.videoID) as [String])
return (try? context.fetch(watchFetchRequest)) ?? []
}
}

128
Model/HistoryModel.swift Normal file
View File

@ -0,0 +1,128 @@
import CoreData
import CoreMedia
import Defaults
import Foundation
import Siesta
import SwiftyJSON
extension PlayerModel {
func historyVideo(_ id: String) -> Video? {
historyVideos.first { $0.videoID == id }
}
func loadHistoryVideoDetails(_ watch: Watch, onCompletion: @escaping () -> Void = {}) {
guard historyVideo(watch.videoID).isNil else {
onCompletion()
return
}
if !Video.VideoID.isValid(watch.videoID), let url = URL(string: watch.videoID) {
historyVideos.append(.local(url))
onCompletion()
return
}
if let video = VideosCacheModel.shared.retrieveVideo(watch.video.cacheKey) {
historyVideos.append(video)
onCompletion()
return
}
guard let api = playerAPI(watch.video) else { return }
api.video(watch.videoID)
.load()
.onSuccess { [weak self] response in
guard let self else { return }
if let video: Video = response.typedContent() {
VideosCacheModel.shared.storeVideo(video)
self.historyVideos.append(video)
onCompletion()
}
}
.onCompletion { _ in
self.logger.info("LOADED history details: \(watch.videoID)")
}
}
func updateWatch(finished: Bool = false, time: CMTime? = nil) {
guard let currentVideo, saveHistory, isPlaying else { return }
let id = currentVideo.videoID
let time = time ?? backend.currentTime
let seconds = time?.seconds ?? 0
if seconds < 3 {
return
}
let watchFetchRequest = Watch.fetchRequest()
watchFetchRequest.predicate = NSPredicate(format: "videoID = %@", id as String)
let results = try? backgroundContext.fetch(watchFetchRequest)
backgroundContext.perform { [weak self] in
guard let self, finished || time != nil || self.backend.isPlaying else {
return
}
let watch: Watch!
let duration = self.activeBackend == .mpv ? self.playerTime.duration.seconds : self.avPlayerBackend.playerItemDuration?.seconds ?? 0
if results?.isEmpty ?? true {
watch = Watch(context: self.backgroundContext)
watch.videoID = id
watch.appName = currentVideo.app.rawValue
watch.instanceURL = currentVideo.instanceURL
} else {
watch = results?.first
}
if duration.isFinite, duration > 0 {
watch.videoDuration = duration
}
if watch.finished {
if !finished, self.resetWatchedStatusOnPlaying, seconds.isFinite, seconds > 0 {
watch.stoppedAt = seconds
}
} else if seconds.isFinite, seconds > 0 {
watch.stoppedAt = seconds
}
watch.watchedAt = Date()
try? self.backgroundContext.save()
}
}
func removeHistory() {
removeAllWatches()
BookmarksCacheModel.shared.clear()
}
func removeWatch(_ watch: Watch) {
context.perform { [weak self] in
guard let self else { return }
self.context.delete(watch)
try? self.context.save()
FeedModel.shared.calculateUnwatchedFeed()
WatchModel.shared.watchesChanged()
}
}
func removeAllWatches() {
let watchesFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Watch")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: watchesFetchRequest)
do {
try context.executeAndMergeChanges(deleteRequest)
try context.save()
} catch let error as NSError {
logger.info(.init(stringLiteral: error.localizedDescription))
}
}
}

View File

@ -0,0 +1,23 @@
import Defaults
import SwiftyJSON
final class AdvancedSettingsGroupExporter: SettingsGroupExporter {
override var globalJSON: JSON {
[
"showPlayNowInBackendContextMenu": Defaults[.showPlayNowInBackendContextMenu],
"videoLoadingRetryCount": Defaults[.videoLoadingRetryCount],
"showMPVPlaybackStats": Defaults[.showMPVPlaybackStats],
"mpvEnableLogging": Defaults[.mpvEnableLogging],
"mpvCacheSecs": Defaults[.mpvCacheSecs],
"mpvCachePauseWait": Defaults[.mpvCachePauseWait],
"mpvCachePauseInital": Defaults[.mpvCachePauseInital],
"mpvDeinterlace": Defaults[.mpvDeinterlace],
"mpvHWdec": Defaults[.mpvHWdec],
"mpvDemuxerLavfProbeInfo": Defaults[.mpvDemuxerLavfProbeInfo],
"mpvSetRefreshToContentFPS": Defaults[.mpvSetRefreshToContentFPS],
"mpvInitialAudioSync": Defaults[.mpvInitialAudioSync],
"showCacheStatus": Defaults[.showCacheStatus],
"feedCacheSize": Defaults[.feedCacheSize]
]
}
}

View File

@ -0,0 +1,55 @@
import Defaults
import SwiftyJSON
final class BrowsingSettingsGroupExporter: SettingsGroupExporter {
override var globalJSON: JSON {
[
"showHome": Defaults[.showHome],
"showOpenActionsInHome": Defaults[.showOpenActionsInHome],
"showQueueInHome": Defaults[.showQueueInHome],
"showFavoritesInHome": Defaults[.showFavoritesInHome],
"favorites": Defaults[.favorites].compactMap { jsonFromString(FavoriteItem.bridge.serialize($0)) },
"widgetsSettings": Defaults[.widgetsSettings].compactMap { widgetSettingsJSON($0) },
"startupSection": Defaults[.startupSection].rawValue,
"showSearchSuggestions": Defaults[.showSearchSuggestions],
"visibleSections": Defaults[.visibleSections].compactMap { $0.rawValue },
"showOpenActionsToolbarItem": Defaults[.showOpenActionsToolbarItem],
"accountPickerDisplaysAnonymousAccounts": Defaults[.accountPickerDisplaysAnonymousAccounts],
"showUnwatchedFeedBadges": Defaults[.showUnwatchedFeedBadges],
"expandChannelDescription": Defaults[.expandChannelDescription],
"keepChannelsWithUnwatchedFeedOnTop": Defaults[.keepChannelsWithUnwatchedFeedOnTop],
"showChannelAvatarInChannelsLists": Defaults[.showChannelAvatarInChannelsLists],
"showChannelAvatarInVideosListing": Defaults[.showChannelAvatarInVideosListing],
"playerButtonSingleTapGesture": Defaults[.playerButtonSingleTapGesture].rawValue,
"playerButtonDoubleTapGesture": Defaults[.playerButtonDoubleTapGesture].rawValue,
"playerButtonShowsControlButtonsWhenMinimized": Defaults[.playerButtonShowsControlButtonsWhenMinimized],
"playerButtonIsExpanded": Defaults[.playerButtonIsExpanded],
"playerBarMaxWidth": Defaults[.playerBarMaxWidth],
"channelOnThumbnail": Defaults[.channelOnThumbnail],
"timeOnThumbnail": Defaults[.timeOnThumbnail],
"roundedThumbnails": Defaults[.roundedThumbnails],
"thumbnailsQuality": Defaults[.thumbnailsQuality].rawValue
]
}
override var platformJSON: JSON {
var export = JSON()
#if os(iOS)
export["showDocuments"].bool = Defaults[.showDocuments]
export["lockPortraitWhenBrowsing"].bool = Defaults[.lockPortraitWhenBrowsing]
#endif
#if !os(tvOS)
export["accountPickerDisplaysUsername"].bool = Defaults[.accountPickerDisplaysUsername]
#endif
return export
}
private func widgetSettingsJSON(_ settings: WidgetSettings) -> JSON {
var json = JSON()
json.dictionaryObject = WidgetSettingsBridge().serialize(settings)
return json
}
}

View File

@ -0,0 +1,42 @@
import Defaults
import SwiftyJSON
final class ConstrolsSettingsGroupExporter: SettingsGroupExporter {
override var globalJSON: JSON {
[
"avPlayerUsesSystemControls": Defaults[.avPlayerUsesSystemControls],
"fullscreenPlayerGestureEnabled": Defaults[.fullscreenPlayerGestureEnabled],
"horizontalPlayerGestureEnabled": Defaults[.horizontalPlayerGestureEnabled],
"seekGestureSensitivity": Defaults[.seekGestureSensitivity],
"seekGestureSpeed": Defaults[.seekGestureSpeed],
"playerControlsLayout": Defaults[.playerControlsLayout].rawValue,
"fullScreenPlayerControlsLayout": Defaults[.fullScreenPlayerControlsLayout].rawValue,
"playerControlsBackgroundOpacity": Defaults[.playerControlsBackgroundOpacity],
"systemControlsCommands": Defaults[.systemControlsCommands].rawValue,
"buttonBackwardSeekDuration": Defaults[.buttonBackwardSeekDuration],
"buttonForwardSeekDuration": Defaults[.buttonForwardSeekDuration],
"gestureBackwardSeekDuration": Defaults[.gestureBackwardSeekDuration],
"gestureForwardSeekDuration": Defaults[.gestureForwardSeekDuration],
"systemControlsSeekDuration": Defaults[.systemControlsSeekDuration],
"playerControlsSettingsEnabled": Defaults[.playerControlsSettingsEnabled],
"playerControlsCloseEnabled": Defaults[.playerControlsCloseEnabled],
"playerControlsRestartEnabled": Defaults[.playerControlsRestartEnabled],
"playerControlsAdvanceToNextEnabled": Defaults[.playerControlsAdvanceToNextEnabled],
"playerControlsPlaybackModeEnabled": Defaults[.playerControlsPlaybackModeEnabled],
"playerControlsMusicModeEnabled": Defaults[.playerControlsMusicModeEnabled],
"playerActionsButtonLabelStyle": Defaults[.playerActionsButtonLabelStyle].rawValue,
"actionButtonShareEnabled": Defaults[.actionButtonShareEnabled],
"actionButtonAddToPlaylistEnabled": Defaults[.actionButtonAddToPlaylistEnabled],
"actionButtonSubscribeEnabled": Defaults[.actionButtonSubscribeEnabled],
"actionButtonSettingsEnabled": Defaults[.actionButtonSettingsEnabled],
"actionButtonHideEnabled": Defaults[.actionButtonHideEnabled],
"actionButtonCloseEnabled": Defaults[.actionButtonCloseEnabled],
"actionButtonFullScreenEnabled": Defaults[.actionButtonFullScreenEnabled],
"actionButtonPipEnabled": Defaults[.actionButtonPipEnabled],
"actionButtonLockOrientationEnabled": Defaults[.actionButtonLockOrientationEnabled],
"actionButtonRestartEnabled": Defaults[.actionButtonRestartEnabled],
"actionButtonAdvanceToNextItemEnabled": Defaults[.actionButtonAdvanceToNextItemEnabled],
"actionButtonMusicModeEnabled": Defaults[.actionButtonMusicModeEnabled]
]
}
}

View File

@ -0,0 +1,24 @@
import Defaults
import SwiftyJSON
final class HistorySettingsGroupExporter: SettingsGroupExporter {
override var globalJSON: JSON {
[
"saveRecents": Defaults[.saveRecents],
"saveHistory": Defaults[.saveHistory],
"showRecents": Defaults[.showRecents],
"limitRecents": Defaults[.limitRecents],
"limitRecentsAmount": Defaults[.limitRecentsAmount],
"showWatchingProgress": Defaults[.showWatchingProgress],
"saveLastPlayed": Defaults[.saveLastPlayed],
"watchedVideoPlayNowBehavior": Defaults[.watchedVideoPlayNowBehavior].rawValue,
"watchedThreshold": Defaults[.watchedThreshold],
"resetWatchedStatusOnPlaying": Defaults[.resetWatchedStatusOnPlaying],
"watchedVideoStyle": Defaults[.watchedVideoStyle].rawValue,
"watchedVideoBadgeColor": Defaults[.watchedVideoBadgeColor].rawValue,
"showToggleWatchedStatusButton": Defaults[.showToggleWatchedStatusButton]
]
}
}

View File

@ -0,0 +1,56 @@
import Defaults
import SwiftyJSON
final class LocationsSettingsGroupExporter: SettingsGroupExporter {
var includePublicInstances = true
var includeInstances = true
var includeAccounts = true
var includeAccountsUnencryptedPasswords = false
init(includePublicInstances: Bool = true, includeInstances: Bool = true, includeAccounts: Bool = true, includeAccountsUnencryptedPasswords: Bool = false) {
self.includePublicInstances = includePublicInstances
self.includeInstances = includeInstances
self.includeAccounts = includeAccounts
self.includeAccountsUnencryptedPasswords = includeAccountsUnencryptedPasswords
}
override var globalJSON: JSON {
var json = JSON()
if includePublicInstances {
json["instancesManifest"].string = Defaults[.instancesManifest]
json["countryOfPublicInstances"].string = Defaults[.countryOfPublicInstances] ?? ""
}
if includeInstances {
json["instances"].arrayObject = Defaults[.instances].compactMap { instanceJSON($0) }
}
if includeAccounts {
json["accounts"].arrayObject = Defaults[.accounts].compactMap { account in
var account = account
let (username, password) = AccountsModel.getCredentials(account)
account.username = username ?? ""
if includeAccountsUnencryptedPasswords {
account.password = password ?? ""
}
return accountJSON(account).dictionaryObject
}
}
return json
}
private func instanceJSON(_ instance: Instance) -> JSON {
var json = JSON()
json.dictionaryObject = InstancesBridge().serialize(instance)
return json
}
private func accountJSON(_ account: Account) -> JSON {
var json = JSON()
json.dictionaryObject = AccountsBridge().serialize(account)
return json
}
}

View File

@ -0,0 +1,27 @@
import Defaults
import SwiftyJSON
final class OtherDataSettingsGroupExporter: SettingsGroupExporter {
override var globalJSON: JSON {
[
"lastAccountID": Defaults[.lastAccountID] ?? "",
"lastInstanceID": Defaults[.lastInstanceID] ?? "",
"playerRate": Defaults[.playerRate],
"trendingCategory": Defaults[.trendingCategory].rawValue,
"trendingCountry": Defaults[.trendingCountry].rawValue,
"subscriptionsViewPage": Defaults[.subscriptionsViewPage].rawValue,
"subscriptionsListingStyle": Defaults[.subscriptionsListingStyle].rawValue,
"popularListingStyle": Defaults[.popularListingStyle].rawValue,
"trendingListingStyle": Defaults[.trendingListingStyle].rawValue,
"playlistListingStyle": Defaults[.playlistListingStyle].rawValue,
"channelPlaylistListingStyle": Defaults[.channelPlaylistListingStyle].rawValue,
"searchListingStyle": Defaults[.searchListingStyle].rawValue,
"hideShorts": Defaults[.hideShorts],
"hideWatched": Defaults[.hideWatched]
]
}
}

View File

@ -0,0 +1,54 @@
import Defaults
import SwiftyJSON
final class PlayerSettingsGroupExporter: SettingsGroupExporter {
override var globalJSON: JSON {
[
"playerInstanceID": Defaults[.playerInstanceID] ?? "",
"pauseOnHidingPlayer": Defaults[.pauseOnHidingPlayer],
"closeVideoOnEOF": Defaults[.closeVideoOnEOF],
"exitFullscreenOnEOF": Defaults[.exitFullscreenOnEOF],
"expandVideoDescription": Defaults[.expandVideoDescription],
"collapsedLinesDescription": Defaults[.collapsedLinesDescription],
"showChapters": Defaults[.showChapters],
"showChapterThumbnails": Defaults[.showChapterThumbnails],
"showChapterThumbnailsOnlyWhenDifferent": Defaults[.showChapterThumbnailsOnlyWhenDifferent],
"expandChapters": Defaults[.expandChapters],
"showRelated": Defaults[.showRelated],
"showInspector": Defaults[.showInspector].rawValue,
"playerSidebar": Defaults[.playerSidebar].rawValue,
"showKeywords": Defaults[.showKeywords],
"enableReturnYouTubeDislike": Defaults[.enableReturnYouTubeDislike],
"closePiPOnNavigation": Defaults[.closePiPOnNavigation],
"closePiPOnOpeningPlayer": Defaults[.closePiPOnOpeningPlayer],
"closePlayerOnOpeningPiP": Defaults[.closePlayerOnOpeningPiP],
"captionsAutoShow": Defaults[.captionsAutoShow],
"captionsDefaultLanguageCode": Defaults[.captionsDefaultLanguageCode],
"captionsFallbackLanguageCode": Defaults[.captionsFallbackLanguageCode],
"captionsFontScaleSize": Defaults[.captionsFontScaleSize],
"captionsFontColor": Defaults[.captionsFontColor]
]
}
override var platformJSON: JSON {
var export = JSON()
#if !os(macOS)
export["pauseOnEnteringBackground"].bool = Defaults[.pauseOnEnteringBackground]
#endif
export["showComments"].bool = Defaults[.showComments]
#if !os(tvOS)
export["showScrollToTopInComments"].bool = Defaults[.showScrollToTopInComments]
#endif
#if os(iOS)
export["isOrientationLocked"].bool = Defaults[.isOrientationLocked]
export["enterFullscreenInLandscape"].bool = Defaults[.enterFullscreenInLandscape]
export["rotateToLandscapeOnEnterFullScreen"].string = Defaults[.rotateToLandscapeOnEnterFullScreen].rawValue
#endif
return export
}
}

View File

@ -0,0 +1,21 @@
import Defaults
import SwiftyJSON
final class QualitySettingsGroupExporter: SettingsGroupExporter {
override var globalJSON: JSON {
[
"batteryCellularProfile": Defaults[.batteryCellularProfile],
"batteryNonCellularProfile": Defaults[.batteryNonCellularProfile],
"chargingCellularProfile": Defaults[.chargingCellularProfile],
"chargingNonCellularProfile": Defaults[.chargingNonCellularProfile],
"forceAVPlayerForLiveStreams": Defaults[.forceAVPlayerForLiveStreams],
"qualityProfiles": Defaults[.qualityProfiles].compactMap { qualityProfileJSON($0) }
]
}
func qualityProfileJSON(_ profile: QualityProfile) -> JSON {
var json = JSON()
json.dictionaryObject = QualityProfileBridge().serialize(profile)
return json
}
}

View File

@ -0,0 +1,16 @@
import Defaults
import SwiftyJSON
final class RecentlyOpenedExporter: SettingsGroupExporter {
override var globalJSON: JSON {
[
"recentlyOpened": Defaults[.recentlyOpened].compactMap { recentItemJSON($0) }
]
}
private func recentItemJSON(_ recentItem: RecentItem) -> JSON {
var json = JSON()
json.dictionaryObject = RecentItemBridge().serialize(recentItem)
return json
}
}

View File

@ -0,0 +1,32 @@
import Foundation
import SwiftyJSON
class SettingsGroupExporter { // swiftlint:disable:this final_class
var globalJSON: JSON {
[]
}
var platformJSON: JSON {
[]
}
var exportJSON: JSON {
var json = globalJSON
if !platformJSON.isEmpty {
try? json.merge(with: platformJSON)
}
return json
}
func jsonFromString(_ string: String?) -> JSON? {
if let data = string?.data(using: .utf8, allowLossyConversion: false),
let json = try? JSON(data: data)
{
return json
}
return nil
}
}

View File

@ -0,0 +1,15 @@
import Defaults
import SwiftyJSON
final class SponsorBlockSettingsGroupExporter: SettingsGroupExporter {
override var globalJSON: JSON {
[
"sponsorBlockInstance": Defaults[.sponsorBlockInstance],
"sponsorBlockCategories": Array(Defaults[.sponsorBlockCategories]),
"sponsorBlockColors": Defaults[.sponsorBlockColors],
"sponsorBlockShowTimeWithSkipsRemoved": Defaults[.sponsorBlockShowTimeWithSkipsRemoved],
"sponsorBlockShowCategoriesInTimeline": Defaults[.sponsorBlockShowCategoriesInTimeline],
"sponsorBlockShowNoticeAfterSkip": Defaults[.sponsorBlockShowNoticeAfterSkip]
]
}
}

View File

@ -0,0 +1,193 @@
import Defaults
import Foundation
import SwiftUI
import SwiftyJSON
final class ImportExportSettingsModel: ObservableObject {
static let shared = ImportExportSettingsModel()
static var exportFile: URL {
YatteeApp.settingsExportDirectory
.appendingPathComponent("Yattee Settings from \(Constants.deviceName).\(settingsExtension)")
}
static var settingsExtension: String {
"yatteesettings"
}
enum ExportGroup: String, Identifiable, CaseIterable {
case browsingSettings
case playerSettings
case controlsSettings
case qualitySettings
case historySettings
case sponsorBlockSettings
case advancedSettings
case locationsSettings
case instances
case accounts
case accountsUnencryptedPasswords
case recentlyOpened
case otherData
static var settingsGroups: [Self] {
[.browsingSettings, .playerSettings, .controlsSettings, .qualitySettings, .historySettings, .sponsorBlockSettings, .advancedSettings]
}
static var locationsGroups: [Self] {
[.locationsSettings, .instances, .accounts, .accountsUnencryptedPasswords]
}
static var otherGroups: [Self] {
[.recentlyOpened, .otherData]
}
var id: RawValue {
rawValue
}
var label: String {
switch self {
case .browsingSettings:
return "Browsing"
case .playerSettings:
return "Player"
case .controlsSettings:
return "Controls"
case .qualitySettings:
return "Quality"
case .historySettings:
return "History"
case .sponsorBlockSettings:
return "SponsorBlock"
case .locationsSettings:
return "Public Locations"
case .instances:
return "Custom Locations"
case .accounts:
return "Accounts"
case .accountsUnencryptedPasswords:
return "Accounts passwords (unencrypted)"
case .advancedSettings:
return "Advanced"
case .recentlyOpened:
return "Recents"
case .otherData:
return "Other data"
}
}
}
@Published var selectedExportGroups = Set<ExportGroup>()
static var defaultExportGroups = Set<ExportGroup>([
.browsingSettings,
.playerSettings,
.controlsSettings,
.qualitySettings,
.historySettings,
.sponsorBlockSettings,
.locationsSettings,
.instances,
.accounts,
.advancedSettings
])
@Published var isExportInProgress = false
private var navigation = NavigationModel.shared
private var settings = SettingsModel.shared
func toggleExportGroupSelection(_ group: ExportGroup) {
if isGroupSelected(group) {
selectedExportGroups.remove(group)
} else {
selectedExportGroups.insert(group)
}
removeNotEnabledSelectedGroups()
}
func reset() {
isExportInProgress = false
selectedExportGroups = Self.defaultExportGroups
}
func reset(_ model: ImportSettingsFileModel? = nil) {
reset()
guard let model else { return }
selectedExportGroups = selectedExportGroups.filter { model.isGroupIncludedInFile($0) }
}
func exportAction() {
DispatchQueue.global(qos: .background).async { [weak self] in
var writingOptions: JSONSerialization.WritingOptions = []
#if DEBUG
writingOptions.insert(.prettyPrinted)
writingOptions.insert(.sortedKeys)
#endif
try? self?.jsonForExport?.rawString(options: writingOptions)?.write(to: Self.exportFile, atomically: true, encoding: String.Encoding.utf8)
#if os(macOS)
DispatchQueue.main.async { [weak self] in
self?.isExportInProgress = false
}
NSWorkspace.shared.selectFile(Self.exportFile.path, inFileViewerRootedAtPath: YatteeApp.settingsExportDirectory.path)
#endif
}
}
private var jsonForExport: JSON? {
[
"metadata": metadataJSON,
"browsingSettings": selectedExportGroups.contains(.browsingSettings) ? BrowsingSettingsGroupExporter().exportJSON : JSON(),
"playerSettings": selectedExportGroups.contains(.playerSettings) ? PlayerSettingsGroupExporter().exportJSON : JSON(),
"controlsSettings": selectedExportGroups.contains(.controlsSettings) ? ConstrolsSettingsGroupExporter().exportJSON : JSON(),
"qualitySettings": selectedExportGroups.contains(.qualitySettings) ? QualitySettingsGroupExporter().exportJSON : JSON(),
"historySettings": selectedExportGroups.contains(.historySettings) ? HistorySettingsGroupExporter().exportJSON : JSON(),
"sponsorBlockSettings": selectedExportGroups.contains(.sponsorBlockSettings) ? SponsorBlockSettingsGroupExporter().exportJSON : JSON(),
"locationsSettings": LocationsSettingsGroupExporter(
includePublicInstances: isGroupSelected(.locationsSettings),
includeInstances: isGroupSelected(.instances),
includeAccounts: isGroupSelected(.accounts),
includeAccountsUnencryptedPasswords: isGroupSelected(.accountsUnencryptedPasswords)
).exportJSON,
"advancedSettings": selectedExportGroups.contains(.advancedSettings) ? AdvancedSettingsGroupExporter().exportJSON : JSON(),
"recentlyOpened": selectedExportGroups.contains(.recentlyOpened) ? RecentlyOpenedExporter().exportJSON : JSON(),
"otherData": selectedExportGroups.contains(.otherData) ? OtherDataSettingsGroupExporter().exportJSON : JSON()
]
}
private var metadataJSON: JSON {
[
"build": YatteeApp.build,
"timestamp": "\(Date().timeIntervalSince1970)",
"platform": Constants.platform
]
}
func isGroupSelected(_ group: ExportGroup) -> Bool {
selectedExportGroups.contains(group)
}
func isGroupEnabled(_ group: ExportGroup) -> Bool {
switch group {
case .accounts:
return selectedExportGroups.contains(.instances)
case .accountsUnencryptedPasswords:
return selectedExportGroups.contains(.instances) && selectedExportGroups.contains(.accounts)
default:
return true
}
}
func removeNotEnabledSelectedGroups() {
selectedExportGroups = selectedExportGroups.filter { isGroupEnabled($0) }
}
var isExportAvailable: Bool {
!selectedExportGroups.isEmpty && !isExportInProgress
}
}

View File

@ -0,0 +1,150 @@
import Defaults
import Foundation
import SwiftyJSON
final class ImportSettingsFileModel: ObservableObject {
static let shared = ImportSettingsFileModel()
var locationsSettingsGroupImporter: LocationsSettingsGroupImporter? {
if let locationsSettings = json.dictionaryValue["locationsSettings"] {
return LocationsSettingsGroupImporter(
json: locationsSettings,
includePublicLocations: importExportModel.isGroupEnabled(.locationsSettings),
includedInstancesIDs: sheetViewModel.selectedInstances,
includedAccountsIDs: sheetViewModel.selectedAccounts,
includedAccountsPasswords: sheetViewModel.importableAccountsPasswords
)
}
return nil
}
var importExportModel = ImportExportSettingsModel.shared
var sheetViewModel = ImportSettingsSheetViewModel.shared
var loadTask: URLSessionTask?
func isGroupIncludedInFile(_ group: ImportExportSettingsModel.ExportGroup) -> Bool {
switch group {
case .locationsSettings:
return isPublicInstancesSettingsGroupInFile || instancesOrAccountsInFile
default:
return !groupJSON(group).isEmpty
}
}
var isPublicInstancesSettingsGroupInFile: Bool {
guard let dict = groupJSON(.locationsSettings).dictionary else { return false }
return dict.keys.contains("instancesManifest") || dict.keys.contains("countryOfPublicInstances")
}
var instancesOrAccountsInFile: Bool {
guard let dict = groupJSON(.locationsSettings).dictionary else { return false }
return (dict.keys.contains("instances") && !(dict["instances"]?.arrayValue.isEmpty ?? true)) ||
(dict.keys.contains("accounts") && !(dict["accounts"]?.arrayValue.isEmpty ?? true))
}
func groupJSON(_ group: ImportExportSettingsModel.ExportGroup) -> JSON {
json.dictionaryValue[group.rawValue] ?? .init()
}
func performImport() {
if importExportModel.isGroupSelected(.browsingSettings), isGroupIncludedInFile(.browsingSettings) {
BrowsingSettingsGroupImporter(json: groupJSON(.browsingSettings)).performImport()
}
if importExportModel.isGroupSelected(.playerSettings), isGroupIncludedInFile(.playerSettings) {
PlayerSettingsGroupImporter(json: groupJSON(.playerSettings)).performImport()
}
if importExportModel.isGroupSelected(.controlsSettings), isGroupIncludedInFile(.controlsSettings) {
ConstrolsSettingsGroupImporter(json: groupJSON(.controlsSettings)).performImport()
}
if importExportModel.isGroupSelected(.qualitySettings), isGroupIncludedInFile(.qualitySettings) {
QualitySettingsGroupImporter(json: groupJSON(.qualitySettings)).performImport()
}
if importExportModel.isGroupSelected(.historySettings), isGroupIncludedInFile(.historySettings) {
HistorySettingsGroupImporter(json: groupJSON(.historySettings)).performImport()
}
if importExportModel.isGroupSelected(.sponsorBlockSettings), isGroupIncludedInFile(.sponsorBlockSettings) {
SponsorBlockSettingsGroupImporter(json: groupJSON(.sponsorBlockSettings)).performImport()
}
locationsSettingsGroupImporter?.performImport()
if importExportModel.isGroupSelected(.advancedSettings), isGroupIncludedInFile(.advancedSettings) {
AdvancedSettingsGroupImporter(json: groupJSON(.advancedSettings)).performImport()
}
if importExportModel.isGroupSelected(.recentlyOpened), isGroupIncludedInFile(.recentlyOpened) {
RecentlyOpenedImporter(json: groupJSON(.recentlyOpened)).performImport()
}
if importExportModel.isGroupSelected(.otherData), isGroupIncludedInFile(.otherData) {
OtherDataSettingsGroupImporter(json: groupJSON(.otherData)).performImport()
}
}
@Published var json = JSON()
func loadData(_ url: URL) {
json = JSON()
loadTask?.cancel()
loadTask = URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
guard let data else { return }
if let json = try? JSON(data: data) {
DispatchQueue.main.async { [weak self] in
guard let self else { return }
self.json = json
self.sheetViewModel.reset(locationsSettingsGroupImporter)
self.importExportModel.reset(self)
}
}
}
loadTask?.resume()
}
func filename(_ url: URL) -> String {
String(url.lastPathComponent.dropLast(ImportExportSettingsModel.settingsExtension.count + 1))
}
var metadataBuild: String? {
if let build = json.dictionaryValue["metadata"]?.dictionaryValue["build"]?.string {
return build
}
return nil
}
var metadataPlatform: String? {
if let platform = json.dictionaryValue["metadata"]?.dictionaryValue["platform"]?.string {
return platform
}
return nil
}
var metadataDate: String? {
if let timestamp = json.dictionaryValue["metadata"]?.dictionaryValue["timestamp"]?.doubleValue {
let date = Date(timeIntervalSince1970: timestamp)
return dateFormatter.string(from: date)
}
return nil
}
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .medium
return formatter
}
}

View File

@ -0,0 +1,64 @@
import Defaults
import SwiftyJSON
struct AdvancedSettingsGroupImporter {
var json: JSON
func performImport() {
if let showPlayNowInBackendContextMenu = json["showPlayNowInBackendContextMenu"].bool {
Defaults[.showPlayNowInBackendContextMenu] = showPlayNowInBackendContextMenu
}
if let videoLoadingRetryCount = json["videoLoadingRetryCount"].int {
Defaults[.videoLoadingRetryCount] = videoLoadingRetryCount
}
if let showMPVPlaybackStats = json["showMPVPlaybackStats"].bool {
Defaults[.showMPVPlaybackStats] = showMPVPlaybackStats
}
if let mpvEnableLogging = json["mpvEnableLogging"].bool {
Defaults[.mpvEnableLogging] = mpvEnableLogging
}
if let mpvCacheSecs = json["mpvCacheSecs"].string {
Defaults[.mpvCacheSecs] = mpvCacheSecs
}
if let mpvCachePauseWait = json["mpvCachePauseWait"].string {
Defaults[.mpvCachePauseWait] = mpvCachePauseWait
}
if let mpvCachePauseInital = json["mpvCachePauseInital"].bool {
Defaults[.mpvCachePauseInital] = mpvCachePauseInital
}
if let mpvDeinterlace = json["mpvDeinterlace"].bool {
Defaults[.mpvDeinterlace] = mpvDeinterlace
}
if let mpvHWdec = json["mpvHWdec"].string {
Defaults[.mpvHWdec] = mpvHWdec
}
if let mpvDemuxerLavfProbeInfo = json["mpvDemuxerLavfProbeInfo"].string {
Defaults[.mpvDemuxerLavfProbeInfo] = mpvDemuxerLavfProbeInfo
}
if let mpvSetRefreshToContentFPS = json["mpvSetRefreshToContentFPS"].bool {
Defaults[.mpvSetRefreshToContentFPS] = mpvSetRefreshToContentFPS
}
if let mpvInitialAudioSync = json["mpvInitialAudioSync"].bool {
Defaults[.mpvInitialAudioSync] = mpvInitialAudioSync
}
if let showCacheStatus = json["showCacheStatus"].bool {
Defaults[.showCacheStatus] = showCacheStatus
}
if let feedCacheSize = json["feedCacheSize"].string {
Defaults[.feedCacheSize] = feedCacheSize
}
}
}

View File

@ -0,0 +1,148 @@
import Defaults
import SwiftyJSON
struct BrowsingSettingsGroupImporter {
var json: JSON
func performImport() {
if let showHome = json["showHome"].bool {
Defaults[.showHome] = showHome
}
if let showOpenActionsInHome = json["showOpenActionsInHome"].bool {
Defaults[.showOpenActionsInHome] = showOpenActionsInHome
}
if let showQueueInHome = json["showQueueInHome"].bool {
Defaults[.showQueueInHome] = showQueueInHome
}
if let showFavoritesInHome = json["showFavoritesInHome"].bool {
Defaults[.showFavoritesInHome] = showFavoritesInHome
}
if let favorites = json["favorites"].array {
for favoriteJSON in favorites {
if let jsonString = favoriteJSON.rawString(options: []),
let item = FavoriteItem.bridge.deserialize(jsonString)
{
FavoritesModel.shared.add(item)
}
}
}
if let widgetsFavorites = json["widgetsSettings"].array {
for widgetJSON in widgetsFavorites {
let dict = widgetJSON.dictionaryValue.mapValues { json in json.stringValue }
if let item = WidgetSettingsBridge().deserialize(dict) {
FavoritesModel.shared.updateWidgetSettings(item)
}
}
}
if let startupSectionString = json["startupSection"].string,
let startupSection = StartupSection(rawValue: startupSectionString)
{
Defaults[.startupSection] = startupSection
}
if let showSearchSuggestions = json["showSearchSuggestions"].bool {
Defaults[.showSearchSuggestions] = showSearchSuggestions
}
if let visibleSections = json["visibleSections"].array {
let sections = visibleSections.compactMap { visibleSectionJSON in
if let visibleSectionString = visibleSectionJSON.rawString(options: []),
let section = VisibleSection(rawValue: visibleSectionString)
{
return section
}
return nil
}
Defaults[.visibleSections] = Set(sections)
}
#if os(iOS)
if let showOpenActionsToolbarItem = json["showOpenActionsToolbarItem"].bool {
Defaults[.showOpenActionsToolbarItem] = showOpenActionsToolbarItem
}
if let lockPortraitWhenBrowsing = json["lockPortraitWhenBrowsing"].bool {
Defaults[.lockPortraitWhenBrowsing] = lockPortraitWhenBrowsing
}
#endif
#if !os(tvOS)
if let accountPickerDisplaysUsername = json["accountPickerDisplaysUsername"].bool {
Defaults[.accountPickerDisplaysUsername] = accountPickerDisplaysUsername
}
#endif
if let accountPickerDisplaysAnonymousAccounts = json["accountPickerDisplaysAnonymousAccounts"].bool {
Defaults[.accountPickerDisplaysAnonymousAccounts] = accountPickerDisplaysAnonymousAccounts
}
if let showUnwatchedFeedBadges = json["showUnwatchedFeedBadges"].bool {
Defaults[.showUnwatchedFeedBadges] = showUnwatchedFeedBadges
}
if let expandChannelDescription = json["expandChannelDescription"].bool {
Defaults[.expandChannelDescription] = expandChannelDescription
}
if let keepChannelsWithUnwatchedFeedOnTop = json["keepChannelsWithUnwatchedFeedOnTop"].bool {
Defaults[.keepChannelsWithUnwatchedFeedOnTop] = keepChannelsWithUnwatchedFeedOnTop
}
if let showChannelAvatarInChannelsLists = json["showChannelAvatarInChannelsLists"].bool {
Defaults[.showChannelAvatarInChannelsLists] = showChannelAvatarInChannelsLists
}
if let showChannelAvatarInVideosListing = json["showChannelAvatarInVideosListing"].bool {
Defaults[.showChannelAvatarInVideosListing] = showChannelAvatarInVideosListing
}
if let playerButtonSingleTapGestureString = json["playerButtonSingleTapGesture"].string,
let playerButtonSingleTapGesture = PlayerTapGestureAction(rawValue: playerButtonSingleTapGestureString)
{
Defaults[.playerButtonSingleTapGesture] = playerButtonSingleTapGesture
}
if let playerButtonDoubleTapGestureString = json["playerButtonDoubleTapGesture"].string,
let playerButtonDoubleTapGesture = PlayerTapGestureAction(rawValue: playerButtonDoubleTapGestureString)
{
Defaults[.playerButtonDoubleTapGesture] = playerButtonDoubleTapGesture
}
if let playerButtonShowsControlButtonsWhenMinimized = json["playerButtonShowsControlButtonsWhenMinimized"].bool {
Defaults[.playerButtonShowsControlButtonsWhenMinimized] = playerButtonShowsControlButtonsWhenMinimized
}
if let playerButtonIsExpanded = json["playerButtonIsExpanded"].bool {
Defaults[.playerButtonIsExpanded] = playerButtonIsExpanded
}
if let playerBarMaxWidth = json["playerBarMaxWidth"].string {
Defaults[.playerBarMaxWidth] = playerBarMaxWidth
}
if let channelOnThumbnail = json["channelOnThumbnail"].bool {
Defaults[.channelOnThumbnail] = channelOnThumbnail
}
if let timeOnThumbnail = json["timeOnThumbnail"].bool {
Defaults[.timeOnThumbnail] = timeOnThumbnail
}
if let roundedThumbnails = json["roundedThumbnails"].bool {
Defaults[.roundedThumbnails] = roundedThumbnails
}
if let thumbnailsQualityString = json["thumbnailsQuality"].string,
let thumbnailsQuality = ThumbnailsQuality(rawValue: thumbnailsQualityString)
{
Defaults[.thumbnailsQuality] = thumbnailsQuality
}
}
}

View File

@ -0,0 +1,148 @@
import Defaults
import SwiftyJSON
struct ConstrolsSettingsGroupImporter {
var json: JSON
func performImport() {
if let avPlayerUsesSystemControls = json["avPlayerUsesSystemControls"].bool {
Defaults[.avPlayerUsesSystemControls] = avPlayerUsesSystemControls
}
if let fullscreenPlayerGestureEnabled = json["fullscreenPlayerGestureEnabled"].bool {
Defaults[.fullscreenPlayerGestureEnabled] = fullscreenPlayerGestureEnabled
}
if let horizontalPlayerGestureEnabled = json["horizontalPlayerGestureEnabled"].bool {
Defaults[.horizontalPlayerGestureEnabled] = horizontalPlayerGestureEnabled
}
if let seekGestureSensitivity = json["seekGestureSensitivity"].double {
Defaults[.seekGestureSensitivity] = seekGestureSensitivity
}
if let seekGestureSpeed = json["seekGestureSpeed"].double {
Defaults[.seekGestureSpeed] = seekGestureSpeed
}
if let playerControlsLayoutString = json["playerControlsLayout"].string,
let playerControlsLayout = PlayerControlsLayout(rawValue: playerControlsLayoutString)
{
Defaults[.playerControlsLayout] = playerControlsLayout
}
if let fullScreenPlayerControlsLayoutString = json["fullScreenPlayerControlsLayout"].string,
let fullScreenPlayerControlsLayout = PlayerControlsLayout(rawValue: fullScreenPlayerControlsLayoutString)
{
Defaults[.fullScreenPlayerControlsLayout] = fullScreenPlayerControlsLayout
}
if let playerControlsBackgroundOpacity = json["playerControlsBackgroundOpacity"].double {
Defaults[.playerControlsBackgroundOpacity] = playerControlsBackgroundOpacity
}
if let systemControlsCommandsString = json["systemControlsCommands"].string,
let systemControlsCommands = SystemControlsCommands(rawValue: systemControlsCommandsString)
{
Defaults[.systemControlsCommands] = systemControlsCommands
}
if let buttonBackwardSeekDuration = json["buttonBackwardSeekDuration"].string {
Defaults[.buttonBackwardSeekDuration] = buttonBackwardSeekDuration
}
if let buttonForwardSeekDuration = json["buttonForwardSeekDuration"].string {
Defaults[.buttonForwardSeekDuration] = buttonForwardSeekDuration
}
if let gestureBackwardSeekDuration = json["gestureBackwardSeekDuration"].string {
Defaults[.gestureBackwardSeekDuration] = gestureBackwardSeekDuration
}
if let gestureForwardSeekDuration = json["gestureForwardSeekDuration"].string {
Defaults[.gestureForwardSeekDuration] = gestureForwardSeekDuration
}
if let systemControlsSeekDuration = json["systemControlsSeekDuration"].string {
Defaults[.systemControlsSeekDuration] = systemControlsSeekDuration
}
if let playerControlsSettingsEnabled = json["playerControlsSettingsEnabled"].bool {
Defaults[.playerControlsSettingsEnabled] = playerControlsSettingsEnabled
}
if let playerControlsCloseEnabled = json["playerControlsCloseEnabled"].bool {
Defaults[.playerControlsCloseEnabled] = playerControlsCloseEnabled
}
if let playerControlsRestartEnabled = json["playerControlsRestartEnabled"].bool {
Defaults[.playerControlsRestartEnabled] = playerControlsRestartEnabled
}
if let playerControlsAdvanceToNextEnabled = json["playerControlsAdvanceToNextEnabled"].bool {
Defaults[.playerControlsAdvanceToNextEnabled] = playerControlsAdvanceToNextEnabled
}
if let playerControlsPlaybackModeEnabled = json["playerControlsPlaybackModeEnabled"].bool {
Defaults[.playerControlsPlaybackModeEnabled] = playerControlsPlaybackModeEnabled
}
if let playerControlsMusicModeEnabled = json["playerControlsMusicModeEnabled"].bool {
Defaults[.playerControlsMusicModeEnabled] = playerControlsMusicModeEnabled
}
if let playerActionsButtonLabelStyleString = json["playerActionsButtonLabelStyle"].string,
let playerActionsButtonLabelStyle = ButtonLabelStyle(rawValue: playerActionsButtonLabelStyleString)
{
Defaults[.playerActionsButtonLabelStyle] = playerActionsButtonLabelStyle
}
if let actionButtonShareEnabled = json["actionButtonShareEnabled"].bool {
Defaults[.actionButtonShareEnabled] = actionButtonShareEnabled
}
if let actionButtonAddToPlaylistEnabled = json["actionButtonAddToPlaylistEnabled"].bool {
Defaults[.actionButtonAddToPlaylistEnabled] = actionButtonAddToPlaylistEnabled
}
if let actionButtonSubscribeEnabled = json["actionButtonSubscribeEnabled"].bool {
Defaults[.actionButtonSubscribeEnabled] = actionButtonSubscribeEnabled
}
if let actionButtonSettingsEnabled = json["actionButtonSettingsEnabled"].bool {
Defaults[.actionButtonSettingsEnabled] = actionButtonSettingsEnabled
}
if let actionButtonHideEnabled = json["actionButtonHideEnabled"].bool {
Defaults[.actionButtonHideEnabled] = actionButtonHideEnabled
}
if let actionButtonCloseEnabled = json["actionButtonCloseEnabled"].bool {
Defaults[.actionButtonCloseEnabled] = actionButtonCloseEnabled
}
if let actionButtonFullScreenEnabled = json["actionButtonFullScreenEnabled"].bool {
Defaults[.actionButtonFullScreenEnabled] = actionButtonFullScreenEnabled
}
if let actionButtonPipEnabled = json["actionButtonPipEnabled"].bool {
Defaults[.actionButtonPipEnabled] = actionButtonPipEnabled
}
if let actionButtonLockOrientationEnabled = json["actionButtonLockOrientationEnabled"].bool {
Defaults[.actionButtonLockOrientationEnabled] = actionButtonLockOrientationEnabled
}
if let actionButtonRestartEnabled = json["actionButtonRestartEnabled"].bool {
Defaults[.actionButtonRestartEnabled] = actionButtonRestartEnabled
}
if let actionButtonAdvanceToNextItemEnabled = json["actionButtonAdvanceToNextItemEnabled"].bool {
Defaults[.actionButtonAdvanceToNextItemEnabled] = actionButtonAdvanceToNextItemEnabled
}
if let actionButtonMusicModeEnabled = json["actionButtonMusicModeEnabled"].bool {
Defaults[.actionButtonMusicModeEnabled] = actionButtonMusicModeEnabled
}
}
}

View File

@ -0,0 +1,66 @@
import Defaults
import SwiftyJSON
struct HistorySettingsGroupImporter {
var json: JSON
func performImport() {
if let saveRecents = json["saveRecents"].bool {
Defaults[.saveRecents] = saveRecents
}
if let saveHistory = json["saveHistory"].bool {
Defaults[.saveHistory] = saveHistory
}
if let showRecents = json["showRecents"].bool {
Defaults[.showRecents] = showRecents
}
if let limitRecents = json["limitRecents"].bool {
Defaults[.limitRecents] = limitRecents
}
if let limitRecentsAmount = json["limitRecentsAmount"].int {
Defaults[.limitRecentsAmount] = limitRecentsAmount
}
if let showWatchingProgress = json["showWatchingProgress"].bool {
Defaults[.showWatchingProgress] = showWatchingProgress
}
if let saveLastPlayed = json["saveLastPlayed"].bool {
Defaults[.saveLastPlayed] = saveLastPlayed
}
if let watchedVideoPlayNowBehaviorString = json["watchedVideoPlayNowBehavior"].string,
let watchedVideoPlayNowBehavior = WatchedVideoPlayNowBehavior(rawValue: watchedVideoPlayNowBehaviorString)
{
Defaults[.watchedVideoPlayNowBehavior] = watchedVideoPlayNowBehavior
}
if let watchedThreshold = json["watchedThreshold"].int {
Defaults[.watchedThreshold] = watchedThreshold
}
if let resetWatchedStatusOnPlaying = json["resetWatchedStatusOnPlaying"].bool {
Defaults[.resetWatchedStatusOnPlaying] = resetWatchedStatusOnPlaying
}
if let watchedVideoStyleString = json["watchedVideoStyle"].string,
let watchedVideoStyle = WatchedVideoStyle(rawValue: watchedVideoStyleString)
{
Defaults[.watchedVideoStyle] = watchedVideoStyle
}
if let watchedVideoBadgeColorString = json["watchedVideoBadgeColor"].string,
let watchedVideoBadgeColor = WatchedVideoBadgeColor(rawValue: watchedVideoBadgeColorString)
{
Defaults[.watchedVideoBadgeColor] = watchedVideoBadgeColor
}
if let showToggleWatchedStatusButton = json["showToggleWatchedStatusButton"].bool {
Defaults[.showToggleWatchedStatusButton] = showToggleWatchedStatusButton
}
}
}

View File

@ -0,0 +1,84 @@
import Defaults
import SwiftyJSON
struct LocationsSettingsGroupImporter {
var json: JSON
var includePublicLocations = true
var includedInstancesIDs = Set<Instance.ID>()
var includedAccountsIDs = Set<Account.ID>()
var includedAccountsPasswords = [Account.ID: String]()
init(
json: JSON,
includePublicLocations: Bool = true,
includedInstancesIDs: Set<Instance.ID> = [],
includedAccountsIDs: Set<Account.ID> = [],
includedAccountsPasswords: [Account.ID: String] = [:]
) {
self.json = json
self.includePublicLocations = includePublicLocations
self.includedInstancesIDs = includedInstancesIDs
self.includedAccountsIDs = includedAccountsIDs
self.includedAccountsPasswords = includedAccountsPasswords
}
var instances: [Instance] {
if let instances = json["instances"].array {
return instances.compactMap { instanceJSON in
let dict = instanceJSON.dictionaryValue.mapValues { json in json.stringValue }
return InstancesBridge().deserialize(dict)
}
}
return []
}
var accounts: [Account] {
if let accounts = json["accounts"].array {
return accounts.compactMap { accountJSON in
let dict = accountJSON.dictionaryValue.mapValues { json in json.stringValue }
return AccountsBridge().deserialize(dict)
}
}
return []
}
func performImport() {
if includePublicLocations {
Defaults[.instancesManifest] = json["instancesManifest"].string ?? ""
Defaults[.countryOfPublicInstances] = json["countryOfPublicInstances"].string ?? ""
}
instances.filter { includedInstancesIDs.contains($0.id) }.forEach { instance in
_ = InstancesModel.shared.insert(id: instance.id, app: instance.app, name: instance.name, url: instance.apiURLString)
}
if let accounts = json["accounts"].array {
for accountJSON in accounts {
let dict = accountJSON.dictionaryValue.mapValues { json in json.stringValue }
if let account = AccountsBridge().deserialize(dict),
includedAccountsIDs.contains(account.id)
{
var password = account.password
if password?.isEmpty ?? true {
password = includedAccountsPasswords[account.id]
}
if let password,
!password.isEmpty,
let instanceID = account.instanceID,
let instance = InstancesModel.shared.find(instanceID) ?? InstancesModel.shared.findByURLString(account.urlString)
{
if !instance.accounts.contains(where: { instanceAccount in
let (username, _) = instanceAccount.credentials
return username == account.username
}) {
_ = AccountsModel.add(instance: instance, id: account.id, name: account.name, username: account.username, password: password)
}
}
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More