Compare commits

...

1029 commits
0.1.0 ... main

Author SHA1 Message Date
cranci1
e609916df6 Merge branch 'dev' 2025-07-11 10:12:48 +02:00
cranci1
ac9d4f9e39 Revert "maybe test"
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build Mac Catalyst (push) Has been cancelled
This reverts commit 19fe680a86.
2025-07-11 10:11:01 +02:00
cranci1
19fe680a86 maybe test 2025-07-11 10:03:33 +02:00
cranci1
b67b44a069 yeah looks a bit better tbh no?
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build Mac Catalyst (push) Has been cancelled
2025-07-08 10:56:47 +02:00
cranci1
27b4212567 kinda fixed the title maybe?
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-07-08 10:51:51 +02:00
cranci1
94735cd23d buttons fixes 2025-07-08 10:25:41 +02:00
cranci1
982fe468c8 fixed build yaml file too 2025-07-08 10:11:06 +02:00
cranci1
681c43ec69 ok starting player refactor 2025-07-08 10:10:08 +02:00
50/50
e9da8de67e
title (#218) 2025-07-08 09:51:34 +02:00
cranci1
112770024c made default chunk size to 50
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-07-07 10:30:14 +02:00
cranci1
f6f9bcaa8f Fixed episode chunk alignment 2025-07-07 10:27:25 +02:00
cranci1
5607ec70ff what was this monster 😭
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-07-07 10:23:25 +02:00
cranci1
bca07c9c46 fixed logger issue 2025-07-07 10:17:10 +02:00
cranci1
501a3da48f fixed recent search issues 2025-07-07 10:15:36 +02:00
cranci
b9523c259f
Update README.md
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-07-06 16:06:49 +02:00
ibro
5d0c3fd977
Fixed chapters not loading (#216)
* hey

* mediainfoview fixed chapters not loading

* added back that

* ts annoying 🥀
2025-07-06 15:53:07 +02:00
cranci1
508dcd4a42 fixed TMDB not being primary choice when nil
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
(Seiike fault ofc)
2025-07-05 16:29:55 +02:00
cranci1
4aaf5ab518 removed paul tabs 2025-07-05 16:04:17 +02:00
cranci
0a411e8421
better download icon
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build Mac Catalyst (push) Has been cancelled
2025-07-02 17:01:26 +02:00
realdoomsboygaming
3974fc7003
Add downloaded indicator to EpisodeCell (#215) 2025-07-02 17:00:30 +02:00
cranci1
52e7101472 nice
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-07-01 18:21:49 +02:00
cranci1
09b1d9b0b1 test crash fix + layout fix 2025-07-01 18:11:16 +02:00
realdoomsboygaming
d707858ad7
Add autoplay feature and playback end handling (#214)
- Introduced a new setting for "Autoplay Next" in the player settings.
- Implemented autoplay functionality in the CustomMediaPlayerViewController to automatically start the next episode when playback ends.
- Added notification observers for handling playback completion and managing the autoplay behavior.

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-07-01 18:08:14 +02:00
cranci1
95d6d1ac14 removed non used files 2025-07-01 18:05:13 +02:00
cranci1
f5e0dc7cc7 added back trackers view 2025-07-01 17:58:38 +02:00
cranci1
1b6c62a204 better safe handling? 2025-07-01 17:56:00 +02:00
cranci1
ad323efe34 oh hell nah i aint changing the Package.resolved 2025-07-01 17:46:34 +02:00
cranci1
81393601e1 images reduction (-22kb)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-07-01 14:43:49 +02:00
realdoomsboygaming
f03f4c0b8a
Fix One Pace (#212)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-30 22:21:51 +02:00
50/50
0dac0566dd
Minor bug fixes (#211)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
* Modified season selector location

* Increase back button opacity

* Fix drop when exiting reader

* Fix tab bar appearing in reader

* Next chapter button

* removed old commentary

* Fix collection image not updating after module removal

* Fix next chapter button size

* Modified small bookmark type indicator

* Align season selector

* Continue reading, not fully done yet tho

* fixed continue reading issues + added some stuff

* correct resetting

* pretty continue reading cells :3

* Test building

* Fixed continue reading by caching

* inshallah only build issue

* Fixed chapter number for continue reading

* Fix tab bar not appearing in search

* Added github and discord icon

* fix next chapter button

* disable locking for dim

* two finger tap to pause

* 4 hours to fix this, from 8 pm till now

* fix that bichass dim button

* Fix downloadview

* more tab bar fixes

* smoother search bar

* time till done indicator

* someone stop me

* fix bounce scroll

* Fixed most of the localizations

* back up system (experimental)

* fuck main actor

* fix reader crash when no network

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-30 10:04:11 +02:00
cranci1
92343aee2b reverted continue watching 2025-06-30 10:03:39 +02:00
cranci1
5d3939e61f nvm 😭
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build Mac Catalyst (push) Has been cancelled
2025-06-25 11:30:33 +02:00
cranci1
0eef3dd918 added whats new maybe + UI fixes 2025-06-25 11:23:56 +02:00
cranci1
4b4bb0ad7b fixed UI 2025-06-25 10:58:16 +02:00
cranci1
9bafb790cf Revert "test label"
This reverts commit 54c75da756.
2025-06-25 10:55:47 +02:00
cranci1
54c75da756 test label 2025-06-25 10:49:46 +02:00
cranci1
b64f44c341 made players use <= for better results 2025-06-25 10:45:38 +02:00
cranci1
9b29b40ff3 Circular Progress fixes + Continue watching fixes 2025-06-25 10:44:01 +02:00
cranci1
4ce60d957d fixed nil case + error 2025-06-25 10:37:09 +02:00
cranci1
44c5b59601 added percentage switcher 2025-06-25 10:33:42 +02:00
50/50
fc1cff477b
Build fix (will flush commits after this) (#210)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
* Quick Czech fix

* Bookmark collection system + migration system (Video in discord))

* Check discord

* Fix mediainfoview

* Title always expanded

* Reader header enhancements

* Fix tab bar gradient

* MORE/LESS below synopsis instead of next to it (less wasted space))

* Font + Weight + Size buttons for reader (with correct UI))

* Change icon

* Theming and auto scroll

* fucking cool shit

* added new theme for reader

* Fixed reader header

* Added italian

* made italian usable

* changed credits

* finally fucking italian works

* Fix novel details

* Fix loading issue

* made chapter cells less tall

* Fix current label

* Create ios.yml

* Delete .github/workflows/ios.yml

* Hopefully fixed building error
2025-06-24 21:09:31 +02:00
50/50
e348ed243f
Check discord (#208)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
* Quick Czech fix

* Bookmark collection system + migration system (Video in discord))

* Check discord

* Fix mediainfoview

* Title always expanded

* Reader header enhancements

* Fix tab bar gradient

* MORE/LESS below synopsis instead of next to it (less wasted space))

* Font + Weight + Size buttons for reader (with correct UI))

* Change icon

* Theming and auto scroll

* fucking cool shit

* added new theme for reader

* Fixed reader header

* Added italian

* made italian usable

* changed credits

* finally fucking italian works

* Fix novel details

* Fix loading issue

* made chapter cells less tall

* Fix current label
2025-06-24 14:47:08 +02:00
cranci1
55dfa9cbf4 Improve fetchV2 header handling in JSContext extension
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
Updated the fetchV2 native function to accept headers as Any type and safely convert them to [String: String]. Added error logging for invalid header formats and non-string header values to improve robustness.
2025-06-24 10:52:59 +02:00
cranci1
22ba348959 fixed build issue maybe
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-23 21:14:58 +02:00
undeaD_D
78df4df73b
Fix simulator crash & add conditional support for iOS 26+ native TabView (#209)
* add ability to use native tabbar on ios 26 or later

* add tabview lcalization

* fix library / home tab label and localization

* add missing localization & german translation
2025-06-23 21:03:37 +02:00
cranci1
868e2251fe Update project.pbxproj 2025-06-19 15:22:58 +02:00
50/50
82eec0688f
Bookmark collection system + migration system (Video in discord) (#207)
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build Mac Catalyst (push) Has been cancelled
2025-06-19 08:32:10 +02:00
cranci1
cf5451599b Merge branch 'dev' 2025-06-18 17:59:18 +02:00
50/50
14ebc82fc6
Quick Czech fix (#205)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-18 12:32:22 +02:00
cranci1
c0e94cbe5a fixed battery indicator not hiding 2025-06-18 11:26:31 +02:00
cranci1
9b610a3b16 fixed module addition view 2025-06-18 11:10:19 +02:00
cranci1
b686c452cd paul + seiike issues 😭 2025-06-18 11:06:26 +02:00
cranci1
4704f27e30 removed debug things 2025-06-18 11:03:31 +02:00
cranci1
7957601814 seiike cant even add a shadow 😭 2025-06-18 11:02:15 +02:00
cranci1
baa42e643c ok just some fixes i guess 2025-06-18 10:52:14 +02:00
cranci
1bce1e1c5d
Update AllWatching.swift (#204) 2025-06-17 23:58:28 +02:00
cranci
a9e1328cec
Merge branch 'main' into dev
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-17 23:58:22 +02:00
cranci
e3fda69df3
Update AllWatching.swift 2025-06-17 23:55:41 +02:00
50/50
cdcac3fab7
Type Shit (Future reference) (#203) 2025-06-17 23:49:27 +02:00
cranci1
082a6b2b83 fixed downlaods 2025-06-17 16:22:33 +02:00
cranci1
be325f84b2 Merge branch 'dev' 2025-06-17 11:07:03 +02:00
cranci1
0af3176166 Revert "maybe its fixed now"
This reverts commit c75c576641.
2025-06-17 11:06:59 +02:00
cranci1
6b9ca1b6c8 Update DownloadView.swift
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-17 11:02:19 +02:00
cranci1
c75c576641 maybe its fixed now 2025-06-17 10:54:43 +02:00
50/50
8420b373b4
Whole Lotta Fixes (Carti reference) - Read description (#202)
* German and Slovak

* Seiike cant do shit dawggggg

* Fix translator credits

* Added safari button for movie view

* Search button for AllBookmarks and AllWatching

* Delete all in download view without opening item (long hold))

* Redo downloadview

* Added Kazakh

* Added Swedish

* Fixed searchview images being squeezed in
2025-06-17 09:59:50 +02:00
cranci1
0fdbd3e452 Merge branch 'dev' 2025-06-16 11:52:42 +02:00
cranci1
53fa2950e6 yeah ofc
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-16 11:49:16 +02:00
cranci
1771b5843b
k (#200)
* Update VideoPlayer.swift

* can you work now please
2025-06-16 11:25:07 +02:00
cranci1
3ac882de8c can you work now please 2025-06-16 11:22:42 +02:00
cranci1
e784b666c5 Update VideoPlayer.swift 2025-06-16 11:19:14 +02:00
cranci1
44195d0d87 ops 2025-06-16 11:14:58 +02:00
cranci1
e7044891c3 yaeh 2025-06-16 11:09:11 +02:00
cranci1
f0f41c378a Merge branch 'dev' 2025-06-16 10:40:18 +02:00
cranci1
04fc467cb4 not 100% sure byt maybe it works now 2025-06-16 10:33:34 +02:00
cranci
7ffc876d9a
testflightss (#198)
* fxied trackers text

* Update SettingsViewTrackers.swift

* Update SettingsViewGeneral.swift

* Languages fix + Arabic + French + disable flip by Arabic (#193)

* fixed tmp folder issues

* 😭

* yeah

* ok well lets test

* Update Sora.entitlements

* fixed fetch metadata when off

* test GroupActivity

* Fixes (Check description) (#196)

* IBH bug report fixes (Read description) (#197)

* Organized sttufs + maybe testflight build now

---------

Co-authored-by: 50/50 <80717571+50n50@users.noreply.github.com>
2025-06-16 09:48:31 +02:00
cranci1
718f5b4a75 Organized sttufs + maybe testflight build now 2025-06-16 09:45:37 +02:00
50/50
5ef6316036
IBH bug report fixes (Read description) (#197)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-15 21:33:38 +02:00
50/50
019989df4f
Fixes (Check description) (#196) 2025-06-15 20:29:48 +02:00
cranci1
a4899f1196 test GroupActivity 2025-06-15 17:39:28 +02:00
cranci1
cc6a24d65c fixed fetch metadata when off 2025-06-15 17:30:42 +02:00
cranci
d03b151750
ops (#195)
* fxied trackers text

* Update SettingsViewTrackers.swift

* Update SettingsViewGeneral.swift

* Languages fix + Arabic + French + disable flip by Arabic (#193)

* fixed tmp folder issues

* 😭

* yeah

* ok well lets test

* Update Sora.entitlements

---------

Co-authored-by: 50/50 <80717571+50n50@users.noreply.github.com>
2025-06-15 11:16:25 +02:00
cranci1
924b77a35d Merge branch 'main' into dev
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-15 11:16:14 +02:00
cranci
086ddf951b
Update Sora.entitlements 2025-06-15 11:15:32 +02:00
cranci
862286840f
Public Testflight update? (#194)
* fxied trackers text

* Update SettingsViewTrackers.swift

* Update SettingsViewGeneral.swift

* Languages fix + Arabic + French + disable flip by Arabic (#193)

* fixed tmp folder issues

* 😭

* yeah

* ok well lets test

---------

Co-authored-by: 50/50 <80717571+50n50@users.noreply.github.com>
2025-06-15 10:53:31 +02:00
cranci1
0ca54ea38d ok well lets test 2025-06-15 10:50:35 +02:00
cranci1
634bda6a94 yeah 2025-06-15 10:36:36 +02:00
cranci1
a271858f77 😭 2025-06-15 10:26:48 +02:00
cranci1
ef4b18f622 fixed tmp folder issues 2025-06-15 10:21:22 +02:00
50/50
583075abaa
Languages fix + Arabic + French + disable flip by Arabic (#193) 2025-06-15 08:37:38 +02:00
cranci
b47a888cc6
Update SettingsViewGeneral.swift
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-14 19:54:38 +02:00
cranci
b216f55b0d
Update SettingsViewTrackers.swift 2025-06-14 19:53:31 +02:00
cranci1
951bdab694 fxied trackers text 2025-06-14 17:12:10 +02:00
cranci1
0f8acca4df Merge branch 'dev' 2025-06-14 16:50:14 +02:00
cranci1
3d4c500a15 ops 2025-06-14 16:40:27 +02:00
cranci1
b5efb1ad19 fixed multi API calls 2025-06-14 16:35:25 +02:00
cranci1
51dcae1a54 please trakt please 🙏 2025-06-14 16:19:10 +02:00
Seiike
ffeddb37e6
instead of matched id being an int now its the actual name of the series (#190)
* removed double bs for id telling

* improved anilist logic, single episode anilist sync, anilist sync also avaiaiable with tmdb as provider, tmdb posters avaiabale with anilist as provider

* instead of telling the id of the match now it tells the name

* gotta release a testflight 🙏
2025-06-14 15:50:53 +02:00
cranci1
c42d53f8f5 Revert "maybe DNS"
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
This reverts commit 63b1b20f5a.
2025-06-14 10:24:27 +02:00
cranci1
63b1b20f5a maybe DNS 2025-06-14 10:18:43 +02:00
Seiike
2026e43630
anilist logic improved basically (#189)
* removed double bs for id telling

* improved anilist logic, single episode anilist sync, anilist sync also avaiaiable with tmdb as provider, tmdb posters avaiabale with anilist as provider
2025-06-14 09:16:00 +02:00
realdoomsboygaming
b03ff287fe
Fix downloads of modules that state they are HLS but might return mp4 streams + Consolidated download methods into 1 file (#188)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
* Add MP4 Fallback redirect if stream URL is MP4 for some reason

* Consolidate Download Method

* Clean up download logic

* Further Cleanup

* Fix Logging Oopsie
2025-06-13 17:11:49 +02:00
cranci1
c48dbbe3cb little tweaks 2025-06-13 17:11:03 +02:00
cranci1
473759085e test
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-13 09:33:37 +02:00
50/50
02314a75e3
bundle ID fix (sowy) (#187)
* Fixed ALL view strings + Dutch translations

I THINK I fixed all view strings, i went through all so idk

* Fix

* Fixed type shi

* List number + small text fixes

* Update project.pbxproj
2025-06-13 09:08:21 +02:00
Seiike
706a448698
removed double bs for id telling (#186) 2025-06-13 09:08:03 +02:00
50/50
525771927c
paul is not liable for any issues (#185)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-12 22:45:56 +02:00
50/50
375fe1806b
merge it fg (#184)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-12 21:54:58 +02:00
cranci1
35c4f35f5e Merge branch 'pr/183' into dev 2025-06-12 21:34:08 +02:00
Bshar Esfky
c15ef04c80
Improved README + many things inside app for people (#181) 2025-06-12 21:31:40 +02:00
realdoomsboygaming
b56ef52ae3
Video Quality Prefrences Addition (#180) 2025-06-12 21:31:11 +02:00
Seiike
46db2f5751 Update MediaInfoView.swift 2025-06-12 20:06:40 +02:00
Seiike
02f8f165a1 i fucking hate merges 2025-06-12 20:06:00 +02:00
50/50
2beaaf5575
Changes (#179)
* Fixed episodecells getting stuck sliding

* Enabled device scaling for ipad

not good enough yet, not applied everywhere cuz idk where to apply exactly 💯

* Fixed blur in continue watching cells

* Keyboard controls player

* fixed downloadview buttons

* Reduced tab bar outline opacity

* Increased module selector hitbox

* Fixed module add view

* Fixed mediainfoview issues (description) + changed settingsviewdata footer

medainfoview:
1: no swipe to go back
2: image shadows were fucked

* Fixes

* Splashscreen

* Update Contents.json

* Episodecell getting stuck fix

* Improved blur + pushed items more down

* hihi

* Module selector changes
2025-06-12 16:56:34 +02:00
cranci1
208827ec78 added SoraCore 2025-06-12 16:33:05 +02:00
cranci1
853ed19507 yeah not really much kinda yes 2025-06-12 15:22:30 +02:00
50/50
c0742c6e20
FIx for episodecells getting stuck (#177)
* Fixed episodecells getting stuck sliding

* Enabled device scaling for ipad

not good enough yet, not applied everywhere cuz idk where to apply exactly 💯

* Fixed blur in continue watching cells

* Keyboard controls player

* fixed downloadview buttons

* Reduced tab bar outline opacity

* Increased module selector hitbox

* Fixed module add view

* Fixed mediainfoview issues (description) + changed settingsviewdata footer

medainfoview:
1: no swipe to go back
2: image shadows were fucked

* Fixes

* Splashscreen

* Update Contents.json

* Episodecell getting stuck fix
2025-06-12 15:05:05 +02:00
cranci1
b66ddb1679 alright
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-12 10:12:38 +02:00
cranci1
af8b1c1773 fixed push updates hopefully + About fixes 2025-06-12 10:05:39 +02:00
cranci1
bbba94625a fixed constants 2025-06-12 10:02:08 +02:00
cranci1
632830278c fixed shimmer maybe? 2025-06-12 09:41:34 +02:00
realdoomsboygaming
8249d0a0f5
Dumb Show Title Bug Fix (#175)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-11 23:02:21 +02:00
cranci1
117639514e im even dumber 😭
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-11 21:23:30 +02:00
realdoomsboygaming
05c9722142
Yes (#173)
* MediaInfoViewRefactor

* EpisodeCellRefactor

* Fix Crash YATTA

* removed comments

* fix

* Cranc1 is a picky boi

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-11 21:09:09 +02:00
cranci1
23ecb6f53a splash screen fixes cuz people too pichy 2025-06-11 21:06:49 +02:00
cranci1
0bd5b37edc fixed paul ok 2025-06-11 20:57:52 +02:00
cranci1
3e54e2d7d8 less animation time 2025-06-11 20:52:38 +02:00
cranci1
2fc8e1205e opssii 😭 2025-06-11 20:51:15 +02:00
cranci1
3901010309 ? 2025-06-11 20:43:11 +02:00
cranci1
0933bc448c im dumb 😭 2025-06-11 20:35:02 +02:00
50/50
626e5df595
Splashscreen (#172) 2025-06-11 18:19:34 +02:00
realdoomsboygaming
c281290350
Fix Single Episode Download Jank (#171) 2025-06-11 16:51:51 +02:00
cranci1
0bbb99fc18 crazy stuff 💔 2025-06-11 16:39:54 +02:00
50/50
eaa6a6d9e0
Fixes (#170)
* Fixed episodecells getting stuck sliding

* Enabled device scaling for ipad

not good enough yet, not applied everywhere cuz idk where to apply exactly 💯

* Fixed blur in continue watching cells

* Keyboard controls player

* fixed downloadview buttons

* Reduced tab bar outline opacity

* Increased module selector hitbox

* Fixed module add view

* Fixed mediainfoview issues (description) + changed settingsviewdata footer

medainfoview:
1: no swipe to go back
2: image shadows were fucked

* Fixes
2025-06-11 16:37:28 +02:00
cranci1
75c9d6bf07 plenty of things (fr)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-11 11:14:21 +02:00
cranci1
efe05e9a04 1.0.0 is better ngl 2025-06-11 10:37:40 +02:00
cranci1
175f011d01 Let's test Trakt Updates 2025-06-11 10:36:48 +02:00
cranci1
1a4b78e64e Fixed poster revertion 2025-06-11 10:02:19 +02:00
cranci1
e4e5fc520a little tweaks 2025-06-11 09:56:02 +02:00
cranci1
7c5cec2285 opsi my fault
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-10 21:15:16 +02:00
cranci1
cc4c75f88a let's text @bshar1865 code
Co-Authored-By: Bshar Esfky <98615778+bshar1865@users.noreply.github.com>
2025-06-10 21:06:42 +02:00
50/50
440ec57d59
Fixes, read description (#168)
* Fixed episodecells getting stuck sliding

* Enabled device scaling for ipad

not good enough yet, not applied everywhere cuz idk where to apply exactly 💯

* Fixed blur in continue watching cells

* Keyboard controls player

* fixed downloadview buttons

* Reduced tab bar outline opacity

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-10 20:37:54 +02:00
cranci1
fd9e68513d ops 2025-06-10 20:36:42 +02:00
cranci
f1f993f763
toggle for subtitles (#166) (#167) 2025-06-10 17:19:47 +02:00
Seiike
66353bc80c
toggle for subtitles (#166)
* added persistance for subtitle toggling

* fixed typo

* now we good (i think fuck windows)

* adasd WSEFG

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-10 17:10:23 +02:00
cranci1
c6e704c04d revert poster 2025-06-10 15:32:15 +02:00
cranci1
f3ade3e7b2 fixed data view
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-10 12:10:04 +02:00
cranci1
0751a090d0 Merge branch 'main' into dev 2025-06-10 12:09:27 +02:00
cranci1
f90149baa2 fixed settings data view 2025-06-10 08:57:10 +02:00
cranci1
31e7fdca3e Reapply "yeah it was weird sorry"
This reverts commit 7b35100f4c.
2025-06-10 08:56:58 +02:00
realdoomsboygaming
51fe07e463
Many Fix (#165)
* FIx MP4 Thumbnails

* Fix Play/Pause State Management for MP4 downlaods

* Yes

* Fix All The Things
2025-06-10 08:34:39 +02:00
cranci1
7b35100f4c Revert "yeah it was weird sorry"
This reverts commit cbb30fd965.
2025-06-09 21:16:54 +02:00
cranci1
8c09010a9a Merge branch 'dev' 2025-06-09 20:52:41 +02:00
cranci1
cbb30fd965 yeah it was weird sorry
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-09 20:51:57 +02:00
cranci1
bc47afaf4b macos build sorru 2025-06-09 20:40:02 +02:00
cranci1
7313762775 test 2025-06-09 20:34:47 +02:00
cranci1
5e872c0033 Update project.pbxproj 2025-06-09 17:51:26 +02:00
50/50
e63c27d649
Episodecell fixes + iPad scaling (not perfect) (#163)
* Fixed episodecells getting stuck sliding

* Enabled device scaling for ipad

not good enough yet, not applied everywhere cuz idk where to apply exactly 💯
2025-06-09 17:17:46 +02:00
cranci1
ce266cf2a8 test for iOS 16+ devices 2025-06-09 16:58:45 +02:00
cranci1
27dbdcbd55 test encoding support for modules 😭 2025-06-09 16:50:02 +02:00
cranci1
cf4af358f5 bro i hate ts 😭 2025-06-09 16:39:15 +02:00
cranci1
d0a622c2b7 Revert "yeah i dont even know what is this"
This reverts commit 3478a3f11e.
2025-06-09 16:30:44 +02:00
cranci1
3478a3f11e yeah i dont even know what is this 2025-06-09 14:41:32 +02:00
cranci1
8b6facd015 less banner size + no quality loss 2025-06-09 14:30:12 +02:00
cranci1
73773f3265 localization + LazyVStack 2025-06-09 14:25:27 +02:00
cranci1
65f1fcf0d4 tetst 2025-06-09 14:10:58 +02:00
cranci1
a8770a6abe Merge branch 'dev' 2025-06-09 11:48:45 +02:00
cranci
fdc05a13ca
testflgith (#162)
* Main (#158)

* dwo (#132)

* bug fixes  (#127)

* yeah @realdoomsboygaming fault

* fixes

* freaky ahh update

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>

* opds

* bug fixes around the downloads (#133) (#134)

* yeah nice xcode

* yeah fuck ts

* yeah

* ok this shit is really fucked then

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>

* Yeah idk what i am even doing at this point

* opsi

* would this wrk?

* test UI mode

* should be better now

* my bad sorru

* ok should be good now + better quality parsesr

* ohhhh i forgot my bad

* ok now its fixed

* who know if this works 😭

* ohhh yeah my bad

* ok should work now

* brooo come one 😭

* who tf does this work 😭

* ok yeah im done bro 😭

* oh yeah my bad ok

* audio track please work holy moly

* ok yeah no audio for now

* ok please this time audio should work

* Revert "ok please this time audio should work"

This reverts commit a14d7db5ea.

* d

* Updated Dark and Light mode thumbnails  (#159)

* Update CustomPlayer.swift

* Update README.md

* boom shakalaka (#160)

* now if the user leaves mediainfoview on chunk 51-100 it will remember it and put him back the next time

same with seasons

* fixed memory leak from urldelegate

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>

* yeah idk tf is ts 😭

* migrated to NukeUI from KingFisher

* why was it even imported 😭

* Update README.md

* POP THE CHAMPAGNE 🍾 (#161)

* this is a test i guess. macOS ventura on top

* updated Nuke to branch + adjusted some stuffs

* yeah this is not needed

* Update README.md

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
Co-authored-by: CiroHoodLove <3issawii667@gmail.com>
2025-06-09 11:34:43 +02:00
Seiike
785297cd2a
POP THE CHAMPAGNE 🍾 (#161)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-09 07:55:44 +02:00
cranci
291b2d8c66
Update README.md
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-08 23:36:59 +02:00
Francesco
8c4c275efd why was it even imported 😭 2025-06-08 21:30:10 +02:00
Francesco
fa758159dc migrated to NukeUI from KingFisher 2025-06-08 21:28:02 +02:00
Francesco
1692ac2c3d yeah idk tf is ts 😭 2025-06-08 20:49:34 +02:00
Seiike
a348c9a63a
boom shakalaka (#160)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
* now if the user leaves mediainfoview on chunk 51-100 it will remember it and put him back the next time

same with seasons

* fixed memory leak from urldelegate

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-08 20:05:09 +02:00
cranci
193b16fc8c
Update README.md 2025-06-08 20:00:47 +02:00
cranci
86a408afd9
Update CustomPlayer.swift 2025-06-08 20:00:07 +02:00
CiroHoodLove
b59420efb0
Updated Dark and Light mode thumbnails (#159) 2025-06-08 19:11:35 +02:00
Francesco
d09ae407b3 d 2025-06-08 18:01:25 +02:00
Francesco
fd1f18ca9a Revert "ok please this time audio should work"
This reverts commit a14d7db5ea.
2025-06-08 17:52:08 +02:00
Francesco
a14d7db5ea ok please this time audio should work 2025-06-08 17:41:12 +02:00
Francesco
6ea5afb0d8 ok yeah no audio for now 2025-06-08 17:08:38 +02:00
Francesco
eb70b83bb1 audio track please work holy moly 2025-06-08 16:53:42 +02:00
Francesco
a66ac56246 oh yeah my bad ok
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-08 11:58:42 +02:00
Francesco
aa886ba17c ok yeah im done bro 😭 2025-06-08 11:56:05 +02:00
Francesco
728b7a0d45 who tf does this work 😭 2025-06-08 11:47:54 +02:00
Francesco
cd84d5f3fa brooo come one 😭 2025-06-08 11:36:28 +02:00
Francesco
8900230cb6 ok should work now 2025-06-08 11:29:56 +02:00
Francesco
6b2473d382 ohhh yeah my bad 2025-06-08 11:12:54 +02:00
Francesco
26ace39177 who know if this works 😭 2025-06-08 11:06:21 +02:00
Francesco
c4e2d23387 ok now its fixed 2025-06-08 10:52:09 +02:00
Francesco
7fc01c7b08 ohhhh i forgot my bad 2025-06-08 10:41:06 +02:00
Francesco
af764cf9e9 ok should be good now + better quality parsesr 2025-06-08 10:34:54 +02:00
Francesco
0fd90a7385 my bad sorru 2025-06-08 10:25:18 +02:00
Francesco
0747c86a50 should be better now 2025-06-08 10:21:21 +02:00
Francesco
1b20a7cdbb test UI mode 2025-06-08 10:15:00 +02:00
Francesco
92585beaba would this wrk?
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-07 18:39:29 +02:00
Francesco
ad2ade2f45 opsi 2025-06-07 18:30:35 +02:00
Francesco
d94daaf177 Yeah idk what i am even doing at this point 2025-06-07 18:27:47 +02:00
cranci
22685e688f
Main (#158)
* dwo (#132)

* bug fixes  (#127)

* yeah @realdoomsboygaming fault

* fixes

* freaky ahh update

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>

* opds

* bug fixes around the downloads (#133) (#134)

* yeah nice xcode

* yeah fuck ts

* yeah

* ok this shit is really fucked then

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
2025-06-07 15:56:19 +02:00
Francesco
7d6e2e65d4 ok this shit is really fucked then 2025-06-07 15:46:34 +02:00
Francesco
dbf2c2516b yeah 2025-06-07 15:37:17 +02:00
Francesco
1540193a88 yeah fuck ts 2025-06-07 15:32:14 +02:00
Francesco
fde26dab5d yeah nice xcode 2025-06-07 15:17:18 +02:00
Francesco
76ac93bce1 Merge branch 'dev' 2025-06-07 14:52:23 +02:00
Francesco
40bf680058 well maybe this fixed movie downloads idk 😭 2025-06-07 14:47:49 +02:00
Francesco
210e98c5a1 test now
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-06 17:34:08 +02:00
Francesco
ec9dbd86f1 ops 2025-06-06 17:25:24 +02:00
Francesco
1e909ca9eb test cache stuff removed 2025-06-06 17:23:19 +02:00
Francesco
a834e41570 this is ebtter? 2025-06-06 14:33:51 +02:00
Francesco
6488c345c8 Revert "LIFE OR DEATH PR (#154)"
This reverts commit 4a91aa022d.
2025-06-06 14:29:05 +02:00
Francesco
becf87591f Revert "test animation"
This reverts commit 5fc633921a.
2025-06-06 14:29:03 +02:00
Francesco
5fc633921a test animation 2025-06-06 14:20:02 +02:00
Francesco
d712e5267d yes 2025-06-06 14:19:02 +02:00
Seiike
16d863a22b
now if the user leaves mediainfoview on chunk 51-100 it will remember it and put him back the next time (#155)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-06 07:24:05 +02:00
50/50
4a91aa022d
LIFE OR DEATH PR (#154) 2025-06-06 07:23:33 +02:00
Francesco
92200a674d Update MediaInfoView.swift
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-05 19:04:45 +02:00
Francesco
2a4af77e6a ok maybe too many UI elements 2025-06-05 18:58:42 +02:00
Francesco
aca5cc6906 uh?
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-05 18:49:25 +02:00
Francesco
ff2ce35654 Revert "test (wont work)"
This reverts commit 8d1ae7b636.
2025-06-05 16:46:35 +02:00
Francesco
8d1ae7b636 test (wont work) 2025-06-05 16:42:29 +02:00
Francesco
1111cf1a0b oh wait test?
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-04 20:44:52 +02:00
Francesco
6b0f020e6d fixed temp color 2025-06-04 20:42:09 +02:00
Francesco
a6ba2db81f ok yes im dumb. Also maybe fixed #153 2025-06-04 20:24:41 +02:00
Francesco
491716b1e2 ohhhh yeah my bad 2025-06-04 20:19:12 +02:00
Francesco
26957de534 test? 2025-06-04 20:16:38 +02:00
Francesco
0daaf8027f Revert "test x22222"
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build Mac Catalyst (push) Has been cancelled
This reverts commit f65eeeea41.
2025-06-03 21:00:06 +02:00
Francesco
f65eeeea41 test x22222 2025-06-03 20:56:09 +02:00
Francesco
f04638ccf3 ops 2025-06-03 20:43:11 +02:00
Francesco
46d95975fe asdasdasdasdasd 2025-06-03 20:37:38 +02:00
Francesco
479e83420e Update MediaInfoView.swift 2025-06-03 20:28:12 +02:00
Francesco
fa65ff7740 Reapply "test again"
This reverts commit 42fb670abb.
2025-06-03 20:27:46 +02:00
Francesco
49f541755c ok 2025-06-03 20:26:39 +02:00
Francesco
31d02ff6fa r 2025-06-03 20:24:04 +02:00
Francesco
f83d8251d1 bro 😭 2025-06-03 20:17:37 +02:00
Francesco
eb07f50d3d changed my mind 😭 2025-06-03 20:08:57 +02:00
Francesco
42fb670abb Revert "test again"
This reverts commit 491b7175b5.
2025-06-03 20:08:41 +02:00
Francesco
491b7175b5 test again 2025-06-03 20:07:49 +02:00
Francesco
2705dc7485 maybe this doesnt crash 2025-06-03 20:02:32 +02:00
Francesco
2d63225008 Revert "pleasee"
This reverts commit 9d7e2f1730.
2025-06-03 20:00:20 +02:00
Francesco
9d7e2f1730 pleasee 2025-06-03 18:39:29 +02:00
Francesco
94bb886b4e Revert "test"
This reverts commit a582bf7ab8.
2025-06-03 18:36:18 +02:00
Francesco
a582bf7ab8 test 2025-06-03 18:25:02 +02:00
Francesco
159c48b1ab test, implemented multi-server 2025-06-03 18:20:01 +02:00
Francesco
cee4f59ea4 Revert "Multi-Stream Download Support (#150)"
This reverts commit c37879fa31.
2025-06-03 18:06:09 +02:00
Francesco
5d5365949e Revert "bunch of stuffs"
This reverts commit 611802607e.
2025-06-03 18:05:47 +02:00
Francesco
01d36394c6 Revert "cachec"
This reverts commit 4ffa34b9ab.
2025-06-03 18:05:45 +02:00
Francesco
243e6fe54d Revert "kingfisher 😭"
This reverts commit d0ac33afb9.
2025-06-03 18:05:43 +02:00
Francesco
1d7415bbf1 Revert "test animation"
This reverts commit 0962fd15d1.
2025-06-03 18:05:40 +02:00
Francesco
fc452fe77b Revert "test"
This reverts commit a81db75fd9.
2025-06-03 18:05:38 +02:00
Francesco
469d8d854e Revert "Update MediaInfoView.swift"
This reverts commit b696ef7b4a.
2025-06-03 18:05:35 +02:00
Francesco
173b6b18f0 Revert "Update MediaInfoView.swift"
This reverts commit 4be7382843.
2025-06-03 18:05:33 +02:00
Francesco
b5ef3fbb6d Revert "imma touch sky"
This reverts commit 8cd5c55dd4.
2025-06-03 18:05:31 +02:00
Francesco
b60db521cd Revert "this?"
This reverts commit ceb53f2c7a.
2025-06-03 18:05:28 +02:00
Francesco
4ea16043da Revert "test"
This reverts commit 3a172bc878.
2025-06-03 18:05:26 +02:00
Francesco
3fdb75e48c Revert "ds"
This reverts commit f8cc12ff15.
2025-06-03 18:05:24 +02:00
Francesco
43d2dd2f01 Revert "test progess bytton"
This reverts commit 6cad58ac33.
2025-06-03 18:05:22 +02:00
Francesco
0e1a1db526 Revert "ok maybe now pretty please"
This reverts commit 41953477a5.
2025-06-03 18:05:18 +02:00
Francesco
d580f9dfd9 Revert "test"
This reverts commit 0857ab3024.
2025-06-03 18:05:16 +02:00
Francesco
49039fc216 Revert "test"
This reverts commit a0c7bbf734.
2025-06-03 18:05:14 +02:00
Francesco
2b1f4807fd Revert "test Jetsam dont kill sulfur please 😭"
This reverts commit 53ae9b2d23.
2025-06-03 18:05:12 +02:00
Francesco
cd73becd03 Revert "idk this works"
This reverts commit 832cd759d3.
2025-06-03 18:05:10 +02:00
Francesco
2e7687c704 Revert "test memory leaks"
This reverts commit 652bfe1766.
2025-06-03 18:05:08 +02:00
Francesco
634cc8f51f Revert "test"
This reverts commit 1bc76cf0d1.
2025-06-03 18:05:05 +02:00
Francesco
7531dbd879 Revert "yes"
This reverts commit 5cfb0acc92.
2025-06-03 18:05:03 +02:00
Francesco
92bf2a0df8 Revert "ststufs"
This reverts commit c0771d8832.
2025-06-03 18:05:01 +02:00
Francesco
4c534be61b Revert "Update MediaInfoView.swift"
This reverts commit 369f83a26e.
2025-06-03 18:04:58 +02:00
Francesco
26fce675aa Revert "Update MediaInfoView.swift"
This reverts commit 194172c26d.
2025-06-03 18:04:56 +02:00
Francesco
50640acb19 Revert "Update CustomPlayer.swift"
This reverts commit c59462f42d.
2025-06-03 18:04:53 +02:00
Francesco
7e9336d55b Revert "Fixes (#152)"
This reverts commit 8075de46fb.
2025-06-03 18:04:50 +02:00
Francesco
87a77d08f5 Revert "test presentation model"
This reverts commit 2a3c58adbd.
2025-06-03 18:04:41 +02:00
Francesco
08e9991528 Reapply "test presentation model"
This reverts commit 8e0563facc.
2025-06-03 18:04:38 +02:00
Francesco
566be2af25 Revert "test"
This reverts commit 519744616e.
2025-06-03 18:04:35 +02:00
Francesco
771e67e9d2 Revert "hell yeah"
This reverts commit 7e1d10aa08.
2025-06-03 18:04:32 +02:00
Francesco
dd4bb5f437 Reapply "Update CustomPlayer.swift"
This reverts commit 03a6f5c79e.
2025-06-03 18:04:30 +02:00
Francesco
3ebe078507 Revert "test"
This reverts commit 7a380967ea.
2025-06-03 18:04:28 +02:00
Francesco
6d310eb6c8 Revert "test"
This reverts commit bc987e63eb.
2025-06-03 18:04:26 +02:00
Francesco
3f6bf3aca4 Revert "Update MediaInfoView.swift"
This reverts commit 96304b21c5.
2025-06-03 18:04:24 +02:00
Francesco
daff0020cb Revert "Update MediaInfoView.swift"
This reverts commit f99d6cf34f.
2025-06-03 18:04:22 +02:00
cranci
f99d6cf34f
Update MediaInfoView.swift
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-03 16:29:00 +02:00
cranci
96304b21c5
Update MediaInfoView.swift 2025-06-03 16:16:02 +02:00
Francesco
bc987e63eb test 2025-06-03 15:46:19 +02:00
Francesco
7a380967ea test 2025-06-03 15:36:43 +02:00
Francesco
03a6f5c79e Revert "Update CustomPlayer.swift"
This reverts commit c59462f42d.
2025-06-03 15:24:15 +02:00
Francesco
7e1d10aa08 hell yeah 2025-06-03 15:17:02 +02:00
Francesco
519744616e test 2025-06-03 15:07:58 +02:00
Francesco
8e0563facc Revert "test presentation model"
This reverts commit 2a3c58adbd.
2025-06-03 14:55:11 +02:00
50/50
8075de46fb
Fixes (#152)
* Mediainfoview: Fixed test in start button + pushed items a bit down for more clarity

* Capped logs showing to 50k characters (copying logs still copies all the logs of that session))

* Fixed gradient + made it so that the progress bar in the play button resets when 90%>

Resetting progress bar makes sense, as it now shows text for the next episode which doesn't have any progress.

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-03 14:50:11 +02:00
Francesco
2a3c58adbd test presentation model 2025-06-03 14:49:45 +02:00
cranci
c59462f42d
Update CustomPlayer.swift
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-03 06:49:40 +02:00
cranci
194172c26d
Update MediaInfoView.swift 2025-06-03 06:45:51 +02:00
cranci
369f83a26e
Update MediaInfoView.swift
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-02 22:48:00 +02:00
Francesco
c0771d8832 ststufs 2025-06-02 21:41:56 +02:00
Francesco
5cfb0acc92 yes 2025-06-02 21:34:20 +02:00
realdoomsboygaming
c37879fa31
Multi-Stream Download Support (#150)
Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-02 21:23:52 +02:00
Francesco
1bc76cf0d1 test 2025-06-02 21:17:35 +02:00
Francesco
652bfe1766 test memory leaks 2025-06-02 20:45:22 +02:00
Francesco
832cd759d3 idk this works
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-02 17:22:26 +02:00
Francesco
53ae9b2d23 test Jetsam dont kill sulfur please 😭 2025-06-02 17:07:51 +02:00
Francesco
a0c7bbf734 test 2025-06-02 16:49:48 +02:00
Francesco
0857ab3024 test 2025-06-02 16:32:53 +02:00
Francesco
41953477a5 ok maybe now pretty please 2025-06-02 16:24:52 +02:00
Francesco
6cad58ac33 test progess bytton 2025-06-02 16:17:21 +02:00
Francesco
f8cc12ff15 ds 2025-06-02 16:09:45 +02:00
Francesco
3a172bc878 test 2025-06-02 12:00:33 +02:00
Francesco
ceb53f2c7a this? 2025-06-02 11:47:16 +02:00
Francesco
8cd5c55dd4 imma touch sky 2025-06-02 11:44:02 +02:00
cranci
4be7382843
Update MediaInfoView.swift 2025-06-02 10:25:04 +02:00
cranci
b696ef7b4a
Update MediaInfoView.swift 2025-06-02 10:09:47 +02:00
Francesco
a81db75fd9 test 2025-06-02 09:35:58 +02:00
Francesco
0962fd15d1 test animation
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-02 09:14:20 +02:00
Francesco
d0ac33afb9 kingfisher 😭 2025-06-02 09:05:22 +02:00
Francesco
4ffa34b9ab cachec 2025-06-02 08:57:37 +02:00
Francesco
611802607e bunch of stuffs
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-01 23:01:34 +02:00
50/50
d260c81d58
More UI enhancements (#148)
* Minor changes

* more minor changes

* MORE MINOR CHANGES

* Fixed one singular bug

* Update SettingsViewGeneral.swift

* fuck conflicts

* fuck you and your credits

* buh buh

* Update SettingsViewAbout.swift

* What's that? What's a properly working code without unnecessary issues?

* Type shit maybe?

* Create ios.yml

* smol

* type shi

shi

* end

* Fixed system theme bug

* (hopefully) fixed sliding items in search + recent searches spam

* Fixed open keyboard in media view

* Fixed searchviewdata + fixed toggle color being too light

* fixed episode slider sensitivity

* new recent searches, not fully done but wtv WHO CARES 💯

* Add module screen fix

* Delete .github/workflows/ios.yml

* UI modifications

* Scroll to close keyboard

* Text change

* Downloadview transition

* Search cards text fixed

* Reduced header spacing + moved down a to match library

* Text change

* Small tab bar tweak for search view

* added gradient to player buttons

* reduced summary size

* IBH special 💯

* Fixed different height text?

* Removed seperator

* Some more fixes

start watching button

* MediaInfoView modifications

* Fixed settingsviewdata

* Changed x mark and fixed episode swipe for 4 items

* Delete Package.resolved

* fix

* fix

* Update Package.resolved

* Removed version lalel type shi

* Who thought this view existed? 😭

* Less wide tab bar

* Actions button for movies + resetting watch progress

* Disabled switching back from search to other views for 0.5 seconds (no other fix available rn)

* Added divider (IBH)

* Hide tab bar in module lib

* Update SettingsViewAbout.swift

* Decreased xmark size and increased search bar clear button size

* Disabled bounce scroll, make date more visible, added swipe to go back

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-01 22:47:35 +02:00
Francesco
d5259523c5 ops 2025-06-01 22:43:40 +02:00
Francesco
ff76642b0c asd 2025-06-01 22:35:13 +02:00
Francesco
ca9deebb18 test 2025-06-01 22:30:17 +02:00
Francesco
19f7a7a04c Revert "test"
This reverts commit 67bb6bc3d9.
2025-06-01 22:24:12 +02:00
Francesco
67bb6bc3d9 test 2025-06-01 22:21:35 +02:00
Francesco
271636ed64 tf is this 2025-06-01 22:16:02 +02:00
Francesco
f4f6707f5f uh? 2025-06-01 22:02:03 +02:00
Francesco
ac0507e615 fixed some syntax and stuffs 2025-06-01 21:55:25 +02:00
Francesco
cbddeb6eb0 fr 2025-06-01 21:51:25 +02:00
Francesco
5ad17b7166 Revert "test"
This reverts commit 7c289eee8c.
2025-06-01 21:51:10 +02:00
Francesco
1e1baa0e88 nvm imma test again 2025-06-01 21:47:11 +02:00
Francesco
7c289eee8c test 2025-06-01 21:46:20 +02:00
Francesco
50bdc0c78a i cant even do math 😭 2025-06-01 21:41:07 +02:00
Francesco
7513727ced idk 2025-06-01 21:37:39 +02:00
50/50
5a56dadc59
VERY important change 💯 (#147)
* Minor changes

* more minor changes

* MORE MINOR CHANGES

* Fixed one singular bug

* Update SettingsViewGeneral.swift

* fuck conflicts

* fuck you and your credits

* buh buh

* Update SettingsViewAbout.swift

* What's that? What's a properly working code without unnecessary issues?

* Type shit maybe?

* Create ios.yml

* smol

* type shi

shi

* end

* Fixed system theme bug

* (hopefully) fixed sliding items in search + recent searches spam

* Fixed open keyboard in media view

* Fixed searchviewdata + fixed toggle color being too light

* fixed episode slider sensitivity

* new recent searches, not fully done but wtv WHO CARES 💯

* Add module screen fix

* Delete .github/workflows/ios.yml

* UI modifications

* Scroll to close keyboard

* Text change

* Downloadview transition

* Search cards text fixed

* Reduced header spacing + moved down a to match library

* Text change

* Small tab bar tweak for search view

* added gradient to player buttons

* reduced summary size

* IBH special 💯

* Fixed different height text?

* Removed seperator

* Some more fixes

start watching button

* MediaInfoView modifications

* Fixed settingsviewdata

* Changed x mark and fixed episode swipe for 4 items

* Delete Package.resolved

* fix

* fix

* Update Package.resolved

* Removed version lalel type shi

* Who thought this view existed? 😭

* Less wide tab bar

* Actions button for movies + resetting watch progress

* Disabled switching back from search to other views for 0.5 seconds (no other fix available rn)

* Added divider (IBH)

* Hide tab bar in module lib

* Update SettingsViewAbout.swift

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-01 21:34:38 +02:00
Francesco
a428b3311f too much? 2025-06-01 21:30:04 +02:00
50/50
9237324fcf
Some fixes from IBH list (not all) (#146)
* Minor changes

* more minor changes

* MORE MINOR CHANGES

* Fixed one singular bug

* Update SettingsViewGeneral.swift

* fuck conflicts

* fuck you and your credits

* buh buh

* Update SettingsViewAbout.swift

* What's that? What's a properly working code without unnecessary issues?

* Type shit maybe?

* Create ios.yml

* smol

* type shi

shi

* end

* Fixed system theme bug

* (hopefully) fixed sliding items in search + recent searches spam

* Fixed open keyboard in media view

* Fixed searchviewdata + fixed toggle color being too light

* fixed episode slider sensitivity

* new recent searches, not fully done but wtv WHO CARES 💯

* Add module screen fix

* Delete .github/workflows/ios.yml

* UI modifications

* Scroll to close keyboard

* Text change

* Downloadview transition

* Search cards text fixed

* Reduced header spacing + moved down a to match library

* Text change

* Small tab bar tweak for search view

* added gradient to player buttons

* reduced summary size

* IBH special 💯

* Fixed different height text?

* Removed seperator

* Some more fixes

start watching button

* MediaInfoView modifications

* Fixed settingsviewdata

* Changed x mark and fixed episode swipe for 4 items

* Delete Package.resolved

* fix

* fix

* Update Package.resolved

* Removed version lalel type shi

* Who thought this view existed? 😭

* Less wide tab bar

* Actions button for movies + resetting watch progress

* Disabled switching back from search to other views for 0.5 seconds (no other fix available rn)

* Added divider (IBH)

* Hide tab bar in module lib

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-01 21:24:37 +02:00
cranci
4f0c705660
Update README.md 2025-06-01 21:22:29 +02:00
Francesco
4d769237b6 tst 2025-06-01 21:14:26 +02:00
Francesco
a379c4278f ops 2025-06-01 21:11:33 +02:00
Francesco
8e0dd3d7b0 test??????? 2025-06-01 21:09:36 +02:00
Francesco
e05991a631 test 2025-06-01 21:01:13 +02:00
Francesco
ec380f06d9 test 2025-06-01 20:44:39 +02:00
50/50
3cf264fab8
action button for movies (#145)
* Minor changes

* more minor changes

* MORE MINOR CHANGES

* Fixed one singular bug

* Update SettingsViewGeneral.swift

* fuck conflicts

* fuck you and your credits

* buh buh

* Update SettingsViewAbout.swift

* What's that? What's a properly working code without unnecessary issues?

* Type shit maybe?

* Create ios.yml

* smol

* type shi

shi

* end

* Fixed system theme bug

* (hopefully) fixed sliding items in search + recent searches spam

* Fixed open keyboard in media view

* Fixed searchviewdata + fixed toggle color being too light

* fixed episode slider sensitivity

* new recent searches, not fully done but wtv WHO CARES 💯

* Add module screen fix

* Delete .github/workflows/ios.yml

* UI modifications

* Scroll to close keyboard

* Text change

* Downloadview transition

* Search cards text fixed

* Reduced header spacing + moved down a to match library

* Text change

* Small tab bar tweak for search view

* added gradient to player buttons

* reduced summary size

* IBH special 💯

* Fixed different height text?

* Removed seperator

* Some more fixes

start watching button

* MediaInfoView modifications

* Fixed settingsviewdata

* Changed x mark and fixed episode swipe for 4 items

* Delete Package.resolved

* fix

* fix

* Update Package.resolved

* Removed version lalel type shi

* Who thought this view existed? 😭

* Less wide tab bar

* Actions button for movies + resetting watch progress

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-06-01 18:54:07 +02:00
Francesco
e0a8d46fdb yes 2025-06-01 18:53:40 +02:00
Francesco
14f87c071b made it save overtime 2025-06-01 18:41:12 +02:00
Francesco
e6b766ab94 test, idk if it compiles tbh 2025-06-01 18:32:42 +02:00
Francesco
a07b43a750 yes 2025-06-01 18:09:00 +02:00
Francesco
ebe38209bb better 2025-06-01 18:07:11 +02:00
Francesco
c95c249843 maed TMDB default + image size selector 2025-06-01 18:01:51 +02:00
Francesco
c3e000da9d ok now also the title ayy 2025-06-01 17:47:04 +02:00
Francesco
99a5c6303c TMDB thanks to @qooode
Co-Authored-By: qooode <71751652+qooode@users.noreply.github.com>
2025-06-01 17:39:51 +02:00
Francesco
62802fd30e test 2025-06-01 17:13:54 +02:00
Francesco
fddf940b95 idk i changed layout constants
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-06-01 16:41:11 +02:00
Francesco
afe49abcfa Revert "does ts work?"
This reverts commit 1afd46f9a7.
2025-06-01 16:29:15 +02:00
cranci
b5d868dcfa
Update SoraApp.swift 2025-06-01 15:49:04 +02:00
Francesco
1afd46f9a7 does ts work? 2025-06-01 13:53:09 +02:00
Francesco
3f69385ffc Revert "well maybe this does work"
This reverts commit e86a7b229a.
2025-06-01 12:18:11 +02:00
Francesco
e86a7b229a well maybe this does work 2025-06-01 12:09:47 +02:00
Francesco
17c8a61416 Revert "will it work?"
This reverts commit 1d10d542a0.
2025-06-01 12:04:19 +02:00
Francesco
1d10d542a0 will it work? 2025-06-01 11:57:22 +02:00
Francesco
8bb221cd36 test fix 2025-06-01 11:48:26 +02:00
Francesco
ef9d911d9d little tweaks 2025-06-01 10:18:19 +02:00
Seiike
ceea5c9206
shooting like im curry or lebron 🏀 (#144) 2025-06-01 09:42:20 +02:00
Francesco
991ff443c2 yes
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-31 22:47:51 +02:00
50/50
1ce01ea90a
Less wide tab bar (#143)
* Minor changes

* more minor changes

* MORE MINOR CHANGES

* Fixed one singular bug

* Update SettingsViewGeneral.swift

* fuck conflicts

* fuck you and your credits

* buh buh

* Update SettingsViewAbout.swift

* What's that? What's a properly working code without unnecessary issues?

* Type shit maybe?

* Create ios.yml

* smol

* type shi

shi

* end

* Fixed system theme bug

* (hopefully) fixed sliding items in search + recent searches spam

* Fixed open keyboard in media view

* Fixed searchviewdata + fixed toggle color being too light

* fixed episode slider sensitivity

* new recent searches, not fully done but wtv WHO CARES 💯

* Add module screen fix

* Delete .github/workflows/ios.yml

* UI modifications

* Scroll to close keyboard

* Text change

* Downloadview transition

* Search cards text fixed

* Reduced header spacing + moved down a to match library

* Text change

* Small tab bar tweak for search view

* added gradient to player buttons

* reduced summary size

* IBH special 💯

* Fixed different height text?

* Removed seperator

* Some more fixes

start watching button

* MediaInfoView modifications

* Fixed settingsviewdata

* Changed x mark and fixed episode swipe for 4 items

* Delete Package.resolved

* fix

* fix

* Update Package.resolved

* Removed version lalel type shi

* Who thought this view existed? 😭

* Less wide tab bar

---------

Co-authored-by: cranci <100066266+cranci1@users.noreply.github.com>
2025-05-31 22:42:00 +02:00
Francesco
1d81caddf1 works now? 2025-05-31 22:41:02 +02:00
Francesco
edc11d033e ops 2025-05-31 22:36:34 +02:00
Francesco
a8cd13782c works? 2025-05-31 22:34:21 +02:00
Francesco
544dac2cf6 Revert "upscaler fixes?"
This reverts commit a84dacded1.
2025-05-31 22:33:26 +02:00
Francesco
a84dacded1 upscaler fixes? 2025-05-31 22:22:06 +02:00
Francesco
84215ac49b wait fr? 2025-05-31 22:20:50 +02:00
Francesco
e83ec3343c Revert "yeah idk tf is this 😭"
This reverts commit 88a5df88bf.
2025-05-31 22:16:54 +02:00
Francesco
88a5df88bf yeah idk tf is this 😭 2025-05-31 22:16:41 +02:00
Francesco
5567619e0f Revert "test"
This reverts commit e14b00fc13.
2025-05-31 22:04:50 +02:00
Francesco
e14b00fc13 test 2025-05-31 22:00:03 +02:00
Francesco
e9d9101526 yes 2025-05-31 21:49:42 +02:00
50/50
8b2ee00c90
Who thought this view existed? 😭 (#142)
* Minor changes

* more minor changes

* MORE MINOR CHANGES

* Fixed one singular bug

* Update SettingsViewGeneral.swift

* fuck conflicts

* fuck you and your credits

* buh buh

* Update SettingsViewAbout.swift

* What's that? What's a properly working code without unnecessary issues?

* Type shit maybe?

* Create ios.yml

* smol

* type shi

shi

* end

* Fixed system theme bug

* (hopefully) fixed sliding items in search + recent searches spam

* Fixed open keyboard in media view

* Fixed searchviewdata + fixed toggle color being too light

* fixed episode slider sensitivity

* new recent searches, not fully done but wtv WHO CARES 💯

* Add module screen fix

* Delete .github/workflows/ios.yml

* UI modifications

* Scroll to close keyboard

* Text change

* Downloadview transition

* Search cards text fixed

* Reduced header spacing + moved down a to match library

* Text change

* Small tab bar tweak for search view

* added gradient to player buttons

* reduced summary size

* IBH special 💯

* Fixed different height text?

* Removed seperator

* Some more fixes

start watching button

* MediaInfoView modifications

* Fixed settingsviewdata

* Changed x mark and fixed episode swipe for 4 items

* Delete Package.resolved

* fix

* fix

* Update Package.resolved

* Removed version lalel type shi

* Who thought this view existed? 😭
2025-05-31 21:17:27 +02:00
50/50
400079f0da
May God be with you (#141)
* Minor changes

* more minor changes

* MORE MINOR CHANGES

* Fixed one singular bug

* Update SettingsViewGeneral.swift

* fuck conflicts

* fuck you and your credits

* buh buh

* Update SettingsViewAbout.swift

* What's that? What's a properly working code without unnecessary issues?

* Type shit maybe?

* Create ios.yml

* smol

* type shi

shi

* end

* Fixed system theme bug

* (hopefully) fixed sliding items in search + recent searches spam

* Fixed open keyboard in media view

* Fixed searchviewdata + fixed toggle color being too light

* fixed episode slider sensitivity

* new recent searches, not fully done but wtv WHO CARES 💯

* Add module screen fix

* Delete .github/workflows/ios.yml

* UI modifications

* Scroll to close keyboard

* Text change

* Downloadview transition

* Search cards text fixed

* Reduced header spacing + moved down a to match library

* Text change

* Small tab bar tweak for search view

* added gradient to player buttons

* reduced summary size

* IBH special 💯

* Fixed different height text?

* Removed seperator

* Some more fixes

start watching button

* MediaInfoView modifications

* Fixed settingsviewdata

* Changed x mark and fixed episode swipe for 4 items

* Delete Package.resolved

* fix

* fix

* Update Package.resolved

* Removed version lalel type shi
2025-05-31 20:56:06 +02:00
Francesco
fb6dc6219d @50n50 now you cant cry 2025-05-31 18:54:49 +02:00
Francesco
b04bdb6d1f yeah idk bro 2025-05-31 18:46:27 +02:00
Francesco
ced4c6a1b5 @50n50 dont cry now 2025-05-31 18:44:46 +02:00
Francesco
a4fcb80815 yeah imma 😭 2025-05-31 18:42:43 +02:00
Francesco
78ec26f0ee does ts even work 😭 2025-05-31 18:35:49 +02:00
Francesco
893ab70a53 epsidoe progresses fixes 2025-05-31 18:33:39 +02:00
Francesco
73f940f818 yes 2025-05-31 18:30:11 +02:00
Francesco
59e1a47b52 test fixes again 😭 2025-05-31 18:23:37 +02:00
Francesco
b7ec052ab1 Revert "yeah idk"
This reverts commit ac188deda3.
2025-05-31 18:19:44 +02:00
Francesco
ac188deda3 yeah idk 2025-05-31 18:14:42 +02:00
Francesco
5c941b5a34 brooo 😭 2025-05-31 18:07:30 +02:00
Francesco
21f7b79974 nvm no scrollview sorry 2025-05-31 18:04:10 +02:00
Francesco
99d5e6f6cd even more tests 😭 2025-05-31 18:00:35 +02:00
Francesco
abbbdc00ee idk 😭 2025-05-31 17:59:37 +02:00
Francesco
1d5f09be0a attempt more fixes 2025-05-31 17:55:52 +02:00
Francesco
d3c4a0bb98 fixes idk 2025-05-31 17:51:40 +02:00
Francesco
bd37eb9db4 fixed search result grid 2025-05-31 17:42:27 +02:00
Francesco
9feabeaa41 more fixes for mediainfoview + search 2025-05-31 17:31:20 +02:00
Francesco
0160564378 fixed search frequency 2025-05-31 17:27:54 +02:00
Francesco
eb9582f07a yeah just a test
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-31 16:20:41 +02:00
50/50
f3ef58db11
green fn (#140)
* Minor changes

* more minor changes

* MORE MINOR CHANGES

* Fixed one singular bug

* Update SettingsViewGeneral.swift

* fuck conflicts

* fuck you and your credits

* buh buh

* Update SettingsViewAbout.swift

* What's that? What's a properly working code without unnecessary issues?

* Type shit maybe?

* Create ios.yml

* smol

* type shi

shi

* end

* Fixed system theme bug

* (hopefully) fixed sliding items in search + recent searches spam

* Fixed open keyboard in media view

* Fixed searchviewdata + fixed toggle color being too light

* fixed episode slider sensitivity

* new recent searches, not fully done but wtv WHO CARES 💯

* Add module screen fix

* Delete .github/workflows/ios.yml

* UI modifications

* Scroll to close keyboard

* Text change

* Downloadview transition

* Search cards text fixed

* Reduced header spacing + moved down a to match library

* Text change

* Small tab bar tweak for search view

* added gradient to player buttons

* reduced summary size

* IBH special 💯

* Fixed different height text?

* Removed seperator

* Some more fixes

start watching button
2025-05-31 15:56:13 +02:00
Francesco
38df5c71e8 im dumb
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build Mac Catalyst (push) Has been cancelled
2025-05-27 16:26:12 +02:00
Francesco
390b78062c yeah idk again 2025-05-27 15:31:05 +02:00
Francesco
76e2b3ed3b idk is this SharePlay? 2025-05-27 15:23:19 +02:00
Francesco
50cd96bee7 maybe this time? 2025-05-27 15:15:33 +02:00
Francesco
43719ba28b fixed stuffs idk????? 2025-05-27 15:08:15 +02:00
Francesco
f1ae2b27d4 title also now? 2025-05-27 14:54:40 +02:00
Francesco
f845ce3d7f test media controll
700 COMMITS 🎊
2025-05-27 14:48:43 +02:00
Francesco
8c93ec7bed ops
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-27 14:22:17 +02:00
Francesco
a020323bdb fixed credist
Co-Authored-By: 50/50 <80717571+50n50@users.noreply.github.com>
Co-Authored-By: undeaD_D <8116188+undeaDD@users.noreply.github.com>
2025-05-27 14:17:46 +02:00
Seiike
f2d0e55e57
anilist improvement, logic for upscaling images (#137) 2025-05-27 06:33:11 +02:00
Francesco
967791878a a masterpiece ong
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-26 20:09:52 +02:00
Francesco
95c1a29c18 does it works? 2025-05-26 16:59:52 +02:00
Francesco
0c8e0f412c freaky 2025-05-26 16:47:15 +02:00
realdoomsboygaming
e0c6e03c3f
Stagemanager Support & Cache Wording inconsistency fixes (#135)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-26 06:19:17 +02:00
Seiike
d26f066da0
few bug fixes (#136) 2025-05-26 06:18:23 +02:00
Francesco
30aa66bf2a Revert "test backup system"
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
This reverts commit 18075308dd.
2025-05-25 13:26:48 +02:00
Francesco
18075308dd test backup system
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-25 13:20:24 +02:00
Francesco
0a6b3c83c9 asd 2025-05-25 13:16:55 +02:00
Francesco
40e40bab12 Revert "test"
This reverts commit d4bbf87703.
2025-05-25 13:10:21 +02:00
Francesco
d4bbf87703 test 2025-05-25 13:05:53 +02:00
Francesco
fe097bfd14 better now 2025-05-25 13:02:04 +02:00
Francesco
171a283128 optimized immages 2025-05-25 12:54:55 +02:00
Francesco
352fc604d7 added old icon 2025-05-25 12:45:40 +02:00
Francesco
9af22ecfb2 fixed 2025-05-25 12:23:03 +02:00
Francesco
54203e3440 test x555555 2025-05-25 12:13:10 +02:00
Francesco
861cac921f test? 2025-05-25 12:09:16 +02:00
Francesco
a0fb8ab0e4 test 2025-05-25 11:44:16 +02:00
Francesco
c7e7672bf3 fixed fetchV2 crash 2025-05-25 11:26:08 +02:00
Francesco
0cd3be5401 Revert "test"
This reverts commit e32c1d39bc.
2025-05-25 11:23:03 +02:00
Francesco
0df613402e Revert "damn"
This reverts commit cb34e1a265.
2025-05-25 11:23:01 +02:00
Francesco
cb34e1a265 damn 2025-05-25 11:04:56 +02:00
Francesco
e32c1d39bc test 2025-05-25 11:03:17 +02:00
Francesco
8a0a497d81 ops 😭 2025-05-25 10:02:02 +02:00
Francesco
70b27ba55b Update SearchView.swift 2025-05-25 10:00:21 +02:00
Francesco
d74a32c918 search history? 2025-05-25 09:59:01 +02:00
Francesco
6ff7fe06e2 test 2025-05-25 09:45:47 +02:00
Francesco
f7bdb4dc4d donwnload test fix 2025-05-25 09:28:10 +02:00
Francesco
3f370584a4 fixed progress bar infinite 2025-05-25 09:26:10 +02:00
Francesco
34334f8e6e test fix 2025-05-25 08:56:35 +02:00
cranci
0f0a86934e
bug fixes around the downloads (#133) (#134) 2025-05-24 22:10:22 +02:00
Seiike
653e714955
bug fixes around the downloads (#133)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-24 22:09:45 +02:00
Francesco
951a03fbac opds 2025-05-24 20:59:12 +02:00
cranci
0d042cc3e6
dwo (#132)
* bug fixes  (#127)

* yeah @realdoomsboygaming fault

* fixes

* freaky ahh update

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
2025-05-24 20:38:13 +02:00
Francesco
f39ff3fcf3 freaky ahh update
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-24 20:35:29 +02:00
Francesco
6ed1801cbb fixes 2025-05-24 20:21:09 +02:00
Francesco
53a9ff9f19 yeah @realdoomsboygaming fault 2025-05-24 18:20:21 +02:00
Francesco
b95b909b0f Merge branch 'main' into dev 2025-05-24 16:22:13 +02:00
Francesco
92061e868a yes 2025-05-24 16:22:02 +02:00
Seiike
600d338bce
bug fixes (#127)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-24 06:24:00 +02:00
Francesco
a3e84bd865 Merge branch 'dev' 2025-05-23 20:16:13 +02:00
Francesco
dc6236f324 my bad
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-23 20:15:20 +02:00
cranci
e030658947
downloads (#130) 2025-05-23 20:02:23 +02:00
Francesco
21a08e4aa3 fixed stuffs idk 2025-05-23 19:50:59 +02:00
realdoomsboygaming
c85b6690da
The update we all have been waiting for (#129)
* letstry

* actual build

* eeee

* eeeee

* eeeeeeeeeee

* l

* maybe

* yes

* dwadaw

* e

* eee

* LFG

* yes

* letsdothisagain

* yes

* Persistance Added

* lfg

* Buggy Public Build

* Prevent downloading of already downloaded episodes

* Fix file size estimates maybe

* Add downloading progress in episode cell

* yes

* yes again

* yes

* Graceful degradation implementation for Episode Cells

* Fix download Size

* yay

* e

* implement download queue

* Download Quality

* Remove Package.resolved as it may differ per environment

* Restored Xcode project file from backup branch after merge

* yes

* y

* Added set color method to UserDefaults extension

* maybe

* yes

* eeee

* YES

* fix build

* maybe

* yes

* mp4shi

* yes

* Update build.yml

* maybe fix

* yes

* yes :D

* Okay les go

* LETSGO

* Update scratchpad with latest progress before upstream merge

* Delete .cursor/merge_conflicts.md

* Delete .cursor/scratchpad.md

* Fix the AI stealing my work and crediting itself...

* Bug Fixes

* Multi Download Functionality

* Delete .cursor/scratchpad.md

* Update build.yml

* Delete iosbuild.sh

* Download Sorting

* better select stuff things
2025-05-23 19:29:31 +02:00
D Osman
178c847c1c
fix subs for streamas with provided headers (#128)
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build Mac Catalyst (push) Has been cancelled
2025-05-21 17:30:03 +02:00
Francesco
40f8332b4a Revert "crash fixed"
This reverts commit 5d34c4e946.
2025-05-21 14:31:17 +02:00
Francesco
5d34c4e946 crash fixed
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-20 14:52:02 +02:00
Francesco
64db1abf0f nvm my bad
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-19 18:48:12 +02:00
Francesco
73256c5aab idk what is this but alright 2025-05-19 18:45:09 +02:00
Francesco
bd2b7ccb41 works still?
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-19 17:25:26 +02:00
cranci
14a177ce97
Update README.md
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-18 10:36:57 +02:00
Francesco
2b9a9da558 improved images sizes 2025-05-18 10:35:48 +02:00
Francesco
b8345c2e7f Revert "test history"
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
This reverts commit 00228a4012.
2025-05-17 17:34:31 +02:00
Francesco
9a0dbd41e9 wait what? 2025-05-17 17:33:49 +02:00
Francesco
00228a4012 test history 2025-05-17 17:32:10 +02:00
Francesco
935dc2792b Revert "idk tf is this 😭"
This reverts commit 5fc30927a7.
2025-05-17 17:25:22 +02:00
Francesco
15a9a36c00 Updated version in settings 2025-05-17 17:14:57 +02:00
Francesco
5fc30927a7 idk tf is this 😭 2025-05-17 17:13:57 +02:00
Francesco
fa89c472aa fixed module library 2025-05-17 17:01:46 +02:00
Francesco
29f9538066 modules type support now 2025-05-17 16:59:50 +02:00
Francesco
2b8d3b0cde nvm 0.5MB is better 2025-05-17 16:52:56 +02:00
Francesco
cb76d39715 logger improvements 2025-05-17 16:51:25 +02:00
Francesco
0926b5b9d2 SearchView fixes 2025-05-17 16:46:40 +02:00
Francesco
8aaa7111bf test view 2025-05-17 16:37:24 +02:00
Francesco
b19a1bcada my bad i forgot 2025-05-17 16:29:58 +02:00
Francesco
ac6e687107 fixed code missing + range/season is now saved
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run
2025-05-16 17:25:46 +02:00
Francesco
dcd7c363c6 tests
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build Mac Catalyst (push) Has been cancelled
2025-05-15 19:42:19 +02:00
Francesco
6996df091b improvements 2025-05-15 18:56:07 +02:00
Francesco
76b43ba1a0 fixed issues 2025-05-15 18:46:49 +02:00
Francesco
e875fead9b Added downlaods 2025-05-15 18:45:38 +02:00
Francesco
e1a671390c test
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build Mac Catalyst (push) Has been cancelled
2025-05-12 19:53:58 +02:00
Francesco
46962385b2 fxied macos build???????? 2025-05-12 19:41:18 +02:00
Francesco
ef8f42de0f ops 2025-05-12 19:06:26 +02:00
Francesco
631b99ab01 macOS build workflow too now 2025-05-12 19:05:07 +02:00
Francesco
1839f31f9a Update ModuleAdditionSettingsView.swift
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-05-11 17:31:08 +02:00
Francesco
41efac21f2 accent color fixes 2025-05-11 17:26:54 +02:00
Seiike
aa769f8241
fabled lock button (#123)
* the fabled lock controls button

* Update CustomPlayer.swift

* im back
2025-05-11 17:05:03 +02:00
Francesco
5c150c48f5 Merge branch 'main' into dev
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-05-10 17:15:23 +02:00
cranci
1a4078d2c2
0.2.3 (#126)
* add debounce to SearchBar

* add debounce to searchBar

* test download

* provided headers for streams

* Revert "test download"

This reverts commit 72fce2f14b.

* Real branch for #90 (#122)

* Add App Store Link

* Add App Store icon

* Add Testflight link and icon

* Fixed icon sizing

* typo

* fixed readme

---------

Co-authored-by: Storm <stormisfjeld@gmail.com>

* Update README.md

* allow string as post body data type

* Update README.md

* ops

* Update README.md

* Update README.md

* frrfrfr

* Update README.md

* fixed speed on pause

* Update README.md

* Update README.md

* test

* Update README.md (#125)

interesting

* logger in main thread

* fixed segments crash if show is longer

* fixed issues

---------

Co-authored-by: DawudOsman <d.osman@outlook.com>
Co-authored-by: D Osman <80430633+DawudOsman@users.noreply.github.com>
Co-authored-by: Storm <stormisfjeld@gmail.com>
Co-authored-by: Bshar Esfky <98615778+bshar1865@users.noreply.github.com>
2025-05-10 16:29:46 +02:00
Francesco
e0943b84d2 fixed issues 2025-05-10 16:26:23 +02:00
Francesco
640223ea7c fixed segments crash if show is longer 2025-05-10 16:19:49 +02:00
Francesco
9419f93a1d logger in main thread 2025-05-10 16:15:48 +02:00
Bshar Esfky
498d2bc0ea
Update README.md (#125)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
interesting
2025-05-09 17:28:35 +02:00
Francesco
d2cd010151 test 2025-05-09 14:40:12 +02:00
cranci
df0d29724b
Update README.md
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-05-08 16:57:59 +02:00
cranci
33942dea90
Update README.md 2025-05-08 16:52:32 +02:00
Francesco
fdfe614867 fixed speed on pause 2025-05-08 16:51:54 +02:00
cranci
f71a353945
Update README.md 2025-05-08 16:36:06 +02:00
Francesco
f63a1fed21 frrfrfr 2025-05-08 16:34:02 +02:00
cranci
308e9f1861
Update README.md
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-05-07 18:37:34 +02:00
cranci
e8dae6db6a
Update README.md 2025-05-07 18:37:10 +02:00
Francesco
298bd88fd9 Merge branch 'pr/124' into dev 2025-05-07 18:19:41 +02:00
Francesco
5c97d757b3 ops 2025-05-07 17:20:04 +02:00
cranci
ad6494fde9
Update README.md
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-05-05 18:15:19 +02:00
DawudOsman
4b4f76392d allow string as post body data type 2025-05-05 17:07:48 +01:00
cranci
1ec726913f
Update README.md
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-05-01 08:56:25 +02:00
cranci
f11d8706d6
Real branch for #90 (#122)
* Add App Store Link

* Add App Store icon

* Add Testflight link and icon

* Fixed icon sizing

* typo

* fixed readme

---------

Co-authored-by: Storm <stormisfjeld@gmail.com>
2025-05-01 08:44:56 +02:00
Francesco
ea8306b13b Revert "test download"
This reverts commit 72fce2f14b.
2025-05-01 08:37:12 +02:00
DawudOsman
791b033efc provided headers for streams 2025-05-01 01:09:40 +01:00
Francesco
72fce2f14b test download
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-30 21:59:51 +02:00
Francesco
a77b57e3d2 Merge branch 'dev' 2025-04-30 19:39:03 +02:00
cranci
d57b803129
Update CustomPlayer.swift 2025-04-30 18:52:20 +02:00
cranci
17428c45bf
Update Info.plist 2025-04-30 18:37:47 +02:00
cranci
1be1eeaf31
Update MediaInfoView.swift 2025-04-30 18:33:02 +02:00
cranci
a2c2c04d74
Update SettingsViewPlayer.swift
SenPlayrt
2025-04-30 18:30:37 +02:00
Francesco
982f482d84 no way 😭
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-29 18:49:22 +02:00
D Osman
1fca6b712b
Merge branch 'cranci1:dev' into dev 2025-04-27 23:33:05 +01:00
cranci
d34099d49d
Little hotfix (#120)
* fixed stuffs

* OHHHH it was "CURRENT"

* now they use UIStackView

* subtitle delay maybe

* nvm this better

* better text
2025-04-27 17:17:06 +02:00
Francesco
7f4a60bf56 better text
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-04-27 11:28:29 +02:00
Francesco
8f76989ae8 nvm this better
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-26 23:02:10 +02:00
Francesco
3ca0610544 subtitle delay maybe 2025-04-26 23:01:17 +02:00
Francesco
f0d83a7cd5 now they use UIStackView 2025-04-26 22:37:22 +02:00
Francesco
713046ce64 OHHHH it was "CURRENT" 2025-04-26 16:30:36 +02:00
cranci
ed6fa24f92
fixed stuffs (#116) 2025-04-25 21:45:35 +02:00
Francesco
15c54b3d0c fixed stuffs
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-25 21:43:33 +02:00
Francesco
4f524964d0 come on man 2025-04-25 21:39:02 +02:00
Francesco
37b330be45 Merge branch 'dev' 2025-04-25 21:31:24 +02:00
Francesco
4beed31d3e Update MediaInfoView.swift 2025-04-25 21:31:09 +02:00
cranci
b5a7b43b52
Build x2 (#115)
* fixed plist

* made Anilist push updaes correctly

* test episode order

* fixed display order

* Revert "fixed display order"

This reverts commit fd3591c666.

* Revert "test episode order"

This reverts commit 1637383a19.

* there is now 😏  (#105)

* the great logic of not sending the user back when unbookmark

* fixed subtitle view being behind the skip 85s button

* bug fix progress bar

no longer flashes back to the previous position it was in before scrubbing

* moved dim button

* skip intro/outro bug fix

using invisble overlay no longer lets the skip buttons be visible

* bug fix segment marker being outside of the progress bar

* beautiful ahh skip buttons

https://discord.com/channels/1293430817841741899/1318240587886891029/1364701327120269476

* community library???

* now it will 😼

* Update CommunityLib.swift

* no comments + restored older code

* cuts off at the tab bar

* its perfect now

just need to add some drops

* eh

* donezo

* test

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
2025-04-25 21:12:19 +02:00
Francesco
448e3a0947 test 2025-04-25 21:10:41 +02:00
Seiike
8dbc7e6591
there is now 😏 (#105)
* the great logic of not sending the user back when unbookmark

* fixed subtitle view being behind the skip 85s button

* bug fix progress bar

no longer flashes back to the previous position it was in before scrubbing

* moved dim button

* skip intro/outro bug fix

using invisble overlay no longer lets the skip buttons be visible

* bug fix segment marker being outside of the progress bar

* beautiful ahh skip buttons

https://discord.com/channels/1293430817841741899/1318240587886891029/1364701327120269476

* community library???

* now it will 😼

* Update CommunityLib.swift

* no comments + restored older code

* cuts off at the tab bar

* its perfect now

just need to add some drops

* eh

* donezo
2025-04-25 21:08:41 +02:00
Francesco
05e23a1553 Revert "test episode order"
This reverts commit 1637383a19.
2025-04-25 21:04:48 +02:00
Francesco
f77aab696c Revert "fixed display order"
This reverts commit fd3591c666.
2025-04-25 21:04:45 +02:00
cranci
ec3b251d83
Build please (#114)
* fixed plist

* made Anilist push updaes correctly

* test episode order

* fixed display order
2025-04-25 20:54:03 +02:00
Francesco
fd3591c666 fixed display order 2025-04-25 20:51:44 +02:00
Francesco
1637383a19 test episode order 2025-04-25 20:50:32 +02:00
Francesco
82727700c9 made Anilist push updaes correctly 2025-04-25 20:39:30 +02:00
Francesco
7419a40b4e fixed plist 2025-04-25 20:30:59 +02:00
Francesco
3635fe377f fixed player so @Seeike doesnt complain 2025-04-25 20:11:02 +02:00
Francesco
d52f811ac5 removed mf iCloud 2025-04-25 20:08:50 +02:00
Francesco
d51f8fd5ed Merge branch 'dev' 2025-04-25 19:01:59 +02:00
Francesco
63518cf90b test 5 2025-04-25 19:00:43 +02:00
cranci
2bdaa65a7d
te (#111)
* even better hold up

* test recode?
2025-04-25 18:48:13 +02:00
Francesco
9b82caa1d0 test recode? 2025-04-25 18:45:50 +02:00
cranci
9eed14606a
even better hold up (#110) 2025-04-25 18:31:49 +02:00
Francesco
2805e3b705 even better hold up 2025-04-25 18:30:54 +02:00
Francesco
a0405e2b61 Merge branch 'dev' 2025-04-25 18:27:28 +02:00
Francesco
85dea2177d test x3 2025-04-25 18:25:30 +02:00
cranci
54f2ec5e2a
yes (#108)
* few player bug fixes (#104)

* icloud safe checking

* more tests

* removed ffmpeg sorry

* test

* Revert "test"

This reverts commit cbf7412d47.

* custom player stuffs idk if it builds

* fire Seiike moment

* ok my fault this time

* Create banner1.png

* seiike ahh moment

* added light mode banner

* Update EpisodeCell.swift

* seiike ahh moment x2

* ops

* fixed intros skipper buttons

* fixed pan crashes

* added speed indicator for hold speed

* added safecheck

* oh yeah my bad

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
2025-04-25 18:10:12 +02:00
Francesco
0611d9b068 oh yeah my bad 2025-04-25 18:09:29 +02:00
Francesco
d847fc3a4f Merge branch 'main' into dev 2025-04-25 18:01:10 +02:00
Francesco
a822f05934 added safecheck 2025-04-25 18:00:55 +02:00
cranci
8c73798195
many improvements (#107)
* few player bug fixes (#104)

* icloud safe checking

* more tests

* removed ffmpeg sorry

* test

* Revert "test"

This reverts commit cbf7412d47.

* custom player stuffs idk if it builds

* fire Seiike moment

* ok my fault this time

* Create banner1.png

* seiike ahh moment

* added light mode banner

* Update EpisodeCell.swift

* seiike ahh moment x2

* ops

* fixed intros skipper buttons

* fixed pan crashes

* added speed indicator for hold speed

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
2025-04-25 17:38:29 +02:00
Francesco
af2dc41f76 added speed indicator for hold speed 2025-04-25 17:36:12 +02:00
Francesco
db4d74252d fixed pan crashes 2025-04-25 17:12:15 +02:00
Francesco
7dd3609078 fixed intros skipper buttons 2025-04-25 17:09:38 +02:00
Francesco
8cbcf2ac74 ops 2025-04-25 16:58:41 +02:00
Francesco
4c99eb315d seiike ahh moment x2 2025-04-25 16:56:54 +02:00
Francesco
753d50879e Update EpisodeCell.swift 2025-04-25 16:49:17 +02:00
Francesco
e2f4e8bcab added light mode banner 2025-04-25 16:38:27 +02:00
Francesco
69a8da58e7 seiike ahh moment 2025-04-25 16:33:46 +02:00
Francesco
ef21614137 Create banner1.png 2025-04-25 16:31:56 +02:00
Francesco
b83e7cdf30 ok my fault this time
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-25 09:35:22 +02:00
Francesco
521c247546 fire Seiike moment 2025-04-25 09:32:27 +02:00
Francesco
be82bf4fdd custom player stuffs idk if it builds 2025-04-25 09:31:34 +02:00
Francesco
591b60db0b Revert "test"
This reverts commit cbf7412d47.
2025-04-25 09:22:21 +02:00
Francesco
cbf7412d47 test 2025-04-25 09:20:10 +02:00
Francesco
6ae8232e09 removed ffmpeg sorry 2025-04-25 09:19:34 +02:00
Francesco
f6bc1326cf more tests
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-24 21:42:57 +02:00
Francesco
1511a0ed26 icloud safe checking 2025-04-24 21:32:30 +02:00
Seiike
219088f3d1
few player bug fixes (#104) 2025-04-24 15:11:50 +02:00
Francesco
cefd996115 Merge branch 'main' into dev
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-04-22 18:18:09 +02:00
Francesco
11e48ed280 yeah more safe handling idk 2025-04-22 18:17:27 +02:00
Francesco
6209591ea5 ghj 2025-04-22 17:57:07 +02:00
cranci
0b3b04ab45
testlfyfgdufhgoudyfgh (#103)
* Implementation of loading modal

* little things but this is good

* Update README.md

* dim mode

* hello 👋  (#95)

* bug fix dimming

* improved the fetchEpisodeMetadata logic

* Aniskip logic and basic buttons (#96)

* Aniskip logic and basic buttons

* good fuckin enough for now

* im callin good enough

* bug fix

* its something

* hallelujah

* Update SearchView.swift

* made subs go up the progress bar if it is showing

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
Co-authored-by: Francesco <100066266+cranci1@users.noreply.github.com>

* Updated workflow to change file extension from .zip to .ipa for easier access. (#98)

* Update README.md

* Auto: Update IPA [skip ci]

* Color picker in settings for intro and outro segments (#99)

* Color picker in settings for intro and outro segments

* Color picker in settings for intro and outro segments

* Auto: Update IPA [skip ci]

---------

Co-authored-by: cranci1 <cranci1@github.com>

* fixed crash???? please yes

* Auto: Update IPA [skip ci]

* even more handling crazy fixes

* Auto: Update IPA [skip ci]

* Auto: Update IPA [skip ci]

* test carsh fix

* Update build.yml

---------

Co-authored-by: Ibrahim Sulejmenov <batonchik2004@gmail.com>
Co-authored-by: ibro <54913038+xibrox@users.noreply.github.com>
Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
Co-authored-by: bshar <98615778+bshar1865@users.noreply.github.com>
Co-authored-by: cranci1 <cranci1@github.com>
2025-04-22 17:55:00 +02:00
Francesco
58c2d89fd6 Merge branch 'main' into dev 2025-04-22 17:53:27 +02:00
Francesco
b74136584a Update build.yml 2025-04-22 17:52:37 +02:00
Francesco
9c43dc30d2 test carsh fix 2025-04-22 17:50:22 +02:00
cranci1
47325e1e07 Auto: Update IPA [skip ci] 2025-04-22 15:36:32 +00:00
cranci
0cc8f9166d
sduj (#102)
* Implementation of loading modal

* little things but this is good

* Update README.md

* dim mode

* hello 👋  (#95)

* bug fix dimming

* improved the fetchEpisodeMetadata logic

* Aniskip logic and basic buttons (#96)

* Aniskip logic and basic buttons

* good fuckin enough for now

* im callin good enough

* bug fix

* its something

* hallelujah

* Update SearchView.swift

* made subs go up the progress bar if it is showing

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
Co-authored-by: Francesco <100066266+cranci1@users.noreply.github.com>

* Updated workflow to change file extension from .zip to .ipa for easier access. (#98)

* Update README.md

* Auto: Update IPA [skip ci]

* Color picker in settings for intro and outro segments (#99)

* Color picker in settings for intro and outro segments

* Color picker in settings for intro and outro segments

* Auto: Update IPA [skip ci]

---------

Co-authored-by: cranci1 <cranci1@github.com>

* fixed crash???? please yes

* Auto: Update IPA [skip ci]

* even more handling crazy fixes

* Auto: Update IPA [skip ci]

---------

Co-authored-by: Ibrahim Sulejmenov <batonchik2004@gmail.com>
Co-authored-by: ibro <54913038+xibrox@users.noreply.github.com>
Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
Co-authored-by: bshar <98615778+bshar1865@users.noreply.github.com>
Co-authored-by: cranci1 <cranci1@github.com>
2025-04-22 17:35:22 +02:00
Francesco
1f692b460b Merge branch 'main' into dev 2025-04-22 17:35:13 +02:00
cranci1
c2d6116fda Auto: Update IPA [skip ci] 2025-04-22 15:33:52 +00:00
Francesco
5ea21da2cc even more handling crazy fixes 2025-04-22 17:30:55 +02:00
cranci1
304ddbf2fd Auto: Update IPA [skip ci] 2025-04-22 15:28:13 +00:00
Francesco
d9752a8002 fixed crash???? please yes 2025-04-22 17:26:04 +02:00
cranci
00586fcf3e
TestFlight build (#101)
* Implementation of loading modal

* little things but this is good

* Update README.md

* dim mode

* hello 👋  (#95)

* bug fix dimming

* improved the fetchEpisodeMetadata logic

* Aniskip logic and basic buttons (#96)

* Aniskip logic and basic buttons

* good fuckin enough for now

* im callin good enough

* bug fix

* its something

* hallelujah

* Update SearchView.swift

* made subs go up the progress bar if it is showing

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
Co-authored-by: Francesco <100066266+cranci1@users.noreply.github.com>

* Updated workflow to change file extension from .zip to .ipa for easier access. (#98)

* Update README.md

* Auto: Update IPA [skip ci]

* Color picker in settings for intro and outro segments (#99)

* Color picker in settings for intro and outro segments

* Color picker in settings for intro and outro segments

* Auto: Update IPA [skip ci]

---------

Co-authored-by: cranci1 <cranci1@github.com>

---------

Co-authored-by: Ibrahim Sulejmenov <batonchik2004@gmail.com>
Co-authored-by: ibro <54913038+xibrox@users.noreply.github.com>
Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
Co-authored-by: bshar <98615778+bshar1865@users.noreply.github.com>
Co-authored-by: cranci1 <cranci1@github.com>
2025-04-22 16:54:11 +02:00
ibro
ce7913c578
Color picker in settings for intro and outro segments (#99)
* Color picker in settings for intro and outro segments

* Color picker in settings for intro and outro segments

* Auto: Update IPA [skip ci]

---------

Co-authored-by: cranci1 <cranci1@github.com>
2025-04-22 16:45:41 +02:00
cranci1
062e073eb0 Auto: Update IPA [skip ci] 2025-04-22 14:10:08 +00:00
cranci
2e6afa53f8
Update README.md 2025-04-22 16:08:03 +02:00
bshar
882afcdc1e
Updated workflow to change file extension from .zip to .ipa for easier access. (#98) 2025-04-22 16:03:15 +02:00
ibro
745e295e0d
Aniskip logic and basic buttons (#96)
* Aniskip logic and basic buttons

* good fuckin enough for now

* im callin good enough

* bug fix

* its something

* hallelujah

* Update SearchView.swift

* made subs go up the progress bar if it is showing

---------

Co-authored-by: Seiike <122684677+Seeike@users.noreply.github.com>
Co-authored-by: Francesco <100066266+cranci1@users.noreply.github.com>
2025-04-22 15:02:06 +02:00
Seiike
0ad4659d2c
hello 👋 (#95)
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
* bug fix dimming

* improved the fetchEpisodeMetadata logic
2025-04-20 19:50:15 +02:00
cranci
83cf7b0e9f
Implementation of loading modal and dim mode (#93)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-20 08:53:08 +02:00
ibro
68e8196c70
Merge branch 'dev' into dev 2025-04-19 22:33:46 +02:00
Ibrahim Sulejmenov
163358b3fa dim mode 2025-04-19 22:33:17 +02:00
Francesco
d28a55a48f Update README.md
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-19 22:00:21 +02:00
Francesco
3a76110886 little things but this is good
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-19 10:31:17 +02:00
cranci
6cf88cb50d
ok last thing please (#92) 2025-04-19 10:12:57 +02:00
Francesco
43892a3d43 ok last thing please 2025-04-19 10:10:38 +02:00
cranci
501657c42f
why am i this dumb 😭 (#91) 2025-04-19 09:57:34 +02:00
Francesco
d8e35b7525 why am i this dumb 😭 2025-04-19 09:55:44 +02:00
cranci
079fd395d7
yeah tf is this idk (#89) 2025-04-19 09:42:44 +02:00
Francesco
d86ee0cd00 yeah tf is this idk 2025-04-19 09:42:02 +02:00
cranci
7922f8940e
fixed module icloud sync maybe (#88) 2025-04-19 09:23:19 +02:00
Francesco
7ed5048b0b fixed module icloud sync maybe 2025-04-19 09:20:17 +02:00
Ibrahim Sulejmenov
5ba84b26ab Implementation of loading modal 2025-04-18 22:52:55 +02:00
cranci
a1147fd832
added icloud documents support (#86) 2025-04-18 16:34:31 +02:00
Francesco
e9c9340986 added icloud documents support
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-18 16:32:47 +02:00
cranci
9c5eacf1ec
iCloud support ong (#85) 2025-04-18 14:33:26 +02:00
Francesco
b2eee5e09c fixed double subs? #58 2025-04-18 14:29:49 +02:00
Francesco
edec2d6693 technically everything is saved idk though 2025-04-18 14:23:16 +02:00
Francesco
0415df2cd9 module sync right? 2025-04-18 14:18:22 +02:00
Francesco
842fd5b01c test
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-17 17:31:24 +02:00
Francesco
c4d16f4ed9 fixed trakers page 2025-04-17 17:15:11 +02:00
cranci
e076be593d
New testflight build (#84) 2025-04-17 17:05:56 +02:00
cranci
e6c854eb8b
Update SettingsViewTrackers.swift 2025-04-17 17:01:08 +02:00
Seiike
f512faba06
i despise unclean merges 🙏 (also heres ur fidget spinner) (#83) 2025-04-17 16:59:26 +02:00
D Osman
ad07457cf7
add debounce to SearchBar (#81) 2025-04-17 16:58:57 +02:00
D Osman
63c7b8229b
Merge branch 'dev' into dev 2025-04-17 13:43:30 +01:00
DawudOsman
78865f9330 add debounce to searchBar 2025-04-17 13:36:48 +01:00
Francesco
c582d45fb5 iCloud idk? Docs says yeah but idk 2025-04-17 11:40:54 +02:00
Francesco
dd95e66e16 lmao my bad 2025-04-17 11:27:05 +02:00
Francesco
82ff26f073 good right? 2025-04-17 11:17:43 +02:00
DawudOsman
6640a8a4b7 add debounce to SearchBar 2025-04-16 11:30:47 +01:00
Ylruhc
0f85714268
Add redirect option to Fetch function. (#79)
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
* Add Headers,Status to Response Object

* add redirect option to Fetch

---------

Co-authored-by: DawudOsman <d.osman@outlook.com>
2025-04-15 17:50:00 +02:00
Seiike
ae2bf1fca1
its more respsponsive now (#80) 2025-04-15 17:07:12 +02:00
Francesco
88b63f96cf crazy
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-04-13 16:31:12 +02:00
Francesco
b9cac9c91b added Trakt loggin 2025-04-13 16:30:21 +02:00
Francesco
d5abd35beb yeah weird ass things 2025-04-13 12:11:54 +02:00
Francesco
9d45c12a6e remove TMDB that somewhy doesnt work 2025-04-13 11:47:40 +02:00
Francesco
427102ecd8 Update UIDevice+Model.swift 2025-04-13 09:27:47 +02:00
Francesco
540e29e7fa works 2025-04-13 09:25:16 +02:00
Seiike
6b819326b1
icons done (#78)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-13 08:17:04 +02:00
Francesco
f4b16dfbca ersgedg
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-12 21:49:12 +02:00
Francesco
839970fe2b Revert "test"
This reverts commit 2a2fafd03f.
2025-04-12 16:37:01 +02:00
Francesco
2a2fafd03f test
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-12 15:42:55 +02:00
Francesco
0dfaa4614f test 2025-04-12 15:17:56 +02:00
Francesco
0c35749855 cleared old stuffs 2025-04-12 15:11:39 +02:00
Seiike
f99dc3e6f1
volume slider 🤤 (#77) 2025-04-12 12:49:25 +02:00
Ylruhc
df3a9e8f74
Add Headers,Status to Response Object (#76)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-12 06:48:07 +02:00
Seiike
6d05282916
player got a facelift (#75)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-11 06:26:17 +02:00
Seiike
f58b8b8733
incinerated the buffer indicator, brightness bar restored old code (#74)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-10 18:10:37 +02:00
Francesco
c256388497 added TMDB support 2025-04-10 17:50:39 +02:00
Francesco
5dfacc8db4 no print only logger 2025-04-10 17:31:38 +02:00
Francesco
7bace8936d fixed title name fetching for anilist 2025-04-10 17:29:28 +02:00
Francesco
75c9fb712b fixed typo 2025-04-10 17:27:33 +02:00
cranci
41dbd6892c
Testflight (#73) 2025-04-09 20:28:25 +02:00
Francesco
8e5758d900 test
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-09 15:04:51 +02:00
Francesco
026ff73be4 fixed continue watching 2025-04-09 15:03:21 +02:00
Francesco
316eba80fe fuck seiike
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-09 14:51:59 +02:00
cranci
6245c812af
Update SettingsViewTrackers.swift 2025-04-09 07:00:31 +02:00
cranci
6bd3c41a86
opsi x2 (#71) 2025-04-08 20:50:36 +02:00
cranci
5d93a0b07c
opsi (#70)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-08 20:50:06 +02:00
Francesco
e322076cb5 ong my bad 2025-04-08 20:48:59 +02:00
cranci
97b366d4c3
v0.2.1 - beta 2 (#69) 2025-04-08 20:43:47 +02:00
Seiike
67433e1143
catch mario 🏈 (#68)
* fixed brightness bar not hiding

* u can now toggle skip 85s button

* more appealing title text
2025-04-08 20:41:33 +02:00
Francesco
5fef2f15b8 better 💴 💴💴💴 2025-04-08 20:33:16 +02:00
Francesco
89c8311809 HELL YEAHHH 2025-04-08 20:17:22 +02:00
Francesco
aadea3f178 test 2025-04-08 16:35:11 +02:00
Francesco
c002671281 fixed media player 2025-04-08 14:25:40 +02:00
Francesco
cb6ce4c720 yeah imma sleep
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-07 21:17:44 +02:00
Francesco
14affcdd01 test 2025-04-07 21:11:08 +02:00
Francesco
f80f18cd1c fiexd seiike mess 2025-04-07 20:23:58 +02:00
Seiike
ec3507a16b
its now in safe view (#67)
* bug fixes + optimization buffer indicator

* snappier pause unpause buttons (animations are the same)

* buffer indicator and bar fixes

* player optmizations

* beautiful title text in the player

* brightness slider in safe view
2025-04-07 16:12:20 +02:00
Seiike
1f3ea9c267
commits say it all (#66)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-07 06:35:37 +02:00
Francesco
7fb6d2d92e episode
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-04-05 16:33:24 +02:00
Francesco
4d9f9ac28f fixed episode cell crash maybe? 2025-04-05 16:29:08 +02:00
Francesco
c95a0f39e8 fixed previous episode marking crash 2025-04-05 16:26:39 +02:00
Francesco
9ae8f5eb54 module refresh crash fixed 2025-04-05 16:24:12 +02:00
Francesco
315d507485 Update Info.plist
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-04 18:04:02 +02:00
Francesco
d6aaf562c5 fixde ipad support 2025-04-04 17:53:34 +02:00
Francesco
71acdefd0b MULTI SERVER SUPPORT!!!!!!!!!!!!!!!!!!!!! 2025-04-04 17:42:47 +02:00
Francesco
d878b13528 mb mb mb 2025-04-04 17:27:01 +02:00
Francesco
c427c59797 this is good? 2025-04-04 17:26:23 +02:00
cranci
648c4ee4ff
Commits say it all (#65)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-04 06:36:08 +02:00
Seiike
6c43d792b2 fixed buffer bar not updating
fixed = sometimes when stream is opened from continue watching section the bar doesnt update
2025-04-03 23:52:40 +02:00
Seiike
2ead0dc6d4 rem dev notes 2025-04-03 23:44:54 +02:00
Seiike
0f3ed42340 added brightness bar and check desc.
https://discord.com/channels/1293430817841741899/1357429131054289118/1357442581784559847
2025-04-03 23:42:44 +02:00
Seiike
441cb22abc mb done 2025-04-03 23:19:57 +02:00
Seiike
85355336c9 a beautiful brightness slider 2025-04-03 23:06:01 +02:00
Seiike
78a5660bed not great 2025-04-03 22:34:47 +02:00
cranci
2711cafc93
ta da 💥 (#64)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-04-03 17:19:49 +02:00
Seiike
ce22ffa50e donezo 2025-04-03 17:15:17 +02:00
Seiike
fc261e06ab buttons are smaller for aesthetic 2025-04-03 16:58:28 +02:00
Seiike
b4ea1bc29b perfect 2025-04-03 15:36:33 +02:00
Seiike
6de740ee6c js fix the crashing 2025-04-03 14:51:22 +02:00
Seiike
6869f97689 serie name and episode number in the player 2025-04-02 23:47:24 +02:00
Seiike
22e01248ae its finally symetrical 🙏 2025-04-02 22:46:50 +02:00
Seiike
47f45f63bb long press seek buttons now work again 2025-04-02 22:43:14 +02:00
Seiike
e0c2092563 everyting same as org repo 2025-04-01 21:30:01 +02:00
Seiike
84030eaeda
Merge branch 'cranci1:dev' into dev 2025-03-31 21:23:59 +02:00
Francesco
6243d456ca made it use an Array if needed
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-03-30 17:25:39 +02:00
Francesco
ee81815afa added Origin 2025-03-30 17:02:10 +02:00
Seiike
1652a0d0a0 js use 1.1.1.1 warp 🙏 2025-03-29 08:02:23 +01:00
Seiike
37808f6af9 save before editing xcodeproj 2025-03-28 23:02:20 +01:00
cranci
fb42ad7f60
Update Info.plist (#63) 2025-03-28 17:37:19 +01:00
Francesco
d72421806f Update Info.plist
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-03-28 17:36:23 +01:00
cranci
2a08ae5f16
v0.2.1 (#62) 2025-03-28 16:58:51 +01:00
Francesco
fe19ffda1f Update ContinueWatchingManager.swift
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-28 14:36:47 +01:00
Francesco
2eb07aebf1 yeah fuck @Seeike not the ISPs 😭 2025-03-28 14:23:55 +01:00
Francesco
6989fbfe87 fixed maybe? at least on my machine
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-27 16:09:19 +01:00
Francesco
07b4a0280e does ts even work? 😭 2025-03-27 16:03:47 +01:00
Francesco
9bd4d6e74d fixed randomness again 2025-03-27 15:59:34 +01:00
Francesco
4764e16ccd fuck @Seeike 😭 2025-03-27 15:55:25 +01:00
Francesco
d29d340d84 d 2025-03-27 15:50:16 +01:00
cranci
8f3c855fe2
fuck the isp's 🗣️ 🔥 (#60)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-27 06:18:56 +01:00
Seiike
b1ce1e86dd as cranci wanted 2025-03-26 22:04:40 +01:00
Seiike
ad96b5fdaa changed it to series
https://discord.com/channels/1293430817841741899/1318240587886891029/1354558523824144504
2025-03-26 21:54:16 +01:00
Seiike
fe874f3692 ta fucking da 2025-03-26 21:29:45 +01:00
Seiike
4da986eaa3 fuck xcodeproj 2025-03-26 21:10:19 +01:00
Seiike
284bc11ad7 fuck region restricions 📢 2025-03-26 21:01:35 +01:00
Seiike
fff3449174 prob very works 2025-03-26 20:52:12 +01:00
Seiike
11bcb663d2 dns settings now actually work 2025-03-26 20:23:12 +01:00
Francesco
ec6c8c94eb fixed stream crash
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-26 15:47:35 +01:00
Francesco
4ace715a8f Update SettingsView.swift 2025-03-26 15:40:22 +01:00
Francesco
37425a2e55 idk maybe i've added DNS services?
Yeah it should
2025-03-26 15:39:49 +01:00
Francesco
0b9dae6b98 Revert "yeah idk tf is this"
This reverts commit 5826f89866.
2025-03-26 15:06:10 +01:00
Francesco
e06751d458 Revert "less stuffs"
This reverts commit 43c0509b93.
2025-03-26 15:06:07 +01:00
Francesco
43c0509b93 less stuffs
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-25 16:30:41 +01:00
Francesco
5826f89866 yeah idk tf is this 2025-03-25 16:04:26 +01:00
cranci
c12aa5b8b2
Added POST, PUT, PATCH Support (#59)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-25 06:29:57 +01:00
Hamzenis Kryeziu
a5c71c6743 Added POST, PUT, PATCH Support
- "fetchv2" javascript function now supports proper methods, header and body support
- Backwards compatibility with previous implementation
- Defaults to GET if only url is provided
2025-03-25 01:10:59 +01:00
cranci1
f030f65ce0 idk if this uses 1.1.1.1 DNS 😭
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-24 16:31:01 +01:00
cranci1
3661e601e5 Update SettingsViewTrackers.swift
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-23 18:01:01 +01:00
cranci1
59b5f84077 trackers page
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-23 10:58:32 +01:00
cranci1
748cc4e999 added auth exchange 2025-03-23 10:25:53 +01:00
cranci
63d093b47c
as the commits say (#57)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
![image](https://github.com/user-attachments/assets/d48db758-df04-454d-a1b7-185070433d14)
2025-03-22 15:21:20 +01:00
Seiike
6d08ce08ca info about double tapping the screen in settings 2025-03-22 13:32:53 +01:00
Seiike
607e8a9677 problem solved 🎊 2025-03-22 13:24:12 +01:00
Seiike
0a526b2fe9 adjusted the positions of the buttons for symmetry 2025-03-22 13:12:05 +01:00
Seiike
a5f9877474 progression timer wont show hours unless media is longer than an hour 2025-03-22 12:29:58 +01:00
cranci1
4f6b7fbb13 idk tf is this MainActor
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-21 16:56:12 +01:00
cranci1
0b3df78cdd remade the mf source selector menu 2025-03-21 16:52:46 +01:00
cranci1
d2099d65af fixed crash 2025-03-21 16:44:40 +01:00
cranci1
63e9658857 Update Info.plist 2025-03-21 16:33:28 +01:00
cranci1
faa7dfbb72 Update project.pbxproj 2025-03-21 16:33:08 +01:00
cranci1
d099267f6a just made the sora player support hours 2025-03-21 16:31:53 +01:00
cranci
baece1defd
Added toggle for subtitles + fixed mark all previous as watched in series organized in seasons (#56)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-20 20:29:14 +01:00
Ibrahim Sulejmenov
d5d13fd407 Added toggle for subtitles + fixed mark all previous as watched in series organized in seasons 2025-03-20 20:13:54 +01:00
cranci
341c512ef9
New JavaScriptCore Extension Class & more (#54)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
- Dedicated JavaScripteCore extension class
- Seperated JS logic from JSController class
- New available functions for js code: "atob()", "btoa()",
"console.error()"
- New fetch method "fetchv2()" that can handle ".json()" &".text()" and
possible to be expanded for post method support and more (new function
for backwards compatibility)
- New Logger function to print in Debug mode to xcode console
2025-03-20 15:13:56 +01:00
cranci
9c7f13e12e
Added double tap feature to skip + swipe down to dismiss player (#55) 2025-03-20 15:06:07 +01:00
Ibrahim Sulejmenov
d86cfe6d8a Added double tap feature to skip + swipe down to dismiss player 2025-03-20 08:13:56 +01:00
cranci
82f7870b07
Added invisible control overlays + fixed overlapping images in portrait mode in library view (#53)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-20 06:26:32 +01:00
Hamzenis Kryeziu
1af56c719d New JavaScriptCore Extension Class & more
- Dedicated JavaScripteCore extension class
- Seperated JS logic from JSController class
- New available functions for js code:
"atob()", "btoa()", "console.error()"
- New fetch method "fetchv2()" that can handle ".json()" &".text()" and possible to be expanded for post method support and more
- New Logger function to print in Debug mode to xcode console
2025-03-20 01:35:20 +01:00
Ibrahim Sulejmenov
09c43fa896 Fixed overlapping images in library view 2025-03-20 01:09:26 +01:00
Ibrahim Sulejmenov
e175babc5e Adding InvisibleControlOverlays 2025-03-20 01:03:39 +01:00
cranci
def3302bff
Main (#52)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-19 18:00:19 +01:00
cranci
8005e98af7
Update README.md (#51) 2025-03-19 17:42:24 +01:00
cranci
0a55202d4e
Update README.md 2025-03-19 17:39:48 +01:00
cranci
77797d9d42
huge things (#50) 2025-03-19 17:16:03 +01:00
cranci
913940e2eb
bettar button 🍕 (#48)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-19 06:38:16 +01:00
cranci
b0c02df8f5
Implemented season seperator for tv shows 2 (#47) 2025-03-19 06:38:04 +01:00
Seiike
d4c50910b3 im only human after alll 2025-03-18 23:08:29 +01:00
Seiike
b510f8a19b settings toggle now actually does some 🙏 2025-03-18 22:52:28 +01:00
Seiike
b6a9185fa0 THE goat of buttons 2025-03-18 22:46:48 +01:00
Ibrahim Sulejmenov
418435957e Implemented season seperator for tv shows 2 2025-03-18 21:36:26 +01:00
cranci
abcb4c6a22
Implemented season seperator for tv shows (#46)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-18 17:29:48 +01:00
Ibrahim Sulejmenov
c54d4d799a Implemented season seperator for tv shows 2025-03-18 17:22:58 +01:00
cranci
9621fba368
Update SettingsViewGeneral.swift 2025-03-18 16:24:18 +01:00
cranci1
ff57939a39 quality picker and more improvements 😭 2025-03-18 16:15:48 +01:00
cranci1
9b8a389def added srt support maybe 2025-03-18 15:47:57 +01:00
cranci1
53e1b08956 forgot about continue watching
should work?
2025-03-18 15:29:55 +01:00
cranci1
bb65945ac1 update progresses instaly 2025-03-18 15:28:01 +01:00
cranci1
37f5be5960 fixed cusotm player more 2025-03-18 15:24:04 +01:00
cranci
3082b9d626
fire ass button animations (#44) 2025-03-18 13:59:27 +01:00
Seiike
706daafd5a fire ass button animations
this bs took way too much time logic later 🙏
2025-03-18 01:30:45 +01:00
cranci
23ff8a9ba5
bookmark logic (#43)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-17 20:58:04 +01:00
Seiike
91087854bb here you go mario 2025-03-17 20:55:34 +01:00
Seiike
de08c14918 skip increments logic 2025-03-16 22:20:09 +01:00
Seiike
517ccb8983 bookmark logic
fixed "Mark all previous" counting in the logger and removing a series from bookmarks when you open them from the home tab it no longer kicks you out
2025-03-16 17:55:38 +01:00
cranci
243f85702d
Added setting to change anime per row in landscape and portrait modes + fixed stretched images (#42)
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-03-16 06:33:11 +01:00
Ibrahim Sulejmenov
11d5f65b60 added setting to change anime per row in landscape and portrait modes + fixed stretched images 4 2025-03-15 23:07:43 +01:00
Ibrahim Sulejmenov
a1bbc51c11 added setting to change anime per row in landscape and portrait modes + fixed stretched images 3 2025-03-15 22:35:26 +01:00
Ibrahim Sulejmenov
a089097e90 Merge branch 'dev' of https://github.com/xibrox/Sora into dev 2025-03-15 22:05:16 +01:00
Ibrahim Sulejmenov
6d6bc33dfe added setting to change anime per row in landscape and portrait modes + fixed stretched images 2 2025-03-15 22:04:49 +01:00
ibro
3623be1166
Merge branch 'cranci1:dev' into dev 2025-03-15 22:02:56 +01:00
Ibrahim Sulejmenov
bbebf14837 added setting to change anime per row in landscape and portrait modes + fixed stretched images 2025-03-15 22:02:28 +01:00
cranci
4b05ac4258
fixed skip 85 seconds button for ipads (#40)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
did what title says
2025-03-15 16:23:32 +01:00
Ibrahim Sulejmenov
4d3bf1f645 Merge branch 'dev' of https://github.com/xibrox/Sora into dev 2025-03-15 14:40:00 +01:00
Ibrahim Sulejmenov
d09989cec2 added skip 85 seconds button: fix for ipads 2025-03-15 14:37:23 +01:00
cranci
19f390961a
added skip 85 seconds button (#39) 2025-03-15 13:44:45 +01:00
Ibrahim Sulejmenov
dc87e6a9d1 added skip 85 seconds button 2025-03-15 08:16:02 +01:00
cranci
81d7f29deb
maybe idk (#38) 2025-03-14 18:00:38 +01:00
cranci1
c513b02483 maybe idk
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-14 17:36:22 +01:00
cranci
ef50c317b8
test (#37) 2025-03-14 17:14:35 +01:00
cranci1
1f6eda1d55 test 2025-03-14 17:14:04 +01:00
cranci
4db8539739
TestFlight update (#36) 2025-03-14 17:05:37 +01:00
cranci1
04cf2c00fa testfkught build 2025-03-14 17:02:10 +01:00
cranci
801390ca70
Mark all previous watched option added (#34)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-14 06:40:28 +01:00
Seiike
d491f4f585 Mark all previous watched option added
didnt get to doing the ui cus...
cus
2025-03-14 00:40:35 +01:00
cranci1
416446b397 works?
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-13 16:20:17 +01:00
cranci1
abfb633db5 works 2025-03-13 16:08:23 +01:00
cranci1
6cb59b9c34 ok so maybe ffmpeg works better now? 2025-03-13 15:54:38 +01:00
cranci1
5415a1fc99 added headers 2025-03-13 15:45:47 +01:00
cranci
f4b313f3e0
Update MediaInfoView.swift
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-12 18:18:40 +01:00
cranci1
000ca0ff04 options 2025-03-12 18:10:52 +01:00
cranci1
508319452e Update MediaInfoView.swift 2025-03-12 18:05:17 +01:00
cranci1
ee3f21cc57 well lets see
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-12 15:13:49 +01:00
cranci1
c03ac54a93 forgot this shared task 2025-03-12 14:29:53 +01:00
cranci1
ae80a9aa06 removed the ai stuff he added
(was better before 😭 )
2025-03-12 14:23:43 +01:00
cranci1
ca35fc7b33 made multithread optional 2025-03-12 14:22:22 +01:00
cranci1
8cd40687ff improved library layout and design
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-03-10 19:51:57 +01:00
cranci1
570aa596c5 ong 2025-03-10 17:25:53 +01:00
cranci1
1b4da98bd3 Revert "test cuz my mac doesnt build"
This reverts commit 4f30dfb588.
2025-03-10 17:13:37 +01:00
cranci1
4f30dfb588 test cuz my mac doesnt build 2025-03-10 16:58:07 +01:00
cranci1
be3a968f3e made Sora player the default 2025-03-10 16:45:59 +01:00
cranci1
79bd797240 should work with subtitles now 2025-03-10 16:40:30 +01:00
cranci
4c6cabca59
Update SettingsViewGeneral.swift
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-10 13:23:24 +01:00
cranci1
7e611ce77f yes -DownloadManager
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-09 16:57:05 +01:00
cranci1
020c753eb8 added ffmpeg-lame
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-09 13:58:00 +01:00
cranci1
5664c0f242 documents browse added 2025-03-09 13:49:10 +01:00
cranci1
b8d1567efb too many things idk 2025-03-09 11:32:06 +01:00
cranci1
c7c9e5407c ok added settings 2025-03-09 10:58:58 +01:00
cranci1
2f91209647 HOLY MOLY MAN 2025-03-09 10:45:31 +01:00
cranci1
e66de7f5d4 yes
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-08 18:37:26 +01:00
cranci
86e18c14a6
ReadMe - test (#31) 2025-03-05 15:53:08 +01:00
cranci1
6ea97f70f2 test
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-03-05 15:52:04 +01:00
cranci
89489a0aa0
Dev -> Main (#30) 2025-03-05 15:30:26 +01:00
cranci1
2153f8b675 thank god plesae work 2025-03-05 14:34:30 +01:00
cranci
7974417952
Update TMDB-Seasonal.swift 2025-03-05 13:17:21 +01:00
cranci
116e0295a7
Update TMDB-Trending.swift 2025-03-05 13:16:45 +01:00
cranci
e1cad2548e
Update TMDB-Seasonal.swift 2025-03-05 13:15:54 +01:00
cranci1
9af4984cc6 added TMDB 2025-03-05 13:07:44 +01:00
cranci
087d657ad9
Small hotfixes (#29) 2025-03-04 17:45:20 +01:00
cranci1
eb65912998 yeah ops
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-04 17:42:58 +01:00
cranci1
e13333afe7 test 2025-03-04 17:34:08 +01:00
cranci1
81db923989 DAMN x10 size 😭
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-04 11:35:49 +01:00
cranci
c141300d2f
Update build.yml 2025-03-04 11:28:53 +01:00
cranci
912e8ed4ec
Update build.yml 2025-03-04 11:28:15 +01:00
cranci1
9ef235169c too much things idk
Fixed many issues on the custom player
2025-03-04 10:38:27 +01:00
cranci
c243dea3a2
crash fixes (#28) 2025-03-04 09:40:42 +01:00
cranci1
b7260b41b1 crash fixes
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-03-04 09:34:04 +01:00
cranci1
da6106ab3b opds
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-03 11:38:39 +01:00
cranci1
8f68b5ec3a well added this yeah 2025-03-03 11:32:13 +01:00
cranci
ae27ecbe6a
Update ipabuild.sh 2025-03-03 11:30:44 +01:00
cranci
8705539a87
Update ipabuild.sh
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-03 11:23:16 +01:00
cranci1
1299d365b7 renamed it to sulfur for testflight 2025-03-03 11:04:03 +01:00
cranci1
36f19ba8df fixed logger 2025-03-03 09:20:52 +01:00
cranci1
6397f120fc fixed player even more 2025-03-03 09:10:01 +01:00
cranci1
c47d226153 player fixes 2025-03-03 08:52:54 +01:00
cranci1
5472c62739 my bad 2025-03-03 08:13:06 +01:00
cranci1
35f6822bb3 fixed build issue 2025-03-03 08:09:01 +01:00
cranci
a3f319daec
New Analytics feature (#27)
- Implemented Analysitcs Feature base on
[Sora-Analytics-Api](https://github.com/hamzenis/sora-analytics-api)
2025-03-03 08:06:44 +01:00
cranci1
883e5cc179 Merge branch 'main' into pr/27 2025-03-03 08:06:21 +01:00
cranci1
60829dc2f8 yeah
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-02 23:27:12 +01:00
Hamzenis Kryeziu
54d2bbdaa8 Placed class to the right place
modified:   Sora.xcodeproj/project.pbxproj
	renamed:    Sora/Utils/Analytics/UIDevice+Model.swift -> Sora/Utils/Extensions/UIDevice+Model.swift
2025-03-02 19:57:33 +01:00
Hamzenis Kryeziu
ab016deb5b
Update Sora/Utils/Analytics/Analytics.swift
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
2025-03-02 17:43:31 +01:00
Hamzenis Kryeziu
e7fa2d3eba Little merge conflict 2025-03-02 17:36:01 +01:00
Hamzenis Kryeziu
80e82e60ae New Analytics feature 2025-03-02 17:31:58 +01:00
cranci1
37a596fc8e when tf i added this things?
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-03-02 17:07:03 +01:00
cranci1
901586a99e yeah had to rewrite ts 2025-03-02 17:05:29 +01:00
cranci
f8408aa959
Update README.md
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-02-25 17:02:50 +01:00
cranci1
ab80261b1c had to rewrite ts 😭
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-02-23 11:01:20 +01:00
cranci1
2f4e51ffc2 fixed main thread issue 2025-02-23 09:22:32 +01:00
cranci1
7c3b8e74d9 fixed typo
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-02-20 16:29:04 +01:00
cranci1
eec9b380dd fixed toggles + no episode data fetch 2025-02-20 16:27:53 +01:00
cranci1
63cd8e0b5a fixed the watch next episode 2025-02-20 16:19:38 +01:00
cranci1
982d524c72 fixed fill/fit 2025-02-20 16:10:28 +01:00
cranci1
9bc58fd763 fixed isFetchingEpisode 2025-02-20 16:09:49 +01:00
cranci1
7c5e116b4c idk but this should work
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-02-16 16:23:05 +01:00
cranci1
083946699d added some more graphical things
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-16 09:47:33 +01:00
cranci1
ce83066ced ok fixed contrinu watching order 2025-02-16 09:33:33 +01:00
cranci1
bdd82f3f57 holy moly hopefully this works 2025-02-16 09:28:02 +01:00
cranci1
972af358ca sora player made working
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-15 23:53:09 +01:00
cranci1
4bb2d77c74 ok fixed the subtitles having HTML tags
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-15 17:28:21 +01:00
cranci1
152b4d585f ok this works my bad 2025-02-15 17:25:45 +01:00
cranci1
94e2447f64 ok maybe this works? 2025-02-15 17:24:35 +01:00
cranci1
b9cf065cf8 ops my bad 2025-02-15 17:23:21 +01:00
cranci1
be1a156c37 yeah no, idk tf im doing 2025-02-15 17:17:00 +01:00
cranci1
1f48612a67 subs loader added now module support 😭 2025-02-15 17:08:01 +01:00
cranci1
9aa56fa8a3 hella nice things 2025-02-15 16:33:53 +01:00
cranci1
07ad7b80e5 continue watching items added working fine
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-14 16:06:55 +01:00
cranci1
01f90882f9 test logger that should work now 2025-02-14 14:56:38 +01:00
cranci1
30a194f5b8 why cant i compile this thing 😭 2025-02-14 14:52:17 +01:00
cranci1
0ef9fd47fb tf is this error 😭 2025-02-14 14:44:27 +01:00
cranci1
7dec41138f idk lets see 2025-02-14 14:34:51 +01:00
cranci1
7dd09dabc8 idk doesnt build for me 😭 2025-02-14 14:25:29 +01:00
cranci1
16976d8e00 test
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-02-12 18:37:48 +01:00
cranci1
1d938505cf test 2025-02-12 18:29:24 +01:00
cranci1
b088196133 fixed divider
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-12 16:24:08 +01:00
cranci1
cdf4f9a1a5 test 2025-02-12 15:05:24 +01:00
cranci1
ae75fd80ce not to bad you know
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-11 15:27:04 +01:00
cranci
842df29bfe
Update SearchView.swift
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-02-09 20:35:20 +01:00
cranci1
d85ca35a7d added the user request stuff idk 2025-02-09 16:58:47 +01:00
cranci1
85bca0447a alright this is better ong
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-09 16:33:31 +01:00
cranci1
08619176ba uh ong bro im cooked 2025-02-09 14:11:30 +01:00
cranci1
701a1d6371 idk 2025-02-09 10:08:51 +01:00
cranci1
051a710c70 damn this looks nice ong 2025-02-09 10:02:19 +01:00
cranci1
c7a35b11a5 fixed the playback speed remeber stuff 2025-02-09 09:06:55 +01:00
cranci1
76e1d8ddce lmao im dumb ong 2025-02-09 08:57:57 +01:00
cranci1
fcd99e4018 yeah ong idk 2025-02-09 08:51:37 +01:00
cranci1
7f000a0eca test playback speed saving fix 2025-02-09 08:40:23 +01:00
cranci1
1cbcc2a107 removed the mf localisation
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-09 08:37:53 +01:00
cranci1
2be67493f6 idk tf is this
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-08 16:43:09 +01:00
cranci1
83583fb27f added playback speed saving
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-08 16:04:41 +01:00
cranci1
2bd37b50a4 added headers support i think
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-02-05 16:06:28 +01:00
cranci1
c1e296ce80 added data management (kinda) 2025-02-05 15:48:49 +01:00
cranci1
f13a43f783 Update README.md 2025-02-05 15:15:15 +01:00
cranci1
325726e716 mac support idk 2025-02-05 15:02:51 +01:00
cranci1
bda1629210 Update VideoPlayer.swift
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-02-03 18:18:37 +01:00
cranci1
6fc516a0d4 ops 2025-02-03 17:26:23 +01:00
cranci1
a69f65242f continue watching button working i think 2025-02-03 17:17:25 +01:00
cranci1
a0588fedfa uh maybe??? 2025-02-03 16:35:21 +01:00
cranci1
63d72399e0 Update VideoPlayer.swift
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-02 17:41:36 +01:00
cranci1
2d8fa4a706 who knows? 2025-02-02 17:07:13 +01:00
cranci1
56706ecc10 fxied 2025-02-02 13:38:39 +01:00
cranci1
7b2da2ea74 fixed watchnext button
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-02 13:14:40 +01:00
cranci1
2658f7e63f search view improved fr 2025-02-02 10:02:57 +01:00
cranci1
c06fe99fa0 ops 2025-02-02 09:48:42 +01:00
cranci1
78ccc08545 uh idk 2025-02-02 09:48:37 +01:00
cranci1
182244b708 little adjustement to the module add view 2025-02-02 09:30:24 +01:00
cranci1
ae692907bd sorry @Seeike
alright i'll give you co-author

Co-Authored-By: Seiike <122684677+Seeike@users.noreply.github.com>
2025-02-02 09:13:35 +01:00
cranci1
c6f91f16f8 added yes
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-02-01 17:41:21 +01:00
cranci1
8c8cff3f73 fixed info.plist
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-01-31 17:40:40 +01:00
cranci
230eaeede6
Update README.md 2025-01-31 15:45:15 +01:00
cranci1
d16c009360 added external player 2025-01-31 15:42:40 +01:00
cranci1
586e9731f8 added nil checker for hold speed 2025-01-31 15:29:52 +01:00
cranci1
f5de72c3f0 fixed force landscape 2025-01-31 15:28:06 +01:00
cranci1
fe1d26ab96 media player settings 2025-01-31 15:16:59 +01:00
cranci1
8e35ab5eab Update README.md 2025-01-31 15:09:04 +01:00
cranci1
af71cb6a2f added module refresh.
It does only check the version string
2025-01-31 14:37:09 +01:00
cranci1
71f5f9fe25 removed prints 2025-01-31 14:26:38 +01:00
cranci1
1a1ce973e8 fixed the double stream option 2025-01-31 14:23:06 +01:00
cranci1
6b876f8ee0 yeah nothung
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-30 16:18:57 +01:00
cranci1
6c764a38bb s
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-01-28 18:27:13 +01:00
cranci1
69cd035ba1 yesyesyes 2025-01-28 18:14:19 +01:00
cranci1
ac500c58ac fixed issue on episode stream on failure 2025-01-28 18:03:19 +01:00
cranci
b6eda454bf
Merge pull request #23 from stormfjeld/concise-readme
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-27 18:56:05 +01:00
Storm
b6790f9bbf Making the readme more concise 2025-01-27 18:51:34 +01:00
cranci1
6a09da92de maybe
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-27 18:15:06 +01:00
cranci1
e74b9d8c78 added duplicate module prevention 2025-01-27 15:47:09 +01:00
cranci1
24793e1310 stuffs 2025-01-27 15:45:38 +01:00
cranci1
bcb1b6851b Update SettingsViewUI.swift 2025-01-27 15:19:28 +01:00
cranci1
3345f8374e fixed? 2025-01-27 15:13:42 +01:00
cranci1
e2252e13d0 episodes range + critical bug
the critical bug was an issue of the episodes response being cached in the userdefaults causing more than 120mb to be saved for 1k episodes
2025-01-27 15:11:59 +01:00
cranci1
a75ca179ce added url scheme 2025-01-27 14:38:04 +01:00
cranci1
75fb10a673 im done
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-26 16:41:56 +01:00
cranci1
54a953a2ea added episodes refetching
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-26 09:19:16 +01:00
cranci1
19cb608934 removed not needed things 2025-01-26 08:56:56 +01:00
cranci1
b0142ba192 alright #22
Co-Authored-By: Seiike <122684677+Seeike@users.noreply.github.com>
2025-01-26 08:55:37 +01:00
cranci1
c0eafa1c74 little thing
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-25 15:50:08 +01:00
cranci1
0aa11c7529 Update MediaInfoView.swift 2025-01-25 15:40:47 +01:00
cranci1
745229cab6 fixed big issue crash 2025-01-25 15:39:20 +01:00
cranci1
3664cf9638 more informations 2025-01-25 15:18:41 +01:00
cranci1
e2dd13e5bf added drops 2025-01-25 15:11:18 +01:00
cranci1
d5aa75e2d2 tes
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
Co-Authored-By: cranci <100066266+cranci1@users.noreply.github.com>
2025-01-24 17:02:00 +01:00
cranci1
65587f8e1d btw the fetchJS made by @50n50
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
Co-Authored-By: 50/50 <80717571+50n50@users.noreply.github.com>
2025-01-24 15:19:23 +01:00
cranci1
c94a35c6ea Update README.md 2025-01-24 15:17:04 +01:00
cranci1
9827215298 Update MediaInfoView.swift 2025-01-24 15:11:51 +01:00
cranci1
2fbcbee45c im dumb asf 2025-01-24 15:11:38 +01:00
cranci1
58c3642dc1 fixed issues 2025-01-24 14:40:01 +01:00
cranci
ce29144951
Merge pull request #20 from Seeike/main
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-23 16:22:48 +01:00
Seiike
2655d57269 done captian! 2025-01-23 14:28:18 +01:00
Seiike
db2eae3819 more logging and stuff 2025-01-23 02:09:49 +01:00
Seiike
9a8ea5f94d Merge branch 'main' of https://github.com/Seeike/Sora 2025-01-23 00:47:31 +01:00
cranci1
9beede786a test
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-22 20:37:25 +01:00
cranci1
f14f39c60c stuff 2025-01-22 16:29:23 +01:00
Seiike
65ee766579 WAY better logging 2025-01-22 01:32:23 +01:00
Seiike
17df9c4c0b
Merge branch 'cranci1:main' into main 2025-01-20 16:52:21 +01:00
cranci1
eec8c3a115 data logger
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-01-20 15:25:25 +01:00
Seiike
292c5ca770 Added logging for js modules, and for modules(adding/removing) 2025-01-20 02:17:03 +01:00
Seiike
9eab39a3bc Merge branch 'main' of https://github.com/Seeike/Sora 2025-01-19 18:51:03 +01:00
Seiike
e5d35a3782 Update Localizable.xcstrings 2025-01-19 12:51:03 +01:00
cranci1
3cba82d95a needed improvements
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-01-18 21:21:06 +01:00
cranci1
a79a7738f4 added loger thatnks to @Seeike
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
Co-Authored-By: Seiike <122684677+Seeike@users.noreply.github.com>
2025-01-17 17:28:27 +01:00
cranci1
ad6f2e3e46 dang
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-16 16:28:01 +01:00
cranci1
a07eef9708 fixed bundle ID 2025-01-16 15:33:13 +01:00
cranci
e9256832c1
Merge pull request #14 from hamzenis/main
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-01-14 06:35:26 +01:00
Hamzenis Kryeziu
73a0d82325 Albanian localization 2025-01-14 02:28:50 +01:00
Hamzenis Kryeziu
3c37f57e5e German localization 2025-01-14 00:46:35 +01:00
cranci1
bb97f5f2c0 fixed typo
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-13 15:44:47 +01:00
cranci1
03855e4fae Update project.pbxproj 2025-01-13 15:06:31 +01:00
cranci
48a82704ae
Merge pull request #12 from Seeike/main
localisation
2025-01-13 15:02:15 +01:00
Seiike
82ff7423b3 localisabe 2025-01-13 15:01:13 +01:00
cranci1
3cb9492791 addde hold cell
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-12 13:57:38 +01:00
cranci1
69d85d4a69 fixed progresses 2025-01-12 11:59:16 +01:00
cranci1
74e03d9733 added library
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-12 09:48:26 +01:00
cranci1
13d666fcf7 little improvement 2025-01-12 09:28:41 +01:00
cranci1
e224360140 d
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-11 18:03:17 +01:00
cranci1
5e3324b681 Update build.yml 2025-01-11 18:01:32 +01:00
cranci1
8c1a39cc71 Merge branch 'Sora-JSCore' 2025-01-11 17:59:47 +01:00
cranci1
ffc3821010 yes 2025-01-11 17:55:40 +01:00
cranci1
06d8ce30b1 Delete build-JS.yml 2025-01-11 17:52:47 +01:00
cranci1
bb9bb5da88 Reapply "update thing"
This reverts commit 6bbe269469.
2025-01-11 17:51:52 +01:00
cranci1
6bbe269469 Revert "update thing"
This reverts commit 76d23e2da0.
2025-01-11 17:51:26 +01:00
cranci1
76d23e2da0 update thing 2025-01-11 17:51:06 +01:00
cranci1
166767a10c clean up 2025-01-11 17:49:18 +01:00
cranci
85d2499a06
Merge pull request #10 from hamzenis/Sora-JSCore
New fetching details method
2025-01-11 17:46:55 +01:00
Hamzenis Kryeziu
d3fc29d8ed New fetching details method
- Second method to fetch details with async js code
2025-01-11 03:44:58 +01:00
cranci
c643e02b8e
Merge pull request #9 from hamzenis/Sora-JSCore
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-01-10 19:01:54 +01:00
Hamzenis Kryeziu
9f1d8f8c4c New search results method
- Async JS with promise handling, custom javascript fetch
- New search Method
- New optional field for module
2025-01-10 18:58:16 +01:00
cranci1
020b792f48 no results found display
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-01-09 17:16:45 +01:00
cranci1
06cf7b9733 im cooked 2025-01-09 17:14:02 +01:00
cranci1
4ae5517cb8 little UI thing 2025-01-09 16:50:13 +01:00
cranci
86d1ed8981
Merge pull request #7 from ordzy/patch-1
Some checks failed
Build and Release IPA / Build IPA and Mac Catalyst (push) Has been cancelled
Update README.md
2025-01-09 06:37:32 +01:00
ordzy
f9632021a8
Update README.md
Fix grammatical errors
2025-01-08 21:55:24 -05:00
cranci1
ac66da899c thank god man thank god
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-08 17:07:31 +01:00
cranci1
3ebf986424 im so dumb i swear 2025-01-08 16:33:01 +01:00
cranci1
c28a47a2f4 added http support for "unsecure" sites 2025-01-08 15:57:57 +01:00
cranci1
0d9ff05bb6 does even work yay
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-07 16:53:36 +01:00
cranci1
fdbb3e1edc added mediainfoView UI
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled
2025-01-06 14:01:21 +01:00
cranci1
a84bdf6a58 uh? 2025-01-06 11:29:30 +01:00
cranci1
253383516d fixed workspace 2025-01-06 11:08:10 +01:00
cranci1
0a14a550b9 nothing 2025-01-06 11:02:18 +01:00
cranci
250d1cec9d
Update README.md
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-06 07:52:12 +01:00
cranci
9ad3632532
Update README.md 2025-01-06 07:51:13 +01:00
cranci1
2189faade6 Update build-JS.yml
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
2025-01-05 17:31:46 +01:00
cranci1
836bc19148 Update ipabuild.sh 2025-01-05 17:30:11 +01:00
cranci1
89038d3186 Update build-JS.yml 2025-01-05 17:29:06 +01:00
cranci1
90e571c613 Update README.md 2025-01-05 17:28:09 +01:00
cranci1
d7e0e61dad Create build-JS.yml
Some checks failed
Build and Release IPA / Build IPA and Mac Catalyst (push) Has been cancelled
2025-01-05 17:27:15 +01:00
cranci1
e7faec0b1d test 2025-01-05 17:26:22 +01:00
cranci1
ee75542b2d Media Info view 2025-01-05 17:21:43 +01:00
cranci1
d519fc6110 added UserAgent support 2025-01-05 16:37:52 +01:00
cranci1
dd3b97b0f2 bug fixes 2025-01-05 16:22:26 +01:00
cranci1
e8f1a7d896 Huge things frfrfrf 2025-01-05 14:26:46 +01:00
cranci1
4dbe5e8c2b bug fixes
Some checks are pending
Build and Release IPA / Build IPA and Mac Catalyst (push) Waiting to run
2025-01-05 10:02:44 +01:00
cranci1
4f9d80c4c2 first public commit of Sora-JS 🎱 2025-01-05 09:40:58 +01:00
cranci
031a36cd75
Merge pull request #4 from hamzenis/main
Some checks failed
Build and Release IPA / Build IPA and Mac Catalyst (push) Has been cancelled
Support for more extractor
2025-01-03 21:35:19 +01:00
cranci
c289601168
Update Sora/Views/MediaViews/MediaExtraction.swift
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
2025-01-03 21:34:59 +01:00
Hamzenis Kryeziu
f10442fec4 More websites support 2025-01-03 21:21:25 +01:00
Hamzenis Kryeziu
30cb6f5838 Added gitignore 2025-01-03 21:15:45 +01:00
cranci1
ee6d371bde module editor 2025-01-03 17:15:13 +01:00
cranci1
8081aec1bf update to non christmas logo
Some checks are pending
Build and Release IPA / Build IPA and Mac Catalyst (push) Waiting to run
2025-01-03 12:41:58 +01:00
cranci1
a63a02e4de fixed formatting
Some checks are pending
Build and Release IPA / Build IPA and Mac Catalyst (push) Waiting to run
2025-01-02 16:35:03 +01:00
cranci1
37429def51 testing some little things 2025-01-02 16:09:29 +01:00
cranci1
8f4f92318e test added open cast
added opencast + dependency:
SwiftyJSON, SwiftProtobuf
2025-01-02 15:26:35 +01:00
cranci1
d9a5fd68ef fxied version chwcekr
Some checks are pending
Build and Release IPA / Build IPA and Mac Catalyst (push) Waiting to run
2025-01-02 12:35:26 +01:00
cranci
08d67c00fa
Fixed the ios version banner
Some checks are pending
Build and Release IPA / Build IPA and Mac Catalyst (push) Waiting to run
2025-01-01 21:37:20 +01:00
cranci1
194be8ff2b add option for blank parameter 2025-01-01 20:12:26 +01:00
cranci1
9addf8325c oprtion for second pattern 2025-01-01 19:21:48 +01:00
cranci1
816001b84e Fixed small issue 2025-01-01 18:32:32 +01:00
cranci1
ecf81a8c0c Update UserInterfaceState.xcuserstate 2025-01-01 18:31:12 +01:00
cranci
0f8852dc98
Merge pull request #3 from cranci1/main-cf-autofix
Some checks are pending
Build and Release IPA / Build IPA and Mac Catalyst (push) Waiting to run
Apply fixes from CodeFactor
2025-01-01 12:33:50 +01:00
codefactor-io
74c9a21330
[CodeFactor] Apply fixes 2025-01-01 11:33:17 +00:00
cranci1
457cfe9480 applied a lot of patches ngl 2025-01-01 11:27:59 +01:00
197 changed files with 37904 additions and 4495 deletions

BIN
.DS_Store vendored

Binary file not shown.

View file

@ -1,11 +1,15 @@
name: Build and Release IPA
name: Build and Release
on:
push:
branches:
- main
- dev
pull_request:
branches:
- dev
jobs:
build:
name: Build IPA and Mac Catalyst
build-ios:
name: Build IPA
runs-on: macOS-latest
steps:
- name: Use Node.js 20
@ -24,18 +28,34 @@ jobs:
- name: Upload IPA artifact
uses: actions/upload-artifact@v4
with:
name: Sora-IPA
path: build/Sora.ipa
name: Sulfur-IPA
path: build/Sulfur.ipa
compression-level: 0
build-mac:
name: Build Mac Catalyst
runs-on: macOS-latest
steps:
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Checkout code
uses: actions/checkout@v4
- name: Run macbuild.sh
run: |
chmod +x macbuild.sh
./macbuild.sh
- name: Upload Mac Catalyst artifact
- name: Create DMG
run: |
hdiutil create -volname "Sulfur" -srcfolder build/Sulfur.app -ov -format UDZO build/Sulfur.dmg
- name: Upload Mac artifact
uses: actions/upload-artifact@v4
with:
name: Sora-Catalyst
path: build/Sora-catalyst.zip
compression-level: 0
name: Sulfur-Mac
path: build/Sulfur.dmg
compression-level: 0

134
.gitignore vendored Normal file
View file

@ -0,0 +1,134 @@
# Created by https://www.toptal.com/developers/gitignore/api/macos,swift,xcode
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,swift,xcode
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Swift ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/
# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# Pods/
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# Accio dependency management
Dependencies/
.accio/
# fastlane
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
### Xcode ###
## Xcode 8 and earlier
### Xcode Patch ###
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcodeproj/project.xcworkspace/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno
**/xcshareddata/WorkspaceSettings.xcsettings
# End of https://www.toptal.com/developers/gitignore/api/macos,swift,xcode

View file

@ -1,18 +1,20 @@
# Sora
> Also known as Sulfur due to copyright considerations.
<div align="center">
<img src="https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner.png" width="500px">
<img src="https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/Sulfur.png" width="750px">
[![Build and Release IPA](https://github.com/cranci1/Sora/actions/workflows/build.yml/badge.svg)](https://github.com/cranci1/Sora/actions/workflows/build.yml) [![Platform](https://img.shields.io/badge/Platform-iOS%20%7C%20iPadOS%2014.0%2B%20%26%20macOS%2012.0%2B-red?logo=apple&logoColor=white)](https://img.shields.io/badge/Platform-iOS%20%7C%20iPadOS%2014.0%2B%20%26%20macOS%2012.0%2B-red?logo=apple&logoColor=white) [![Discord](https://img.shields.io/discord/1293430817841741899.svg?logo=discord&color=blue)](https://discord.gg/XR3SrmUbpd)
[![Build and Release IPA](https://github.com/cranci1/Sora/actions/workflows/build.yml/badge.svg)](https://github.com/cranci1/Sora/actions/workflows/build.yml) [![Discord](https://img.shields.io/discord/1293430817841741899.svg?logo=discord&color=blue)](https://discord.gg/XR3SrmUbpd) [![Platform](https://img.shields.io/badge/Platform-iOS%20%7C%20iPadOS%2015.0%2B%20%26%20macOS%2012.0%2B-red?logo=apple&logoColor=white)](https://img.shields.io/badge/Platform-iOS%20%7C%20iPadOS%2015.0%2B%20%26%20macOS%2012.0%2B-red?logo=apple&logoColor=white)
A modular web scraping app, **Still in early builds** under the GPLv3.0 License
**An iOS and macOS modular web scraping app, under the GPLv3.0 License.**
</div>
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Frequently Asked Questions](#frequently-asked-questions)
- [Acknowledgements](#acknowledgements)
- [License](#license)
@ -20,37 +22,53 @@ A modular web scraping app, **Still in early builds** under the GPLv3.0 License
## Features
- [x] iOS/iPadOS 15+ support
- [x] macOS 12+ support
- [x] JSON module support
- [ ] JavaScript module support
- [x] macOS 12.0+ support
- [x] iOS/iPadOS 15.0+ support
- [x] JavaScript as main loader
- [x] Download support (HLS & MP4)
- [x] Tracking services (AniList, Trakt)
- [x] Apple Keychain support for auth tokens
- [x] Streams support (Jellyfin/Plex-like servers)
- [x] External metadata providers (TMDB, AniList)
- [x] Background playback and Picture-in-Picture (PiP) support
- [x] External media player support (VLC, Infuse, Outplayer, nPlayer, SenPlayer, IINA, TracyPlayer)
## Installation
You can download Sora using Xcode or using the .ipa file, which you can find in the [Releases](https://github.com/cranci1/Sora/releases) tab or the [Nightly](https://nightly.link/cranci1/Sora/workflows/build/dev/Sulfur-IPA.zip) build page.
## Frequently Asked Questions
1. **What is Sora?**
- Sora is a module based web scraping app, made to work with modules only
1. **What is Sora?**
Sora is a modular web scraping application designed to work exclusively with custom modules.
2. **Is Sora safe?**
- Yes, Sora is open-source and does not store user data on external servers.
2. **Is Sora safe?**
Yes, Sora is open-source and prioritizes user privacy. It does not store user data on external servers and does not collect crash logs.
3. **Will Sora ever be paid?**
- No, Sora will always remain free without subscriptions, paid content, or any type of login.
3. **Will Sora ever be paid?**
No, Sora will always remain free without subscriptions, paid content, or any type of login.
4. **How can i get module?**
- I do not provide any module for the app. Search for them on google or i dont know. I only created 1 module to watch videos of my school.
4. **How can I get modules?**
Sora does not include any modules by default. You will need to find and add the necessary modules yourself, or create your own.
## Acknowledgements
FrameWorks:
- [SwiftSoup](https://github.com/scinfu/SwiftSoup) - MIT License
- [KingFisher](https://github.com/onevcat/Kingfisher) - MIT License
Frameworks:
- [Drops](https://github.com/omaralbeik/Drops) - MIT License
- [NukeUI](https://github.com/kean/NukeUI) - MIT License
- [SoraCore](https://github.com/cranci1/SoraCore) - Custom License
- [MarqueeLabel](https://github.com/cbpowell/MarqueeLabel) - MIT License
Misc:
- [50/50](https://github.com/50n50) for the app icon
- [Ciro](https://github.com/CiroHoodLove) for the episodes banners
## License
This project is licensed under the [GNU General Public License v3.0](LICENSE).
This project is licensed under the [GNU General Public License v3.0](LICENSE) (GPLv3.0).
```
Copyright © 2024 cranci. All rights reserved.
Copyright © 2024-2025 cranci. All rights reserved.
Sora is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -68,16 +86,16 @@ along with Sora. If not, see <https://www.gnu.org/licenses/>.
## Legal
**_Sora is not made for Piracy! I don't promote piracy at all!_**
**_Sora is not intended for piracy. The Sora project does not endorse or support any form of piracy._**
### No Liability
The developer(s) of this software assumes no liability for damages, legal claims, or other issues arising from the use or misuse of this software or any third-party modules. Users bear full responsibility for their actions. Use this software and modules at your own risk.
The developer(s) of this software assume no liability for damages, legal claims, or other issues arising from the use or misuse of this software or any third-party modules. Users bear full responsibility for their actions. Use of this software and its modules is at your own risk.
### Third-Party Websites and Intellectual Property
This software is not affiliated with or endorsed by any third-party websites. Any references to third-party sites in user-generated modules do not imply endorsement. Users are responsible for verifying that their scraping activities comply with the terms of service and intellectual property rights of the sites they interact with.
This software is not affiliated with or endorsed by any third-party entities. Any references to third-party sites in user-generated modules do not imply endorsement. Users are responsible for ensuring their scraping activities comply with the terms of service and intellectual property rights of the sites they interact with.
### DMCA
The developer(s) is not responsible for the misuse of any content inside or outside the app and shall not be responsible for the dissemination of any content within the app. Any violations should be send to the source website or module creator. The developer is not legally responsible for any module used inside the app.
The developer(s) are not responsible for the misuse of any content inside or outside the app and shall not be held liable for the dissemination of any content within the app. Any violations should be reported to the source website or module creator. The developer bears no legal responsibility for any module used within the app.

View file

@ -1,650 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
1308CFBC2D19844A004CD38C /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1308CFBB2D19844A004CD38C /* Double+Extension.swift */; };
1308CFBE2D19844D004CD38C /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1308CFBD2D19844D004CD38C /* MusicProgressSlider.swift */; };
1308CFC12D198466004CD38C /* CustomPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1308CFC02D198466004CD38C /* CustomPlayer.swift */; };
132417842D13198000B4F2D2 /* SoraApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417832D13198000B4F2D2 /* SoraApp.swift */; };
132417862D13198000B4F2D2 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417852D13198000B4F2D2 /* ContentView.swift */; };
132417882D13198200B4F2D2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 132417872D13198200B4F2D2 /* Assets.xcassets */; };
1324178B2D13198200B4F2D2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1324178A2D13198200B4F2D2 /* Preview Assets.xcassets */; };
1324179E2D1319E800B4F2D2 /* MiruDataStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417932D1319E800B4F2D2 /* MiruDataStruct.swift */; };
1324179F2D1319E800B4F2D2 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417952D1319E800B4F2D2 /* Notification.swift */; };
132417A02D1319E800B4F2D2 /* HistoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417972D1319E800B4F2D2 /* HistoryManager.swift */; };
132417A12D1319E800B4F2D2 /* ModuleStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417992D1319E800B4F2D2 /* ModuleStruct.swift */; };
132417A22D1319E800B4F2D2 /* ModulesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1324179A2D1319E800B4F2D2 /* ModulesManager.swift */; };
132417A32D1319E800B4F2D2 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1324179C2D1319E800B4F2D2 /* NormalPlayer.swift */; };
132417B82D131A0600B4F2D2 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417A72D131A0600B4F2D2 /* SearchView.swift */; };
132417B92D131A0600B4F2D2 /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417A82D131A0600B4F2D2 /* SearchResultsView.swift */; };
132417BA2D131A0600B4F2D2 /* SettingsAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AB2D131A0600B4F2D2 /* SettingsAboutView.swift */; };
132417BB2D131A0600B4F2D2 /* SettingsIUView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AC2D131A0600B4F2D2 /* SettingsIUView.swift */; };
132417BC2D131A0600B4F2D2 /* SettingsLogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AD2D131A0600B4F2D2 /* SettingsLogsView.swift */; };
132417BD2D131A0600B4F2D2 /* SettingsModuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AE2D131A0600B4F2D2 /* SettingsModuleView.swift */; };
132417BE2D131A0600B4F2D2 /* SettingsPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AF2D131A0600B4F2D2 /* SettingsPlayerView.swift */; };
132417BF2D131A0600B4F2D2 /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B02D131A0600B4F2D2 /* SettingView.swift */; };
132417C02D131A0600B4F2D2 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B12D131A0600B4F2D2 /* HomeView.swift */; };
132417C12D131A0600B4F2D2 /* LibraryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B32D131A0600B4F2D2 /* LibraryManager.swift */; };
132417C22D131A0600B4F2D2 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B42D131A0600B4F2D2 /* LibraryView.swift */; };
132417C32D131A0600B4F2D2 /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B62D131A0600B4F2D2 /* MediaView.swift */; };
132417C42D131A0600B4F2D2 /* MediaExtraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B72D131A0600B4F2D2 /* MediaExtraction.swift */; };
132417CF2D131B7400B4F2D2 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 132417CE2D131B7400B4F2D2 /* SwiftSoup */; };
132417D22D131C5300B4F2D2 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 132417D12D131C5300B4F2D2 /* Kingfisher */; };
132417D52D13240200B4F2D2 /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D42D13240200B4F2D2 /* EpisodeCell.swift */; };
132417D72D13242400B4F2D2 /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D62D13242400B4F2D2 /* CircularProgressBar.swift */; };
132417D92D1328B900B4F2D2 /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */; };
1352BA712D1ABC30000A9AF9 /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1352BA702D1ABC30000A9AF9 /* URLSession.swift */; };
13AEE7BA2D2451F200CA634A /* GitHubAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE7B92D2451F200CA634A /* GitHubAPI.swift */; };
13AEE7BC2D24521200CA634A /* SettingsReleasesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE7BB2D24521200CA634A /* SettingsReleasesView.swift */; };
13B3A4B22D1477F100BCC0D5 /* SettingsStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B3A4B12D1477F100BCC0D5 /* SettingsStorageView.swift */; };
13C9821F2D2152B1007A0132 /* GitHubRelease.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C9821E2D2152B1007A0132 /* GitHubRelease.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1308CFBB2D19844A004CD38C /* Double+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
1308CFBD2D19844D004CD38C /* MusicProgressSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = "<group>"; };
1308CFC02D198466004CD38C /* CustomPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPlayer.swift; sourceTree = "<group>"; };
132417802D13198000B4F2D2 /* Sora.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sora.app; sourceTree = BUILT_PRODUCTS_DIR; };
132417832D13198000B4F2D2 /* SoraApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoraApp.swift; sourceTree = "<group>"; };
132417852D13198000B4F2D2 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
132417872D13198200B4F2D2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
1324178A2D13198200B4F2D2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
132417932D1319E800B4F2D2 /* MiruDataStruct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MiruDataStruct.swift; sourceTree = "<group>"; };
132417952D1319E800B4F2D2 /* Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
132417972D1319E800B4F2D2 /* HistoryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryManager.swift; sourceTree = "<group>"; };
132417992D1319E800B4F2D2 /* ModuleStruct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModuleStruct.swift; sourceTree = "<group>"; };
1324179A2D1319E800B4F2D2 /* ModulesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModulesManager.swift; sourceTree = "<group>"; };
1324179C2D1319E800B4F2D2 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = "<group>"; };
132417A72D131A0600B4F2D2 /* SearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
132417A82D131A0600B4F2D2 /* SearchResultsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = "<group>"; };
132417AB2D131A0600B4F2D2 /* SettingsAboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = "<group>"; };
132417AC2D131A0600B4F2D2 /* SettingsIUView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIUView.swift; sourceTree = "<group>"; };
132417AD2D131A0600B4F2D2 /* SettingsLogsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsLogsView.swift; sourceTree = "<group>"; };
132417AE2D131A0600B4F2D2 /* SettingsModuleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsModuleView.swift; sourceTree = "<group>"; };
132417AF2D131A0600B4F2D2 /* SettingsPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPlayerView.swift; sourceTree = "<group>"; };
132417B02D131A0600B4F2D2 /* SettingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = "<group>"; };
132417B12D131A0600B4F2D2 /* HomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
132417B32D131A0600B4F2D2 /* LibraryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryManager.swift; sourceTree = "<group>"; };
132417B42D131A0600B4F2D2 /* LibraryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
132417B62D131A0600B4F2D2 /* MediaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; };
132417B72D131A0600B4F2D2 /* MediaExtraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaExtraction.swift; sourceTree = "<group>"; };
132417C52D131AA500B4F2D2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
132417D42D13240200B4F2D2 /* EpisodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = "<group>"; };
132417D62D13242400B4F2D2 /* CircularProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = "<group>"; };
132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = "<group>"; };
1352BA6F2D1AB113000A9AF9 /* Sora.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Sora.entitlements; sourceTree = "<group>"; };
1352BA702D1ABC30000A9AF9 /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = "<group>"; };
13AEE7B92D2451F200CA634A /* GitHubAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubAPI.swift; sourceTree = "<group>"; };
13AEE7BB2D24521200CA634A /* SettingsReleasesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsReleasesView.swift; sourceTree = "<group>"; };
13B3A4B12D1477F100BCC0D5 /* SettingsStorageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsStorageView.swift; sourceTree = "<group>"; };
13C9821E2D2152B1007A0132 /* GitHubRelease.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubRelease.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
1324177D2D13198000B4F2D2 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
132417D22D131C5300B4F2D2 /* Kingfisher in Frameworks */,
132417CF2D131B7400B4F2D2 /* SwiftSoup in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1308CFBA2D19843E004CD38C /* CustomPlayer */ = {
isa = PBXGroup;
children = (
1308CFC02D198466004CD38C /* CustomPlayer.swift */,
1308CFBF2D198450004CD38C /* Components */,
);
path = CustomPlayer;
sourceTree = "<group>";
};
1308CFBF2D198450004CD38C /* Components */ = {
isa = PBXGroup;
children = (
1308CFBD2D19844D004CD38C /* MusicProgressSlider.swift */,
1308CFBB2D19844A004CD38C /* Double+Extension.swift */,
);
path = Components;
sourceTree = "<group>";
};
132417772D13198000B4F2D2 = {
isa = PBXGroup;
children = (
132417822D13198000B4F2D2 /* Sora */,
132417812D13198000B4F2D2 /* Products */,
);
sourceTree = "<group>";
};
132417812D13198000B4F2D2 /* Products */ = {
isa = PBXGroup;
children = (
132417802D13198000B4F2D2 /* Sora.app */,
);
name = Products;
sourceTree = "<group>";
};
132417822D13198000B4F2D2 /* Sora */ = {
isa = PBXGroup;
children = (
1352BA6F2D1AB113000A9AF9 /* Sora.entitlements */,
132417C52D131AA500B4F2D2 /* Info.plist */,
132417912D1319E800B4F2D2 /* Utils */,
132417A52D131A0600B4F2D2 /* Views */,
132417832D13198000B4F2D2 /* SoraApp.swift */,
132417852D13198000B4F2D2 /* ContentView.swift */,
132417872D13198200B4F2D2 /* Assets.xcassets */,
132417892D13198200B4F2D2 /* Preview Content */,
);
path = Sora;
sourceTree = "<group>";
};
132417892D13198200B4F2D2 /* Preview Content */ = {
isa = PBXGroup;
children = (
1324178A2D13198200B4F2D2 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
132417912D1319E800B4F2D2 /* Utils */ = {
isa = PBXGroup;
children = (
13C9821D2D2152A0007A0132 /* GitHub */,
1308CFBA2D19843E004CD38C /* CustomPlayer */,
132417922D1319E800B4F2D2 /* Miru */,
132417942D1319E800B4F2D2 /* Extensions */,
132417962D1319E800B4F2D2 /* History */,
132417982D1319E800B4F2D2 /* Modules */,
1324179B2D1319E800B4F2D2 /* Player */,
);
path = Utils;
sourceTree = "<group>";
};
132417922D1319E800B4F2D2 /* Miru */ = {
isa = PBXGroup;
children = (
132417932D1319E800B4F2D2 /* MiruDataStruct.swift */,
);
path = Miru;
sourceTree = "<group>";
};
132417942D1319E800B4F2D2 /* Extensions */ = {
isa = PBXGroup;
children = (
132417952D1319E800B4F2D2 /* Notification.swift */,
1352BA702D1ABC30000A9AF9 /* URLSession.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
132417962D1319E800B4F2D2 /* History */ = {
isa = PBXGroup;
children = (
132417972D1319E800B4F2D2 /* HistoryManager.swift */,
);
path = History;
sourceTree = "<group>";
};
132417982D1319E800B4F2D2 /* Modules */ = {
isa = PBXGroup;
children = (
132417992D1319E800B4F2D2 /* ModuleStruct.swift */,
1324179A2D1319E800B4F2D2 /* ModulesManager.swift */,
);
path = Modules;
sourceTree = "<group>";
};
1324179B2D1319E800B4F2D2 /* Player */ = {
isa = PBXGroup;
children = (
132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */,
1324179C2D1319E800B4F2D2 /* NormalPlayer.swift */,
);
path = Player;
sourceTree = "<group>";
};
132417A52D131A0600B4F2D2 /* Views */ = {
isa = PBXGroup;
children = (
132417B12D131A0600B4F2D2 /* HomeView.swift */,
132417B22D131A0600B4F2D2 /* LibraryViews */,
132417A62D131A0600B4F2D2 /* SearchViews */,
132417A92D131A0600B4F2D2 /* SettingsViews */,
132417B52D131A0600B4F2D2 /* MediaViews */,
);
path = Views;
sourceTree = "<group>";
};
132417A62D131A0600B4F2D2 /* SearchViews */ = {
isa = PBXGroup;
children = (
132417A72D131A0600B4F2D2 /* SearchView.swift */,
132417A82D131A0600B4F2D2 /* SearchResultsView.swift */,
);
path = SearchViews;
sourceTree = "<group>";
};
132417A92D131A0600B4F2D2 /* SettingsViews */ = {
isa = PBXGroup;
children = (
132417AA2D131A0600B4F2D2 /* SubPages */,
132417B02D131A0600B4F2D2 /* SettingView.swift */,
);
path = SettingsViews;
sourceTree = "<group>";
};
132417AA2D131A0600B4F2D2 /* SubPages */ = {
isa = PBXGroup;
children = (
132417AB2D131A0600B4F2D2 /* SettingsAboutView.swift */,
132417AC2D131A0600B4F2D2 /* SettingsIUView.swift */,
132417AD2D131A0600B4F2D2 /* SettingsLogsView.swift */,
132417AE2D131A0600B4F2D2 /* SettingsModuleView.swift */,
132417AF2D131A0600B4F2D2 /* SettingsPlayerView.swift */,
13B3A4B12D1477F100BCC0D5 /* SettingsStorageView.swift */,
13AEE7BB2D24521200CA634A /* SettingsReleasesView.swift */,
);
path = SubPages;
sourceTree = "<group>";
};
132417B22D131A0600B4F2D2 /* LibraryViews */ = {
isa = PBXGroup;
children = (
132417B42D131A0600B4F2D2 /* LibraryView.swift */,
132417B32D131A0600B4F2D2 /* LibraryManager.swift */,
);
path = LibraryViews;
sourceTree = "<group>";
};
132417B52D131A0600B4F2D2 /* MediaViews */ = {
isa = PBXGroup;
children = (
132417D32D1323F500B4F2D2 /* EpisodesCell */,
132417B62D131A0600B4F2D2 /* MediaView.swift */,
132417B72D131A0600B4F2D2 /* MediaExtraction.swift */,
);
path = MediaViews;
sourceTree = "<group>";
};
132417D32D1323F500B4F2D2 /* EpisodesCell */ = {
isa = PBXGroup;
children = (
132417D42D13240200B4F2D2 /* EpisodeCell.swift */,
132417D62D13242400B4F2D2 /* CircularProgressBar.swift */,
);
path = EpisodesCell;
sourceTree = "<group>";
};
13C9821D2D2152A0007A0132 /* GitHub */ = {
isa = PBXGroup;
children = (
13C9821E2D2152B1007A0132 /* GitHubRelease.swift */,
13AEE7B92D2451F200CA634A /* GitHubAPI.swift */,
);
path = GitHub;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
1324177F2D13198000B4F2D2 /* Sora */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1324178E2D13198200B4F2D2 /* Build configuration list for PBXNativeTarget "Sora" */;
buildPhases = (
1324177C2D13198000B4F2D2 /* Sources */,
1324177D2D13198000B4F2D2 /* Frameworks */,
1324177E2D13198000B4F2D2 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = Sora;
packageProductDependencies = (
132417CE2D131B7400B4F2D2 /* SwiftSoup */,
132417D12D131C5300B4F2D2 /* Kingfisher */,
);
productName = Sora;
productReference = 132417802D13198000B4F2D2 /* Sora.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
132417782D13198000B4F2D2 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1320;
LastUpgradeCheck = 1320;
TargetAttributes = {
1324177F2D13198000B4F2D2 = {
CreatedOnToolsVersion = 13.2.1;
};
};
};
buildConfigurationList = 1324177B2D13198000B4F2D2 /* Build configuration list for PBXProject "Sora" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 132417772D13198000B4F2D2;
packageReferences = (
132417CD2D131B7400B4F2D2 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
132417D02D131C5300B4F2D2 /* XCRemoteSwiftPackageReference "Kingfisher" */,
);
productRefGroup = 132417812D13198000B4F2D2 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
1324177F2D13198000B4F2D2 /* Sora */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
1324177E2D13198000B4F2D2 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1324178B2D13198200B4F2D2 /* Preview Assets.xcassets in Resources */,
132417882D13198200B4F2D2 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
1324177C2D13198000B4F2D2 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B3A4B22D1477F100BCC0D5 /* SettingsStorageView.swift in Sources */,
132417BB2D131A0600B4F2D2 /* SettingsIUView.swift in Sources */,
132417C42D131A0600B4F2D2 /* MediaExtraction.swift in Sources */,
132417B82D131A0600B4F2D2 /* SearchView.swift in Sources */,
1308CFBC2D19844A004CD38C /* Double+Extension.swift in Sources */,
132417D92D1328B900B4F2D2 /* VideoPlayerView.swift in Sources */,
1324179F2D1319E800B4F2D2 /* Notification.swift in Sources */,
132417BD2D131A0600B4F2D2 /* SettingsModuleView.swift in Sources */,
132417BC2D131A0600B4F2D2 /* SettingsLogsView.swift in Sources */,
1308CFC12D198466004CD38C /* CustomPlayer.swift in Sources */,
132417A22D1319E800B4F2D2 /* ModulesManager.swift in Sources */,
132417862D13198000B4F2D2 /* ContentView.swift in Sources */,
13AEE7BA2D2451F200CA634A /* GitHubAPI.swift in Sources */,
132417C22D131A0600B4F2D2 /* LibraryView.swift in Sources */,
132417A32D1319E800B4F2D2 /* NormalPlayer.swift in Sources */,
132417D72D13242400B4F2D2 /* CircularProgressBar.swift in Sources */,
132417C02D131A0600B4F2D2 /* HomeView.swift in Sources */,
132417BF2D131A0600B4F2D2 /* SettingView.swift in Sources */,
132417C32D131A0600B4F2D2 /* MediaView.swift in Sources */,
132417A12D1319E800B4F2D2 /* ModuleStruct.swift in Sources */,
132417B92D131A0600B4F2D2 /* SearchResultsView.swift in Sources */,
13AEE7BC2D24521200CA634A /* SettingsReleasesView.swift in Sources */,
132417842D13198000B4F2D2 /* SoraApp.swift in Sources */,
132417BE2D131A0600B4F2D2 /* SettingsPlayerView.swift in Sources */,
132417C12D131A0600B4F2D2 /* LibraryManager.swift in Sources */,
132417BA2D131A0600B4F2D2 /* SettingsAboutView.swift in Sources */,
1324179E2D1319E800B4F2D2 /* MiruDataStruct.swift in Sources */,
1308CFBE2D19844D004CD38C /* MusicProgressSlider.swift in Sources */,
132417D52D13240200B4F2D2 /* EpisodeCell.swift in Sources */,
13C9821F2D2152B1007A0132 /* GitHubRelease.swift in Sources */,
1352BA712D1ABC30000A9AF9 /* URLSession.swift in Sources */,
132417A02D1319E800B4F2D2 /* HistoryManager.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
1324178C2D13198200B4F2D2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
1324178D2D13198200B4F2D2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
1324178F2D13198200B4F2D2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Sora/Sora.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\"";
DEVELOPMENT_TEAM = 399LMK6Q2Y;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Sora/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = me.cranci.Sora;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
132417902D13198200B4F2D2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Sora/Sora.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\"";
DEVELOPMENT_TEAM = 399LMK6Q2Y;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Sora/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = me.cranci.Sora;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1324177B2D13198000B4F2D2 /* Build configuration list for PBXProject "Sora" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1324178C2D13198200B4F2D2 /* Debug */,
1324178D2D13198200B4F2D2 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1324178E2D13198200B4F2D2 /* Build configuration list for PBXNativeTarget "Sora" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1324178F2D13198200B4F2D2 /* Debug */,
132417902D13198200B4F2D2 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
132417CD2D131B7400B4F2D2 /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/scinfu/SwiftSoup.git";
requirement = {
kind = exactVersion;
version = 2.4.0;
};
};
132417D02D131C5300B4F2D2 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
requirement = {
kind = exactVersion;
version = 7.9.1;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
132417CE2D131B7400B4F2D2 /* SwiftSoup */ = {
isa = XCSwiftPackageProductDependency;
package = 132417CD2D131B7400B4F2D2 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
productName = SwiftSoup;
};
132417D12D131C5300B4F2D2 /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = 132417D02D131C5300B4F2D2 /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 132417782D13198000B4F2D2 /* Project object */;
}

View file

@ -1,25 +0,0 @@
{
"object": {
"pins": [
{
"package": "Kingfisher",
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
"state": {
"branch": null,
"revision": "b6f62758f21a8c03cd64f4009c037cfa580a256e",
"version": "7.9.1"
}
},
{
"package": "SwiftSoup",
"repositoryURL": "https://github.com/scinfu/SwiftSoup.git",
"state": {
"branch": null,
"revision": "5386dab25134eec11fc35fc5e43caf422fad0270",
"version": "2.4.0"
}
}
]
},
"version": 1
}

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>Sora.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>

View file

@ -2,8 +2,31 @@
"colors" : [
{
"color" : {
"platform" : "universal",
"reference" : "systemMintColor"
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View file

@ -1,111 +1,33 @@
{
"images" : [
{
"filename" : "40-2.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
"filename" : "lightmode.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"filename" : "60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "darkmode.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"filename" : "58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "120-1.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "40-1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "58-1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "40.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "80-1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "1024.jpg",
"idiom" : "ios-marketing",
"scale" : "1x",
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "tinting.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Discord Icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Github Icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "SplashScreenIcon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View file

@ -2,117 +2,124 @@
// ContentView.swift
// Sora
//
// Created by Francesco on 18/12/24.
// Created by Francesco on 06/01/25.
//
import SwiftUI
struct ContentView: View {
@EnvironmentObject var modulesManager: ModulesManager
var body: some View {
TabView {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
LibraryView()
.tabItem {
Label("Library", systemImage: "books.vertical")
}
SearchView()
.tabItem {
Label("Search", systemImage: "magnifyingglass")
}
SettingsView()
.tabItem {
Label("Settings", systemImage: "gearshape")
}
}
.onAppear {
checkForUpdate()
Logger.shared.log("Started Sora")
}
}
func checkForUpdate() {
fetchLatestRelease { release in
guard let release = release else { return }
let latestVersion = release.tagName
let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
if latestVersion.compare(currentVersion, options: .numeric) == .orderedDescending {
DispatchQueue.main.async {
showUpdateAlert(release: release)
}
}
}
}
func fetchLatestRelease(completion: @escaping (GitHubRelease?) -> Void) {
let url = URL(string: "https://api.github.com/repos/cranci1/Sora/releases/latest")!
URLSession.custom.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
let release = try? JSONDecoder().decode(GitHubRelease.self, from: data)
completion(release)
}.resume()
}
func showUpdateAlert(release: GitHubRelease) {
let alert = UIAlertController(title: "Update Available", message: "A new version (\(release.tagName)) is available. Would you like to update Sora?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Update", style: .default, handler: { _ in
self.showInstallOptionsAlert(release: release)
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController {
rootViewController.present(alert, animated: true, completion: nil)
}
}
func showInstallOptionsAlert(release: GitHubRelease) {
let installAlert = UIAlertController(title: "Install Update", message: "Choose an installation method:", preferredStyle: .alert)
let downloadUrl = release.assets.first?.browserDownloadUrl ?? ""
installAlert.addAction(UIAlertAction(title: "Install in AltStore", style: .default, handler: { _ in
if let url = URL(string: "altstore://install?url=\(downloadUrl)") {
UIApplication.shared.open(url)
}
}))
installAlert.addAction(UIAlertAction(title: "Install in Sidestore", style: .default, handler: { _ in
if let url = URL(string: "sidestore://install?url=\(downloadUrl)") {
UIApplication.shared.open(url)
}
}))
installAlert.addAction(UIAlertAction(title: "Open in Safari", style: .default, handler: { _ in
if let url = URL(string: downloadUrl) {
UIApplication.shared.open(url)
}
}))
installAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController {
rootViewController.present(installAlert, animated: true, completion: nil)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(LibraryManager())
.environmentObject(ModuleManager())
.environmentObject(Settings())
}
}
struct ContentView: View {
@AppStorage("useNativeTabBar") private var useNativeTabBar: Bool = false
@State var selectedTab: Int = 0
@State var lastTab: Int = 0
@State private var searchQuery: String = ""
@State private var shouldShowTabBar: Bool = true
@State private var tabBarOffset: CGFloat = 0
@State private var tabBarVisible: Bool = true
@State private var lastHideTime: Date = Date()
let tabs: [TabItem] = [
TabItem(icon: "square.stack", title: NSLocalizedString("LibraryTab", comment: "")),
TabItem(icon: "arrow.down.circle", title: NSLocalizedString("DownloadsTab", comment: "")),
TabItem(icon: "gearshape", title: NSLocalizedString("SettingsTab", comment: "")),
TabItem(icon: "magnifyingglass", title: NSLocalizedString("SearchTab", comment: ""))
]
private func tabView(for index: Int) -> some View {
switch index {
case 1: return AnyView(DownloadView())
case 2: return AnyView(SettingsView())
case 3: return AnyView(SearchView(searchQuery: $searchQuery))
default: return AnyView(LibraryView())
}
}
var body: some View {
if #available(iOS 26, *), useNativeTabBar == true {
TabView {
ForEach(Array(tabs.enumerated()), id: \.offset) { index, item in
tabView(for: index)
.tabItem {
Label(item.title, systemImage: item.icon)
}
}
}
.searchable(text: $searchQuery)
} else {
ZStack(alignment: .bottom) {
ZStack {
tabView(for: selectedTab)
.id(selectedTab)
.transition(.opacity)
.animation(.easeInOut(duration: 0.3), value: selectedTab)
}
.onPreferenceChange(TabBarVisibilityKey.self) { shouldShowTabBar = $0 }
if shouldShowTabBar {
TabBar(
tabs: tabs,
selectedTab: $selectedTab
)
.opacity(shouldShowTabBar && tabBarVisible ? 1 : 0)
.offset(y: tabBarVisible ? 0 : 120)
.animation(.spring(response: 0.15, dampingFraction: 0.7), value: tabBarVisible)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
.ignoresSafeArea(.keyboard, edges: .bottom)
.padding(.bottom, -20)
.onAppear {
setupNotificationObservers()
}
.onDisappear {
removeNotificationObservers()
}
}
}
private func setupNotificationObservers() {
NotificationCenter.default.addObserver(
forName: .hideTabBar,
object: nil,
queue: .main
) { _ in
lastHideTime = Date()
tabBarVisible = false
Logger.shared.log("Tab bar hidden", type: "Debug")
}
NotificationCenter.default.addObserver(
forName: .showTabBar,
object: nil,
queue: .main
) { _ in
let timeSinceHide = Date().timeIntervalSince(lastHideTime)
if timeSinceHide > 0.2 {
tabBarVisible = true
Logger.shared.log("Tab bar shown after \(timeSinceHide) seconds", type: "Debug")
} else {
Logger.shared.log("Tab bar show request ignored, only \(timeSinceHide) seconds since hide", type: "Debug")
}
}
}
private func removeNotificationObservers() {
NotificationCenter.default.removeObserver(self, name: .hideTabBar, object: nil)
NotificationCenter.default.removeObserver(self, name: .showTabBar, object: nil)
}
}
struct TabBarVisibilityKey: PreferenceKey {
static var defaultValue: Bool = true
static func reduce(value: inout Bool, nextValue: () -> Bool) {
value = nextValue()
}
}

View file

@ -2,6 +2,30 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>ar</string>
<string>bos</string>
<string>cs</string>
<string>nl</string>
<string>fr</string>
<string>de</string>
<string>it</string>
<string>kk</string>
<string>mn</string>
<string>nn</string>
<string>ru</string>
<string>sk</string>
<string>es</string>
<string>sv</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
@ -11,13 +35,30 @@
<string>me.cranci.scheme</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ryu</string>
<string>sora</string>
</array>
</dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>tracy</string>
<string>iina</string>
<string>outplayer</string>
<string>infuse</string>
<string>vlc</string>
<string>nplayer-https</string>
<string>senplayer</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
<string>processing</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,492 @@
/* General */
"About" = "حول";
"About Sora" = "حول Sora";
"Active" = "نشط";
"Active Downloads" = "التنزيلات النشطة";
"Actively downloading media can be tracked from here." = "يمكن تتبع المحتوى الذي يتم تنزيله حاليًا من هنا.";
"Add Module" = "إضافة وحدة";
"Adjust the number of media items per row in portrait and landscape modes." = "اضبط عدد عناصر المحتوى في كل صف في الوضعين الرأسي والأفقي.";
"Advanced" = "متقدم";
"AKA Sulfur" = "يعرف أيضًا باسم Sulfur";
"All Bookmarks" = "العناصر المحفوظة";
"All Watching" = "الكل قيد المشاهدة";
"Also known as Sulfur" = "يعرف أيضًا باسم Sulfur";
"AniList" = "AniList";
"AniList ID" = "معرّف AniList";
"AniList Match" = "مطابقة AniList";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "يتم جمع بيانات مجهولة المصدر لتحسين التطبيق. لا يتم جمع أي معلومات شخصية. يمكن تعطيل هذا في أي وقت.";
"App Info" = "معلومات التطبيق";
"App Language" = "لغة التطبيق";
"App Storage" = "تخزين التطبيق";
"Appearance" = "المظهر";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "هل أنت متأكد من أنك تريد مسح جميع بيانات ذاكرة التخزين المؤقت؟ سيساعد هذا في تحرير مساحة التخزين.";
"Are you sure you want to delete '%@'?" = "هل أنت متأكد أنك تريد حذف '%@'؟";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "هل أنت متأكد أنك تريد حذف جميع الحلقات البالغ عددها %1$d في '%2$@'؟";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "هل أنت متأكد من أنك تريد حذف جميع الأصول التي تم تنزيلها؟ يمكنك اختيار مسح المكتبة فقط مع الاحتفاظ بالملفات التي تم تنزيلها للاستخدام في المستقبل.";
"Are you sure you want to erase all app data? This action cannot be undone." = "هل أنت متأكد أنك تريد محو جميع بيانات التطبيق؟ هذا الإجراء لا يمكن التراجع عنه.";
/* Features */
"Background Enabled" = "التفعيل في الخلفية";
"Bookmark items for an easier access later." = "احفظ العناصر للوصول إليها بسهولة لاحقًا.";
"Bookmarks" = "العناصر المحفوظة";
"Bottom Padding" = "الحشوة السفلية";
"Cancel" = "إلغاء";
"Cellular Quality" = "جودة بيانات الجوال";
"Check out some community modules here!" = "اطلع على بعض وحدات المجتمع هنا!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "اختر دقة الفيديو المفضلة لاتصالات WiFi وبيانات الجوال. الدقة الأعلى تستهلك المزيد من البيانات ولكنها توفر جودة أفضل. إذا لم تكن الجودة الدقيقة متاحة، سيتم اختيار الخيار الأقرب تلقائيًا.\n\nملاحظة: ليست كل مصادر الفيديو والمشغلات تدعم اختيار الجودة. تعمل هذه الميزة بشكل أفضل مع تدفقات HLS باستخدام مشغل Sora.";
"Clear" = "مسح";
"Clear All Downloads" = "مسح كل التنزيلات";
"Clear Cache" = "مسح ذاكرة التخزين المؤقت";
"Clear Library Only" = "مسح المكتبة فقط";
"Clear Logs" = "مسح السجلات";
"Click the plus button to add a module!" = "انقر على زر الإضافة لإضافة وحدة!";
"Continue Watching" = "متابعة المشاهدة";
"Continue Watching Episode %d" = "متابعة مشاهدة الحلقة %d";
"Contributors" = "المساهمون";
"Copied to Clipboard" = "تم النسخ إلى الحافظة";
"Copy to Clipboard" = "نسخ إلى الحافظة";
"Copy URL" = "نسخ الرابط";
/* Episodes */
"%lld Episodes" = "%lld حلقات";
"%lld of %lld" = "%1$lld من %2$lld";
"%lld-%lld" = "%1$lld-%2$lld";
"%lld%% seen" = "تمت مشاهدة %lld%%";
"Episode %lld" = "الحلقة %lld";
"Episodes" = "الحلقات";
"Episodes might not be available yet or there could be an issue with the source." = "قد لا تكون الحلقات متاحة بعد أو قد تكون هناك مشكلة في المصدر.";
"Episodes Range" = "نطاق الحلقات";
/* System */
"cranci1" = "cranci1";
"Dark" = "داكن";
"DATA & LOGS" = "البيانات والسجلات";
"Debug" = "تصحيح الأخطاء";
"Debugging and troubleshooting." = "تصحيح الأخطاء وإصلاحها.";
/* Actions */
"Delete" = "حذف";
"Delete All" = "حذف الكل";
"Delete All Downloads" = "حذف كل التنزيلات";
"Delete All Episodes" = "حذف كل الحلقات";
"Delete Download" = "حذف التنزيل";
"Delete Episode" = "حذف الحلقة";
/* Player */
"Double Tap to Seek" = "انقر نقرًا مزدوجًا للتقديم";
"Double tapping the screen on it's sides will skip with the short tap setting." = "النقر المزدوج على جانبي الشاشة سيقوم بالتقديم حسب إعداد النقر القصير.";
/* Downloads */
"Download" = "تنزيل";
"Download Episode" = "تنزيل الحلقة";
"Download Summary" = "ملخص التنزيل";
"Download This Episode" = "تنزيل هذه الحلقة";
"Downloaded" = "تم التنزيل";
"Downloaded Shows" = "العروض المنزّلة";
"Downloading" = "جاري التنزيل";
"Downloads" = "التنزيلات";
/* Settings */
"Enable Analytics" = "تفعيل التحليلات";
"Enable Subtitles" = "تفعيل الترجمة";
/* Data Management */
"Erase" = "محو";
"Erase all App Data" = "محو كل بيانات التطبيق";
"Erase App Data" = "محو بيانات التطبيق";
/* Errors */
"Error" = "خطأ";
"Error Fetching Results" = "خطأ في جلب النتائج";
"Errors and critical issues." = "الأخطاء والمشاكل الحرجة.";
"Failed to load contributors" = "فشل تحميل المساهمين";
/* Features */
"Fetch Episode metadata" = "جلب بيانات الحلقة الوصفية";
"Files Downloaded" = "الملفات المنزّلة";
"Font Size" = "حجم الخط";
/* Interface */
"Force Landscape" = "فرض الوضع الأفقي";
"General" = "عام";
"General events and activities." = "الأحداث والأنشطة العامة.";
"General Preferences" = "الإعدادات العامة";
"Hide Splash Screen" = "إخفاء شاشة البداية";
"HLS video downloading." = "تنزيل فيديو HLS.";
"Hold Speed" = "سرعة الضغط المطول";
/* Info */
"Info" = "معلومات";
"INFOS" = "معلومات";
"Installed Modules" = "الوحدات المثبتة";
"Interface" = "الواجهة";
/* Social */
"Join the Discord" = "انضم إلى ديسكورد";
/* Layout */
"Landscape Columns" = "أعمدة الوضع الأفقي";
"Language" = "اللغة";
"LESS" = "أقل";
/* Library */
"Library" = "المكتبة";
"License (GPLv3.0)" = "الرخصة (GPLv3.0)";
"Light" = "فاتح";
/* Loading States */
"Loading Episode %lld..." = "جاري تحميل الحلقة %lld...";
"Loading logs..." = "جاري تحميل السجلات...";
"Loading module information..." = "جاري تحميل معلومات الوحدة...";
"Loading Stream" = "جاري تحميل البث";
/* Logging */
"Log Debug Info" = "تسجيل معلومات تصحيح الأخطاء";
"Log Filters" = "مرشحات السجل";
"Log In with AniList" = "تسجيل الدخول باستخدام AniList";
"Log In with Trakt" = "تسجيل الدخول باستخدام Trakt";
"Log Out from AniList" = "تسجيل الخروج من AniList";
"Log Out from Trakt" = "تسجيل الخروج من Trakt";
"Log Types" = "أنواع السجل";
"Logged in as" = "تم تسجيل الدخول باسم";
"Logged in as " = "تم تسجيل الدخول باسم ";
/* Logs and Settings */
"Logs" = "السجلات";
"Long press Skip" = "ضغط مطول للتخطي";
"MAIN" = "الرئيسية";
"Main Developer" = "المطور الرئيسي";
"MAIN SETTINGS" = "الإعدادات الرئيسية";
/* Media Actions */
"Mark All Previous Watched" = "تمييز كل ما سبق كمشاهد";
"Mark as Watched" = "تمييز كمشاهد";
"Mark Episode as Watched" = "تمييز الحلقة كمشاهدة";
"Mark Previous Episodes as Watched" = "تمييز الحلقات السابقة كمشاهدة";
"Mark watched" = "تمييز كمشاهد";
"Match with AniList" = "مطابقة مع AniList";
"Match with TMDB" = "مطابقة مع TMDB";
"Matched ID: %lld" = "المعرّف المطابق: %lld";
"Matched with: %@" = "تمت المطابقة مع: %@";
"Max Concurrent Downloads" = "الحد الأقصى للتنزيلات المتزامنة";
/* Media Interface */
"Media Grid Layout" = "تخطيط شبكة المحتوى";
"Media Player" = "مشغل المحتوى";
"Media View" = "عرض المحتوى";
"Metadata Provider" = "مزود البيانات الوصفية";
"Metadata Providers Order" = "ترتيب مزودي البيانات الوصفية";
"Module Removed" = "تمت إزالة الوحدة";
"Modules" = "الوحدات";
/* Headers */
"MODULES" = "الوحدات";
"MORE" = "المزيد";
/* Status Messages */
"No Active Downloads" = "لا توجد تنزيلات نشطة";
"No AniList matches found" = "لم يتم العثور على مطابقات في AniList";
"No Data Available" = "لا توجد بيانات متاحة";
"No Downloads" = "لا توجد تنزيلات";
"No episodes available" = "لا توجد حلقات متاحة";
"No Episodes Available" = "لا توجد حلقات متاحة";
"No items to continue watching." = "لا توجد عناصر لمتابعة مشاهدتها.";
"No matches found" = "لم يتم العثور على نتائج";
"No Module Selected" = "لم يتم تحديد أي وحدة";
"No Modules" = "لا توجد وحدات";
"No Results Found" = "لم يتم العثور على نتائج";
"No Search Results Found" = "لم يتم العثور على نتائج بحث";
"Nothing to Continue Watching" = "لا شيء لمتابعة مشاهدته";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "ملاحظة: سيتم استبدال الوحدات فقط في حالة وجود سلسلة إصدار مختلفة داخل ملف JSON.";
/* Actions */
"OK" = "موافق";
"Open Community Library" = "فتح مكتبة المجتمع";
/* External Services */
"Open in AniList" = "فتح في AniList";
"Original Poster" = "الملصق الأصلي";
/* Playback */
"Paused" = "متوقف مؤقتًا";
"Play" = "تشغيل";
"Player" = "المشغل";
/* System Messages */
"Please restart the app to apply the language change." = "يرجى إعادة تشغيل التطبيق لتطبيق تغيير اللغة.";
"Please select a module from settings" = "يرجى تحديد وحدة من الإعدادات";
/* Interface */
"Portrait Columns" = "أعمدة الوضع الرأسي";
"Progress bar Marker Color" = "لون علامة شريط التقدم";
"Provider: %@" = "المزود: %@";
/* Queue */
"Queue" = "قائمة الانتظار";
"Queued" = "في قائمة الانتظار";
/* Content */
"Recently watched content will appear here." = "سيظهر المحتوى الذي تمت مشاهدته مؤخرًا هنا.";
/* Settings */
"Refresh Modules on Launch" = "تحديث الوحدات عند التشغيل";
"Refresh Storage Info" = "تحديث معلومات التخزين";
"Remember Playback speed" = "تذكر سرعة التشغيل";
/* Actions */
"Remove" = "إزالة";
"Remove All Cache" = "إزالة كل ذاكرة التخزين المؤقت";
/* File Management */
"Remove All Documents" = "إزالة كل المستندات";
"Remove Documents" = "إزالة المستندات";
"Remove Downloaded Media" = "إزالة المحتوى المنزل";
"Remove Downloads" = "إزالة التنزيلات";
"Remove from Bookmarks" = "إزالة من العناصر المحفوظة";
"Remove Item" = "إزالة العنصر";
/* Support */
"Report an Issue" = "الإبلاغ عن مشكلة";
/* Reset Options */
"Reset" = "إعادة تعيين";
"Reset AniList ID" = "إعادة تعيين معرّف AniList";
"Reset Episode Progress" = "إعادة تعيين تقدم الحلقة";
"Reset progress" = "إعادة تعيين التقدم";
"Reset Progress" = "إعادة تعيين التقدم";
/* System */
"Restart Required" = "إعادة التشغيل مطلوبة";
"Running Sora %@ - cranci1" = "يعمل Sora %@ - بواسطة cranci1";
/* Actions */
"Save" = "حفظ";
"Search" = "بحث";
/* Search */
"Search downloads" = "بحث في التنزيلات";
"Search for something..." = "ابحث عن شيء ما...";
"Search..." = "بحث...";
/* Content */
"Season %d" = "الموسم %d";
"Season %lld" = "الموسم %lld";
"Segments Color" = "لون الأجزاء";
/* Modules */
"Select Module" = "تحديد وحدة";
"Set Custom AniList ID" = "تعيين معرّف AniList مخصص";
/* Interface */
"Settings" = "الإعدادات";
"Shadow" = "ظل";
"Show More (%lld more characters)" = "عرض المزيد (%lld أحرف إضافية)";
"Show PiP Button" = "إظهار زر صورة داخل صورة";
"Show Skip 85s Button" = "إظهار زر تخطي 85 ثانية";
"Show Skip Intro / Outro Buttons" = "إظهار أزرار تخطي المقدمة / الخاتمة";
"Shows" = "العروض";
"Size (%@)" = "الحجم (%@)";
"Skip Settings" = "إعدادات التخطي";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "بعض الميزات تقتصر على مشغل Sora والمشغل الافتراضي، مثل فرض الوضع الأفقي، وسرعة الضغط المطول، وزيادات التخطي الزمني المخصصة.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ بواسطة cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate." = "Sora و cranci1 غير تابعين لـ AniList أو Trakt بأي شكل من الأشكال.\n\nيرجى ملاحظة أن تحديثات التقدم قد لا تكون دقيقة بنسبة 100%.";
"Sora GitHub Repository" = "مستودع Sora على GitHub";
"Sora/Sulfur will always remain free with no ADs!" = "سيظل Sora/Sulfur دائمًا مجانيًا وبدون إعلانات!";
/* Interface */
"Sort" = "فرز";
"Speed Settings" = "إعدادات السرعة";
/* Playback */
"Start Watching" = "ابدأ المشاهدة";
"Start Watching Episode %d" = "ابدأ مشاهدة الحلقة %d";
"Storage Used" = "المساحة المستخدمة";
"Stream" = "بث";
"Streaming and video playback." = "البث وتشغيل الفيديو.";
/* Subtitles */
"Subtitle Color" = "لون الترجمة";
"Subtitle Settings" = "إعدادات الترجمة";
/* Sync */
"Sync anime progress" = "مزامنة تقدم الأنمي";
"Sync TV shows progress" = "مزامنة تقدم المسلسلات";
/* System */
"System" = "النظام";
/* Instructions */
"Tap a title to override the current match." = "انقر على عنوان لتجاوز المطابقة الحالية.";
"Tap Skip" = "انقر للتخطي";
"Tap to manage your modules" = "انقر لإدارة وحداتك";
"Tap to select a module" = "انقر لتحديد وحدة";
/* App Information */
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "تساعد ذاكرة التخزين المؤقت للتطبيق في تحميل الصور بشكل أسرع.\n\nسيؤدي مسح مجلد المستندات إلى حذف جميع الوحدات التي تم تنزيلها.\n\nلا تقم بمحو بيانات التطبيق إلا إذا كنت تفهم العواقب — فقد يتسبب ذلك في تعطل التطبيق.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "يتحكم نطاق الحلقات في عدد الحلقات التي تظهر في كل صفحة. يتم تجميع الحلقات في مجموعات (مثل 1-25، 26-50، وهكذا)، مما يتيح لك التنقل بينها بسهولة أكبر.\n\nبالنسبة لبيانات الحلقة الوصفية، فإنها تشير إلى الصورة المصغرة للحلقة وعنوانها، حيث يمكن أن تحتوي أحيانًا على حرق للأحداث.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "قدمت الوحدة حلقة واحدة فقط، ومن المرجح أن يكون هذا فيلمًا، لذلك قررنا إنشاء شاشات منفصلة لهذه الحالات.";
/* Interface */
"Thumbnails Width" = "عرض الصور المصغرة";
"TMDB Match" = "مطابقة TMDB";
"Trackers" = "منصات المتابعة";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "جرب كلمات مفتاحية مختلفة";
"Try different search terms" = "جرب مصطلحات بحث مختلفة";
/* Player Controls */
"Two Finger Hold for Pause" = "اضغط بإصبعين للإيقاف المؤقت";
"Unable to fetch matches. Please try again later." = "تعذر جلب المطابقات. يرجى المحاولة مرة أخرى لاحقًا.";
"Use TMDB Poster Image" = "استخدام صورة ملصق TMDB";
/* Version */
"v%@" = "v%@";
"Video Player" = "مشغل الفيديو";
/* Video Settings */
"Video Quality Preferences" = "تفضيلات جودة الفيديو";
"View All" = "عرض الكل";
"Watched" = "تمت مشاهدته";
"Why am I not seeing any episodes?" = "لماذا لا أرى أي حلقات؟";
"WiFi Quality" = "جودة WiFi";
/* User Status */
"You are not logged in" = "أنت غير مسجل الدخول";
"You have no items saved." = "ليس لديك عناصر محفوظة.";
"Your downloaded episodes will appear here" = "ستظهر حلقاتك المنزّلة هنا";
"Your recently watched content will appear here" = "سيظهر المحتوى الذي شاهدته مؤخرًا هنا";
/* Download Settings */
"Download Settings" = "إعدادات التنزيل";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "يتحكم الحد الأقصى للتنزيلات المتزامنة في عدد الحلقات التي يمكن تنزيلها في وقت واحد. قد تستهلك القيم الأعلى المزيد من النطاق الترددي وموارد الجهاز.";
"Quality" = "الجودة";
"Max Concurrent Downloads" = "الحد الأقصى للتنزيلات المتزامنة";
"Allow Cellular Downloads" = "السماح بالتنزيلات عبر بيانات الجوال";
"Quality Information" = "معلومات الجودة";
/* Storage */
"Storage Management" = "إدارة التخزين";
"Storage Used" = "المساحة المستخدمة";
"Library cleared successfully" = "تم مسح المكتبة بنجاح";
"All downloads deleted successfully" = "تم حذف جميع التنزيلات بنجاح";
/* New additions */
"Recent searches" = "عمليات البحث الأخيرة";
"me frfr" = "me frfr";
"Data" = "البيانات";
"Maximum Quality Available" = "أعلى جودة متاحة";
/* New additions */
"DownloadCountFormat" = "%d من %d";
"Error loading chapter" = "حدث خطأ أثناء تحميل الفصل";
"Font Size: %dpt" = "حجم الخط: %d نقطة";
"Line Spacing: %.1f" = "تباعد الأسطر: %.1f";
"Line Spacing" = "تباعد الأسطر";
"Margin: %dpx" = "الهامش: %d بكسل";
"Margin" = "الهامش";
"Auto Scroll Speed" = "سرعة التمرير التلقائي";
"Speed" = "السرعة";
"Speed: %.1fx" = "السرعة: %.1fx";
"Matched %@: %@" = "%@: %@ متطابق";
"Enter the AniList ID for this series" = "أدخل معرف AniList لهذه السلسلة";
/* New additions */
"Create Collection" = "إنشاء مجموعة";
"Collection Name" = "اسم المجموعة";
"Rename Collection" = "إعادة تسمية المجموعة";
"Rename" = "إعادة تسمية";
"All Reading" = "كل القراءة";
"Recently Added" = "أضيفت مؤخراً";
"Novel Title" = "عنوان الرواية";
"Read Progress" = "تقدم القراءة";
"Date Created" = "تاريخ الإنشاء";
"Name" = "الاسم";
"Item Count" = "عدد العناصر";
"Date Added" = "تاريخ الإضافة";
"Title" = "العنوان";
"Source" = "المصدر";
"Search reading..." = "ابحث في القراءة...";
"Search collections..." = "ابحث في المجموعات...";
"Search bookmarks..." = "ابحث في الإشارات المرجعية...";
"%d items" = "%d عناصر";
"Fetching Data" = "جاري جلب البيانات";
"Please wait while fetching." = "يرجى الانتظار أثناء الجلب.";
"Start Reading" = "ابدأ القراءة";
"Chapters" = "الفصول";
"Completed" = "مكتمل";
"Drag to reorder" = "اسحب لإعادة الترتيب";
"Drag to reorder sections" = "اسحب لإعادة ترتيب الأقسام";
"Library View" = "عرض المكتبة";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "خصص الأقسام المعروضة في مكتبتك. يمكنك إعادة ترتيب الأقسام أو تعطيلها بالكامل.";
"Library Sections Order" = "ترتيب أقسام المكتبة";
"Completion Percentage" = "نسبة الإكمال";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "بعض الميزات محدودة في مشغل Sora والمشغل الافتراضي فقط، مثل الوضع الأفقي الإجباري، سرعة التثبيت، وزيادات تخطي الوقت المخصصة.\n\nإعداد نسبة الإكمال يحدد عند أي نقطة قبل نهاية الفيديو سيتم اعتبار العمل مكتمل في AniList وTrakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "ذاكرة التخزين المؤقت تساعد التطبيق على تحميل الصور بشكل أسرع.\n\nمسح مجلد المستندات سيحذف جميع الوحدات التي تم تنزيلها.\n\nمسح بيانات التطبيق سيحذف جميع إعداداتك وبياناتك.";
"Translators" = "المترجمون";
"Paste URL" = "الصق الرابط";
/* New additions */
"Series Title" = "عنوان السلسلة";
"Content Source" = "مصدر المحتوى";
"Watch Progress" = "تقدم المشاهدة";
"Nothing to Continue Reading" = "لا شيء لمتابعة القراءة";
"Your recently read novels will appear here" = "ستظهر الروايات التي قرأتها مؤخرًا هنا";
"No Bookmarks" = "لا توجد إشارات مرجعية";
"Add bookmarks to this collection" = "أضف إشارات مرجعية إلى هذه المجموعة";
"items" = "عناصر";
"All Watching" = "كل المشاهدة";
"No Reading History" = "لا يوجد سجل قراءة";
"Books you're reading will appear here" = "ستظهر الكتب التي تقرأها هنا";
"Create Collection" = "إنشاء مجموعة";
"Collection Name" = "اسم المجموعة";
"Rename Collection" = "إعادة تسمية المجموعة";
"Rename" = "إعادة تسمية";
"Novel Title" = "عنوان الرواية";
"Read Progress" = "تقدم القراءة";
"Date Created" = "تاريخ الإنشاء";
"Name" = "الاسم";
"Item Count" = "عدد العناصر";
"Date Added" = "تاريخ الإضافة";
"Title" = "العنوان";
"Source" = "المصدر";
"Search reading..." = "ابحث في القراءة...";
"Search collections..." = "ابحث في المجموعات...";
"Search bookmarks..." = "ابحث في الإشارات المرجعية...";
"%d items" = "%d عناصر";
"Fetching Data" = "جاري جلب البيانات";
"Please wait while fetching." = "يرجى الانتظار أثناء الجلب.";
"Start Reading" = "ابدأ القراءة";
"Chapters" = "الفصول";
"Completed" = "مكتمل";
"Drag to reorder" = "اسحب لإعادة الترتيب";
"Drag to reorder sections" = "اسحب لإعادة ترتيب الأقسام";
"Library View" = "عرض المكتبة";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "خصص الأقسام المعروضة في مكتبتك. يمكنك إعادة ترتيب الأقسام أو تعطيلها بالكامل.";
"Library Sections Order" = "ترتيب أقسام المكتبة";
"Completion Percentage" = "نسبة الإكمال";
"Translators" = "المترجمون";
"Paste URL" = "الصق الرابط";
/* New additions */
"Collections" = "المجموعات";
"Continue Reading" = "متابعة القراءة";
/* New additions */
"Backup & Restore" = "النسخ الاحتياطي والاستعادة";
"Export Backup" = "تصدير النسخة الاحتياطية";
"Import Backup" = "استيراد النسخة الاحتياطية";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "تنبيه: هذه الميزة لا تزال تجريبية. يرجى التحقق من بياناتك بعد التصدير/الاستيراد.";
"Backup" = "نسخة احتياطية";

View file

@ -0,0 +1,510 @@
/* General */
"About" = "O aplikaciji";
"About Sora" = "O Sora aplikaciji";
"Active" = "Aktivno";
"Active Downloads" = "Aktivna preuzimanja";
"Actively downloading media can be tracked from here." = "Mediji koji se trenutno preuzimaju mogu se pratiti odavde.";
"Add Module" = "Dodaj modul";
"Adjust the number of media items per row in portrait and landscape modes." = "Podesi broj medijskih stavki po redu u portretnom i pejzažnom načinu.";
"Advanced" = "Napredno";
"AKA Sulfur" = "Također poznat kao Sulfur";
"All Bookmarks" = "Sve zabilješke";
"All Watching" = "Sve što gledam";
"Also known as Sulfur" = "Također poznat kao Sulfur";
"AniList" = "AniList";
"AniList ID" = "AniList ID";
"AniList Match" = "AniList poklapanje";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Anonimni podaci se prikupljaju za poboljšanje aplikacije. Lične informacije se ne prikupljaju. Ovo se može onemogućiti u bilo kojem trenutku.";
"App Info" = "Informacije o aplikaciji";
"App Language" = "Jezik aplikacije";
"App Storage" = "Spremište aplikacije";
"Appearance" = "Izgled";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Jeste li sigurni da želite obrisati sve keširane podatke? Ovo će pomoći oslobađanju prostora za spremanje.";
"Are you sure you want to delete '%@'?" = "Jeste li sigurni da želite obrisati '%@'?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "Jeste li sigurni da želite obrisati sve %1$d epizode u '%2$@'?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Jeste li sigurni da želite obrisati sve preuzete sadržaje? Možete odabrati da obrišete samo biblioteku zadržavajući preuzete datoteke za buduću upotrebu.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Jeste li sigurni da želite obrisati sve podatke aplikacije? Ova radnja se ne može poništiti.";
/* Features */
"Background Enabled" = "Pozadina omogućena";
"Bookmark items for an easier access later." = "Zabilježite stavke za lakši pristup kasnije.";
"Bookmarks" = "Zabilješke";
"Bottom Padding" = "Donja margina";
"Cancel" = "Otkaži";
"Cellular Quality" = "Kvaliteta mobilne mreže";
"Check out some community modules here!" = "Pogledajte neke module zajednice ovdje!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Odaberite željenu rezoluciju videa za WiFi i mobilne konekcije. Veće rezolucije koriste više podataka ali pružaju bolji kvalitet. Ako točna kvaliteta nije dostupna, najbliža opcija će biti automatski odabrana.\n\nNapomena: Nisu svi video izvori i playeri podržavaju odabir kvalitete. Ova funkcija najbolje radi s HLS streamovima koristeći Sora player.";
"Clear" = "Obriši";
"Clear All Downloads" = "Obriši sva preuzimanja";
"Clear Cache" = "Obriši keš";
"Clear Library Only" = "Obriši samo biblioteku";
"Clear Logs" = "Obriši logove";
"Click the plus button to add a module!" = "Kliknite plus dugme da dodate modul!";
"Continue Watching" = "Nastavi gledanje";
"Continue Watching Episode %d" = "Nastavi gledanje epizode %d";
"Contributors" = "Saradnici";
"Copied to Clipboard" = "Kopirano u međuspremnik";
"Copy to Clipboard" = "Kopiraj u međuspremnik";
"Copy URL" = "Kopiraj URL";
/* Episodes */
"%lld Episodes" = "%lld epizoda";
"%lld of %lld" = "%lld od %lld";
"%lld-%lld" = "%lld-%lld";
"%lld%% seen" = "%lld%% pogledano";
"Episode %lld" = "Epizoda %lld";
"Episodes" = "Epizode";
"Episodes might not be available yet or there could be an issue with the source." = "Epizode možda još nisu dostupne ili možda postoji problem s izvorom.";
"Episodes Range" = "Raspon epizoda";
/* System */
"cranci1" = "cranci1";
"Dark" = "Tamno";
"DATA & LOGS" = "PODACI I LOGOVI";
"Debug" = "Otkrivanje grešaka";
"Debugging and troubleshooting." = "Otkrivanje i rješavanje problema.";
/* Actions */
"Delete" = "Obriši";
"Delete All" = "Obriši sve";
"Delete All Downloads" = "Obriši sva preuzimanja";
"Delete All Episodes" = "Obriši sve epizode";
"Delete Download" = "Obriši preuzimanje";
"Delete Episode" = "Obriši epizodu";
/* Player */
"Double Tap to Seek" = "Dvostruki dodir za traženje";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Dvostruki dodir ekrana sa strane će preskočiti s kratkim dodirom.";
/* Downloads */
"Download" = "Preuzmi";
"Download Episode" = "Preuzmi epizodu";
"Download Summary" = "Sažetak preuzimanja";
"Download This Episode" = "Preuzmi ovu epizodu";
"Downloaded" = "Preuzeto";
"Downloaded Shows" = "Preuzete serije";
"Downloading" = "Preuzimanje";
"Downloads" = "Preuzimanja";
/* Settings */
"Enable Analytics" = "Omogući analitiku";
"Enable Subtitles" = "Omogući titlove";
/* Data Management */
"Erase" = "Obriši";
"Erase all App Data" = "Obriši sve podatke aplikacije";
"Erase App Data" = "Obriši podatke aplikacije";
/* Errors */
"Error" = "Greška";
"Error Fetching Results" = "Greška pri dohvaćanju rezultata";
"Errors and critical issues." = "Greške i kritični problemi.";
"Failed to load contributors" = "Neuspješno učitavanje saradnika";
/* Features */
"Fetch Episode metadata" = "Dohvati metapodatke epizode";
"Files Downloaded" = "Datoteke preuzete";
"Font Size" = "Veličina fonta";
/* Interface */
"Force Landscape" = "Forsiraj pejzažni način";
"General" = "Općenito";
"General events and activities." = "Općeniti događaji i aktivnosti.";
"General Preferences" = "Općenite postavke";
"Hide Splash Screen" = "Sakrij početni ekran";
"HLS video downloading." = "HLS video preuzimanje.";
"Hold Speed" = "Brzina držanja";
/* Info */
"Info" = "Informacije";
"INFOS" = "INFORMACIJE";
"Installed Modules" = "Instalirani moduli";
"Interface" = "Sučelje";
/* Social */
"Join the Discord" = "Pridruži se Discordu";
/* Layout */
"Landscape Columns" = "Stupci u pejzažnom načinu";
"Language" = "Jezik";
"LESS" = "MANJE";
/* Library */
"Library" = "Biblioteka";
"License (GPLv3.0)" = "Licenca (GPLv3.0)";
"Light" = "Svijetlo";
/* Loading States */
"Loading Episode %lld..." = "Učitavam epizodu %lld...";
"Loading logs..." = "Učitavam logove...";
"Loading module information..." = "Učitavam informacije o modulu...";
"Loading Stream" = "Učitavam stream";
/* Logging */
"Log Debug Info" = "Logiraj debug informacije";
"Log Filters" = "Filtri logova";
"Log In with AniList" = "Prijavite se s AniList";
"Log In with Trakt" = "Prijavite se s Trakt";
"Log Out from AniList" = "Odjavite se s AniList";
"Log Out from Trakt" = "Odjavite se s Trakt";
"Log Types" = "Tipovi logova";
"Logged in as" = "Prijavljen kao";
"Logged in as " = "Prijavljen kao ";
/* Logs and Settings */
"Logs" = "Logovi";
"Long press Skip" = "Dugi pritisak za preskačanje";
"MAIN" = "GLAVNO";
"Main Developer" = "Glavni developer";
"MAIN SETTINGS" = "GLAVNE POSTAVKE";
/* Media Actions */
"Mark All Previous Watched" = "Označi sve prethodne kao pogledane";
"Mark as Watched" = "Označi kao pogledano";
"Mark Episode as Watched" = "Označi epizodu kao pogledanu";
"Mark Previous Episodes as Watched" = "Označi prethodne epizode kao pogledane";
"Mark watched" = "Označi pogledano";
"Match with AniList" = "Poklopi s AniList";
"Match with TMDB" = "Poklopi s TMDB";
"Matched ID: %lld" = "Poklopljeni ID: %lld";
"Matched with: %@" = "Poklopljeno s: %@";
"Max Concurrent Downloads" = "Maksimalno istovremenih preuzimanja";
/* Media Interface */
"Media Grid Layout" = "Mrežni raspored medija";
"Media Player" = "Medijski player";
"Media View" = "Pregled medija";
"Metadata Provider" = "Pružatelj metapodataka";
"Metadata Providers Order" = "Redoslijed pružatelja metapodataka";
"Module Removed" = "Modul uklonjen";
"Modules" = "Moduli";
/* Headers */
"MODULES" = "MODULI";
"MORE" = "VIŠE";
/* Status Messages */
"No Active Downloads" = "Nema aktivnih preuzimanja";
"No AniList matches found" = "Nisu pronađena AniList poklapanja";
"No Data Available" = "Nema dostupnih podataka";
"No Downloads" = "Nema preuzimanja";
"No episodes available" = "Nema dostupnih epizoda";
"No Episodes Available" = "Nema dostupnih epizoda";
"No items to continue watching." = "Nema stavki za nastavak gledanja.";
"No matches found" = "Nisu pronađena poklapanja";
"No Module Selected" = "Nije odabran modul";
"No Modules" = "Nema modula";
"No Results Found" = "Nisu pronađeni rezultati";
"No Search Results Found" = "Nisu pronađeni rezultati pretrage";
"Nothing to Continue Watching" = "Nema ništa za nastavak gledanja";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Imajte na umu da će moduli biti zamijenjeni samo ako postoji drugačiji string verzije u JSON datoteci.";
/* Actions */
"OK" = "U redu";
"Open Community Library" = "Otvori biblioteku zajednice";
/* External Services */
"Open in AniList" = "Otvori u AniList";
"Original Poster" = "Originalni poster";
/* Playback */
"Paused" = "Pauzirano";
"Play" = "Reproduciraj";
"Player" = "Player";
/* System Messages */
"Please restart the app to apply the language change." = "Molimo restartajte aplikaciju da primijenite promjenu jezika.";
"Please select a module from settings" = "Molimo odaberite modul iz postavki";
/* Interface */
"Portrait Columns" = "Stupci u portretnom načinu";
"Progress bar Marker Color" = "Boja oznake progress bara";
"Provider: %@" = "Pružatelj: %@";
/* Queue */
"Queue" = "Red čekanja";
"Queued" = "U redu čekanja";
/* Content */
"Recently watched content will appear here." = "Nedavno pogledani sadržaj će se pojaviti ovdje.";
/* Settings */
"Refresh Modules on Launch" = "Osvježi module pri pokretanju";
"Refresh Storage Info" = "Osvježi informacije o spremištu";
"Remember Playback speed" = "Zapamti brzinu reprodukcije";
/* Actions */
"Remove" = "Ukloni";
"Remove All Cache" = "Ukloni sav keš";
/* File Management */
"Remove All Documents" = "Ukloni sve dokumente";
"Remove Documents" = "Ukloni dokumente";
"Remove Downloaded Media" = "Ukloni preuzete medije";
"Remove Downloads" = "Ukloni preuzimanja";
"Remove from Bookmarks" = "Ukloni iz zabilježaka";
"Remove Item" = "Ukloni stavku";
/* Support */
"Report an Issue" = "Prijavite problem";
/* Reset Options */
"Reset" = "Resetiraj";
"Reset AniList ID" = "Resetiraj AniList ID";
"Reset Episode Progress" = "Resetiraj napredak epizode";
"Reset progress" = "Resetiraj napredak";
"Reset Progress" = "Resetiraj napredak";
/* System */
"Restart Required" = "Potreban restart";
"Running Sora %@ - cranci1" = "Pokrenut Sora %@ - cranci1";
/* Actions */
"Save" = "Spremi";
"Search" = "Pretraži";
/* Search */
"Search downloads" = "Pretraži preuzimanja";
"Search for something..." = "Pretraži nešto...";
"Search..." = "Pretraži...";
/* Content */
"Season %d" = "Sezona %d";
"Season %lld" = "Sezona %lld";
"Segments Color" = "Boja segmenata";
/* Modules */
"Select Module" = "Odaberi modul";
"Set Custom AniList ID" = "Postavi prilagođeni AniList ID";
/* Interface */
"Settings" = "Postavke";
"Shadow" = "Sjena";
"Show More (%lld more characters)" = "Prikaži više (%lld više znakova)";
"Show PiP Button" = "Prikaži PiP dugme";
"Show Skip 85s Button" = "Prikaži dugme za preskakanje 85s";
"Show Skip Intro / Outro Buttons" = "Prikaži dugmad za preskakanje intro / outro";
"Shows" = "Serije";
"Size (%@)" = "Veličina (%@)";
"Skip Settings" = "Postavke preskakanja";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Neke funkcije su ograničene na Sora i zadani player, kao što su ForceLandscape, holdSpeed i prilagođeni vremenski intervali preskakanja.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ od cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.
Also note that progress updates may not be 100% accurate." = "Sora i cranci1 nisu povezani s AniList ili Trakt na bilo koji način.
Također imajte na umu da ažuriranja napretka možda neće biti 100% točna.";
"Sora GitHub Repository" = "Sora GitHub repozitorij";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur će uvijek ostati besplatan bez reklama!";
/* Interface */
"Sort" = "Sortiraj";
"Speed Settings" = "Postavke brzine";
/* Playback */
"Start Watching" = "Počni gledati";
"Start Watching Episode %d" = "Počni gledati epizodu %d";
"Storage Used" = "Iskorišten prostor za spremanje";
"Stream" = "Stream";
"Streaming and video playback." = "Streaming i reprodukcija videa.";
/* Subtitles */
"Subtitle Color" = "Boja titlova";
"Subtitle Settings" = "Postavke titlova";
/* Sync */
"Sync anime progress" = "Sinhroniziraj napredak animea";
"Sync TV shows progress" = "Sinhroniziraj napredak TV serija";
/* System */
"System" = "Sistem";
/* Instructions */
"Tap a title to override the current match." = "Dodirnite naslov da pregazite trenutno poklapanje.";
"Tap Skip" = "Dodirnite za preskakanje";
"Tap to manage your modules" = "Dodirnite za upravljanje modulima";
"Tap to select a module" = "Dodirnite da odaberete modul";
/* App Information */
"The app cache helps the app load images faster.
Clearing the Documents folder will delete all downloaded modules.
Do not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "Keš aplikacije pomaže aplikaciji da učita slike brže.
Brisanje mape Dokumenti će obrisati sve preuzete module.
Ne brišite podatke aplikacije osim ako ne razumijete posljedice — može dovesti do kvarova aplikacije.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.
For episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "Raspon epizoda kontroliše koliko epizoda se pojavljuje na svakoj stranici. Epizode su grupisane u setove (kao 125, 2650, i tako dalje), omogućavajući vam lakše navigiranje kroz njih.
Za metapodatke epizode, odnosi se na sličicu i naslov epizode, jer ponekad mogu sadržavati spojlere.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "Modul je pružio samo jednu epizodu, ovo je najvjerojatniji film, pa smo odlučili napraviti zasebne ekrane za ove slučajeve.";
/* Interface */
"Thumbnails Width" = "Širina sličica";
"TMDB Match" = "TMDB poklapanje";
"Trackers" = "Pratitelji";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Pokušajte s drugačijim ključnim riječima";
"Try different search terms" = "Pokušajte s drugačijim pojmovima za pretragu";
/* Player Controls */
"Two Finger Hold for Pause" = "Držanje dva prsta za pauzu";
"Unable to fetch matches. Please try again later." = "Nije moguće dohvatiti poklapanja. Molimo pokušajte ponovo kasnije.";
"Use TMDB Poster Image" = "Koristi TMDB poster sliku";
/* Version */
"v%@" = "v%@";
"Video Player" = "Video player";
/* Video Settings */
"Video Quality Preferences" = "Postavke kvalitete videa";
"View All" = "Prikaži sve";
"Watched" = "Pogledano";
"Why am I not seeing any episodes?" = "Zašto ne vidim epizode?";
"WiFi Quality" = "WiFi kvaliteta";
/* User Status */
"You are not logged in" = "Niste prijavljeni";
"You have no items saved." = "Nemate spremljenih stavki.";
"Your downloaded episodes will appear here" = "Vaše preuzete epizode će se pojaviti ovdje";
"Your recently watched content will appear here" = "Vaš nedavno pogledani sadržaj će se pojaviti ovdje";
/* Download Settings */
"Download Settings" = "Postavke preuzimanja";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Maksimalno istovremenih preuzimanja kontroliše koliko epizoda se može preuzeti istovremeno. Veće vrijednosti mogu koristiti više propusnog opsega i resursa uređaja.";
"Quality" = "Kvaliteta";
"Max Concurrent Downloads" = "Maksimalno istovremenih preuzimanja";
"Allow Cellular Downloads" = "Dozvoli preuzimanja preko mobilne mreže";
"Quality Information" = "Informacije o kvaliteti";
/* Storage */
"Storage Management" = "Upravljanje spremištem";
"Storage Used" = "Iskorišten prostor";
"Library cleared successfully" = "Biblioteka uspješno obrisana";
"All downloads deleted successfully" = "Sva preuzimanja uspješno obrisana";
/* New additions */
"Recent searches" = "Nedavne pretrage";
"me frfr" = "ja stvarno";
"Data" = "Podaci";
"Maximum Quality Available" = "Maksimalna dostupna kvaliteta";
/* Additional translations */
"DownloadCountFormat" = "%d od %d";
"Error loading chapter" = "Greška pri učitavanju poglavlja";
"Font Size: %dpt" = "Veličina fonta: %dpt";
"Line Spacing: %.1f" = "Razmak između redova: %.1f";
"Line Spacing" = "Razmak između redova";
"Margin: %dpx" = "Margina: %dpx";
"Margin" = "Margina";
"Auto Scroll Speed" = "Brzina automatskog pomicanja";
"Speed" = "Brzina";
"Speed: %.1fx" = "Brzina: %.1fx";
"Matched %@: %@" = "Poklapanje %@: %@";
"Enter the AniList ID for this series" = "Unesite AniList ID za ovu seriju";
/* Added missing localizations */
"Create Collection" = "Kreiraj kolekciju";
"Collection Name" = "Naziv kolekcije";
"Rename Collection" = "Preimenuj kolekciju";
"Rename" = "Preimenuj";
"All Reading" = "Sva čitanja";
"Recently Added" = "Nedavno dodano";
"Novel Title" = "Naslov romana";
"Read Progress" = "Napredak čitanja";
"Date Created" = "Datum kreiranja";
"Name" = "Naziv";
"Item Count" = "Broj stavki";
"Date Added" = "Datum dodavanja";
"Title" = "Naslov";
"Source" = "Izvor";
"Search reading..." = "Pretraži čitanje...";
"Search collections..." = "Pretraži kolekcije...";
"Search bookmarks..." = "Pretraži oznake...";
"%d items" = "%d stavki";
"Fetching Data" = "Preuzimanje podataka";
"Please wait while fetching." = "Molimo sačekajte dok se preuzima.";
"Start Reading" = "Započni čitanje";
"Chapters" = "Poglavlja";
"Completed" = "Završeno";
"Drag to reorder" = "Povuci za promjenu redoslijeda";
"Drag to reorder sections" = "Povuci za promjenu redoslijeda sekcija";
"Library View" = "Prikaz biblioteke";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Prilagodite sekcije prikazane u vašoj biblioteci. Možete ih preurediti ili potpuno onemogućiti.";
"Library Sections Order" = "Redoslijed sekcija biblioteke";
"Completion Percentage" = "Procenat završetka";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Neke funkcije su ograničene na Sora i zadani player, kao što su prisilni pejzaž, držanje brzine i prilagođeni intervali preskakanja.\n\nPostavka procenta završetka određuje u kojoj tački prije kraja videa će aplikacija označiti kao završeno na AniList i Trakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "Keš aplikacije pomaže bržem učitavanju slika.\n\nBrisanje Documents foldera će ukloniti sve preuzete module.\n\nBrisanje podataka aplikacije briše sve vaše postavke i podatke.";
"Translators" = "Prevoditelji";
"Paste URL" = "Zalijepi URL";
/* New additions */
"Series Title" = "Naslov serije";
"Content Source" = "Izvor sadržaja";
"Watch Progress" = "Napredak gledanja";
"Recent searches" = "Nedavne pretrage";
"All Reading" = "Sve što čitam";
"Nothing to Continue Reading" = "Nema ništa za nastaviti čitanje";
"Your recently read novels will appear here" = "Vaši nedavno pročitani romani će se pojaviti ovdje";
"No Bookmarks" = "Nema zabilješki";
"Add bookmarks to this collection" = "Dodajte zabilješke u ovu kolekciju";
"items" = "stavke";
"All Watching" = "Sve što gledam";
"No Reading History" = "Nema historije čitanja";
"Books you're reading will appear here" = "Knjige koje čitate će se pojaviti ovdje";
"Create Collection" = "Kreiraj kolekciju";
"Collection Name" = "Naziv kolekcije";
"Rename Collection" = "Preimenuj kolekciju";
"Rename" = "Preimenuj";
"Novel Title" = "Naslov romana";
"Read Progress" = "Napredak čitanja";
"Date Created" = "Datum kreiranja";
"Name" = "Ime";
"Item Count" = "Broj stavki";
"Date Added" = "Datum dodavanja";
"Title" = "Naslov";
"Source" = "Izvor";
"Search reading..." = "Pretraži čitanje...";
"Search collections..." = "Pretraži kolekcije...";
"Search bookmarks..." = "Pretraži zabilješke...";
"%d items" = "%d stavki";
"Fetching Data" = "Dohvatanje podataka";
"Please wait while fetching." = "Molimo sačekajte dok se podaci dohvaćaju.";
"Start Reading" = "Započni čitanje";
"Chapters" = "Poglavlja";
"Completed" = "Završeno";
"Drag to reorder" = "Povucite za promjenu redoslijeda";
"Drag to reorder sections" = "Povucite za promjenu redoslijeda sekcija";
"Library View" = "Prikaz biblioteke";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Prilagodite sekcije prikazane u vašoj biblioteci. Možete promijeniti redoslijed ili ih potpuno onemogućiti.";
"Library Sections Order" = "Redoslijed sekcija biblioteke";
"Completion Percentage" = "Procenat završetka";
"Translators" = "Prevodioci";
"Paste URL" = "Zalijepi URL";
/* New additions */
"Collections" = "Kolekcije";
"Continue Reading" = "Nastavi čitanje";
/* Backup & Restore */
"Backup & Restore" = "Sigurnosna kopija i vraćanje";
"Export Backup" = "Izvezi sigurnosnu kopiju";
"Import Backup" = "Uvezi sigurnosnu kopiju";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Napomena: Ova funkcija je još uvijek eksperimentalna. Molimo provjerite svoje podatke nakon izvoza/uvoza.";
"Backup" = "Sigurnosna kopija";

View file

@ -0,0 +1,511 @@
/* General */
"About" = "O aplikaci";
"About Sora" = "O Sora";
"Active" = "Aktivní";
"Active Downloads" = "Aktivní stahování";
"Actively downloading media can be tracked from here." = "Aktivně stahovaná média lze sledovat zde.";
"Add Module" = "Přidat modul";
"Adjust the number of media items per row in portrait and landscape modes." = "Upravte počet položek médií na řádek v režimu na výšku a na šířku.";
"Advanced" = "Pokročilé";
"AKA Sulfur" = "Známý jako Sulfur";
"All Bookmarks" = "Všechny záložky";
"All Watching" = "Vše sledované";
"Also known as Sulfur" = "Také známý jako Sulfur";
"AniList" = "AniList";
"AniList ID" = "AniList ID";
"AniList Match" = "Shoda AniList";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Anonymní data jsou shromažďována za účelem vylepšení aplikace. Žádné osobní údaje nejsou shromažďovány. Toto lze kdykoli vypnout.";
"App Info" = "Informace o aplikaci";
"App Language" = "Jazyk aplikace";
"App Storage" = "Úložiště aplikace";
"Appearance" = "Vzhled";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Opravdu chcete vymazat všechna data z cache? Toto pomůže uvolnit úložný prostor.";
"Are you sure you want to delete '%@'?" = "Opravdu chcete smazat '%@'?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "Opravdu chcete smazat všech %1$d epizod v '%2$@'?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Opravdu chcete smazat všechny stažené soubory? Můžete zvolit vymazání pouze knihovny při zachování stažených souborů pro budoucí použití.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Opravdu chcete vymazat všechna data aplikace? Tuto akci nelze vrátit zpět.";
/* Features */
"Background Enabled" = "Pozadí povoleno";
"Bookmark items for an easier access later." = "Uložte položky do záložek pro snadnější přístup později.";
"Bookmarks" = "Záložky";
"Bottom Padding" = "Spodní odsazení";
"Cancel" = "Zrušit";
"Cellular Quality" = "Kvalita na mobilních datech";
"Check out some community modules here!" = "Podívejte se na některé komunitní moduly zde!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Vyberte preferované rozlišení videa pro WiFi a mobilní připojení. Vyšší rozlišení používají více dat, ale poskytují lepší kvalitu. Pokud není k dispozici přesná kvalita, bude automaticky vybrána nejbližší možnost.\n\nPoznámka: Ne všechny video zdroje a přehrávače podporují výběr kvality. Tato funkce funguje nejlépe s HLS streamy pomocí přehrávače Sora.";
"Clear" = "Vymazat";
"Clear All Downloads" = "Vymazat všechna stahování";
"Clear Cache" = "Vymazat cache";
"Clear Library Only" = "Vymazat pouze knihovnu";
"Clear Logs" = "Vymazat logy";
"Click the plus button to add a module!" = "Klikněte na tlačítko plus pro přidání modulu!";
"Continue Watching" = "Pokračovat ve sledování";
"Continue Watching Episode %d" = "Pokračovat ve sledování epizody %d";
"Contributors" = "Přispěvatelé";
"Copied to Clipboard" = "Zkopírováno do schránky";
"Copy to Clipboard" = "Kopírovat do schránky";
"Copy URL" = "Kopírovat URL";
/* Episodes */
"%lld Episodes" = "%lld epizod";
"%lld of %lld" = "%lld z %lld";
"%lld-%lld" = "%lld-%lld";
"%lld%% seen" = "%lld%% shlédnuto";
"Episode %lld" = "Epizoda %lld";
"Episodes" = "Epizody";
"Episodes might not be available yet or there could be an issue with the source." = "Epizody možná ještě nejsou dostupné nebo může být problém se zdrojem.";
"Episodes Range" = "Rozsah epizod";
/* System */
"cranci1" = "cranci1";
"Dark" = "Tmavý";
"DATA & LOGS" = "DATA & LOGY";
"Debug" = "Ladění";
"Debugging and troubleshooting." = "Ladění a řešení problémů.";
/* Actions */
"Delete" = "Smazat";
"Delete All" = "Smazat vše";
"Delete All Downloads" = "Smazat všechna stahování";
"Delete All Episodes" = "Smazat všechny epizody";
"Delete Download" = "Smazat stahování";
"Delete Episode" = "Smazat epizodu";
/* Player */
"Double Tap to Seek" = "Dvojitý dotyk pro posun";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Dvojitý dotyk na strany obrazovky přeskočí s nastavením krátkého dotyku.";
/* Downloads */
"Download" = "Stáhnout";
"Download Episode" = "Stáhnout epizodu";
"Download Summary" = "Přehled stahování";
"Download This Episode" = "Stáhnout tuto epizodu";
"Downloaded" = "Staženo";
"Downloaded Shows" = "Stažené seriály";
"Downloading" = "Stahování";
"Downloads" = "Stahování";
/* Settings */
"Enable Analytics" = "Povolit analytiku";
"Enable Subtitles" = "Povolit titulky";
/* Data Management */
"Erase" = "Vymazat";
"Erase all App Data" = "Vymazat všechna data aplikace";
"Erase App Data" = "Vymazat data aplikace";
/* Errors */
"Error" = "Chyba";
"Error Fetching Results" = "Chyba při načítání výsledků";
"Errors and critical issues." = "Chyby a kritické problémy.";
"Failed to load contributors" = "Nepodařilo se načíst přispěvatele";
/* Features */
"Fetch Episode metadata" = "Načíst metadata epizody";
"Files Downloaded" = "Stažené soubory";
"Font Size" = "Velikost písma";
/* Interface */
"Force Landscape" = "Vynutit na šířku";
"General" = "Obecné";
"General events and activities." = "Obecné události a aktivity.";
"General Preferences" = "Obecné předvolby";
"Hide Splash Screen" = "Skrýt úvodní obrazovku (Splash Screen)";
"HLS video downloading." = "Stahování HLS videa.";
"Hold Speed" = "Rychlost při podržení";
/* Info */
"Info" = "Informace";
"INFOS" = "INFORMACE";
"Installed Modules" = "Nainstalované moduly";
"Interface" = "Rozhraní";
/* Social */
"Join the Discord" = "Připojit se na Discord";
/* Layout */
"Landscape Columns" = "Sloupce na šířku";
"Language" = "Jazyk";
"LESS" = "MÉNĚ";
/* Library */
"Library" = "Knihovna";
"License (GPLv3.0)" = "Licence (GPLv3.0)";
"Light" = "Světlý";
/* Loading States */
"Loading Episode %lld..." = "Načítá se epizoda %lld...";
"Loading logs..." = "Načítají se logy...";
"Loading module information..." = "Načítají se informace o modulu...";
"Loading Stream" = "Načítá se stream";
/* Logging */
"Log Debug Info" = "Logovat ladicí informace";
"Log Filters" = "Logovat filtry";
"Log In with AniList" = "Přihlásit se pomocí AniList";
"Log In with Trakt" = "Přihlásit se pomocí Trakt";
"Log Out from AniList" = "Odhlásit se z AniList";
"Log Out from Trakt" = "Odhlásit se z Trakt";
"Log Types" = "Logovat typy";
"Logged in as" = "Přihlášen jako";
"Logged in as " = "Přihlášen jako ";
/* Logs and Settings */
"Logs" = "Logy";
"Long press Skip" = "Dlouhý stisk pro přeskočení";
"MAIN" = "HLAVNÍ";
"Main Developer" = "Hlavní vývojář";
"MAIN SETTINGS" = "HLAVNÍ NASTAVENÍ";
/* Media Actions */
"Mark All Previous Watched" = "Označit všechny předchozí jako shlédnuté";
"Mark as Watched" = "Označit jako shlédnuté";
"Mark Episode as Watched" = "Označit epizodu jako shlédnutou";
"Mark Previous Episodes as Watched" = "Označit předchozí epizody jako shlédnuté";
"Mark watched" = "Označit jako shlédnuté";
"Match with AniList" = "Párovat s AniList";
"Match with TMDB" = "Párovat s TMDB";
"Matched ID: %lld" = "Spárované ID: %lld";
"Matched with: %@" = "Spárováno s: %@";
"Max Concurrent Downloads" = "Max současných stahování";
/* Media Interface */
"Media Grid Layout" = "Rozložení mřížky médií";
"Media Player" = "Přehrávač médií";
"Media View" = "Zobrazení médií";
"Metadata Provider" = "Poskytovatel metadat";
"Metadata Providers Order" = "Pořadí poskytovatelů metadat";
"Module Removed" = "Modul odstraněn";
"Modules" = "Moduly";
/* Headers */
"MODULES" = "MODULY";
"MORE" = "VÍCE";
/* Status Messages */
"No Active Downloads" = "Žádná aktivní stahování";
"No AniList matches found" = "Nenalezeny žádné shody z AniListu";
"No Data Available" = "Žádná data k dispozici";
"No Downloads" = "Žádná stahování";
"No episodes available" = "Žádné epizody k dispozici";
"No Episodes Available" = "Žádné epizody k dispozici";
"No items to continue watching." = "Žádné položky k pokračování ve sledování.";
"No matches found" = "Nenalezeny žádné shody";
"No Module Selected" = "Žádný modul nevybrán";
"No Modules" = "Žádné moduly";
"No Results Found" = "Nenalezeny žádné výsledky";
"No Search Results Found" = "Nenalezeny žádné výsledky vyhledávání";
"Nothing to Continue Watching" = "Nic k pokračování ve sledování";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Poznámka: moduly budou nahrazeny pouze pokud je v JSON souboru jiný řetězec verze.";
/* Actions */
"OK" = "OK";
"Open Community Library" = "Otevřít komunitní knihovnu";
/* External Services */
"Open in AniList" = "Otevřít v AniList";
"Original Poster" = "Původní plakát";
/* Playback */
"Paused" = "Pozastaveno";
"Play" = "Přehrát";
"Player" = "Přehrávač";
/* System Messages */
"Please restart the app to apply the language change." = "Prosím restartujte aplikaci pro použití změny jazyka.";
"Please select a module from settings" = "Prosím vyberte modul v nastavení";
/* Interface */
"Portrait Columns" = "Sloupce na výšku";
"Progress bar Marker Color" = "Barva značky v progres baru";
"Provider: %@" = "Poskytovatel: %@";
/* Queue */
"Queue" = "Fronta";
"Queued" = "Ve frontě";
/* Content */
"Recently watched content will appear here." = "Nedávno sledovaný obsah se zobrazí zde.";
/* Settings */
"Refresh Modules on Launch" = "Obnovit moduly při spuštění aplikace";
"Refresh Storage Info" = "Obnovit informace o úložišti";
"Remember Playback speed" = "Zapamatovat rychlost přehrávání";
/* Actions */
"Remove" = "Odebrat";
"Remove All Cache" = "Odebrat veškerou cache paměť";
/* File Management */
"Remove All Documents" = "Odebrat všechny dokumenty";
"Remove Documents" = "Odebrat dokumenty";
"Remove Downloaded Media" = "Odebrat stažená média";
"Remove Downloads" = "Odebrat stahování";
"Remove from Bookmarks" = "Odebrat ze záložek";
"Remove Item" = "Odebrat položku";
/* Support */
"Report an Issue" = "Hlásit problém";
/* Reset Options */
"Reset" = "Resetovat";
"Reset AniList ID" = "Resetovat AniList ID";
"Reset Episode Progress" = "Resetovat progres epizody";
"Reset progress" = "Resetovat progres";
"Reset Progress" = "Resetovat progres";
/* System */
"Restart Required" = "Vyžadován restart";
"Running Sora %@ - cranci1" = "Spuštěna Sora %@ - cranci1";
/* Actions */
"Save" = "Uložit";
"Search" = "Hledat";
/* Search */
"Search downloads" = "Hledat ve stahováních";
"Search for something..." = "Hledat něco...";
"Search..." = "Hledat...";
/* Content */
"Season %d" = "Sezóna %d";
"Season %lld" = "Sezóna %lld";
"Segments Color" = "Barva segmentů";
/* Modules */
"Select Module" = "Vybrat modul";
"Set Custom AniList ID" = "Nastavit vlastní AniList ID";
/* Interface */
"Settings" = "Nastavení";
"Shadow" = "Stín";
"Show More (%lld more characters)" = "Zobrazit více (%lld dalších znaků)";
"Show PiP Button" = "Zobrazit PiP tlačítko";
"Show Skip 85s Button" = "Zobrazit tlačítko přeskočení 85s";
"Show Skip Intro / Outro Buttons" = "Zobrazit tlačítka přeskočení intro/outro";
"Shows" = "Seriály";
"Size (%@)" = "Velikost (%@)";
"Skip Settings" = "Nastavení přeskakování";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Některé funkce jsou omezeny na přehrávač Sora a výchozí, jako je vynucení orientace na šířku (ForceLandscape), rychlost při podržení (holdSpeed) a vlastní časové skoky.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ od cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.
Also note that progress updates may not be 100% accurate." = "Sora a cranci1 nejsou nijak spojeni s AniList nebo Trakt.
Také si všimněte, že aktualizace progresu nemusí být 100% přesné.";
"Sora GitHub Repository" = "Sora GitHub repozitář";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur vždy zůstane zdarma bez reklam!";
/* Interface */
"Sort" = "Seřadit";
"Speed Settings" = "Nastavení rychlosti";
/* Playback */
"Start Watching" = "Začít sledovat";
"Start Watching Episode %d" = "Začít sledovat epizodu %d";
"Storage Used" = "Využité úložiště";
"Stream" = "Stream";
"Streaming and video playback." = "Streamování a přehrávání videa.";
/* Subtitles */
"Subtitle Color" = "Barva titulků";
"Subtitle Settings" = "Nastavení titulků";
/* Sync */
"Sync anime progress" = "Synchronizovat progres anime";
"Sync TV shows progress" = "Synchronizovat progres TV seriálů";
/* System */
"System" = "Systém";
/* Instructions */
"Tap a title to override the current match." = "Dotkněte se názvu pro přepsání aktuální shody.";
"Tap Skip" = "Klepnutím přeskočit";
"Tap to manage your modules" = "Dotkněte se pro správu vašich modulů";
"Tap to select a module" = "Dotkněte se pro výběr modulu";
/* App Information */
"The app cache helps the app load images faster.
Clearing the Documents folder will delete all downloaded modules.
Do not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "Cache aplikace pomáhá aplikaci načítat obrázky rychleji.
Vymazání složky Dokumenty smaže všechny stažené moduly.
Nemažte Data aplikace, pokud nerozumíte důsledkům — může to způsobit selhání aplikace.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.
For episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "Rozsah epizod určuje, kolik epizod se zobrazí na každé stránce. Epizody jsou seskupeny do bloků (jako 125, 2650, atd.), což vám umožňuje procházet je snadněji.
Metadata epizody se týkají náhledu a názvu epizody, které mohou někdy obsahovat spoilery.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "Modul poskytl pouze jednu epizodu, toto je pravděpodobně film, takže jsme se rozhodli vytvořit samostatné obrazovky pro tyto případy.";
/* Interface */
"Thumbnails Width" = "Šířka náhledů";
"TMDB Match" = "TMDB shoda";
"Trackers" = "Trackery";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Zkuste jiná klíčová slova";
"Try different search terms" = "Zkuste jiné vyhledávací termíny";
/* Player Controls */
"Two Finger Hold for Pause" = "Držení dvěma prsty pro pozastavení";
"Unable to fetch matches. Please try again later." = "Nelze načíst shody. Prosím zkuste to později.";
"Use TMDB Poster Image" = "Použít plakát z TMDB";
/* Version */
"v%@" = "v%@";
"Video Player" = "Přehrávač videa";
/* Video Settings */
"Video Quality Preferences" = "Předvolby kvality videa";
"View All" = "Zobrazit vše";
"Watched" = "Shlédnuto";
"Why am I not seeing any episodes?" = "Proč nevidím žádné epizody?";
"WiFi Quality" = "WiFi kvalita";
/* User Status */
"You are not logged in" = "Nejste přihlášeni";
"You have no items saved." = "Nemáte uložené žádné položky.";
"Your downloaded episodes will appear here" = "Vaše stažené epizody se zobrazí zde";
"Your recently watched content will appear here" = "Váš nedávno sledovaný obsah se zobrazí zde";
/* Download Settings */
"Download Settings" = "Nastavení stahování";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Max současných stahování určuje, kolik epizod se může stahovat současně. Vyšší hodnoty mohou používat více šířky pásma a zdrojů zařízení.";
"Quality" = "Kvalita";
"Max Concurrent Downloads" = "Max současných stahování";
"Allow Cellular Downloads" = "Povolit stahování přes mobilní síť";
"Quality Information" = "Informace o kvalitě";
/* Storage */
"Storage Management" = "Správa úložiště";
"Storage Used" = "Využité úložiště";
"Library cleared successfully" = "Knihovna úspěšně vymazána";
"All downloads deleted successfully" = "Všechna stahování úspěšně smazána";
/* Recent searches */
"Recent searches" = "Nedávná hledání";
"me frfr" = "já frfr";
"Data" = "Data";
/* New string */
"Maximum Quality Available" = "Maximální dostupná kvalita";
/* Additional translations */
"DownloadCountFormat" = "%d z %d";
"Error loading chapter" = "Chyba při načítání kapitoly";
"Font Size: %dpt" = "Velikost písma: %dpt";
"Line Spacing: %.1f" = "Řádkování: %.1f";
"Line Spacing" = "Řádkování";
"Margin: %dpx" = "Okraj: %dpx";
"Margin" = "Okraj";
"Auto Scroll Speed" = "Rychlost automatického posunu";
"Speed" = "Rychlost";
"Speed: %.1fx" = "Rychlost: %.1fx";
"Matched %@: %@" = "Shoda %@: %@";
"Enter the AniList ID for this series" = "Zadejte AniList ID pro tuto sérii";
/* Added missing localizations */
"Create Collection" = "Vytvořit kolekci";
"Collection Name" = "Název kolekce";
"Rename Collection" = "Přejmenovat kolekci";
"Rename" = "Přejmenovat";
"All Reading" = "Všechny knihy";
"Recently Added" = "Nedávno přidáno";
"Novel Title" = "Název románu";
"Read Progress" = "Postup čtení";
"Date Created" = "Datum vytvoření";
"Name" = "Název";
"Item Count" = "Počet položek";
"Date Added" = "Datum přidání";
"Title" = "Titul";
"Source" = "Zdroj";
"Search reading..." = "Hledat v knihách...";
"Search collections..." = "Hledat v kolekcích...";
"Search bookmarks..." = "Hledat v záložkách...";
"%d items" = "%d položek";
"Fetching Data" = "Načítání dat";
"Please wait while fetching." = "Počkejte prosím během načítání.";
"Start Reading" = "Začít číst";
"Chapters" = "Kapitoly";
"Completed" = "Dokončeno";
"Drag to reorder" = "Přetáhněte pro změnu pořadí";
"Drag to reorder sections" = "Přetáhněte pro změnu pořadí sekcí";
"Library View" = "Zobrazení knihovny";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Přizpůsobte sekce zobrazené ve vaší knihovně. Můžete je přeuspořádat nebo zcela vypnout.";
"Library Sections Order" = "Pořadí sekcí knihovny";
"Completion Percentage" = "Procento dokončení";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Některé funkce jsou omezeny na Sora a výchozí přehrávač, například vynucená krajina, podržení rychlosti a vlastní intervaly přeskočení.\n\nNastavení procenta dokončení určuje, v jakém bodě před koncem videa bude aplikace označovat jako dokončené na AniList a Trakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "Mezipaměť aplikace pomáhá rychlejšímu načítání obrázků.\n\nVymazání složky Documents odstraní všechny stažené moduly.\n\nVymazání dat aplikace smaže všechna vaše nastavení a data.";
"Translators" = "Překladatelé";
"Paste URL" = "Vložit URL";
/* New localizations */
"Series Title" = "Název série";
"Content Source" = "Zdroj obsahu";
"Watch Progress" = "Průběh sledování";
"All Reading" = "Vše ke čtení";
"Nothing to Continue Reading" = "Nic k pokračování ve čtení";
"Your recently read novels will appear here" = "Vaše nedávno čtené romány se zobrazí zde";
"No Bookmarks" = "Žádné záložky";
"Add bookmarks to this collection" = "Přidejte záložky do této kolekce";
"items" = "položky";
"All Watching" = "Vše ke sledování";
"No Reading History" = "Žádná historie čtení";
"Books you're reading will appear here" = "Knihy, které čtete, se zobrazí zde";
"Create Collection" = "Vytvořit kolekci";
"Collection Name" = "Název kolekce";
"Rename Collection" = "Přejmenovat kolekci";
"Rename" = "Přejmenovat";
"Novel Title" = "Název románu";
"Read Progress" = "Průběh čtení";
"Date Created" = "Datum vytvoření";
"Name" = "Jméno";
"Item Count" = "Počet položek";
"Date Added" = "Datum přidání";
"Title" = "Název";
"Source" = "Zdroj";
"Search reading..." = "Hledat ve čtení...";
"Search collections..." = "Hledat v kolekcích...";
"Search bookmarks..." = "Hledat v záložkách...";
"%d items" = "%d položek";
"Fetching Data" = "Načítání dat";
"Please wait while fetching." = "Počkejte prosím, načítají se data.";
"Start Reading" = "Začít číst";
"Chapters" = "Kapitoly";
"Completed" = "Dokončeno";
"Drag to reorder" = "Přetáhněte pro změnu pořadí";
"Drag to reorder sections" = "Přetáhněte pro změnu pořadí sekcí";
"Library View" = "Zobrazení knihovny";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Přizpůsobte sekce zobrazené ve vaší knihovně. Můžete změnit jejich pořadí nebo je úplně vypnout.";
"Library Sections Order" = "Pořadí sekcí knihovny";
"Completion Percentage" = "Procento dokončení";
"Translators" = "Překladatelé";
"Paste URL" = "Vložit URL";
/* New localizations */
"Collections" = "Kolekce";
"Continue Reading" = "Pokračovat ve čtení";
/* Backup & Restore */
"Backup & Restore" = "Zálohování a obnovení";
"Export Backup" = "Exportovat zálohu";
"Import Backup" = "Importovat zálohu";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Upozornění: Tato funkce je stále experimentální. Po exportu/importu si prosím zkontrolujte svá data.";
"Backup" = "Záloha";

View file

@ -0,0 +1,499 @@
/* General */
"About" = "Über";
"About Sora" = "Über Sora";
"Active" = "Aktiv";
"Active Downloads" = "Aktive Downloads";
"Actively downloading media can be tracked from here." = "Hier kannst du deine laufenden Downloads verfolgen.";
"Add Module" = "Modul hinzufügen";
"Adjust the number of media items per row in portrait and landscape modes." = "Passe die Anzahl der Medienelemente pro Zeile im Hoch- und Querformat an.";
"Advanced" = "Erweitert";
"AKA Sulfur" = "Alias Sulfur";
"All Bookmarks" = "Alle Lesezeichen";
"All Watching" = "Alle ansehen";
"Also known as Sulfur" = "Auch bekannt als Sulfur";
"AniList" = "AniList";
"AniList ID" = "AniList-ID";
"AniList Match" = "AniList-Zuordnung";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Anonyme Daten werden gesammelt, um die App zu verbessern. Es werden keine persönlichen Informationen erfasst. Du kannst dies jederzeit deaktivieren.";
"App Info" = "App-Infos";
"App Language" = "App-Sprache";
"App Storage" = "App-Speicher";
"Appearance" = "Darstellung";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Bist du sicher, dass du den Cache leeren möchtest? Dies hilft, Speicherplatz freizugeben.";
"Are you sure you want to delete '%@'?" = "Bist du sicher, dass du '%@' löschen möchtest?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "Bist du sicher, dass du alle %1$d Folgen in '%2$@' löschen möchtest?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Bist du sicher, dass du alle Downloads löschen möchtest? Du kannst auch nur die Bibliothek leeren und die heruntergeladenen Dateien für später behalten.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Bist du sicher, dass du alle App-Daten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.";
/* Features */
"Background Enabled" = "Hintergrund aktiviert";
"Bookmark items for an easier access later." = "Setze Lesezeichen für späteren schnellen Zugriff.";
"Bookmarks" = "Lesezeichen";
"Bottom Padding" = "Abstand unten";
"Cancel" = "Abbrechen";
"Cellular Quality" = "Qualität (Mobilfunk)";
"Check out some community modules here!" = "Entdecke Community-Module hier!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Wähle deine bevorzugte Videoqualität für WLAN und Mobilfunk. Höhere Qualität verbraucht mehr Daten. Falls nicht verfügbar, wird automatisch die nächstbeste Option gewählt.\n\nHinweis: Nicht alle Quellen unterstützen Qualitätsauswahl. Funktioniert am besten mit HLS-Streams im Sora-Player.";
"Clear" = "Löschen";
"Clear All Downloads" = "Alle Downloads löschen";
"Clear Cache" = "Cache leeren";
"Clear Library Only" = "Nur Bibliothek leeren";
"Clear Logs" = "Logs löschen";
"Click the plus button to add a module!" = "Tippe auf das Plus-Symbol, um ein Modul hinzuzufügen!";
"Continue Watching" = "Weiterschauen";
"Continue Watching Episode %d" = "Folge %d weiterschauen";
"Contributors" = "Mitwirkende";
"Copied to Clipboard" = "In die Zwischenablage kopiert";
"Copy to Clipboard" = "In die Zwischenablage kopieren";
"Copy URL" = "URL kopieren";
"Collections" = "Sammlungen";
"No Collections" = "Keine Sammlungen vorhanden";
"Create a collection to organize your bookmarks" = "Erstelle eine Sammlung für mehr Organisation";
/* Episodes */
"%lld Episodes" = "%lld Folgen";
"%lld of %lld" = "%lld von %lld";
"%lld-%lld" = "%lld-%lld";
"%lld%% seen" = "%lld%% gesehen";
"Episode %lld" = "Folge %lld";
"Episodes" = "Folgen";
"Episodes might not be available yet or there could be an issue with the source." = "Die Folgen sind möglicherweise noch nicht verfügbar oder es gibt ein Problem mit der Quelle.";
"Episodes Range" = "Folgenbereich";
/* System */
"cranci1" = "cranci1";
"Dark" = "Dunkel";
"DATA & LOGS" = "DATEN & LOGS";
"Debug" = "Debug";
"Debugging and troubleshooting." = "Fehlersuche und Problembehebung.";
/* Actions */
"Delete" = "Löschen";
"Delete All" = "Alle löschen";
"Delete All Downloads" = "Alle Downloads löschen";
"Delete All Episodes" = "Alle Folgen löschen";
"Delete Download" = "Download löschen";
"Delete Episode" = "Folge löschen";
/* Player */
"Double Tap to Seek" = "Tippe doppelt zum Spulen";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Doppeltippen auf die Bildschirmseiten überspringt entsprechend deiner Einstellung.";
/* Downloads */
"Download" = "Herunterladen";
"Download Episode" = "Folge herunterladen";
"Download Summary" = "Download-Übersicht";
"Download This Episode" = "Diese Folge herunterladen";
"Downloaded" = "Geladen";
"Downloaded Shows" = "Heruntergeladene Serien";
"Downloading" = "Lädt herunter";
"Downloads" = "Downloads";
/* Settings */
"Enable Analytics" = "Analytik aktivieren";
"Enable Subtitles" = "Untertitel aktivieren";
/* Data Management */
"Erase" = "Löschen";
"Erase all App Data" = "Alle App-Daten löschen";
"Erase App Data" = "App-Daten löschen";
/* Errors */
"Error" = "Fehler";
"Error Fetching Results" = "Fehler beim Laden";
"Errors and critical issues." = "Fehler und kritische Probleme.";
"Failed to load contributors" = "Mitwirkende konnten nicht geladen werden";
/* Features */
"Fetch Episode metadata" = "Folgen-Metadaten abrufen";
"Files Downloaded" = "Dateien heruntergeladen";
"Font Size" = "Schriftgröße";
/* Interface */
"Force Landscape" = "Querformat erzwingen";
"General" = "Allgemein";
"General events and activities." = "Allgemeine Aktivitäten.";
"General Preferences" = "Allgemeine Einstellungen";
"Hide Splash Screen" = "Startbildschirm ausblenden";
"Use Native Tab Bar" = "System Bar verwenden";
"HLS video downloading." = "HLS Video-Downloads.";
"Hold Speed" = "Geschwindigkeit halten";
/* Info */
"Info" = "Info";
"INFOS" = "INFOS";
"Installed Modules" = "Deine Module";
"Interface" = "Oberfläche";
/* Social */
"Join the Discord" = "Tritt unserem Discord-Server bei";
/* Layout */
"Landscape Columns" = "Spalten (Querformat)";
"Language" = "Sprache";
"LESS" = "WENIGER";
/* Library */
"Library" = "Bibliothek";
"License (GPLv3.0)" = "Lizenz (GPLv3.0)";
"Light" = "Hell";
/* Loading States */
"Loading Episode %lld..." = "Lade Folge %lld...";
"Loading logs..." = "Lade Logs...";
"Loading module information..." = "Lade Modulinfos...";
"Loading Stream" = "Stream wird geladen";
/* Logging */
"Log Debug Info" = "Debug-Infos protokollieren";
"Log Filters" = "Log-Filter";
"Log In with AniList" = "Mit AniList anmelden";
"Log In with Trakt" = "Mit Trakt anmelden";
"Log Out from AniList" = "Von AniList abmelden";
"Log Out from Trakt" = "Von Trakt abmelden";
"Log Types" = "Log-Typen";
"Logged in as" = "Angemeldet als";
"Logged in as " = "Angemeldet als ";
/* Logs and Settings */
"Logs" = "Logs";
"Long press Skip" = "Lang drücken zum Überspringen";
"MAIN" = "MAIN";
"Main Developer" = "Hauptentwickler";
"MAIN SETTINGS" = "HAUPTEINSTELLUNGEN";
/* Media Actions */
"Mark All Previous Watched" = "Alle vorherigen als gesehen markieren";
"Mark as Watched" = "Als gesehen markieren";
"Mark Episode as Watched" = "Folge als gesehen markieren";
"Mark Previous Episodes as Watched" = "Vorherige Folgen als gesehen markieren";
"Mark watched" = "Als gesehen markieren";
"Match with AniList" = "Mit AniList abgleichen";
"Match with TMDB" = "Mit TMDB abgleichen";
"Matched ID: %lld" = "Zugeordnete ID: %lld";
"Matched with: %@" = "Abgeglichen mit: %@";
"Max Concurrent Downloads" = "Max. gleichzeitige Downloads";
/* Media Interface */
"Media Grid Layout" = "Medienraster-Layout";
"Media Player" = "Media-Player";
"Media View" = "Medienansicht";
"Metadata Provider" = "Metadaten-Anbieter";
"Metadata Providers Order" = "Reihenfolge der Metadaten-Anbieter";
"Module Removed" = "Modul entfernt";
"Modules" = "Module";
/* Headers */
"MODULES" = "MODULE";
"MORE" = "MEHR";
/* Status Messages */
"No Active Downloads" = "Keine aktiven Downloads";
"No AniList matches found" = "Keine AniList-Übereinstimmungen";
"No Data Available" = "Keine Daten";
"No Downloads" = "Keine Downloads";
"No episodes available" = "Keine Folgen verfügbar";
"No Episodes Available" = "Keine Folgen verfügbar";
"No items to continue watching." = "Keine Inhalte zum Weiterschauen verfügbar.";
"No matches found" = "Keine Übereinstimmungen gefunden";
"No Module Selected" = "Kein Modul ausgewählt";
"No Modules" = "Keine Module";
"No Results Found" = "Keine Ergebnisse";
"No Search Results Found" = "Keine Suchergebnisse";
"Nothing to Continue Watching" = "Keine Inhalte zum Weiterschauen";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Beachte, dass Module nur ersetzt werden, wenn in der JSON-Datei eine andere Versionskennung enthalten ist.";
/* Actions */
"OK" = "OK";
"Open Community Library" = "Community-Bibliothek öffnen";
/* External Services */
"Open in AniList" = "In AniList öffnen";
"Original Poster" = "Original-Poster";
/* Playback */
"Paused" = "Pausiert";
"Play" = "Abspielen";
"Player" = "Player";
/* System Messages */
"Please restart the app to apply the language change." = "Starte die App neu, um die Sprache zu ändern.";
"Please select a module from settings" = "Wähle ein Modul in den Einstellungen";
/* Interface */
"Portrait Columns" = "Spalten (Hochformat)";
"Progress bar Marker Color" = "Farbe der Fortschrittsanzeige";
"Provider: %@" = "Anbieter: %@";
/* Queue */
"Queue" = "Warteschlange";
"Queued" = "In Warteschlange";
/* Content */
"Recently watched content will appear here." = "Deine kürzlich angesehenen Inhalte werden hier angezeigt.";
/* Settings */
"Refresh Modules on Launch" = "Module beim Start aktualisieren";
"Refresh Storage Info" = "Speicherinfo aktualisieren";
"Remember Playback speed" = "Wiedergabegeschwindigkeit merken";
/* Actions */
"Remove" = "Entfernen";
"Remove All Cache" = "Cache komplett löschen";
/* File Management */
"Remove All Documents" = "Alle Dokumente löschen";
"Remove Documents" = "Dokumente löschen";
"Remove Downloaded Media" = "Downloads entfernen";
"Remove Downloads" = "Downloads entfernen";
"Remove from Bookmarks" = "Aus Lesezeichen entfernen";
"Remove Item" = "Element entfernen";
/* Support */
"Report an Issue" = "Problem melden";
/* Reset Options */
"Reset" = "Zurücksetzen";
"Reset AniList ID" = "AniList-ID zurücksetzen";
"Reset Episode Progress" = "Folgenfortschritt zurücksetzen";
"Reset progress" = "Fortschritt zurücksetzen";
"Reset Progress" = "Fortschritt zurücksetzen";
/* System */
"Restart Required" = "Neustart erforderlich";
"Running Sora %@ - cranci1" = "Sora %@ - cranci1";
/* Actions */
"Save" = "Speichern";
"Search" = "Suchen";
/* Search */
"Search downloads" = "Downloads durchsuchen";
"Search for something..." = "Suche nach etwas...";
"Search..." = "Suchen...";
/* Content */
"Season %d" = "Staffel %d";
"Season %lld" = "Staffel %lld";
"Segments Color" = "Segmentfarbe";
/* Modules */
"Select Module" = "Modul auswählen";
"Set Custom AniList ID" = "Eigene AniList-ID festlegen";
/* Interface */
"Settings" = "Einstellungen";
"Shadow" = "Schatten";
"Show More (%lld more characters)" = "Mehr anzeigen (%lld Zeichen mehr)";
"Show PiP Button" = "PiP-Taste anzeigen";
"Show Skip 85s Button" = "85s-Überspringen-Taste anzeigen";
"Show Skip Intro / Outro Buttons" = "Intro/Outro-Tasten anzeigen";
"Shows" = "Serien";
"Size (%@)" = "Größe (%@)";
"Skip Settings" = "Überspringen-Einstellungen";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Einige Funktionen sind auf den Sora- und Standard-Player beschränkt, wie z.B. Querformat erzwingen, Geschwindigkeit halten und individuelle Zeitsprünge.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ von cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate." = "Sora und cranci1 sind in keiner Weise mit AniList oder Trakt verbunden.\n\nBitte beachte, dass Fortschrittsaktualisierungen möglicherweise nicht zu 100% genau sind.";
"Sora GitHub Repository" = "Sora GitHub-Repository";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur bleibt für immer kostenlos und ohne Werbung!";
/* Interface */
"Sort" = "Sortieren";
"Speed Settings" = "Geschwindigkeitseinstellungen";
/* Playback */
"Start Watching" = "Jetzt ansehen";
"Start Watching Episode %d" = "Folge %d starten";
"Storage Used" = "Belegter Speicher";
"Stream" = "Streamen";
"Streaming and video playback." = "Streaming und Videowiedergabe.";
/* Subtitles */
"Subtitle Color" = "Untertitel-Farbe";
"Subtitle Settings" = "Untertitel-Einstellungen";
/* Sync */
"Sync anime progress" = "Anime-Fortschritt syncen";
"Sync TV shows progress" = "Serien-Fortschritt syncen";
/* System */
"System" = "System";
/* Instructions */
"Tap a title to override the current match." = "Tippe einen Titel, um die Zuordnung zu ändern.";
"Tap Skip" = "Tippe zum Überspringen";
"Tap to manage your modules" = "Tippe hier, um deine Module zu verwalten";
"Tap to select a module" = "Tippe hier, um ein Modul auszuwählen";
/* App Information */
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "Der App-Cache hilft, Bilder schneller zu laden.\n\nDas Löschen des Dokumentenordners entfernt alle heruntergeladenen Module.\n\nLösche App-Daten nur, wenn du die Konsequenzen verstehst — dies könnte dazu führen, dass die App nicht mehr richtig funktioniert.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "Der Folgenbereich legt fest, wie viele Folgen pro Seite angezeigt werden. Folgen werden in Gruppen zusammengefasst (z.B. 1-25, 26-50 usw.), um die Navigation zu erleichtern.\n\nFolgen-Metadaten beziehen sich auf Vorschaubilder und Titel, die manchmal Spoiler enthalten können.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "Das Modul stellt nur eine einzelne Folge bereit, was wahrscheinlich ein Film ist, daher haben wir für diese Fälle separate Ansichten erstellt.";
/* Interface */
"Thumbnails Width" = "Thumbnail-Breite";
"TMDB Match" = "TMDB-Zuordnung";
"Trackers" = "Tracker";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Versuche es mit anderen Suchbegriffen";
"Try different search terms" = "Verwende andere Suchbegriffe";
/* Player Controls */
"Two Finger Hold for Pause" = "Halte mit zwei Fingern zum Pausieren";
"Unable to fetch matches. Please try again later." = "Konnte keine Übereinstimmungen abrufen. Bitte versuche es später erneut.";
"Use TMDB Poster Image" = "TMDB-Poster verwenden";
/* Version */
"v%@" = "v%@";
"Video Player" = "Video-Player";
/* Video Settings */
"Video Quality Preferences" = "Videoqualität";
"View All" = "Alle anzeigen";
"Watched" = "Gesehen";
"Why am I not seeing any episodes?" = "Warum werden keine Folgen angezeigt?";
"WiFi Quality" = "Qualität (WLAN)";
/* User Status */
"You are not logged in" = "Du bist nicht angemeldet";
"You have no items saved." = "Du hast keine gespeicherten Elemente.";
"Your downloaded episodes will appear here" = "Deine heruntergeladenen Folgen werden hier angezeigt.";
"Your recently watched content will appear here" = "Deine kürzlich angesehenen Inhalte werden hier angezeigt.";
/* Download Settings */
"Download Settings" = "Download-Einstellungen";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Die maximale Anzahl gleichzeitiger Downloads legt fest, wie viele Folgen gleichzeitig heruntergeladen werden können. Höhere Werte können mehr Bandbreite und Geräteleistung beanspruchen.";
"Quality" = "Qualität";
"Max Concurrent Downloads" = "Max. gleichzeitige Downloads";
"Allow Cellular Downloads" = "Downloads über Mobilfunk erlauben";
"Quality Information" = "Qualitäts-Info";
/* Storage */
"Storage Management" = "Speicherverwaltung";
"Storage Used" = "Belegter Speicher";
"Library cleared successfully" = "Bibliothek erfolgreich geleert";
"All downloads deleted successfully" = "Alle Downloads erfolgreich gelöscht";
/* TabView */
"LibraryTab" = "Bibliothek";
"DownloadsTab" = "Downloads";
"SettingsTab" = "Einstellungen";
"SearchTab" = "Suchen";
/* New additions */
"Recent searches" = "Letzte Suchanfragen";
"me frfr" = "Ich, ohne Witz";
"Data" = "Daten";
"Maximum Quality Available" = "Maximal verfügbare Qualität";
"DownloadCountFormat" = "%d von %d";
"Error loading chapter" = "Fehler beim Laden des Kapitels";
"Font Size: %dpt" = "Schriftgröße: %dpt";
"Line Spacing: %.1f" = "Zeilenabstand: %.1f";
"Line Spacing" = "Zeilenabstand";
"Margin: %dpx" = "Rand: %dpx";
"Margin" = "Rand";
"Auto Scroll Speed" = "Automatische Scroll-Geschwindigkeit";
"Speed" = "Geschwindigkeit";
"Speed: %.1fx" = "Geschwindigkeit: %.1fx";
"Matched %@: %@" = "Abgeglichen %@: %@";
"Enter the AniList ID for this series" = "Geben Sie die AniList-ID für diese Serie ein";
/* Added missing localizations */
"Create Collection" = "Sammlung erstellen";
"Collection Name" = "Sammlungsname";
"Rename Collection" = "Sammlung umbenennen";
"Rename" = "Umbenennen";
"All Reading" = "Alles Lesen";
"Recently Added" = "Kürzlich hinzugefügt";
"Novel Title" = "Roman Titel";
"Read Progress" = "Lesefortschritt";
"Date Created" = "Erstellungsdatum";
"Name" = "Name";
"Item Count" = "Anzahl der Elemente";
"Date Added" = "Hinzugefügt am";
"Title" = "Titel";
"Source" = "Quelle";
"Search reading..." = "Lesen durchsuchen...";
"Search collections..." = "Sammlungen durchsuchen...";
"Search bookmarks..." = "Lesezeichen durchsuchen...";
"%d items" = "%d Elemente";
"Fetching Data" = "Daten werden abgerufen";
"Please wait while fetching." = "Bitte warten Sie während des Abrufs.";
"Start Reading" = "Lesen starten";
"Chapters" = "Kapitel";
"Completed" = "Abgeschlossen";
"Drag to reorder" = "Ziehen zum Neuordnen";
"Drag to reorder sections" = "Ziehen zum Neuordnen der Abschnitte";
"Library View" = "Bibliotheksansicht";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Passen Sie die in Ihrer Bibliothek angezeigten Abschnitte an. Sie können Abschnitte neu anordnen oder vollständig deaktivieren.";
"Library Sections Order" = "Reihenfolge der Bibliotheksabschnitte";
"Completion Percentage" = "Abschlussprozentsatz";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Einige Funktionen sind nur im Sora- und Standard-Player verfügbar, wie z.B. erzwungene Querformatansicht, Haltegeschwindigkeit und benutzerdefinierte Zeitsprünge.\n\nDie Einstellung des Abschlussprozentsatzes bestimmt, ab welchem Punkt vor dem Ende eines Videos die App es als abgeschlossen auf AniList und Trakt markiert.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "Der App-Cache hilft, Bilder schneller zu laden.\n\nDas Löschen des Dokumente-Ordners entfernt alle heruntergeladenen Module.\n\nDas Löschen der App-Daten entfernt alle Ihre Einstellungen und Daten.";
"Translators" = "Übersetzer";
"Paste URL" = "URL einfügen";
/* Added missing localizations */
"Series Title" = "Serientitel";
"Content Source" = "Inhaltsquelle";
"Watch Progress" = "Fortschritt ansehen";
"All Reading" = "Alles Lesen";
"Nothing to Continue Reading" = "Nichts zum Weiterlesen";
"Your recently read novels will appear here" = "Ihre zuletzt gelesenen Romane erscheinen hier";
"No Bookmarks" = "Keine Lesezeichen";
"Add bookmarks to this collection" = "Fügen Sie dieser Sammlung Lesezeichen hinzu";
"items" = "Elemente";
"All Watching" = "Alles Ansehen";
"No Reading History" = "Kein Leseverlauf";
"Books you're reading will appear here" = "Bücher, die Sie lesen, erscheinen hier";
"Create Collection" = "Sammlung erstellen";
"Collection Name" = "Sammlungsname";
"Rename Collection" = "Sammlung umbenennen";
"Rename" = "Umbenennen";
"Novel Title" = "Roman Titel";
"Read Progress" = "Lesefortschritt";
"Date Created" = "Erstellungsdatum";
"Name" = "Name";
"Item Count" = "Anzahl der Elemente";
"Date Added" = "Hinzugefügt am";
"Title" = "Titel";
"Source" = "Quelle";
"Search reading..." = "Lesen durchsuchen...";
"Search collections..." = "Sammlungen durchsuchen...";
"Search bookmarks..." = "Lesezeichen durchsuchen...";
"%d items" = "%d Elemente";
"Fetching Data" = "Daten werden abgerufen";
"Please wait while fetching." = "Bitte warten Sie während des Abrufs.";
"Start Reading" = "Lesen starten";
"Chapters" = "Kapitel";
"Completed" = "Abgeschlossen";
"Drag to reorder" = "Ziehen zum Neuordnen";
"Drag to reorder sections" = "Ziehen zum Neuordnen der Abschnitte";
"Library View" = "Bibliotheksansicht";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Passen Sie die in Ihrer Bibliothek angezeigten Abschnitte an. Sie können Abschnitte neu anordnen oder vollständig deaktivieren.";
"Library Sections Order" = "Reihenfolge der Bibliotheksabschnitte";
"Completion Percentage" = "Abschlussprozentsatz";
"Translators" = "Übersetzer";
"Paste URL" = "URL einfügen";
"Continue Reading" = "Weiterlesen";
"Backup & Restore" = "Sichern & Wiederherstellen";
"Export Backup" = "Backup exportieren";
"Import Backup" = "Backup importieren";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Hinweis: Diese Funktion ist noch experimentell. Bitte überprüfe deine Daten nach dem Export/Import.";
"Backup" = "Backup";

View file

@ -0,0 +1,461 @@
/* General */
"About" = "About";
"About Sora" = "About Sora";
"Active" = "Active";
"Active Downloads" = "Active Downloads";
"Actively downloading media can be tracked from here." = "Actively downloading media can be tracked from here.";
"Add Module" = "Add Module";
"Adjust the number of media items per row in portrait and landscape modes." = "Adjust the number of media items per row in portrait and landscape modes.";
"Advanced" = "Advanced";
"AKA Sulfur" = "AKA Sulfur";
"All Bookmarks" = "All Bookmarks";
"All Watching" = "All Watching";
"Also known as Sulfur" = "Also known as Sulfur";
"AniList" = "AniList";
"AniList ID" = "AniList ID";
"AniList Match" = "AniList Match";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time.";
"App Info" = "App Info";
"App Language" = "App Language";
"App Storage" = "App Storage";
"Appearance" = "Appearance";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Are you sure you want to clear all cached data? This will help free up storage space.";
"Are you sure you want to delete '%@'?" = "Are you sure you want to delete '%@'?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "Are you sure you want to delete all %1$d episodes in '%2$@'?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Are you sure you want to erase all app data? This action cannot be undone.";
/* Features */
"Background Enabled" = "Background Enabled";
"Bookmark items for an easier access later." = "Bookmark items for an easier access later.";
"Bookmarks" = "Bookmarks";
"Bottom Padding" = "Bottom Padding";
"Cancel" = "Cancel";
"Cellular Quality" = "Cellular Quality";
"Check out some community modules here!" = "Check out some community modules here!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player.";
"Clear" = "Clear";
"Clear All Downloads" = "Clear All Downloads";
"Clear Cache" = "Clear Cache";
"Clear Library Only" = "Clear Library Only";
"Clear Logs" = "Clear Logs";
"Click the plus button to add a module!" = "Click the plus button to add a module!";
"Continue Watching" = "Continue Watching";
"Continue Watching Episode %d" = "Continue Watching Episode %d";
"Contributors" = "Contributors";
"Copied to Clipboard" = "Copied to Clipboard";
"Copy to Clipboard" = "Copy to Clipboard";
"Copy URL" = "Copy URL";
"Collections" = "Collections";
"No Collections" = "No Collections";
"Create a collection to organize your bookmarks" = "Create a collection to organize your bookmarks";
/* Episodes */
"%lld Episodes" = "%lld Episodes";
"%lld of %lld" = "%1$lld of %2$lld";
"%lld-%lld" = "%1$lld-%2$lld";
"%lld%% seen" = "%lld%% seen";
"Episode %lld" = "Episode %lld";
"Episodes" = "Episodes";
"Episodes might not be available yet or there could be an issue with the source." = "Episodes might not be available yet or there could be an issue with the source.";
"Episodes Range" = "Episodes Range";
/* System */
"cranci1" = "cranci1";
"Dark" = "Dark";
"DATA & LOGS" = "DATA & LOGS";
"Debug" = "Debug";
"Debugging and troubleshooting." = "Debugging and troubleshooting.";
/* Actions */
"Delete" = "Delete";
"Delete All" = "Delete All";
"Delete All Downloads" = "Delete All Downloads";
"Delete All Episodes" = "Delete All Episodes";
"Delete Download" = "Delete Download";
"Delete Episode" = "Delete Episode";
/* Player */
"Double Tap to Seek" = "Double Tap to Seek";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Double tapping the screen on it's sides will skip with the short tap setting.";
/* Downloads */
"Download" = "Download";
"Download Episode" = "Download Episode";
"Download Summary" = "Download Summary";
"Download This Episode" = "Download This Episode";
"Downloaded" = "Downloaded";
"Downloaded Shows" = "Downloaded Shows";
"Downloading" = "Downloading";
"Downloads" = "Downloads";
/* Settings */
"Enable Analytics" = "Enable Analytics";
"Enable Subtitles" = "Enable Subtitles";
/* Data Management */
"Erase" = "Erase";
"Erase all App Data" = "Erase all App Data";
"Erase App Data" = "Erase App Data";
/* Errors */
"Error" = "Error";
"Error Fetching Results" = "Error Fetching Results";
"Errors and critical issues." = "Errors and critical issues.";
"Failed to load contributors" = "Failed to load contributors";
/* Features */
"Fetch Episode metadata" = "Fetch Episode metadata";
"Files Downloaded" = "Files Downloaded";
"Font Size" = "Font Size";
/* Interface */
"Force Landscape" = "Force Landscape";
"General" = "General";
"General events and activities." = "General events and activities.";
"General Preferences" = "General Preferences";
"Hide Splash Screen" = "Hide Splash Screen";
"Use Native Tab Bar" = "Use Native Tabs";
"HLS video downloading." = "HLS video downloading.";
"Hold Speed" = "Hold Speed";
/* Info */
"Info" = "Info";
"INFOS" = "INFOS";
"Installed Modules" = "Installed Modules";
"Interface" = "Interface";
/* Social */
"Join the Discord" = "Join the Discord";
/* Layout */
"Landscape Columns" = "Landscape Columns";
"Language" = "Language";
"LESS" = "LESS";
/* Library */
"Library" = "Library";
"License (GPLv3.0)" = "License (GPLv3.0)";
"Light" = "Light";
/* Loading States */
"Loading Episode %lld..." = "Loading Episode %lld...";
"Loading logs..." = "Loading logs...";
"Loading module information..." = "Loading module information...";
"Loading Stream" = "Loading Stream";
/* Logging */
"Log Debug Info" = "Log Debug Info";
"Log Filters" = "Log Filters";
"Log In with AniList" = "Log In with AniList";
"Log In with Trakt" = "Log In with Trakt";
"Log Out from AniList" = "Log Out from AniList";
"Log Out from Trakt" = "Log Out from Trakt";
"Log Types" = "Log Types";
"Logged in as" = "Logged in as";
"Logged in as " = "Logged in as ";
/* Logs and Settings */
"Logs" = "Logs";
"Long press Skip" = "Long press Skip";
"MAIN" = "Main Settings";
"Main Developer" = "Main Developer";
"MAIN SETTINGS" = "MAIN SETTINGS";
/* Media Actions */
"Mark All Previous Watched" = "Mark All Previous Watched";
"Mark as Watched" = "Mark as Watched";
"Mark Episode as Watched" = "Mark Episode as Watched";
"Mark Previous Episodes as Watched" = "Mark Previous Episodes as Watched";
"Mark watched" = "Mark watched";
"Match with AniList" = "Match with AniList";
"Match with TMDB" = "Match with TMDB";
"Matched ID: %lld" = "Matched ID: %lld";
"Matched with: %@" = "Matched with: %@";
"Max Concurrent Downloads" = "Max Concurrent Downloads";
/* Media Interface */
"Media Grid Layout" = "Media Grid Layout";
"Media Player" = "Media Player";
"Media View" = "Media View";
"Metadata Provider" = "Metadata Provider";
"Metadata Providers Order" = "Metadata Providers Order";
"Module Removed" = "Module Removed";
"Modules" = "Modules";
/* Headers */
"MODULES" = "MODULES";
"MORE" = "MORE";
/* Status Messages */
"No Active Downloads" = "No Active Downloads";
"No AniList matches found" = "No AniList matches found";
"No Data Available" = "No Data Available";
"No Downloads" = "No Downloads";
"No episodes available" = "No episodes available";
"No Episodes Available" = "No Episodes Available";
"No items to continue watching." = "No items to continue watching.";
"No matches found" = "No matches found";
"No Module Selected" = "No Module Selected";
"No Modules" = "No Modules";
"No Results Found" = "No Results Found";
"No Search Results Found" = "No Search Results Found";
"Nothing to Continue Watching" = "Nothing to Continue Watching";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Note that the modules will be replaced only if there is a different version string inside the JSON file.";
/* Actions */
"OK" = "OK";
"Open Community Library" = "Open Community Library";
/* External Services */
"Open in AniList" = "Open in AniList";
"Original Poster" = "Original Poster";
/* Playback */
"Paused" = "Paused";
"Play" = "Play";
"Player" = "Player";
/* System Messages */
"Please restart the app to apply the language change." = "Please restart the app to apply the language change.";
"Please select a module from settings" = "Please select a module from settings";
/* Interface */
"Portrait Columns" = "Portrait Columns";
"Progress bar Marker Color" = "Progress bar Marker Color";
"Provider: %@" = "Provider: %@";
/* Queue */
"Queue" = "Queue";
"Queued" = "Queued";
/* Content */
"Recently watched content will appear here." = "Recently watched content will appear here.";
/* Settings */
"Refresh Modules on Launch" = "Refresh Modules on Launch";
"Refresh Storage Info" = "Refresh Storage Info";
"Remember Playback speed" = "Remember Playback speed";
/* Actions */
"Remove" = "Remove";
"Remove All Cache" = "Remove All Cache";
/* File Management */
"Remove All Documents" = "Remove All Documents";
"Remove Documents" = "Remove Documents";
"Remove Downloaded Media" = "Remove Downloaded Media";
"Remove Downloads" = "Remove Downloads";
"Remove from Bookmarks" = "Remove from Bookmarks";
"Remove Item" = "Remove Item";
/* Support */
"Report an Issue" = "Report an Issue";
/* Reset Options */
"Reset" = "Reset";
"Reset AniList ID" = "Reset AniList ID";
"Reset Episode Progress" = "Reset Episode Progress";
"Reset progress" = "Reset progress";
"Reset Progress" = "Reset Progress";
/* System */
"Restart Required" = "Restart Required";
"Running Sora %@ - cranci1" = "Running Sora %@ - cranci1";
/* Actions */
"Save" = "Save";
"Search" = "Search";
/* Search */
"Search downloads" = "Search downloads";
"Search for something..." = "Search for something...";
"Search..." = "Search...";
/* Content */
"Season %d" = "Season %d";
"Season %lld" = "Season %lld";
"Segments Color" = "Segments Color";
/* Modules */
"Select Module" = "Select Module";
"Set Custom AniList ID" = "Set Custom AniList ID";
/* Interface */
"Settings" = "Settings";
"Shadow" = "Shadow";
"Show More (%lld more characters)" = "Show More (%lld more characters)";
"Show PiP Button" = "Show PiP Button";
"Show Skip 85s Button" = "Show Skip 85s Button";
"Show Skip Intro / Outro Buttons" = "Show Skip Intro / Outro Buttons";
"Shows" = "Shows";
"Size (%@)" = "Size (%@)";
"Skip Settings" = "Skip Settings";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ by cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate." = "Sora and cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate.";
"Sora GitHub Repository" = "Sora GitHub Repository";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur will always remain free with no ADs!";
/* Interface */
"Sort" = "Sort";
"Speed Settings" = "Speed Settings";
/* Playback */
"Start Watching" = "Start Watching";
"Start Watching Episode %d" = "Start Watching Episode %d";
"Storage Used" = "Storage Used";
"Stream" = "Stream";
"Streaming and video playback." = "Streaming and video playback.";
/* Subtitles */
"Subtitle Color" = "Subtitle Color";
"Subtitle Settings" = "Subtitle Settings";
/* Sync */
"Sync anime progress" = "Sync anime progress";
"Sync TV shows progress" = "Sync TV shows progress";
/* System */
"System" = "System";
/* Instructions */
"Tap a title to override the current match." = "Tap a title to override the current match.";
"Tap Skip" = "Tap Skip";
"Tap to manage your modules" = "Tap to manage your modules";
"Tap to select a module" = "Tap to select a module";
/* App Information */
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases.";
/* Interface */
"Thumbnails Width" = "Thumbnails Width";
"TMDB Match" = "TMDB Match";
"Trackers" = "Trackers";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Try different keywords";
"Try different search terms" = "Try different search terms";
/* Player Controls */
"Two Finger Hold for Pause" = "Two Finger Hold for Pause";
"Unable to fetch matches. Please try again later." = "Unable to fetch matches. Please try again later.";
"Use TMDB Poster Image" = "Use TMDB Poster Image";
/* Version */
"v%@" = "v%@";
"Video Player" = "Video Player";
/* Video Settings */
"Video Quality Preferences" = "Video Quality Preferences";
"View All" = "View All";
"Watched" = "Watched";
"Why am I not seeing any episodes?" = "Why am I not seeing any episodes?";
"WiFi Quality" = "WiFi Quality";
/* User Status */
"You are not logged in" = "You are not logged in";
"You have no items saved." = "You have no items saved.";
"Your downloaded episodes will appear here" = "Your downloaded episodes will appear here";
"Your recently watched content will appear here" = "Your recently watched content will appear here";
/* Download Settings */
"Download Settings" = "Download Settings";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources.";
"Quality" = "Quality";
"Max Concurrent Downloads" = "Max Concurrent Downloads";
"Allow Cellular Downloads" = "Allow Cellular Downloads";
"Quality Information" = "Quality Information";
"Maximum Quality Available" = "Maximum Quality Available";
/* Storage */
"Storage Management" = "Storage Management";
"Storage Used" = "Storage Used";
"Library cleared successfully" = "Library cleared successfully";
"All downloads deleted successfully" = "All downloads deleted successfully";
/* TabView */
"LibraryTab" = "Library";
"DownloadsTab" = "Downloads";
"SettingsTab" = "Settings";
"SearchTab" = "Search";
/* New additions */
"Recent searches" = "Recent searches";
"me frfr" = "me frfr";
"Data" = "Data";
"All Reading" = "All Reading";
"No Reading History" = "No Reading History";
"Books you're reading will appear here" = "Books you're reading will appear here";
"All Watching" = "All Watching";
"Continue Reading" = "Continue Reading";
"Nothing to Continue Reading" = "Nothing to Continue Reading";
"Your recently read novels will appear here" = "Your recently read novels will appear here";
"No Bookmarks" = "No Bookmarks";
"Add bookmarks to this collection" = "Add bookmarks to this collection";
"items" = "items";
"Chapter %d" = "Chapter %d";
"Episode %d" = "Episode %d";
"%d%%" = "%d%%";
"%d%% seen" = "%d%% seen";
"DownloadCountFormat" = "%d of %d";
"Error loading chapter" = "Error loading chapter";
"Font Size: %dpt" = "Font Size: %dpt";
"Line Spacing: %.1f" = "Line Spacing: %.1f";
"Line Spacing" = "Line Spacing";
"Margin: %dpx" = "Margin: %dpx";
"Margin" = "Margin";
"Auto Scroll Speed" = "Auto Scroll Speed";
"Speed" = "Speed";
"Speed: %.1fx" = "Speed: %.1fx";
"Matched %@: %@" = "Matched %@: %@";
"Enter the AniList ID for this series" = "Enter the AniList ID for this series";
/* New additions */
"Create Collection" = "Create Collection";
"Collection Name" = "Collection Name";
"Rename Collection" = "Rename Collection";
"Rename" = "Rename";
"All Reading" = "All Reading";
"Recently Added" = "Recently Added";
"Novel Title" = "Novel Title";
"Read Progress" = "Read Progress";
"Date Created" = "Date Created";
"Name" = "Name";
"Item Count" = "Item Count";
"Date Added" = "Date Added";
"Title" = "Title";
"Source" = "Source";
"Search reading..." = "Search reading...";
"Search collections..." = "Search collections...";
"Search bookmarks..." = "Search bookmarks...";
"%d items" = "%d items";
"Fetching Data" = "Fetching Data";
"Please wait while fetching." = "Please wait while fetching.";
"Start Reading" = "Start Reading";
"Chapters" = "Chapters";
"Completed" = "Completed";
"Drag to reorder" = "Drag to reorder";
"Drag to reorder sections" = "Drag to reorder sections";
"Library View" = "Library View";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Customize the sections shown in your library. You can reorder sections or disable them completely.";
"Library Sections Order" = "Library Sections Order";
"Completion Percentage" = "Completion Percentage";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app.";
"Translators" = "Translators";
"Paste URL" = "Paste URL";

View file

@ -0,0 +1,506 @@
/* General */
"About" = "Acerca de";
"About Sora" = "Acerca de Sora";
"Active" = "Activo";
"Active Downloads" = "Descargas activas";
"Actively downloading media can be tracked from here." = "El contenido multimedia que se está descargando activamente se puede seguir desde aquí.";
"Add Module" = "Añadir módulo";
"Adjust the number of media items per row in portrait and landscape modes." = "Ajusta el número de elementos multimedia por fila en modo vertical y horizontal.";
"Advanced" = "Avanzado";
"AKA Sulfur" = "También conocido como Sulfur";
"All Bookmarks" = "Todos los marcadores";
"All Watching" = "Todo lo que estás viendo";
"Also known as Sulfur" = "También conocido como Sulfur";
"AniList" = "AniList";
"AniList ID" = "ID de AniList";
"AniList Match" = "Coincidencia de AniList";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Se recopilan datos anónimos para mejorar la aplicación. No se recopila información personal. Esto puede deshabilitarse en cualquier momento.";
"App Info" = "Información de la aplicación";
"App Language" = "Idioma de la aplicación";
"App Storage" = "Almacenamiento de la aplicación";
"Appearance" = "Apariencia";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "¿Estás seguro/a de que quieres borrar todos los datos en caché? Esto ayudará a liberar espacio de almacenamiento.";
"Are you sure you want to delete '%@'?" = "¿Estás seguro/a de que quieres eliminar '%@'?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "¿Estás seguro/a de que quieres eliminar todos los %1$d episodios en '%2$@'?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "¿Estás seguro/a de que quieres eliminar todos los archivos descargados? Puedes optar por borrar solo la biblioteca y conservar los archivos descargados para uso futuro.";
"Are you sure you want to erase all app data? This action cannot be undone." = "¿Estás seguro/a de que quieres borrar todos los datos de la aplicación? Esta acción no se puede deshacer.";
/* Features */
"Background Enabled" = "Fondo habilitado";
"Bookmark items for an easier access later." = "Guarda los elementos en marcadores para facilitar el acceso más adelante.";
"Bookmarks" = "Marcadores";
"Bottom Padding" = "Espacio inferior";
"Cancel" = "Cancelar";
"Cellular Quality" = "Calidad con datos móviles";
"Check out some community modules here!" = "¡Echa un vistazo a algunos módulos de la comunidad aquí!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Elige la resolución de video preferida para conexiones WiFi y de datos móviles. Las resoluciones más altas usan más datos pero brindan mejor calidad. Si la calidad exacta no está disponible, se seleccionará automáticamente la opción más cercana.\n\nNota: No todas las fuentes de video y reproductores admiten la selección de calidad. Esta función funciona mejor con transmisiones HLS usando el reproductor Sora.";
"Clear" = "Borrar";
"Clear All Downloads" = "Borrar todas las descargas";
"Clear Cache" = "Borrar caché";
"Clear Library Only" = "Borrar solo la biblioteca";
"Clear Logs" = "Borrar registros";
"Click the plus button to add a module!" = "¡Haz clic en el botón de más para añadir un módulo!";
"Continue Watching" = "Continuar viendo";
"Continue Watching Episode %d" = "Continuar viendo el episodio %d";
"Contributors" = "Colaboradores";
"Copied to Clipboard" = "Copiado al portapapeles";
"Copy to Clipboard" = "Copiar al portapapeles";
"Copy URL" = "Copiar enlace";
/* Episodes */
"%lld Episodes" = "%lld Episodios";
"%lld of %lld" = "%lld de %lld";
"%lld-%lld" = "%lld-%lld";
"%lld%% seen" = "%lld%% visto(s)";
"Episode %lld" = "Episodio %lld";
"Episodes" = "Episodios";
"Episodes might not be available yet or there could be an issue with the source." = "Es posible que los episodios aún no estén disponibles o que haya un problema con la fuente.";
"Episodes Range" = "Rango de episodios";
/* System */
"cranci1" = "cranci1";
"Dark" = "Oscuro";
"DATA & LOGS" = "DATOS Y REGISTROS";
"Debug" = "Depurar";
"Debugging and troubleshooting." = "Depuración y resolución de problemas.";
/* Actions */
"Delete" = "Eliminar";
"Delete All" = "Eliminar todo";
"Delete All Downloads" = "Eliminar todas las descargas";
"Delete All Episodes" = "Eliminar todos los episodios";
"Delete Download" = "Eliminar descarga";
"Delete Episode" = "Eliminar episodio";
/* Player */
"Double Tap to Seek" = "Toca dos veces para buscar";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Al tocar dos veces la pantalla en sus lados, se saltará con la configuración de toque corto.";
/* Downloads */
"Download" = "Descargar";
"Download Episode" = "Descargar episodio";
"Download Summary" = "Resumen de descarga";
"Download This Episode" = "Descargar este episodio";
"Downloaded" = "Descargado";
"Downloaded Shows" = "Programas descargados";
"Downloading" = "Descargando";
"Downloads" = "Descargas";
/* Settings */
"Enable Analytics" = "Habilitar análisis";
"Enable Subtitles" = "Habilitar subtítulos";
/* Data Management */
"Erase" = "Borrar";
"Erase all App Data" = "Borrar todos los datos de la aplicación";
"Erase App Data" = "Borrar datos de la aplicación";
/* Errors */
"Error" = "Error";
"Error Fetching Results" = "Error al obtener resultados";
"Errors and critical issues." = "Errores y problemas críticos.";
"Failed to load contributors" = "No se pudieron cargar los colaboradores";
/* Features */
"Fetch Episode metadata" = "Obtener metadatos del episodio";
"Files Downloaded" = "Archivos descargados";
"Font Size" = "Tamaño de fuente";
/* Interface */
"Force Landscape" = "Forzar horizontal";
"General" = "General";
"General events and activities." = "Eventos y actividades generales.";
"General Preferences" = "Preferencias generales";
"Hide Splash Screen" = "Ocultar pantalla de inicio";
"HLS video downloading." = "Descarga de video HLS.";
"Hold Speed" = "Velocidad de retención";
/* Info */
"Info" = "Información";
"INFOS" = "INFORMACIÓN";
"Installed Modules" = "Módulos instalados";
"Interface" = "Interfaz";
/* Social */
"Join the Discord" = "Unirse a Discord";
/* Layout */
"Landscape Columns" = "Columnas horizontales";
"Language" = "Idioma";
"LESS" = "MENOS";
/* Library */
"Library" = "Biblioteca";
"License (GPLv3.0)" = "Licencia (GPLv3.0)";
"Light" = "Claro";
/* Loading States */
"Loading Episode %lld..." = "Cargando episodio %lld...";
"Loading logs..." = "Cargando registros...";
"Loading module information..." = "Cargando información del módulo...";
"Loading Stream" = "Cargando transmisión";
/* Logging */
"Log Debug Info" = "Registrar información de depuración";
"Log Filters" = "Filtros de registro";
"Log In with AniList" = "Iniciar sesión con AniList";
"Log In with Trakt" = "Iniciar sesión con Trakt";
"Log Out from AniList" = "Cerrar sesión de AniList";
"Log Out from Trakt" = "Cerrar sesión de Trakt";
"Log Types" = "Tipos de registro";
"Logged in as" = "Sesión iniciada como";
"Logged in as " = "Sesión iniciada como ";
/* Logs and Settings */
"Logs" = "Registros";
"Long press Skip" = "Mantener presionado para saltar";
"MAIN" = "PRINCIPAL";
"Main Developer" = "Desarrollador principal";
"MAIN SETTINGS" = "AJUSTES PRINCIPALES";
/* Media Actions */
"Mark All Previous Watched" = "Marcar todos los anteriores como vistos";
"Mark as Watched" = "Marcar como visto";
"Mark Episode as Watched" = "Marcar episodio como visto";
"Mark Previous Episodes as Watched" = "Marcar episodios anteriores como vistos";
"Mark watched" = "Marcar como visto";
"Match with AniList" = "Coincidir con AniList";
"Match with TMDB" = "Coincidir con TMDB";
"Matched ID: %lld" = "ID coincidente: %lld";
"Matched with: %@" = "Coincidido con: %@";
"Max Concurrent Downloads" = "Máx. descargas simultáneas";
/* Media Interface */
"Media Grid Layout" = "Diseño de cuadrícula multimedia";
"Media Player" = "Reproductor multimedia";
"Media View" = "Vista multimedia";
"Metadata Provider" = "Proveedor de metadatos";
"Metadata Providers Order" = "Orden de proveedores de metadatos";
"Module Removed" = "Módulo eliminado";
"Modules" = "Módulos";
/* Headers */
"MODULES" = "MÓDULOS";
"MORE" = "MÁS";
/* Status Messages */
"No Active Downloads" = "No hay descargas activas";
"No AniList matches found" = "No se encontraron coincidencias en AniList";
"No Data Available" = "No hay datos disponibles";
"No Downloads" = "No hay descargas";
"No episodes available" = "No hay episodios disponibles";
"No Episodes Available" = "No hay episodios disponibles";
"No items to continue watching." = "No hay elementos para seguir viendo.";
"No matches found" = "No se encontraron coincidencias";
"No Module Selected" = "Ningún módulo seleccionado";
"No Modules" = "No hay módulos";
"No Results Found" = "No se encontraron resultados";
"No Search Results Found" = "No se encontraron resultados de búsqueda";
"Nothing to Continue Watching" = "Nada para continuar viendo";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Ten en cuenta que los módulos se reemplazarán solo si hay una cadena de versión diferente dentro del archivo JSON.";
/* Actions */
"OK" = "OK";
"Open Community Library" = "Abrir biblioteca de la comunidad";
/* External Services */
"Open in AniList" = "Abrir en AniList";
"Original Poster" = "Póster original";
/* Playback */
"Paused" = "Pausado";
"Play" = "Reproducir";
"Player" = "Reproductor";
/* System Messages */
"Please restart the app to apply the language change." = "Por favor, reinicia la aplicación para aplicar el cambio de idioma.";
"Please select a module from settings" = "Por favor, selecciona un módulo desde la configuración";
/* Interface */
"Portrait Columns" = "Columnas verticales";
"Progress bar Marker Color" = "Color del marcador de la barra de progreso";
"Provider: %@" = "Proveedor: %@";
/* Queue */
"Queue" = "Cola";
"Queued" = "En cola";
/* Content */
"Recently watched content will appear here." = "El contenido visto recientemente aparecerá aquí.";
/* Settings */
"Refresh Modules on Launch" = "Actualizar módulos al iniciar";
"Refresh Storage Info" = "Actualizar información de almacenamiento";
"Remember Playback speed" = "Recordar velocidad de reproducción";
/* Actions */
"Remove" = "Eliminar";
"Remove All Cache" = "Eliminar toda la caché";
/* File Management */
"Remove All Documents" = "Eliminar todos los documentos";
"Remove Documents" = "Eliminar documentos";
"Remove Downloaded Media" = "Eliminar contenido multimedia descargado";
"Remove Downloads" = "Eliminar descargas";
"Remove from Bookmarks" = "Eliminar de marcadores";
"Remove Item" = "Eliminar elemento";
/* Support */
"Report an Issue" = "Reportar un problema";
/* Reset Options */
"Reset" = "Restablecer";
"Reset AniList ID" = "Restablecer ID de AniList";
"Reset Episode Progress" = "Restablecer progreso del episodio";
"Reset progress" = "Restablecer progreso";
"Reset Progress" = "Restablecer progreso";
/* System */
"Restart Required" = "Reinicio requerido";
"Running Sora %@ - cranci1" = "Ejecutando Sora %@ - cranci1";
/* Actions */
"Save" = "Guardar";
"Search" = "Buscar";
/* Search */
"Search downloads" = "Buscar descargas";
"Search for something..." = "Buscar algo...";
"Search..." = "Buscar...";
/* Content */
"Season %d" = "Temporada %d";
"Season %lld" = "Temporada %lld";
"Segments Color" = "Color de segmentos";
/* Modules */
"Select Module" = "Seleccionar módulo";
"Set Custom AniList ID" = "Establecer ID de AniList personalizado";
/* Interface */
"Settings" = "Ajustes";
"Shadow" = "Sombra";
"Show More (%lld more characters)" = "Mostrar más (%lld caracteres más)";
"Show PiP Button" = "Mostrar botón PiP";
"Show Skip 85s Button" = "Mostrar botón Saltar 85s";
"Show Skip Intro / Outro Buttons" = "Mostrar botones Saltar introducción/final";
"Shows" = "Programas";
"Size (%@)" = "Tamaño (%@)";
"Skip Settings" = "Configuración de saltar";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Algunas funciones están limitadas al reproductor Sora y Default, como Forzar horizontal, velocidad de retención e incrementos de tiempo de salto personalizados.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ por cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.
Also note that progress updates may not be 100% accurate." = "Sora y cranci1 no están afiliados a AniList ni a Trakt de ninguna manera.
Ten en cuenta también que las actualizaciones de progreso pueden no ser 100% precisas.";
"Sora GitHub Repository" = "Repositorio de Sora en GitHub";
"Sora/Sulfur will always remain free with no ADs!" = "¡Sora/Sulfur siempre será gratis y sin anuncios!";
/* Interface */
"Sort" = "Ordenar";
"Speed Settings" = "Configuración de velocidad";
/* Playback */
"Start Watching" = "Empezar a ver";
"Start Watching Episode %d" = "Empezar a ver episodio %d";
"Storage Used" = "Almacenamiento usado";
"Stream" = "Transmisión";
"Streaming and video playback." = "Transmisión y reproducción de video.";
/* Subtitles */
"Subtitle Color" = "Color de subtítulos";
"Subtitle Settings" = "Configuración de subtítulos";
/* Sync */
"Sync anime progress" = "Sincronizar progreso de anime";
"Sync TV shows progress" = "Sincronizar progreso de programas de TV";
/* System */
"System" = "Sistema";
/* Instructions */
"Tap a title to override the current match." = "Toca un título para anular la coincidencia actual.";
"Tap Skip" = "Tocar para saltar";
"Tap to manage your modules" = "Toca para administrar tus módulos";
"Tap to select a module" = "Toca para seleccionar un módulo";
/* App Information */
"The app cache helps the app load images faster.
Clearing the Documents folder will delete all downloaded modules.
Do not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "La caché de la aplicación ayuda a que la aplicación cargue las imágenes más rápido.
Al borrar la carpeta Documentos, se eliminarán todos los módulos descargados.
No borres los datos de la aplicación a menos que comprendas las consecuencias; podría hacer que la aplicación funcione mal.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.
For episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "El rango de episodios controla cuántos episodios aparecen en cada página. Los episodios se agrupan en conjuntos (como 1-25, 26-50, etc.), lo que te permite navegar por ellos más fácilmente.
Para los metadatos del episodio, se refiere a la miniatura y el título del episodio, ya que a veces puede contener spoilers.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "El módulo proporcionó solo un episodio, esto es muy probable que sea una película, por lo que decidimos hacer pantallas separadas para estos casos.";
/* Interface */
"Thumbnails Width" = "Ancho de miniaturas";
"TMDB Match" = "Coincidencia de TMDB";
"Trackers" = "Rastreadores";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Prueba con diferentes palabras clave";
"Try different search terms" = "Prueba con diferentes términos de búsqueda";
/* Player Controls */
"Two Finger Hold for Pause" = "Mantén presionado con dos dedos para pausar";
"Unable to fetch matches. Please try again later." = "No se pudieron obtener las coincidencias. Por favor, inténtalo de nuevo más tarde.";
"Use TMDB Poster Image" = "Usar imagen de póster de TMDB";
/* Version */
"v%@" = "v%@";
"Video Player" = "Reproductor de video";
/* Video Settings */
"Video Quality Preferences" = "Preferencias de calidad de video";
"View All" = "Ver todo";
"Watched" = "Visto";
"Why am I not seeing any episodes?" = "¿Por qué no veo ningún episodio?";
"WiFi Quality" = "Calidad de WiFi";
/* User Status */
"You are not logged in" = "No has iniciado sesión";
"You have no items saved." = "No tienes elementos guardados.";
"Your downloaded episodes will appear here" = "Tus episodios descargados aparecerán aquí";
"Your recently watched content will appear here" = "Tu contenido visto recientemente aparecerá aquí";
/* Download Settings */
"Download Settings" = "Configuración de descarga";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "El número máximo de descargas simultáneas controla cuántos episodios se pueden descargar al mismo tiempo. Valores más altos pueden usar más ancho de banda y recursos del dispositivo.";
"Quality" = "Calidad";
"Max Concurrent Downloads" = "Máx. descargas simultáneas";
"Allow Cellular Downloads" = "Permitir descargas con datos móviles";
"Quality Information" = "Información de calidad";
/* Storage */
"Storage Management" = "Gestión de almacenamiento";
"Storage Used" = "Almacenamiento usado";
"Library cleared successfully" = "Biblioteca borrada con éxito";
"All downloads deleted successfully" = "Todas las descargas eliminadas con éxito";
/* New localizations */
"Recent searches" = "Búsquedas recientes";
"me frfr" = "yo frfr";
"Data" = "Datos";
"Maximum Quality Available" = "Calidad máxima disponible";
"DownloadCountFormat" = "%d de %d";
"Error loading chapter" = "Error al cargar el capítulo";
"Font Size: %dpt" = "Tamaño de fuente: %dpt";
"Line Spacing: %.1f" = "Espaciado de línea: %.1f";
"Line Spacing" = "Espaciado de línea";
"Margin: %dpx" = "Margen: %dpx";
"Margin" = "Margen";
"Auto Scroll Speed" = "Velocidad de desplazamiento automático";
"Speed" = "Velocidad";
"Speed: %.1fx" = "Velocidad: %.1fx";
"Matched %@: %@" = "Coincidencia %@: %@";
"Enter the AniList ID for this series" = "Introduce el ID de AniList para esta serie";
/* Added missing localizations */
"Create Collection" = "Crear colección";
"Collection Name" = "Nombre de la colección";
"Rename Collection" = "Renombrar colección";
"Rename" = "Renombrar";
"All Reading" = "Todas las lecturas";
"Recently Added" = "Añadido recientemente";
"Novel Title" = "Título de la novela";
"Read Progress" = "Progreso de lectura";
"Date Created" = "Fecha de creación";
"Name" = "Nombre";
"Item Count" = "Cantidad de elementos";
"Date Added" = "Fecha de añadido";
"Title" = "Título";
"Source" = "Fuente";
"Search reading..." = "Buscar en lecturas...";
"Search collections..." = "Buscar en colecciones...";
"Search bookmarks..." = "Buscar en marcadores...";
"%d items" = "%d elementos";
"Fetching Data" = "Obteniendo datos";
"Please wait while fetching." = "Por favor, espere mientras se obtienen los datos.";
"Start Reading" = "Comenzar a leer";
"Chapters" = "Capítulos";
"Completed" = "Completado";
"Drag to reorder" = "Arrastrar para reordenar";
"Drag to reorder sections" = "Arrastrar para reordenar secciones";
"Library View" = "Vista de biblioteca";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Personaliza las secciones que se muestran en tu biblioteca. Puedes reordenarlas o desactivarlas completamente.";
"Library Sections Order" = "Orden de secciones de la biblioteca";
"Completion Percentage" = "Porcentaje de finalización";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Algunas funciones están limitadas al reproductor Sora y al predeterminado, como el modo horizontal forzado, la velocidad de retención y los saltos de tiempo personalizados.\n\nEl ajuste del porcentaje de finalización determina en qué punto antes del final de un vídeo la app lo marcará como completado en AniList y Trakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "La caché de la app ayuda a cargar imágenes más rápido.\n\nBorrar la carpeta Documentos eliminará todos los módulos descargados.\n\nBorrar los datos de la app eliminará todos tus ajustes y datos.";
"Translators" = "Traductores";
"Paste URL" = "Pegar URL";
/* Added missing localizations */
"Series Title" = "Título de la serie";
"Content Source" = "Fuente de contenido";
"Watch Progress" = "Progreso de visualización";
"All Reading" = "Todo lo que lees";
"Nothing to Continue Reading" = "Nada para continuar leyendo";
"Your recently read novels will appear here" = "Tus novelas leídas recientemente aparecerán aquí";
"No Bookmarks" = "Sin marcadores";
"Add bookmarks to this collection" = "Agrega marcadores a esta colección";
"items" = "elementos";
"All Watching" = "Todo lo que ves";
"No Reading History" = "Sin historial de lectura";
"Books you're reading will appear here" = "Los libros que estás leyendo aparecerán aquí";
"Create Collection" = "Crear colección";
"Collection Name" = "Nombre de la colección";
"Rename Collection" = "Renombrar colección";
"Rename" = "Renombrar";
"Novel Title" = "Título de la novela";
"Read Progress" = "Progreso de lectura";
"Date Created" = "Fecha de creación";
"Name" = "Nombre";
"Item Count" = "Cantidad de elementos";
"Date Added" = "Fecha de agregado";
"Title" = "Título";
"Source" = "Fuente";
"Search reading..." = "Buscar en lecturas...";
"Search collections..." = "Buscar en colecciones...";
"Search bookmarks..." = "Buscar en marcadores...";
"%d items" = "%d elementos";
"Fetching Data" = "Obteniendo datos";
"Please wait while fetching." = "Por favor, espera mientras se obtienen los datos.";
"Start Reading" = "Comenzar a leer";
"Chapters" = "Capítulos";
"Completed" = "Completado";
"Drag to reorder" = "Arrastra para reordenar";
"Drag to reorder sections" = "Arrastra para reordenar secciones";
"Library View" = "Vista de biblioteca";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Personaliza las secciones que se muestran en tu biblioteca. Puedes reordenar secciones o deshabilitarlas completamente.";
"Library Sections Order" = "Orden de secciones de la biblioteca";
"Completion Percentage" = "Porcentaje de finalización";
"Translators" = "Traductores";
"Paste URL" = "Pegar URL";
"Collections" = "Colecciones";
"Continue Reading" = "Continuar leyendo";
"Backup & Restore" = "Copia de seguridad y restaurar";
"Export Backup" = "Exportar copia de seguridad";
"Import Backup" = "Importar copia de seguridad";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Aviso: Esta función aún es experimental. Por favor, verifica tus datos después de exportar/importar.";
"Backup" = "Copia de seguridad";

View file

@ -0,0 +1,494 @@
/* General */
"About" = "À propos";
"About Sora" = "À propos de Sora";
"Active" = "Actif";
"Active Downloads" = "Téléchargements actifs";
"Actively downloading media can be tracked from here." = "Les médias en cours de téléchargement peuvent être suivis ici.";
"Add Module" = "Ajouter un module";
"Adjust the number of media items per row in portrait and landscape modes." = "Ajustez le nombre d'éléments multimédias par ligne en mode portrait et paysage.";
"Advanced" = "Avancé";
"AKA Sulfur" = "Aussi connu sous le nom de Sulfur";
"All Bookmarks" = "Tous les favoris";
"All Watching" = "Tout ce que je regarde";
"Also known as Sulfur" = "Aussi connu sous le nom de Sulfur";
"AniList" = "AniList";
"AniList ID" = "ID AniList";
"AniList Match" = "Correspondance AniList";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Des données anonymes sont collectées pour améliorer l'application. Aucune information personnelle n'est collectée. Ceci peut être désactivé à tout moment.";
"App Info" = "Infos sur l'application";
"App Language" = "Langue de l'application";
"App Storage" = "Stockage de l'application";
"Appearance" = "Apparence";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Voulez-vous vraiment vider toutes les données en cache ? Cela aidera à libérer de l'espace de stockage.";
"Are you sure you want to delete '%@'?" = "Voulez-vous vraiment supprimer '%@' ?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "Voulez-vous vraiment supprimer les %1$d épisodes de '%2$@' ?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Voulez-vous vraiment supprimer tous les fichiers téléchargés ? Vous pouvez choisir de vider uniquement la bibliothèque tout en conservant les fichiers téléchargés pour une utilisation future.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Voulez-vous vraiment effacer toutes les données de l'application ? Cette action est irréversible.";
/* Features */
"Background Enabled" = "Activé en arrière-plan";
"Bookmark items for an easier access later." = "Mettez des éléments en favoris pour un accès plus facile plus tard.";
"Bookmarks" = "Favoris";
"Bottom Padding" = "Marge intérieure inférieure";
"Cancel" = "Annuler";
"Cellular Quality" = "Qualité cellulaire";
"Check out some community modules here!" = "Découvrez quelques modules communautaires ici !";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Choisissez la résolution vidéo préférée pour les connexions WiFi et cellulaires. Les résolutions plus élevées utilisent plus de données mais offrent une meilleure qualité. Si la qualité exacte n'est pas disponible, l'option la plus proche sera sélectionnée automatiquement.\n\nRemarque : toutes les sources vidéo et tous les lecteurs ne prennent pas en charge la sélection de la qualité. Cette fonctionnalité fonctionne mieux avec les flux HLS utilisant le lecteur Sora.";
"Clear" = "Vider";
"Clear All Downloads" = "Vider tous les téléchargements";
"Clear Cache" = "Vider le cache";
"Clear Library Only" = "Vider la bibliothèque uniquement";
"Clear Logs" = "Vider les journaux";
"Click the plus button to add a module!" = "Cliquez sur le bouton plus pour ajouter un module !";
"Continue Watching" = "Reprendre la lecture";
"Continue Watching Episode %d" = "Reprendre l'épisode %d";
"Contributors" = "Contributeurs";
"Copied to Clipboard" = "Copié dans le presse-papiers";
"Copy to Clipboard" = "Copier dans le presse-papiers";
"Copy URL" = "Copier l'URL";
/* Episodes */
"%lld Episodes" = "%lld épisodes";
"%lld of %lld" = "%1$lld sur %2$lld";
"%lld-%lld" = "%1$lld-%2$lld";
"%lld%% seen" = "%lld%% vus";
"Episode %lld" = "Épisode %lld";
"Episodes" = "Épisodes";
"Episodes might not be available yet or there could be an issue with the source." = "Les épisodes ne sont peut-être pas encore disponibles ou il y a un problème avec la source.";
"Episodes Range" = "Plage d'épisodes";
/* System */
"cranci1" = "cranci1";
"Dark" = "Sombre";
"DATA & LOGS" = "DONNÉES & JOURNEAUX";
"Debug" = "Débogage";
"Debugging and troubleshooting." = "Débogage et résolution de problèmes.";
/* Actions */
"Delete" = "Supprimer";
"Delete All" = "Tout supprimer";
"Delete All Downloads" = "Supprimer tous les téléchargements";
"Delete All Episodes" = "Supprimer tous les épisodes";
"Delete Download" = "Supprimer le téléchargement";
"Delete Episode" = "Supprimer l'épisode";
/* Player */
"Double Tap to Seek" = "Touchez deux fois pour avancer";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Appuyer deux fois sur les côtés de l'écran permet de sauter avec le réglage de pression courte.";
/* Downloads */
"Download" = "Télécharger";
"Download Episode" = "Télécharger l'épisode";
"Download Summary" = "Résumé du téléchargement";
"Download This Episode" = "Télécharger cet épisode";
"Downloaded" = "Téléchargé";
"Downloaded Shows" = "Séries téléchargées";
"Downloading" = "Téléchargement en cours";
"Downloads" = "Téléchargements";
/* Settings */
"Enable Analytics" = "Activer les analyses";
"Enable Subtitles" = "Activer les sous-titres";
/* Data Management */
"Erase" = "Effacer";
"Erase all App Data" = "Effacer toutes les données de l'app";
"Erase App Data" = "Effacer les données de l'app";
/* Errors */
"Error" = "Erreur";
"Error Fetching Results" = "Erreur lors de la récupération des résultats";
"Errors and critical issues." = "Erreurs et problèmes critiques.";
"Failed to load contributors" = "Échec du chargement des contributeurs";
/* Features */
"Fetch Episode metadata" = "Récupérer les métadonnées de l'épisode";
"Files Downloaded" = "Fichiers téléchargés";
"Font Size" = "Taille de la police";
/* Interface */
"Force Landscape" = "Forcer le mode paysage";
"General" = "Général";
"General events and activities." = "Événements et activités générales.";
"General Preferences" = "Préférences générales";
"Hide Splash Screen" = "Masquer l'écran de démarrage";
"HLS video downloading." = "Téléchargement de vidéos HLS.";
"Hold Speed" = "Vitesse de maintien";
/* Info */
"Info" = "Infos";
"INFOS" = "INFOS";
"Installed Modules" = "Modules installés";
"Interface" = "Interface";
/* Social */
"Join the Discord" = "Rejoindre le Discord";
/* Layout */
"Landscape Columns" = "Colonnes en paysage";
"Language" = "Langue";
"LESS" = "MOINS";
/* Library */
"Library" = "Bibliothèque";
"License (GPLv3.0)" = "Licence (GPLv3.0)";
"Light" = "Clair";
/* Loading States */
"Loading Episode %lld..." = "Chargement de l'épisode %lld...";
"Loading logs..." = "Chargement des journaux...";
"Loading module information..." = "Chargement des informations du module...";
"Loading Stream" = "Chargement du flux";
/* Logging */
"Log Debug Info" = "Journaliser les infos de débogage";
"Log Filters" = "Filtres de journal";
"Log In with AniList" = "Se connecter avec AniList";
"Log In with Trakt" = "Se connecter avec Trakt";
"Log Out from AniList" = "Se déconnecter d'AniList";
"Log Out from Trakt" = "Se déconnecter de Trakt";
"Log Types" = "Types de journaux";
"Logged in as" = "Connecté en tant que";
"Logged in as " = "Connecté en tant que ";
/* Logs and Settings */
"Logs" = "Journaux";
"Long press Skip" = "Pression longue pour sauter";
"MAIN" = "PRINCIPAL";
"Main Developer" = "Développeur principal";
"MAIN SETTINGS" = "RÉGLAGES PRINCIPAUX";
/* Media Actions */
"Mark All Previous Watched" = "Marquer tout comme vu";
"Mark as Watched" = "Marquer comme vu";
"Mark Episode as Watched" = "Marquer l'épisode comme vu";
"Mark Previous Episodes as Watched" = "Marquer les épisodes précédents comme vus";
"Mark watched" = "Marquer comme vu";
"Match with AniList" = "Associer avec AniList";
"Match with TMDB" = "Associer avec TMDB";
"Matched ID: %lld" = "ID associé : %lld";
"Matched with: %@" = "Associé avec : %@";
"Max Concurrent Downloads" = "Téléchargements simultanés max";
/* Media Interface */
"Media Grid Layout" = "Mise en page de la grille média";
"Media Player" = "Lecteur multimédia";
"Media View" = "Vue multimédia";
"Metadata Provider" = "Fournisseur de métadonnées";
"Metadata Providers Order" = "Ordre des fournisseurs de métadonnées";
"Module Removed" = "Module supprimé";
"Modules" = "Modules";
/* Headers */
"MODULES" = "MODULES";
"MORE" = "PLUS";
/* Status Messages */
"No Active Downloads" = "Aucun téléchargement actif";
"No AniList matches found" = "Aucune correspondance AniList trouvée";
"No Data Available" = "Aucune donnée disponible";
"No Downloads" = "Aucun téléchargement";
"No episodes available" = "Aucun épisode disponible";
"No Episodes Available" = "Aucun épisode disponible";
"No items to continue watching." = "Aucun élément à reprendre.";
"No matches found" = "Aucune correspondance trouvée";
"No Module Selected" = "Aucun module sélectionné";
"No Modules" = "Aucun module";
"No Results Found" = "Aucun résultat trouvé";
"No Search Results Found" = "Aucun résultat de recherche trouvé";
"Nothing to Continue Watching" = "Rien à reprendre";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Notez que les modules ne seront remplacés que s'il existe une chaîne de version différente dans le fichier JSON.";
/* Actions */
"OK" = "OK";
"Open Community Library" = "Ouvrir la bibliothèque communautaire";
/* External Services */
"Open in AniList" = "Ouvrir dans AniList";
"Original Poster" = "Affiche originale";
/* Playback */
"Paused" = "En pause";
"Play" = "Lecture";
"Player" = "Lecteur";
/* System Messages */
"Please restart the app to apply the language change." = "Veuillez redémarrer l'application pour appliquer le changement de langue.";
"Please select a module from settings" = "Veuillez sélectionner un module dans les paramètres";
/* Interface */
"Portrait Columns" = "Colonnes en portrait";
"Progress bar Marker Color" = "Couleur du marqueur de la barre de progression";
"Provider: %@" = "Fournisseur : %@";
/* Queue */
"Queue" = "File d'attente";
"Queued" = "En attente";
/* Content */
"Recently watched content will appear here." = "Le contenu récemment visionné apparaîtra ici.";
/* Settings */
"Refresh Modules on Launch" = "Actualiser les modules au lancement";
"Refresh Storage Info" = "Actualiser les infos de stockage";
"Remember Playback speed" = "Mémoriser la vitesse de lecture";
/* Actions */
"Remove" = "Supprimer";
"Remove All Cache" = "Supprimer tout le cache";
/* File Management */
"Remove All Documents" = "Supprimer tous les documents";
"Remove Documents" = "Supprimer les documents";
"Remove Downloaded Media" = "Supprimer les médias téléchargés";
"Remove Downloads" = "Supprimer les téléchargements";
"Remove from Bookmarks" = "Supprimer des favoris";
"Remove Item" = "Supprimer l'élément";
/* Support */
"Report an Issue" = "Signaler un problème";
/* Reset Options */
"Reset" = "Réinitialiser";
"Reset AniList ID" = "Réinitialiser l'ID AniList";
"Reset Episode Progress" = "Réinitialiser la progression de l'épisode";
"Reset progress" = "Réinitialiser la progression";
"Reset Progress" = "Réinitialiser la progression";
/* System */
"Restart Required" = "Redémarrage requis";
"Running Sora %@ - cranci1" = "Sora %@ en cours d'exécution - cranci1";
/* Actions */
"Save" = "Enregistrer";
"Search" = "Rechercher";
/* Search */
"Search downloads" = "Rechercher dans les téléchargements";
"Search for something..." = "Rechercher quelque chose...";
"Search..." = "Rechercher...";
/* Content */
"Season %d" = "Saison %d";
"Season %lld" = "Saison %lld";
"Segments Color" = "Couleur des segments";
/* Modules */
"Select Module" = "Sélectionner un module";
"Set Custom AniList ID" = "Définir un ID AniList personnalisé";
/* Interface */
"Settings" = "Paramètres";
"Shadow" = "Ombre";
"Show More (%lld more characters)" = "Afficher plus (%lld caractères de plus)";
"Show PiP Button" = "Afficher le bouton PiP";
"Show Skip 85s Button" = "Afficher le bouton Sauter 85s";
"Show Skip Intro / Outro Buttons" = "Afficher les boutons Sauter l'intro / l'outro";
"Shows" = "Séries";
"Size (%@)" = "Taille (%@)";
"Skip Settings" = "Réglages de saut";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Certaines fonctionnalités sont limitées au lecteur Sora et par défaut, telles que Forcer Paysage, Vitesse par pression longue et les incréments de saut de temps personnalisés.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ par cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate." = "Sora et cranci1 ne sont en aucun cas affiliés à AniList ou Trakt.\n\nNotez également que les mises à jour de progression peuvent ne pas être précises à 100%.";
"Sora GitHub Repository" = "Dépôt GitHub de Sora";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur restera toujours gratuit et sans publicités !";
/* Interface */
"Sort" = "Trier";
"Speed Settings" = "Réglages de vitesse";
/* Playback */
"Start Watching" = "Commencer à regarder";
"Start Watching Episode %d" = "Commencer l'épisode %d";
"Storage Used" = "Stockage utilisé";
"Stream" = "Flux";
"Streaming and video playback." = "Streaming et lecture vidéo.";
/* Subtitles */
"Subtitle Color" = "Couleur des sous-titres";
"Subtitle Settings" = "Réglages des sous-titres";
/* Sync */
"Sync anime progress" = "Synchroniser la progression des animes";
"Sync TV shows progress" = "Synchroniser la progression des séries TV";
/* System */
"System" = "Système";
/* Instructions */
"Tap a title to override the current match." = "Appuyez sur un titre pour remplacer la correspondance actuelle.";
"Tap Skip" = "Appuyer pour sauter";
"Tap to manage your modules" = "Appuyez pour gérer vos modules";
"Tap to select a module" = "Appuyez pour sélectionner un module";
/* App Information */
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "Le cache de l'application permet de charger les images plus rapidement.\n\nLa suppression du dossier Documents effacera tous les modules téléchargés.\n\nN'effacez pas les données de l'application à moins de comprendre les conséquences — cela pourrait entraîner un dysfonctionnement de l'application.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "La plage d'épisodes contrôle le nombre d'épisodes qui apparaissent sur chaque page. Les épisodes sont regroupés en séries (comme 125, 2650, etc.), ce qui vous permet de naviguer plus facilement.\n\nPour les métadonnées d'épisode, cela fait référence à la miniature et au titre de l'épisode, car ils peuvent parfois contenir des spoilers.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "Le module n'a fourni qu'un seul épisode, il s'agit très probablement d'un film, nous avons donc décidé de créer des écrans séparés pour ces cas.";
/* Interface */
"Thumbnails Width" = "Largeur des miniatures";
"TMDB Match" = "Correspondance TMDB";
"Trackers" = "Trackers";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Essayez différents mots-clés";
"Try different search terms" = "Essayez différents termes de recherche";
/* Player Controls */
"Two Finger Hold for Pause" = "Maintenir avec deux doigts pour mettre en pause";
"Unable to fetch matches. Please try again later." = "Impossible de récupérer les correspondances. Veuillez réessayer plus tard.";
"Use TMDB Poster Image" = "Utiliser l'image de l'affiche TMDB";
/* Version */
"v%@" = "v%@";
"Video Player" = "Lecteur vidéo";
/* Video Settings */
"Video Quality Preferences" = "Préférences de qualité vidéo";
"View All" = "Voir tout";
"Watched" = "Vu";
"Why am I not seeing any episodes?" = "Pourquoi ne vois-je aucun épisode ?";
"WiFi Quality" = "Qualité WiFi";
/* User Status */
"You are not logged in" = "Vous n'êtes pas connecté";
"You have no items saved." = "Vous n'avez aucun élément enregistré.";
"Your downloaded episodes will appear here" = "Vos épisodes téléchargés apparaîtront ici";
"Your recently watched content will appear here" = "Votre contenu récemment visionné apparaîtra ici";
/* Download Settings */
"Download Settings" = "Paramètres de téléchargement";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Le nombre maximum de téléchargements simultanés contrôle le nombre d'épisodes pouvant être téléchargés en même temps. Des valeurs plus élevées peuvent utiliser plus de bande passante et de ressources de l'appareil.";
"Quality" = "Qualité";
"Max Concurrent Downloads" = "Téléchargements simultanés max";
"Allow Cellular Downloads" = "Autoriser les téléchargements cellulaires";
"Quality Information" = "Informations sur la qualité";
/* Storage */
"Storage Management" = "Gestion du stockage";
"Storage Used" = "Stockage utilisé";
"Library cleared successfully" = "Bibliothèque vidée avec succès";
"All downloads deleted successfully" = "Tous les téléchargements ont été supprimés avec succès";
/* New additions */
"Recent searches" = "Recherches récentes";
"me frfr" = "moi frfr";
"Data" = "Données";
"Maximum Quality Available" = "Qualité maximale disponible";
/* Additional translations */
"DownloadCountFormat" = "%d sur %d";
"Error loading chapter" = "Erreur lors du chargement du chapitre";
"Font Size: %dpt" = "Taille de police : %dpt";
"Line Spacing: %.1f" = "Interligne : %.1f";
"Line Spacing" = "Interligne";
"Margin: %dpx" = "Marge : %dpx";
"Margin" = "Marge";
"Auto Scroll Speed" = "Vitesse de défilement automatique";
"Speed" = "Vitesse";
"Speed: %.1fx" = "Vitesse : %.1fx";
"Matched %@: %@" = "Correspondance %@ : %@";
"Enter the AniList ID for this series" = "Entrez l'ID AniList pour cette série";
/* Added missing localizations */
"Create Collection" = "Créer une collection";
"Collection Name" = "Nom de la collection";
"Rename Collection" = "Renommer la collection";
"Rename" = "Renommer";
"All Reading" = "Toutes les lectures";
"Recently Added" = "Ajouté récemment";
"Novel Title" = "Titre du roman";
"Read Progress" = "Progression de la lecture";
"Date Created" = "Date de création";
"Name" = "Nom";
"Item Count" = "Nombre d'éléments";
"Date Added" = "Date d'ajout";
"Title" = "Titre";
"Source" = "Source";
"Search reading..." = "Rechercher dans les lectures...";
"Search collections..." = "Rechercher dans les collections...";
"Search bookmarks..." = "Rechercher dans les favoris...";
"%d items" = "%d éléments";
"Fetching Data" = "Récupération des données";
"Please wait while fetching." = "Veuillez patienter pendant la récupération.";
"Start Reading" = "Commencer la lecture";
"Chapters" = "Chapitres";
"Completed" = "Terminé";
"Drag to reorder" = "Glisser pour réorganiser";
"Drag to reorder sections" = "Glisser pour réorganiser les sections";
"Library View" = "Vue de la bibliothèque";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Personnalisez les sections affichées dans votre bibliothèque. Vous pouvez réorganiser ou désactiver complètement les sections.";
"Library Sections Order" = "Ordre des sections de la bibliothèque";
"Completion Percentage" = "Pourcentage d'achèvement";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Certaines fonctionnalités sont limitées au lecteur Sora et au lecteur par défaut, comme le mode paysage forcé, la vitesse de maintien et les sauts de temps personnalisés.\n\nLe réglage du pourcentage d'achèvement détermine à quel moment avant la fin d'une vidéo l'application la marquera comme terminée sur AniList et Trakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "Le cache de l'application aide à charger les images plus rapidement.\n\nVider le dossier Documents supprimera tous les modules téléchargés.\n\nEffacer les données de l'application supprimera tous vos paramètres et données.";
"Translators" = "Traducteurs";
"Paste URL" = "Coller l'URL";
/* New additions */
"Series Title" = "Titre de la série";
"Content Source" = "Source du contenu";
"Watch Progress" = "Progression de visionnage";
"Recent searches" = "Recherches récentes";
"All Reading" = "Tout ce que vous lisez";
"Nothing to Continue Reading" = "Rien à continuer à lire";
"Your recently read novels will appear here" = "Vos romans récemment lus apparaîtront ici";
"No Bookmarks" = "Aucun favori";
"Add bookmarks to this collection" = "Ajoutez des favoris à cette collection";
"items" = "éléments";
"All Watching" = "Tout ce que vous regardez";
"No Reading History" = "Aucun historique de lecture";
"Books you're reading will appear here" = "Les livres que vous lisez apparaîtront ici";
"Create Collection" = "Créer une collection";
"Collection Name" = "Nom de la collection";
"Rename Collection" = "Renommer la collection";
"Rename" = "Renommer";
"Novel Title" = "Titre du roman";
"Read Progress" = "Progression de lecture";
"Date Created" = "Date de création";
"Name" = "Nom";
"Item Count" = "Nombre d'éléments";
"Date Added" = "Date d'ajout";
"Title" = "Titre";
"Source" = "Source";
"Search reading..." = "Rechercher dans les lectures...";
"Search collections..." = "Rechercher dans les collections...";
"Search bookmarks..." = "Rechercher dans les favoris...";
"%d items" = "%d éléments";
"Fetching Data" = "Récupération des données";
"Please wait while fetching." = "Veuillez patienter pendant la récupération.";
"Start Reading" = "Commencer la lecture";
"Chapters" = "Chapitres";
"Completed" = "Terminé";
"Drag to reorder" = "Faites glisser pour réorganiser";
"Drag to reorder sections" = "Faites glisser pour réorganiser les sections";
"Library View" = "Vue de la bibliothèque";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Personnalisez les sections affichées dans votre bibliothèque. Vous pouvez réorganiser les sections ou les désactiver complètement.";
"Library Sections Order" = "Ordre des sections de la bibliothèque";
"Completion Percentage" = "Pourcentage d'achèvement";
"Translators" = "Traducteurs";
"Paste URL" = "Coller l'URL";
/* New additions */
"Collections" = "Collections";
"Continue Reading" = "Continuer la lecture";
/* Backup & Restore */
"Backup & Restore" = "Sauvegarde & Restauration";
"Export Backup" = "Exporter la sauvegarde";
"Import Backup" = "Importer la sauvegarde";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Remarque : Cette fonctionnalité est encore expérimentale. Veuillez vérifier vos données après l'export/import.";
"Backup" = "Sauvegarde";

View file

@ -0,0 +1,425 @@
/* General */
"About" = "informazioni";
"About Sora" = "Informazioni su Sora";
"Active" = "Attivi";
"Active Downloads" = "Download Attivi";
"Actively downloading media can be tracked from here." = "I Download Attivi si possono trovare qui";
"Add Module" = "Aggiungi Modulo";
"Adjust the number of media items per row in portrait and landscape modes." = "Imposta il numero dei media per riga in vista orizzontale e in vista verticale";
"Advanced" = "Avanzate";
"AKA Sulfur" = "Aka Sulfur";
"All Bookmarks" = "Tutti i Preferiti";
"All Watching" = "Tutti i Guardati";
"Also known as Sulfur" = "Conosciuta anche come Sulfur";
"AniList" = "AniList";
"AniList ID" = "AniList ID";
"AniList Match" = "AniList Match";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Vengono raccolti dati anonimi per il miglioramento dell'App, Nessun Dato Personale viene Registrato, Questa opzione può essere disabilitata in ogni momento";
"App Info" = "Informazioni App";
"App Language" = "Lingua App";
"App Storage" = "Memoria Utilizzata dall'app";
"Appearance" = "Aspetto";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Sei sicuro di voler rimuovere tutti i dati presenti nella cache? Questo aiuterà a liberare spazio";
"Are you sure you want to delete '%@'?" = "Sei sicuro di volere rimuovere '%@'?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "Sei sicuro di voler eliminare tutti %1$d episodi in '%2$@'?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Siete sicuri di voler eliminare tutte le risorse scaricate? È possibile scegliere di cancellare solo la libreria, conservando i file scaricati per un uso futuro.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Sei sicuro di voler eliminare tutti i dati dell'app, questa azione non può essere cancellata";
/* Features */
"Background Enabled" = "Background Abilitato";
"Bookmark items for an easier access later." = "Imposta Preferiti per un accesso Più veloce dopo";
"Bookmarks" = "Preferiti";
"Bottom Padding" = "Distanza Sottotitoli";
"Cancel" = "Cancella";
"Cellular Quality" = "Qualità Dati Cellulare";
"Check out some community modules here!" = "Cerca i moduli della community qui!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Seleziona la Risoluzione video preferita per WiFi e Dati Mobili. Una Risoluzione più alta consuma più dati ma fornisce una qualità migliore. Se la stessa qualità non é disponibile la soluzione più vicina verrà scelta automaticamente \n\nNota:Non tutte le fonti e player supportano la scelta della qualità video.Questa Feature funziona meglio usando il Player integrato di Sora";
"Clear" = "Pulisci";
"Clear All Downloads" = "Pulisci Tutti i Download";
"Clear Cache" = "Pulisci Cache";
"Clear Library Only" = "Pulisci Solo La Libreria";
"Clear Logs" = "Pulisci Log";
"Click the plus button to add a module!" = "Premi il più per aggiungere un modulo";
"Continue Watching" = "Continua a Guardare";
"Continue Watching Episode %d" = "Continua a guardare %d";
"Contributors" = "Collaboratori";
"Copied to Clipboard" = "Copiato Negli Appunti";
"Copy to Clipboard" = "Copia negli Appunti";
"Copy URL" = "Copia URL";
/* Episodes */
"%lld Episodes" = "%lld Episodi";
"%lld of %lld" = "%lld di %lld";
"%lld-%lld" = "%lld-%lld";
"%lld%% seen" = "%lld%% visti";
"Episode %lld" = "Episodio %lld";
"Episodes" = "Episodi";
"Episodes might not be available yet or there could be an issue with the source." = "Gli episodi potrebbero non essere ancora disponibili o potrebbe esserci un errore con la fonte";
"Episodes Range" = "Range Episodi";
/* System */
"cranci1" = "cranc1";
"Dark" = "Dark";
"DATA & LOGS" = "DATA & LOGS";
"Debug" = "Debug";
"Debugging and troubleshooting." = "Debugging e Risoluzione Problemi";
/* Actions */
"Delete" = "Elimina";
"Delete All" = "Elimina Tutto";
"Delete All Downloads" = "Elimina Tutti I Download";
"Delete All Episodes" = "Elimina Tutti Gli Episodi";
"Delete Download" = "Elimina Download";
"Delete Episode" = "Elimina Episodio";
/* Player */
"Double Tap to Seek" = "Doppio Tap Per (Seek)";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Se l'opzione per saltare é abilitata, con questo premendo nei lati due volte é possibile skippare";
/* Downloads */
"Download" = "Scarica";
"Download Episode" = "Scarica Episodio";
"Download Summary" = "Scarica Riassunto";
"Download This Episode" = "Scarica questo Episodio";
"Downloaded" = "Scaricato";
"Downloaded Shows" = "Serie Scaricate";
"Downloading" = "Download in Corso";
"Downloads" = "Scaricati";
/* Settings */
"Enable Analytics" = "Attiva Analytics";
"Enable Subtitles" = "Attiva Sottotitoli";
/* Data Management */
"Erase" = "Cancella";
"Erase all App Data" = "Cancella Tutti i Dati dell'App";
"Erase App Data" = "Cancella Dati App";
/* Errors */
"Error" = "Errore";
"Error Fetching Results" = "Errore nella ricerca dei risultati";
"Errors and critical issues." = "Errori e Problemi Critici";
"Failed to load contributors" = "impossibile caricare i collaboratori";
/* Features */
"Fetch Episode metadata" = "Cercando i Metadata";
"Files Downloaded" = "File Scaricati";
"Font Size" = "Dimensione Carattere";
/* Interface */
"Force Landscape" = "Forza Vista Orizzontale";
"General" = "Generali";
"General events and activities." = "Eventi e Attività Generali";
"General Preferences" = "Preferenze Generali";
"Hide Splash Screen" = "Nascondi Splash Screen";
"HLS video downloading." = "Scaricamento Video HLS";
"Hold Speed" = "Mantieni per Velocizzare";
/* Info */
"Info" = "Info";
"INFOS" = "INFORMAZIONI";
"Installed Modules" = "Moduli Installati";
"Interface" = "Interfaccia";
/* Social */
"Join the Discord" = "Entra Nel Nostro Discord!";
/* Layout */
"Landscape Columns" = "Colonne in Vista Orizzontale";
"Language" = "Lingua";
"LESS" = "Mostra Meno";
/* Library */
"Library" = "Libreria";
"License (GPLv3.0)" = "Licenza (GPLv3.0)";
"Light" = "Tema Chiaro";
/* Loading States */
"Loading Episode %lld..." = "Caricando Gli Episodi %lld...";
"Loading logs..." = "Caricando i Logs";
"Loading module information..." = "Caricando le Informazioni del modulo";
"Loading Stream" = "Caricando lo Stream";
/* Logging */
"Log Debug Info" = "Info Log Debug";
"Log Filters" = "Filtri Log";
"Log In with AniList" = "Accedi a AniList";
"Log In with Trakt" = "Accedi a Trakt";
"Log Out from AniList" = "Esci da AniList";
"Log Out from Trakt" = "Esci da Trakt";
"Log Types" = "Tipo Log";
"Logged in as" = "Accesso Effettuato come";
"Logged in as " = "Accesso Effettuato come";
/* Logs and Settings */
"Logs" = "Logs";
"Long press Skip" = "Tener premuto per Skippare";
"MAIN" = "PRINCIPALE";
"Main Developer" = "Sviluppatore Principale";
"MAIN SETTINGS" = "Impostazioni principali";
/* Media Actions */
"Mark All Previous Watched" = "Segna Tutti I Precedenti Come visti";
"Mark as Watched" = "Segna Come Visto";
"Mark Episode as Watched" = "Segna Episodio come Visto";
"Mark Previous Episodes as Watched" = "Segna episodi precedenti come visti";
"Mark watched" = "Segna Come Completato";
"Match with AniList" = "Match with AniList";
"Match with TMDB" = "Match with TMDB";
"Matched ID: %lld" = "Matched ID: %lld";
"Matched with: %@" = "Matched with:%@";
"Max Concurrent Downloads" = "Download massimi in Contemporanea";
/* Media Interface */
"Media Grid Layout" = "Layout della Griglia dei Media";
"Media Player" = "Media Player";
"Media View" = "Vista media";
"Metadata Provider" = "Provider Metadata";
"Metadata Providers Order" = "Ordine dei Provider Metadata";
"Module Removed" = "Modulo Rimosso";
"Modules" = "Moduli";
/* Headers */
"MODULES" = "MODULI";
"MORE" = "PIÙ";
/* Status Messages */
"No Active Downloads" = "Nessun Download Attivo";
"No AniList matches found" = "Nessun";
"No Data Available" = "Nessun Dato Disponibile";
"No Downloads" = "Nessun Download";
"No episodes available" = "Nessun Episodio Disponibile";
"No Episodes Available" = "Nessun Episodio Disponibile";
"No items to continue watching." = "Nessun Contenuto da continuare a guardare";
"No matches found" = "Nessun Elemento Trovato";
"No Module Selected" = "Nessun Modulo Selezionato";
"No Modules" = "Nessun Modulo";
"No Results Found" = "Nessun Risultato Trovato";
"No Search Results Found" = "Nessun risultato di ricerca trovato";
"Nothing to Continue Watching" = "Nulla da Continuare a Guardare";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Si noti che i moduli saranno sostituiti solo se c'è una stringa di versione diversa all'interno del file JSON";
/* Actions */
"OK" = "OK";
"Open Community Library" = "Apri Libreria Community";
/* External Services */
"Open in AniList" = "Apri in AniList";
"Original Poster" = "Poster Originale";
/* Playback */
"Paused" = "Pausa";
"Play" = "Play";
"Player" = "Player";
/* System Messages */
"Please restart the app to apply the language change." = "Riavvia l'app per effettuare i cambiamenti";
"Please select a module from settings" = "Seleziona un Modulo dalle impostazioni";
/* Interface */
"Portrait Columns" = "Colonne in Vista Verticale";
"Progress bar Marker Color" = "Colore Barra di Progresso";
"Provider: %@" = "Provider: %@";
/* Queue */
"Queue" = "Coda";
"Queued" = "In Coda";
/* Content */
"Recently watched content will appear here." = "I Contenuti Precedentemente Visti Appariranno qui";
/* Settings */
"Refresh Modules on Launch" = "Aggiorna i Moduli al Lancio";
"Refresh Storage Info" = "Aggiorna informazioni storage";
"Remember Playback speed" = "Ricorda Velocità Playback";
/* Actions */
"Remove" = "Rimuovi";
"Remove All Cache" = "Pulisci Cache";
/* File Management */
"Remove All Documents" = "Rimuovi Tutti i Documenti";
"Remove Documents" = "Rimuovi Documenti";
"Remove Downloaded Media" = "Rimuovi Media Scaricati";
"Remove Downloads" = "Rimuovi Scaricati";
"Remove from Bookmarks" = "Rimuovi dai Preferiti";
"Remove Item" = "Rimuovi Elemento";
/* Support */
"Report an Issue" = "Segnala un Problema";
/* Reset Options */
"Reset" = "Reimposta";
"Reset AniList ID" = "Reimposta AniList ID";
"Reset Episode Progress" = "Reimposta";
"Reset progress" = "Reimposta Progressi";
"Reset Progress" = "Reimposta Progressi";
/* System */
"Restart Required" = "Riavvio Richiesto";
"Running Sora %@ - cranci1" = "Running Sora %@ - cranc1";
/* Actions */
"Save" = "Salva";
"Search" = "Cerca";
/* Search */
"Search downloads" = "Cerca Download";
"Search for something..." = "Cerca Qualcosa...";
"Search..." = "Cerca...";
/* Content */
"Season %d" = "Stagione %d";
"Season %lld" = "Stagione %lld";
"Segments Color" = "Colore Segmenti";
/* Modules */
"Select Module" = "Seleziona Modulo";
"Set Custom AniList ID" = "Imposta Custom AniList ID";
/* Interface */
"Settings" = "Impostazioni";
"Shadow" = "Ombra";
"Show More (%lld more characters)" = "Mostra Di Più (%lld più Caratteri)";
"Show PiP Button" = "Mostra Bottone PiP";
"Show Skip 85s Button" = "Mostra Skip 85s";
"Show Skip Intro / Outro Buttons" = "Mostra Bottoni Skip Intro/Outro";
"Shows" = "Serie";
"Size (%@)" = "Dimensione (%@)";
"Skip Settings" = "Salta Impostazioni";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Qualche Features sono limitate a Sora e al Default Player, Come Forza Visualizzazione Orizzontale, Mantieni per Velocizzare e Skip di Tempo Custom";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ by cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way. Also note that progress updates may not be 100% accurate." = "Sora e Cranc1 non sono affiliati in nessun modo con AniList o Trakt.";
"Sora GitHub Repository" = "Repository GitHub di Sora";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur rimarrà sempre gratis e senza pubblicità";
/* Interface */
"Sort" = "Filtra";
"Speed Settings" = "Impostazioni velocità";
/* Playback */
"Start Watching" = "Inizia a Guardare";
"Start Watching Episode %d" = "Guarda Episodio %d";
"Storage Used" = "Memoria Usata";
"Stream" = "Stream";
"Streaming and video playback." = "Streaming e VideoPlayBack";
/* Subtitles */
"Subtitle Color" = "Colore Sottotitoli";
"Subtitle Settings" = "Impostazioni Sottotitoli";
/* Sync */
"Sync anime progress" = "Sincronizza Progressi Anime";
"Sync TV shows progress" = "Sincronizza Progressi Serie Tv";
/* System */
"System" = "Sistema";
/* Instructions */
"Tap a title to override the current match." = "Premi per Sovrascrivere il";
"Tap Skip" = "Premi per Saltare";
"Tap to manage your modules" = "Premi Per Gestire i Tuoi Moduli";
"Tap to select a module" = "Premi per Selezionare un Modulo";
/* App Information */
"The app cache helps the app load images faster. Clearing the Documents folder will delete all downloaded modules. Do not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "La Cache dell'App serve a caricare le immagini più velocemente, pulire la cartella dei documenti eliminerà tutti i moduli scaricati. Non cancellare i Dati dell'App senza aver capito le conseguenze- potrebbe causare malfunzionamenti all'app";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily. For episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "Il range degli episodi controlla quanti episodi appaiono in ogni pagina.Gli episodi sono raggruppati in set (tipo 1-25,26-50 e così via), permettendo di accedere ad essi più facilmente";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "";
/* Interface */
"Thumbnails Width" = "Grandezza Miniature";
"TMDB Match" = "TMDB Match";
"Trackers" = "Trackers";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Prova diverse Parole Chiave";
"Try different search terms" = "Prova diversi Termini di Ricerca";
/* Player Controls */
"Two Finger Hold for Pause" = "Mantieni con Due Dita Per Mettere in Pausa";
"Unable to fetch matches. Please try again later." = "Impossibile trovare i match. Riprovare più tardi.";
"Use TMDB Poster Image" = "Usa i poster di IMDB";
/* Version */
"v%@" = "v%@";
"Video Player" = "Video Player";
/* Video Settings */
"Video Quality Preferences" = "Preferenze qualità video";
"View All" = "Vedi Tutti";
"Watched" = "Guardati";
"Why am I not seeing any episodes?" = "Perché non sto vedendo nessun episodio?";
"WiFi Quality" = "Qualità WiFi";
/* User Status */
"You are not logged in" = "Non sei Loggato";
"You have no items saved." = "Non hai Elementi Salvati";
"Your downloaded episodes will appear here" = "I Tuoi Download appariranno Qui";
"Your recently watched content will appear here" = "I Contenuti Guardati Recentemente Appariranno Qui";
/* Download Settings */
"Download Settings" = "Impostazioni Download";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Il valore massimo di download contemporanei controlla il numero di episodi che possono essere scaricati simultaneamente. Valori più alti possono utilizzare più larghezza di banda e risorse del dispositivo.";
"Quality" = "Qualità";
"Max Concurrent Downloads" = "Download Massimi in Contemporanea";
"Allow Cellular Downloads" = "Permetti il download con Connessione Dati";
"Quality Information" = "Informazioni Qualità";
/* Storage */
"Storage Management" = "Gestione Memoria";
"Storage Used" = "Memoria Usata";
"Library cleared successfully" = "Libreria Pulita con Successo";
"All downloads deleted successfully" = "Tutti i Download Sono Stati Eliminati Correttamente";
/* New additions */
"Recent searches" = "Ricerche Recenti";
"me frfr" = "me frfr";
"Data" = "Dati";
"Maximum Quality Available" = "Qualità Massima Disponibile";
"DownloadCountFormat" = "%d di %d";
"Error loading chapter" = "Errore nel caricamento del capitolo";
"Font Size: %dpt" = "Dimensione carattere: %dpt";
"Line Spacing: %.1f" = "Interlinea: %.1f";
"Line Spacing" = "Interlinea";
"Margin: %dpx" = "Margine: %dpx";
"Margin" = "Margine";
"Auto Scroll Speed" = "Velocità di scorrimento automatico";
"Speed" = "Velocità";
"Speed: %.1fx" = "Velocità: %.1fx";
"Matched %@: %@" = "Corrispondenza %@: %@";
"Enter the AniList ID for this series" = "Inserisci l'ID AniList per questa serie";
/* Added missing localizations */
"Create Collection" = "Crea raccolta";
"Collection Name" = "Nome raccolta";
"Rename Collection" = "Rinomina raccolta";
"Rename" = "Rinomina";
"All Reading" = "Tutte le letture";
"Recently Added" = "Aggiunti di recente";
"Novel Title" = "Titolo del romanzo";
"Read Progress" = "Progresso lettura";
"Date Created" = "Data di creazione";
"Name" = "Nome";
"Item Count" = "Numero di elementi";
"Date Added" = "Data di aggiunta";
"Title" = "Titolo";
"Source" = "Fonte";
"Search reading..." = "Cerca nelle letture...";
"Search collections..." = "Cerca nelle raccolte...";
"Search bookmarks..." = "Cerca nei segnalibri...";
"%d items" = "%d elementi";
"Fetching Data" = "Recupero dati";
"Please wait while fetching." = "Attendere durante il recupero.";
"Start Reading" = "Inizia a leggere";
"Chapters" = "Capitoli";
"Completed" = "Completato";
"Drag to reorder" = "Trascina per riordinare";
"Drag to reorder sections" = "Trascina per riordinare le sezioni";
"Library View" = "Vista libreria";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Personalizza le sezioni mostrate nella tua libreria. Puoi riordinare le sezioni o disabilitarle completamente.";
"Library Sections Order" = "Ordine delle sezioni della libreria";
"Completion Percentage" = "Percentuale di completamento";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Alcune funzioni sono limitate al player Sora e a quello predefinito, come la forzatura del paesaggio, la velocità di mantenimento e gli intervalli di salto personalizzati.\n\nL'impostazione della percentuale di completamento determina in quale punto prima della fine di un video l'app lo segnerà come completato su AniList e Trakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "La cache dell'app aiuta a caricare le immagini più velocemente.\n\nCancellare la cartella Documenti eliminerà tutti i moduli scaricati.\n\nCancellare i dati dell'app eliminerà tutte le tue impostazioni e dati.";
"Translators" = "Traduttori";
"Paste URL" = "Incolla URL";
"Series Title" = "Titolo della serie";
"Content Source" = "Fonte del contenuto";
"Watch Progress" = "Progresso di visione";
"Recent searches" = "Ricerche recenti";
"All Reading" = "Tutto ciò che leggi";
"Nothing to Continue Reading" = "Niente da continuare a leggere";
"Your recently read novels will appear here" = "I tuoi romanzi letti di recente appariranno qui";
"No Bookmarks" = "Nessun segnalibro";
"Add bookmarks to this collection" = "Aggiungi segnalibri a questa raccolta";
"items" = "elementi";
"All Watching" = "Tutto ciò che guardi";
"No Reading History" = "Nessuna cronologia di lettura";
"Books you're reading will appear here" = "I libri che stai leggendo appariranno qui";
"Create Collection" = "Crea raccolta";
"Collection Name" = "Nome raccolta";
"Rename Collection" = "Rinomina raccolta";
"Rename" = "Rinomina";
"Novel Title" = "Titolo del romanzo";
"Read Progress" = "Progresso di lettura";
"Date Created" = "Data di creazione";
"Name" = "Nome";
"Item Count" = "Numero di elementi";
"Date Added" = "Data di aggiunta";
"Title" = "Titolo";
"Source" = "Fonte";
"Search reading..." = "Cerca nelle letture...";
"Search collections..." = "Cerca nelle raccolte...";
"Search bookmarks..." = "Cerca nei segnalibri...";
"%d items" = "%d elementi";
"Fetching Data" = "Recupero dati";
"Please wait while fetching." = "Attendere durante il recupero.";
"Start Reading" = "Inizia a leggere";
"Chapters" = "Capitoli";
"Completed" = "Completato";
"Drag to reorder" = "Trascina per riordinare";
"Drag to reorder sections" = "Trascina per riordinare le sezioni";
"Library View" = "Vista libreria";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Personalizza le sezioni mostrate nella tua libreria. Puoi riordinare le sezioni o disabilitarle completamente.";
"Library Sections Order" = "Ordine delle sezioni della libreria";
"Completion Percentage" = "Percentuale di completamento";
"Translators" = "Traduttori";
"Paste URL" = "Incolla URL";
"Collections" = "Raccolte";
"Continue Reading" = "Continua a leggere";
"Backup & Restore" = "Backup e ripristino";
"Export Backup" = "Esporta backup";
"Import Backup" = "Importa backup";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Nota: Questa funzione è ancora sperimentale. Si prega di ricontrollare i dati dopo esportazione/importazione.";
"Backup" = "Backup";

View file

@ -0,0 +1,506 @@
/* General */
"About" = "Туралы";
"About Sora" = "Sora туралы";
"Active" = "Белсенді";
"Active Downloads" = "Белсенді жүктеулер";
"Actively downloading media can be tracked from here." = "Белсенді жүктелетін медианы осы жерден бақылауға болады.";
"Add Module" = "Модуль қосу";
"Adjust the number of media items per row in portrait and landscape modes." = "Портрет және альбом режимдерінде қатардағы медиа элементтерінің санын реттеу.";
"Advanced" = "Кеңейтілген";
"AKA Sulfur" = "Sulfur деп те белгілі";
"All Bookmarks" = "Барлық бетбелгілер";
"All Watching" = "Барлық көрілетін";
"Also known as Sulfur" = "Sulfur деп те белгілі";
"AniList" = "AniList";
"AniList ID" = "AniList ID";
"AniList Match" = "AniList сәйкестігі";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Қолданбаны жақсарту үшін анонимді деректер жиналады. Жеке ақпарат жиналмайды. Мұны кез келген уақытта өшіруге болады.";
"App Info" = "Қолданба ақпараты";
"App Language" = "Қолданба тілі";
"App Storage" = "Қолданба қоймасы";
"Appearance" = "Сыртқы түр";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Барлық кэштелген деректерді өшіруді қалайсыз ба? Бұл қойма орынын босатуға көмектеседі.";
"Are you sure you want to delete '%@'?" = "'%@' жоюды қалайсыз ба?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "'%2$@' ішіндегі барлық %1$d эпизодты жоюды қалайсыз ба?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Барлық жүктелген активтерді жоюды қалайсыз ба? Жүктелген файлдарды болашақ пайдалану үшін сақтай отырып, тек кітапхананы тазартуды таңдай аласыз.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Барлық қолданба деректерін өшіруді қалайсыз ба? Бұл әрекетті қайтаруға болмайды.";
/* Features */
"Background Enabled" = "Фон қосылған";
"Bookmark items for an easier access later." = "Кейін оңай қол жеткізу үшін элементтерге бетбелгі қою.";
"Bookmarks" = "Бетбелгілер";
"Bottom Padding" = "Төменгі жиек";
"Cancel" = "Болдырмау";
"Cellular Quality" = "Ұялы байланыс сапасы";
"Check out some community modules here!" = "Қауымдастық модульдерін осы жерден қараңыз!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "WiFi және ұялы байланыс үшін қалаулы бейне ажыратымдылығын таңдаңыз. Жоғары ажыратымдылық көбірек деректерді пайдаланады, бірақ жақсы сапа береді. Нақты сапа қолжетімді болмаса, ең жақын нұсқа автоматты түрде таңдалады.\n\nЕскерту: Барлық бейне көздері мен ойнатқыштар сапа таңдауды қолдамайды. Бұл мүмкіндік Sora ойнатқышымен HLS ағындарында ең жақсы жұмыс істейді.";
"Clear" = "Тазарту";
"Clear All Downloads" = "Барлық жүктеулерді тазарту";
"Clear Cache" = "Кэшті тазарту";
"Clear Library Only" = "Тек кітапхананы тазарту";
"Clear Logs" = "Журналдарды тазарту";
"Click the plus button to add a module!" = "Модуль қосу үшін плюс түймесін басыңыз!";
"Continue Watching" = "Көруді жалғастыру";
"Continue Watching Episode %d" = "%d эпизодты көруді жалғастыру";
"Contributors" = "Үлес қосушылар";
"Copied to Clipboard" = "Буферге көшірілді";
"Copy to Clipboard" = "Буферге көшіру";
"Copy URL" = "URL көшіру";
/* Episodes */
"%lld Episodes" = "%lld эпизод";
"%lld of %lld" = "%lld / %lld";
"%lld-%lld" = "%lld-%lld";
"%lld%% seen" = "%lld%% көрілді";
"Episode %lld" = "%lld эпизод";
"Episodes" = "Эпизодтар";
"Episodes might not be available yet or there could be an issue with the source." = "Эпизодтар әлі қолжетімді болмауы мүмкін немесе көзде мәселе болуы мүмкін.";
"Episodes Range" = "Эпизодтар ауқымы";
/* System */
"cranci1" = "cranci1";
"Dark" = "Қараңғы";
"DATA & LOGS" = "ДЕРЕКТЕР ЖӘНЕ ЖУРНАЛДАР";
"Debug" = "Жөндеу";
"Debugging and troubleshooting." = "Жөндеу және ақаулықтарды жою.";
/* Actions */
"Delete" = "Жою";
"Delete All" = "Барлығын жою";
"Delete All Downloads" = "Барлық жүктеулерді жою";
"Delete All Episodes" = "Барлық эпизодтарды жою";
"Delete Download" = "Жүктеуді жою";
"Delete Episode" = "Эпизодты жою";
/* Player */
"Double Tap to Seek" = "Іздеу үшін екі рет басу";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Экранның жақтарын екі рет басу қысқа басу параметрімен өткізіп жіберетін болады.";
/* Downloads */
"Download" = "Жүктеу";
"Download Episode" = "Эпизодты жүктеу";
"Download Summary" = "Жүктеу қорытындысы";
"Download This Episode" = "Осы эпизодты жүктеу";
"Downloaded" = "Жүктелді";
"Downloaded Shows" = "Жүктелген шоулар";
"Downloading" = "Жүктелуде";
"Downloads" = "Жүктеулер";
/* Settings */
"Enable Analytics" = "Аналитиканы қосу";
"Enable Subtitles" = "Субтитрлерді қосу";
/* Data Management */
"Erase" = "Өшіру";
"Erase all App Data" = "Барлық қолданба деректерін өшіру";
"Erase App Data" = "Қолданба деректерін өшіру";
/* Errors */
"Error" = "Қате";
"Error Fetching Results" = "Нәтижелерді алуда қате";
"Errors and critical issues." = "Қателер және сыни мәселелер.";
"Failed to load contributors" = "Үлес қосушыларды жүктеу сәтсіз аяқталды";
/* Features */
"Fetch Episode metadata" = "Эпизод метадеректерін алу";
"Files Downloaded" = "Жүктелген файлдар";
"Font Size" = "Қаріп өлшемі";
/* Interface */
"Force Landscape" = "Альбом режимін мәжбүрлеу";
"General" = "Жалпы";
"General events and activities." = "Жалпы оқиғалар мен іс-әрекеттер.";
"General Preferences" = "Жалпы параметрлер";
"Hide Splash Screen" = "Кіріспе экранын жасыру";
"HLS video downloading." = "HLS бейне жүктеу.";
"Hold Speed" = "Ұстау жылдамдығы";
/* Info */
"Info" = "Ақпарат";
"INFOS" = "АҚПАРАТТАР";
"Installed Modules" = "Орнатылған модульдер";
"Interface" = "Интерфейс";
/* Social */
"Join the Discord" = "Discord-қа қосылу";
/* Layout */
"Landscape Columns" = "Альбом бағандары";
"Language" = "Тіл";
"LESS" = "АЗ";
/* Library */
"Library" = "Кітапхана";
"License (GPLv3.0)" = "Лицензия (GPLv3.0)";
"Light" = "Жарық";
/* Loading States */
"Loading Episode %lld..." = "%lld эпизод жүктелуде...";
"Loading logs..." = "Журналдар жүктелуде...";
"Loading module information..." = "Модуль ақпараты жүктелуде...";
"Loading Stream" = "Ағын жүктелуде";
/* Logging */
"Log Debug Info" = "Жөндеу ақпаратын жазу";
"Log Filters" = "Журнал сүзгілері";
"Log In with AniList" = "AniList арқылы кіру";
"Log In with Trakt" = "Trakt арқылы кіру";
"Log Out from AniList" = "AniList-тен шығу";
"Log Out from Trakt" = "Trakt-тен шығу";
"Log Types" = "Журнал түрлері";
"Logged in as" = "Кірген пайдаланушы:";
"Logged in as " = "Кірген пайдаланушы: ";
/* Logs and Settings */
"Logs" = "Журналдар";
"Long press Skip" = "Ұзақ басып өткізу";
"MAIN" = "НЕГІЗГІ";
"Main Developer" = "Негізгі әзірлеуші";
"MAIN SETTINGS" = "НЕГІЗГІ ПАРАМЕТРЛЕР";
/* Media Actions */
"Mark All Previous Watched" = "Алдыңғыларды көрілді деп белгілеу";
"Mark as Watched" = "Көрілді деп белгілеу";
"Mark Episode as Watched" = "Эпизодты көрілді деп белгілеу";
"Mark Previous Episodes as Watched" = "Алдыңғы эпизодтарды көрілді деп белгілеу";
"Mark watched" = "Көрілді деп белгілеу";
"Match with AniList" = "AniList-пен сәйкестендіру";
"Match with TMDB" = "TMDB-мен сәйкестендіру";
"Matched ID: %lld" = "Сәйкестендірілген ID: %lld";
"Matched with: %@" = "Сәйкестендірілді: %@";
"Max Concurrent Downloads" = "Максималды бір мезгілдегі жүктеулер";
/* Media Interface */
"Media Grid Layout" = "Медиа тор орналасуы";
"Media Player" = "Медиа ойнатқыш";
"Media View" = "Медиа көрінісі";
"Metadata Provider" = "Метадерек провайдері";
"Metadata Providers Order" = "Метадерек провайдерлерінің реті";
"Module Removed" = "Модуль жойылды";
"Modules" = "Модульдер";
/* Headers */
"MODULES" = "МОДУЛЬДЕР";
"MORE" = "КӨБІРЕК";
/* Status Messages */
"No Active Downloads" = "Белсенді жүктеулер жоқ";
"No AniList matches found" = "AniList сәйкестіктері табылмады";
"No Data Available" = "Деректер қолжетімді емес";
"No Downloads" = "Жүктеулер жоқ";
"No episodes available" = "Эпизодтар қолжетімді емес";
"No Episodes Available" = "Эпизодтар қолжетімді емес";
"No items to continue watching." = "Көруді жалғастыратын элементтер жоқ.";
"No matches found" = "Сәйкестіктер табылмады";
"No Module Selected" = "Модуль таңдалмады";
"No Modules" = "Модульдер жоқ";
"No Results Found" = "Нәтижелер табылмады";
"No Search Results Found" = "Іздеу нәтижелері табылмады";
"Nothing to Continue Watching" = "Көруді жалғастыратын ешнәрсе жоқ";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Модульдер тек JSON файлының ішінде басқа нұсқа жолы болған жағдайда ғана ауыстырылатынын ескеріңіз.";
/* Actions */
"OK" = "Жарайды";
"Open Community Library" = "Қауымдастық кітапханасын ашу";
/* External Services */
"Open in AniList" = "AniList-те ашу";
"Original Poster" = "Түпнұсқа постер";
/* Playback */
"Paused" = "Тоқтатылды";
"Play" = "Ойнату";
"Player" = "Ойнатқыш";
/* System Messages */
"Please restart the app to apply the language change." = "Тіл өзгерісін қолдану үшін қолданбаны қайта іске қосыңыз.";
"Please select a module from settings" = "Параметрлерден модуль таңдаңыз";
/* Interface */
"Portrait Columns" = "Портрет бағандары";
"Progress bar Marker Color" = "Прогресс жолағы белгішесінің түсі";
"Provider: %@" = "Провайдер: %@";
/* Queue */
"Queue" = "Кезек";
"Queued" = "Кезекте";
/* Content */
"Recently watched content will appear here." = "Жақында көрілген мазмұн осы жерде пайда болады.";
/* Settings */
"Refresh Modules on Launch" = "Іске қосу кезінде модульдерді жаңарту";
"Refresh Storage Info" = "Қойма ақпаратын жаңарту";
"Remember Playback speed" = "Ойнату жылдамдығын есте сақтау";
/* Actions */
"Remove" = "Жою";
"Remove All Cache" = "Барлық кэшті жою";
/* File Management */
"Remove All Documents" = "Барлық құжаттарды жою";
"Remove Documents" = "Құжаттарды жою";
"Remove Downloaded Media" = "Жүктелген медианы жою";
"Remove Downloads" = "Жүктеулерді жою";
"Remove from Bookmarks" = "Бетбелгілерден жою";
"Remove Item" = "Элементті жою";
/* Support */
"Report an Issue" = "Мәселе туралы хабарлау";
/* Reset Options */
"Reset" = "Қалпына келтіру";
"Reset AniList ID" = "AniList ID қалпына келтіру";
"Reset Episode Progress" = "Эпизод прогрессін қалпына келтіру";
"Reset progress" = "Прогрессті қалпына келтіру";
"Reset Progress" = "Прогрессті қалпына келтіру";
/* System */
"Restart Required" = "Қайта іске қосу қажет";
"Running Sora %@ - cranci1" = "Sora %@ жұмыс істеп тұр - cranci1";
/* Actions */
"Save" = "Сақтау";
"Search" = "Іздеу";
/* Search */
"Search downloads" = "Жүктеулерді іздеу";
"Search for something..." = "Бір нәрсені іздеу...";
"Search..." = "Іздеу...";
/* Content */
"Season %d" = "%d маусым";
"Season %lld" = "%lld маусым";
"Segments Color" = "Сегменттер түсі";
/* Modules */
"Select Module" = "Модуль таңдау";
"Set Custom AniList ID" = "Пайдаланушы AniList ID орнату";
/* Interface */
"Settings" = "Параметрлер";
"Shadow" = "Көлеңке";
"Show More (%lld more characters)" = "Көбірек көрсету (тағы %lld таңба)";
"Show PiP Button" = "PiP түймесін көрсету";
"Show Skip 85s Button" = "85с өткізу түймесін көрсету";
"Show Skip Intro / Outro Buttons" = "Кіріспе/шығыспаны өткізу түймелерін көрсету";
"Shows" = "Шоулар";
"Size (%@)" = "Өлшем (%@)";
"Skip Settings" = "Өткізу параметрлері";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Кейбір мүмкіндіктер Sora және Әдепкі ойнатқышпен шектелген, мысалы ForceLandscape, holdSpeed және пайдаланушы уақыт өткізу өсімшелері.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ - cranci1 әзірлеген";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.
Also note that progress updates may not be 100% accurate." = "Sora мен cranci1 AniList немесе Trakt-пен ешқандай байланысы жоқ.
Сонымен қатар прогресс жаңартулары 100% дәл болмауы мүмкін.";
"Sora GitHub Repository" = "Sora GitHub репозиторийі";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur әрқашан жарнамасыз тегін болып қалады!";
/* Interface */
"Sort" = "Сұрыптау";
"Speed Settings" = "Жылдамдық параметрлері";
/* Playback */
"Start Watching" = "Көруді бастау";
"Start Watching Episode %d" = "%d эпизодты көруді бастау";
"Storage Used" = "Пайдаланылған қойма";
"Stream" = "Ағын";
"Streaming and video playback." = "Ағын және бейне ойнату.";
/* Subtitles */
"Subtitle Color" = "Субтитр түсі";
"Subtitle Settings" = "Субтитр параметрлері";
/* Sync */
"Sync anime progress" = "Аниме прогрессін синхрондау";
"Sync TV shows progress" = "ТВ шоулар прогрессін синхрондау";
/* System */
"System" = "Жүйе";
/* Instructions */
"Tap a title to override the current match." = "Ағымдағы сәйкестікті ауыстыру үшін атауды басыңыз.";
"Tap Skip" = "Өткізуді басу";
"Tap to manage your modules" = "Модульдеріңізді басқару үшін басыңыз";
"Tap to select a module" = "Модуль таңдау үшін басыңыз";
/* App Information */
"The app cache helps the app load images faster.
Clearing the Documents folder will delete all downloaded modules.
Do not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "Қолданба кэші қолданбаға суреттерді жылдам жүктеуге көмектеседі.
Құжаттар қалтасын тазарту барлық жүктелген модульдерді жояды.
Салдарын түсінбесеңіз, қолданба деректерін өшірмеңіз - бұл қолданбаның дұрыс жұмыс істемеуіне әкелуі мүмкін.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.
For episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "Эпизодтар ауқымы әр бетте қанша эпизод пайда болатынын басқарады. Эпизодтар топтарға бөлінеді (125, 2650 сияқты), бұл олар арқылы оңайырақ жүруге мүмкіндік береді.
Эпизод метадеректері үшін бұл эпизодтың кішкене суреті мен атауын білдіреді, өйткені кейде ол спойлерлерді қамтуы мүмкін.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "Модуль тек бір эпизод ұсынды, бұл ең ықтимал фильм, сондықтан біз осы жағдайлар үшін жеке экрандар жасауды шештік.";
/* Interface */
"Thumbnails Width" = "Кішкене суреттер ені";
"TMDB Match" = "TMDB сәйкестігі";
"Trackers" = "Трекерлер";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Басқа кілт сөздерді қолданып көріңіз";
"Try different search terms" = "Басқа іздеу терминдерін қолданып көріңіз";
/* Player Controls */
"Two Finger Hold for Pause" = "Тоқтату үшін екі саусақпен ұстау";
"Unable to fetch matches. Please try again later." = "Сәйкестіктерді алу мүмкін емес. Кейінірек қайталап көріңіз.";
"Use TMDB Poster Image" = "TMDB постер суретін пайдалану";
/* Version */
"v%@" = "v%@";
"Video Player" = "Бейне ойнатқыш";
/* Video Settings */
"Video Quality Preferences" = "Бейне сапасы таңдаулары";
"View All" = "Барлығын көру";
"Watched" = "Көрілді";
"Why am I not seeing any episodes?" = "Неліктен мен ешқандай эпизод көрмеймін?";
"WiFi Quality" = "WiFi сапасы";
/* User Status */
"You are not logged in" = "Сіз кірмегенсіз";
"You have no items saved." = "Сізде сақталған элементтер жоқ.";
"Your downloaded episodes will appear here" = "Жүктелген эпизодтарыңыз осында пайда болады";
"Your recently watched content will appear here" = "Жақында көрген мазмұныңыз осында пайда болады";
/* Download Settings */
"Download Settings" = "Жүктеу параметрлері";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Максималды бір мезгілдегі жүктеулер қанша эпизодты бір уақытта жүктеуге болатынын басқарады. Жоғары мәндер көбірек өткізу қабілеті мен құрылғы ресурстарын пайдалануы мүмкін.";
"Quality" = "Сапа";
"Max Concurrent Downloads" = "Максималды бір мезгілдегі жүктеулер";
"Allow Cellular Downloads" = "Ұялы жүктеулерге рұқсат беру";
"Quality Information" = "Сапа ақпараты";
/* Storage */
"Storage Management" = "Қойма басқаруы";
"Storage Used" = "Пайдаланылған қойма";
"Library cleared successfully" = "Кітапхана сәтті тазартылды";
"All downloads deleted successfully" = "Барлық жүктеулер сәтті жойылды";
/* New additions */
"Recent searches" = "Соңғы іздеулер";
"me frfr" = "мен шынында";
"Data" = "Деректер";
"Maximum Quality Available" = "Қолжетімді максималды сапа";
"DownloadCountFormat" = "%d / %d";
"Error loading chapter" = "Тарауды жүктеу қатесі";
"Font Size: %dpt" = "Қаріп өлшемі: %dpt";
"Line Spacing: %.1f" = "Жоларалық қашықтық: %.1f";
"Line Spacing" = "Жоларалық қашықтық";
"Margin: %dpx" = "Шеткі өріс: %dpx";
"Margin" = "Шеткі өріс";
"Auto Scroll Speed" = "Автоматты айналдыру жылдамдығы";
"Speed" = "Жылдамдық";
"Speed: %.1fx" = "Жылдамдық: %.1fx";
"Matched %@: %@" = "Сәйкестік %@: %@";
"Enter the AniList ID for this series" = "Осы серия үшін AniList ID енгізіңіз";
/* Added missing localizations */
"Create Collection" = "Жинақ құру";
"Collection Name" = "Жинақ атауы";
"Rename Collection" = "Жинақты қайта атау";
"Rename" = "Қайта атау";
"All Reading" = "Барлық оқу";
"Recently Added" = "Жуырда қосылған";
"Novel Title" = "Роман атауы";
"Read Progress" = "Оқу барысы";
"Date Created" = "Құрылған күні";
"Name" = "Атауы";
"Item Count" = "Элементтер саны";
"Date Added" = "Қосылған күні";
"Title" = "Тақырып";
"Source" = "Дереккөз";
"Search reading..." = "Оқуды іздеу...";
"Search collections..." = "Жинақтарды іздеу...";
"Search bookmarks..." = "Бетбелгілерді іздеу...";
"%d items" = "%d элемент";
"Fetching Data" = "Деректерді алу";
"Please wait while fetching." = "Алу барысында күтіңіз.";
"Start Reading" = "Оқуды бастау";
"Chapters" = "Тараулар";
"Completed" = "Аяқталды";
"Drag to reorder" = "Ретін өзгерту үшін сүйреңіз";
"Drag to reorder sections" = "Бөлімдердің ретін өзгерту үшін сүйреңіз";
"Library View" = "Кітапхана көрінісі";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Кітапханаңызда көрсетілетін бөлімдерді баптаңыз. Бөлімдерді қайта реттеуге немесе толықтай өшіруге болады.";
"Library Sections Order" = "Кітапхана бөлімдерінің реті";
"Completion Percentage" = "Аяқталу пайызы";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Кейбір функциялар тек Sora және әдепкі ойнатқышта ғана қолжетімді, мысалы, ландшафтты мәжбүрлеу, жылдамдықты ұстау және уақытты өткізіп жіберу.\n\nАяқталу пайызы параметрі бейне соңына дейін қай жерде аяқталған деп белгіленетінін анықтайды (AniList және Trakt).";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "Қолданба кэші суреттерді жылдам жүктеуге көмектеседі.\n\nDocuments қалтасын тазалау барлық жүктелген модульдерді өшіреді.\n\nҚолданба деректерін өшіру барлық баптауларыңызды және деректеріңізді өшіреді.";
"Translators" = "Аудармашылар";
"Paste URL" = "URL қою";
/* Added missing localizations */
"Series Title" = "Серия атауы";
"Content Source" = "Мазмұн көзі";
"Watch Progress" = "Көру барысы";
"Nothing to Continue Reading" = "Оқуды жалғастыруға ештеңе жоқ";
"Your recently read novels will appear here" = "Соңғы оқылған романдар осында көрсетіледі";
"No Bookmarks" = "Бетбелгілер жоқ";
"Add bookmarks to this collection" = "Бұл жинаққа бетбелгілерді қосыңыз";
"items" = "элементтер";
"All Watching" = "Барлық көру";
"No Reading History" = "Оқу тарихы жоқ";
"Books you're reading will appear here" = "Сіз оқып жатқан кітаптар осында көрсетіледі";
"Create Collection" = "Жинақ құру";
"Collection Name" = "Жинақ атауы";
"Rename Collection" = "Жинақты қайта атау";
"Rename" = "Қайта атау";
"Novel Title" = "Роман атауы";
"Read Progress" = "Оқу барысы";
"Date Created" = "Жасалған күні";
"Name" = "Аты";
"Item Count" = "Элементтер саны";
"Date Added" = "Қосылған күні";
"Title" = "Атауы";
"Source" = "Дереккөз";
"Search reading..." = "Оқуды іздеу...";
"Search collections..." = "Жинақтарды іздеу...";
"Search bookmarks..." = "Бетбелгілерді іздеу...";
"%d items" = "%d элемент";
"Fetching Data" = "Деректерді алу";
"Please wait while fetching." = "Алу кезінде күтіңіз.";
"Start Reading" = "Оқуды бастау";
"Chapters" = "Тараулар";
"Completed" = "Аяқталды";
"Drag to reorder" = "Қайта реттеу үшін сүйреңіз";
"Drag to reorder sections" = "Бөлімдерді қайта реттеу үшін сүйреңіз";
"Library View" = "Кітапхана көрінісі";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Кітапханаңызда көрсетілетін бөлімдерді реттеңіз. Бөлімдерді қайта реттеуге немесе толықтай өшіруге болады.";
"Library Sections Order" = "Кітапхана бөлімдерінің реті";
"Completion Percentage" = "Аяқталу пайызы";
"Translators" = "Аудармашылар";
"Paste URL" = "URL қою";
/* Added missing localizations */
"Collections" = "Жинақтар";
"Continue Reading" = "Оқуды жалғастыру";
/* Backup & Restore */
"Backup & Restore" = "Сақтық көшірме және қалпына келтіру";
"Export Backup" = "Сақтық көшірмені экспорттау";
"Import Backup" = "Сақтық көшірмені импорттау";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Ескерту: Бұл мүмкіндік әлі де тәжірибелік. Экспорт/импорттан кейін деректеріңізді тексеріңіз.";
"Backup" = "Сақтық көшірме";

View file

@ -0,0 +1,467 @@
/* General */
"About" = "Бидний тухайд";
"About Sora" = "Sora аппын тухай";
"Active" = "Идэвхтэй";
"Active Downloads" = "Татаж байна";
"Actively downloading media can be tracked from here." = "Татаж байгаа үзвэрүүдийг эндээс харж болно";
"Add Module" = "Модул нэмэх";
"Adjust the number of media items per row in portrait and landscape modes." = "Хэвтээ болон босоо загварын нэг мөрөн харуулах үзвэрийн тоо";
"Advanced" = "Нарийн тохиргоо";
"AKA Sulfur" = "өөрөөр Sulfur";
"All Bookmarks" = "Бүх хадгалсан үзвэрүүд";
"All Watching" = "Үзэж буй үзвэрүүд";
"Also known as Sulfur" = "өөрөөр Sulfur гэж нэрлэдэг";
"AniList" = "AniList";
"AniList ID" = "AniList ХД";
"AniList Match" = "AniList тохирол";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Аппыг сайжруулах зорилгоор мэдээллийг нууцалж цуглуулдаг. Таны хувийн мэдээллийг цуглуулдаггүй болно. Мөн хүссэн үедээ мэдээлэл цуглуулахыг цуцалж болно.";
"App Info" = "Аппын мэдээлэл ";
"App Language" = "Хэл";
"App Storage" = "Багтаамж";
"Appearance" = "Харагдах байдал";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Та хадгалагдсан өгөгдлийн устгахдаа итгэлтэй байна уу? Устгасан тохиолдолд багтаамж чөлөөлөгдөнө.";
"Are you sure you want to delete '%@'?" = "Та '%@' үзвэрийг устгахдаа итгэлтэй байна уу?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "Та '%2$@' үзвэрийн %1$d ангиудыг устгахдаа итгэлтэй байна уу?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Та бүх татаж авсан үзвэрийг устгахдаа итгэлтэй байна уу? Зөвхөн сангаа цэврэлсэнээр, татаж авсан үзвэрүүдээ устгахгүй байж болно.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Та аппын бүх өгөгдлийг утгахдаа итгэлтэй байна уу? Энэ үйлдлийг буцаах боломжгүй.";
/* Features */
"Background Enabled" = "Аппыг идэвхгүй үед татах";
"Bookmark items for an easier access later." = "Үзвэрийг хадгалсанаар дараа нь олоход хялбар болно";
"Bookmarks" = "Хадгалсан үзвэр";
"Bottom Padding" = "Доод зай";
"Cancel" = "Цуцлах";
"Cellular Quality" = "Утасны дата бичлэгийн чанар";
"Check out some community modules here!" = "Илүү олон модулиудыг эндээс олоорой!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Өөрийн WiFi болон утасны датанд тааруулж бичлэгийн чанарыг сонгоорой. Өндөр чанартай бичлэг нь илүү их дата ашиглана. Хэрэв таны сонгосон бичлэгийн чанар байхгүй бол хамгийн ойролцоо чанарыг сонгож тоглуулна.\n\nТэмдэглэл: Бүх үзвэрүүд болон бичлэг тоглуулагч нь чанар сонгох үйлдэлгүй байдаг. Бичлэгийн чанар сонгох үйлдлийг HLS төрлийн үзвэрийг Sora тоглуулагч ашиглан үзэж байгаа тохиолдолд ашиглахад хамгийн тохиромжтой байдаг.";
"Clear" = "Устгах";
"Clear All Downloads" = "Бүх таталтыг устгах";
"Clear Cache" = "Кэш цэвэрлэх";
"Clear Library Only" = "Зөвхөн санг цэвэрлэх";
"Clear Logs" = "Лог цэвэрлэх";
"Click the plus button to add a module!" = "Нэмэх тэмдэг дээр дарж шинэ модуль нэмнэ үү!";
"Continue Watching" = "Үргэлжлүүлж үзэх";
"Continue Watching Episode %d" = "%d ангийг үргэлжлүүлж үзэх";
"Contributors" = "Хувь нэмэр оруулсан";
"Copied to Clipboard" = "Хуулсан";
"Copy to Clipboard" = "Хуулсан";
"Copy URL" = "Холбоосыг хуулах";
/* Episodes */
"%lld Episodes" = "%lld анги";
"%lld of %lld" = "%lld-ийн %lld";
"%lld-%lld" = "%lld-%lld";
"%lld%% seen" = "%lld%% үзсэн";
"Episode %lld" = "%lld-р анги";
"Episodes" = "Ангиуд";
"Episodes might not be available yet or there could be an issue with the source." = " ";
"Episodes Range" = " ";
/* System */
"cranci1" = "cranci1";
"Dark" = "Хар";
"DATA & LOGS" = "Өгөгдөл ба лог";
"Debug" = "Алдаа илрүүлэх";
"Debugging and troubleshooting." = "Алдааг илрүүлэх ба асуудал олох";
/* Actions */
"Delete" = "Устгах";
"Delete All" = "Бүгдийг устгах";
"Delete All Downloads" = "Бүх таталтыг устгах";
"Delete All Episodes" = "Бүх ангийг устгах";
"Delete Download" = "Таталт устгах";
"Delete Episode" = "Анги устгах";
/* Player */
"Double Tap to Seek" = "Хоёр дарж гүйлгэх";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Дэлгэцийн хоёр талд хоёр удаа хурдан дарвал богино хугацаагаар бичлэгийг гүйлгэнэ.";
/* Downloads */
"Download" = "Татах";
"Download Episode" = "Анги татах";
"Download Summary" = "Таталтын түүх";
"Download This Episode" = "Энэ ангийг татах";
"Downloaded" = "Татсан";
"Downloaded Shows" = "Татсан үзвэрүүд";
"Downloading" = "Татаж байна";
"Downloads" = "Таталтууд";
/* Settings */
"Enable Analytics" = "Аналитик ажилуулах";
"Enable Subtitles" = "Хадмал харуулах";
/* Data Management */
"Erase" = "Арилгах";
"Erase all App Data" = "Аппын бүх өгөгдлийг арилгах";
"Erase App Data" = "Аппын өгөгдлийг арилгах";
/* Errors */
"Error" = "Алдаа";
"Error Fetching Results" = "Илэрцийг олоход гарсан алдаа";
"Errors and critical issues." = "Алдаанууд болон ноцтой асуудлууд";
"Failed to load contributors" = "Контрибуторуудыг ачааллаж чадсангүй";
/* Features */
"Fetch Episode metadata" = "Ангийн мета мэдээллийг татах";
"Files Downloaded" = "Татаж авсан файлууд";
"Font Size" = "Үсгийн хэмжээ";
/* Interface */
"Force Landscape" = "Байнга хэвтээ байлгах";
"General" = "Ерөнхий";
"General events and activities." = "Ерөнхий эвэнт ба үйл ажиллагаанууд";
"General Preferences" = "Ерөнхий тохиргоо";
"Hide Splash Screen" = "Эхлэлийн дэлгэцийг нуух";
"HLS video downloading." = "HLS бичлэг таталт";
"Hold Speed" = "Дарах хурд";
/* Info */
"Info" = "Мэдээлэл";
"INFOS" = "МЭДЭЭЛЛҮҮД";
"Installed Modules" = "Суулгасан модулиуд";
"Interface" = "Харилцах хэсэг";
/* Social */
"Join the Discord" = "Дискорд сувагт нэгдэх";
/* Layout */
"Landscape Columns" = "Хэвтээ багана";
"Language" = "Хэл";
"LESS" = "БАГАСГАХ";
/* Library */
"Library" = "Сан";
"License (GPLv3.0)" = "Лиценз (GPLх3.0)";
"Light" = "Цагаан";
/* Loading States */
"Loading Episode %lld..." = "%lld-р ангийг ачаалж байна...";
"Loading logs..." = "Логийг ачаалж байна...";
"Loading module information..." = "Модулийн мэдээллийг ачаалж байна...";
"Loading Stream" = "Үзвэрийг ачаалж байна";
/* Logging */
"Log Debug Info" = "Дибаг мэдээллийг бичих";
"Log Filters" = "Лог шүүлтүүрүүд";
"Log In with AniList" = "AniList-ээр нэвтрэх";
"Log In with Trakt" = "Trakt-аар нэвтрэх";
"Log Out from AniList" = "AniList-ээс гарах";
"Log Out from Trakt" = "Trakt-аас гарах";
"Log Types" = "Логийн төрлүүд";
"Logged in as" = "Нэвтэрсэн байна";
"Logged in as " = " нэвтэрсэн байна";
/* Logs and Settings */
"Logs" = "Логууд";
"Long press Skip" = "Удаан дарж алгасах";
"MAIN" = "ҮНДСЭН";
"Main Developer" = "Үндсэн Хөгжүүлэгч";
"MAIN SETTINGS" = "ҮНДСЭН ТОХИРГООНУУД";
/* Media Actions */
"Mark All Previous Watched" = "Өмнөх бүгдийг үзсэнээр тэмдэглэх";
"Mark as Watched" = "Үзсэнээр тэмдэглэх";
"Mark Episode as Watched" = "Ангийг үзсэнээр тэмдэглэх";
"Mark Previous Episodes as Watched" = "Өмнөх бүх ангийг үзсэнээр тэмдэглэх";
"Mark watched" = "Үзсэнийг тэмдэглэх";
"Match with AniList" = "Anilist-тэй тааруулах";
"Match with TMDB" = "TMDB-тэй тааруулах";
"Matched ID: %lld" = "Тааруулсан ХД: %lld";
"Matched with: %@" = "Тааруулсан: %@";
"Max Concurrent Downloads" = "Зэрэг татах дээд хэмжээ";
/* Media Interface */
"Media Grid Layout" = "Медиа грид байршил";
"Media Player" = "Медиа тоглуулагч";
"Media View" = "Медиа харагдац";
"Metadata Provider" = "Нэмэлт мэдээлэл нийлүүлэгч";
"Metadata Providers Order" = "Нэмэлт мэдээлэл нийлүүлэгчид";
"Module Removed" = "Модуль устсан";
"Modules" = "Модулиуд";
/* Headers */
"MODULES" = "МОДУЛИУД";
"MORE" = "ИЛҮҮ";
/* Status Messages */
"No Active Downloads" = "Идэвхтэй таталт байхгүй байна";
"No AniList matches found" = "Anilist дээр олдсонгүй";
"No Data Available" = "Мэдээлэл байхгүй байна";
"No Downloads" = "Таталт байхгүй байна";
"No episodes available" = "Анги олдсонгүй";
"No Episodes Available" = "Анги Олдсонгүй";
"No items to continue watching." = "Үргэлүүлж үзэх зүйл байхгүй";
"No matches found" = "Илэрц олдсонгүй";
"No Module Selected" = "Модуль сонгоогүй байна";
"No Modules" = "Модуль байгүй";
"No Results Found" = "Хайлт олдсонгүй";
"No Search Results Found" = "Хайлтын Үр Дүн Олдсонгүй";
"Nothing to Continue Watching" = "Үргэлжлүүлж Үзэх Зүйл Байхгүй";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Модулийн JSON файл доторх хувилбарийн нэр өөрчлөгдсөн тохиолдолд л модуль шинэчлэгдэнэ.";
/* Actions */
"OK" = "ЗА";
"Open Community Library" = "Нийтлэг Санг Нээх";
/* External Services */
"Open in AniList" = "Anilist дотор нээх";
"Original Poster" = "Жинхэнэ Постлогч";
/* Playback */
"Paused" = "Зогсоосон";
"Play" = "Тоглуулах";
"Player" = "Тоглуулагч";
/* System Messages */
"Please restart the app to apply the language change." = "Аппаас гарч дахин орсноор хэл солигдоно";
"Please select a module from settings" = "Тохиргооны хэсгээс модуль сонгоно уу";
/* Interface */
"Portrait Columns" = "Босоо Баганууд";
"Progress bar Marker Color" = "Явцын зурвасын тэмдэглэгээний өнгө";
"Provider: %@" = "Нийлүүлэгч: %@";
/* Queue */
"Queue" = "Дараалал";
"Queued" = "Хүлээлтэд орсон";
/* Content */
"Recently watched content will appear here." = "Сүүлд үзсэн үзвэрүүд энд харагдана";
/* Settings */
"Refresh Modules on Launch" = "Апп нээгдэх болгонд модуль шинэчлэх";
"Refresh Storage Info" = "Багтаамжийн мэдээллийг шинэчлэх";
"Remember Playback speed" = "Тоглуулах хурдыг сануулах";
/* Actions */
"Remove" = "Устгах";
"Remove All Cache" = "Бүх Кэшийг Устгах";
/* File Management */
"Remove All Documents" = "Бүх мэдээлийг устгах";
"Remove Documents" = "Мэдээллийг Устгах";
"Remove Downloaded Media" = "Татаж авсан үзвэрийг устгах";
"Remove Downloads" = "Таталтуудыг Устгах";
"Remove from Bookmarks" = "Хадгалахаа болих";
"Remove Item" = "Анги Устгах";
/* Support */
"Report an Issue" = "Алдаа мэдээлэх";
/* Reset Options */
"Reset" = "Анхны төлөвт оруулах";
"Reset AniList ID" = "AniList ХД анхны төлөвт оруулах";
"Reset Episode Progress" = "Эхнээс нь үзэх";
"Reset progress" = "Анхны төлөвт оруулах явц";
"Reset Progress" = "Явцыг ахний төлөвт оруулах";
/* System */
"Restart Required" = "Дахин ачааллах шаардлагатай";
"Running Sora %@ - cranci1" = "Sora %@ ачаалж байна - cranci1";
/* Actions */
"Save" = "Хадгалах";
"Search" = "Хайа";
/* Search */
"Search downloads" = "Татсан үзвэр хайх";
"Search for something..." = "Үзвэр хайх...";
"Search..." = "Хайх...";
/* Content */
"Season %d" = "%d-р Улирал";
"Season %lld" = "%lld-р Улирал";
"Segments Color" = "Ерөнхий өнгө";
/* Modules */
"Select Module" = "Модуль сонгох";
"Set Custom AniList ID" = "AniList ХД харуулах";
/* Interface */
"Settings" = "Тохиргоо";
"Shadow" = "Сүүдэр";
"Show More (%lld more characters)" = "Илүү харуулах (%lld тэмдэгт харагдана)";
"Show PiP Button" = "PiP товч харуулах";
"Show Skip 85s Button" = "85с алгасах товч харуулах";
"Show Skip Intro / Outro Buttons" = "Эхлэл/Төгсгөлийн дууг алгасах точ хөруулах";
"Shows" = "Харуулах";
"Size (%@)" = "Хэмжээ (%d)";
"Skip Settings" = "Алгасах тохиргоо";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Зарим үйлдлүүд нь зөвхөн Sora болон Үндсэн тоглуулагч дээр ажилладаг, тухайлбал Үргэлж хэвтээ байдлаар үзэх, удаан дарж хурд удирдах болон алгасах хугацаа нь тохиргоо";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ by cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.
Also note that progress updates may not be 100% accurate." = "
Sora ба cranci1 нь AniList эсвэл Trakt-тэй ямар ч хамааралгүй болно.
Мөн явцын шинэчлэлтүүд 100% үнэн зөв байж чадахгүй гэдгийг анхаарна уу.";
"Sora GitHub Repository" = "Sora GitHub хуудас";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur нь үргэлж үнэ төлбөргүй, зар сурталчилгаагүй байх болно!";
/* Interface */
"Sort" = "Эрэмблэх";
"Speed Settings" = "Тоглуулах хурд";
/* Playback */
"Start Watching" = "Үзэх";
"Start Watching Episode %d" = "%d ангийг үзэх";
"Storage Used" = "Ашигласан багтаамж";
"Stream" = "Үзвэр";
"Streaming and video playback." = "Үзвэр ба бичлэг";
/* Subtitles */
"Subtitle Color" = "Хадмалын өнгө";
"Subtitle Settings" = "Хадмалын тохиргоо";
/* Sync */
"Sync anime progress" = "Аниме үзсэн ангиудыг тэмдэглэх";
"Sync TV shows progress" = "Цувралын үзсэн ангиудыг тэмдэглэх";
/* System */
"System" = "Систем";
/* Instructions */
"Tap a title to override the current match." = "Нэр дээр дарж одоогийн хайлтыг солино уу";
"Tap Skip" = "Энд дарж гүйлгэнэ үү";
"Tap to manage your modules" = "Энд дарж модуль солино уу";
"Tap to select a module" = "Энд дарж модуль сонгоно уу";
/* App Information */
"The app cache helps the app load images faster. Clearing the Documents folder will delete all downloaded modules. Do not erase App Data unless you understand the consequences — it may cause the app to malfunction." = " Аппын кэш нь зургуудийг илүү хурдан ачаалахад тусалдаг. Documents хавтасыг устгавал бүх татсан үзвэрүүдийг утгана. Гарах үр дагаврыг нь ойлголгүйгээр Апп датаг бүү устга - Энэ нь дараа нь аппыг буруу ажиллахад нөлөөлөх боломжтой";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily. For episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = " Ангийн хязгаар нь нэг хуудсанд хэдэн харагдахыг тохируулдаг. Ингэснээр ангиуд нь багцлагдаж (Жишээ нь 1-25, 26-50 гэх мэт), үзэх ангиа сонгоход илүү хялбар болгоно. Ангийн мета өгөгдөл нь тухайн ангийн харагдац зураг болон нэрийг хадгалдаг тул зарим тохиолдолд спойлер агуулдаг.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "Модуль зөвхөн нэг анги агуулсан байсан тул, кино байх боломжтой гэж үзээд тусгай дэлгэц хийхээр шийдсэн.";
/* Interface */
"Thumbnails Width" = "Үзвэрийн зургийн урт";
"TMDB Match" = "ТМДБ тохирол";
"Trackers" = "Тракерууд";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Өөр үгээр хайж үзнэ үү";
"Try different search terms" = "Өөр төрлөөр хайж үзнэ үү";
/* Player Controls */
"Two Finger Hold for Pause" = "Хоёр хуруугаар дарж бичлэгийг зогсоох";
"Unable to fetch matches. Please try again later." = "Илэрц олж чадсангүй. Дараа дахин оролдоно уу?";
"Use TMDB Poster Image" = "ТМДБ нүүр зураг ашиглах";
/* Version */
"v%@" = "х%@";
"Video Player" = "Бичлэг тоглуулагч";
/* Video Settings */
"Video Quality Preferences" = "Бичлэгийн чанарын тохиргоо";
"View All" = "Бүгдийг харах";
"Watched" = "Үзсэн";
"Why am I not seeing any episodes?" = "Яагаад нэг ч анги байхгүй байна?";
"WiFi Quality" = "WiFi чанар";
/* User Status */
"You are not logged in" = "Та нэвтрээгүй байна";
"You have no items saved." = "Танд хадгалсан үзвэр байхгүй байна";
"Your downloaded episodes will appear here" = "Таны татсан үзвэрийн ангиуд энд харагдана";
"Your recently watched content will appear here" = "Таны сүүлд үзсэн үзвэрүүд энд харагдана";
/* Download Settings */
"Download Settings" = "Таталтын тохиргоо";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Зэрэг таталт хийх хязгаар нь зэрэг татах ангийн тоо юм. Өндөр байх тусам илүү их дата болон утасны нөөцийг ашиглана.";
"Quality" = "Чанар";
"Max Concurrent Downloads" = "Зэрэг таталт хийх хязгаар";
"Allow Cellular Downloads" = "Утасны датагаар татах";
"Quality Information" = "Чанарын мэдээлэл";
/* Storage */
"Storage Management" = "Багтаамж удирдах";
"Storage Used" = "Ашигласан багтаамж";
"Library cleared successfully" = "Санг амжилттай цэвэрлэлээ";
"All downloads deleted successfully" = "Бүх татсан үзвэрүүдийг амжилттай устлаа";
/* New additions */
"Recent searches" = "Сүүлд хайсан";
"me frfr" = "me frfr";
"Data" = "Мэдээлэл";
"Maximum Quality Available" = "Хамгийн өндөр чанартай";
"All Reading" = "Бүх унших зүйл";
"No Reading History" = "Унших түүх байхгүй";
"Books you're reading will appear here" = "Таны уншиж байгаа номууд энд харагдана";
"All Watching" = "Бүх үзэх зүйл";
"Continue Reading" = "Унших үргэлжлүүлэх";
"Nothing to Continue Reading" = "Үргэлжлүүлж унших зүйл байхгүй";
"Your recently read novels will appear here" = "Таны саяхан уншсан зохиолууд энд харагдана";
"No Bookmarks" = "Хадгалсан зүйл байхгүй";
"Add bookmarks to this collection" = "Энэ цуглуулгад хадгалсан зүйл нэмэх";
"items" = "зүйл";
"Chapter %d" = "Бүлэг %d";
"Episode %d" = "Анги %d";
"%d%%" = "%d%%";
"%d%% seen" = "%d%% үзсэн";
"DownloadCountFormat" = "Татаж авсан: %d";
"Error loading chapter" = "Бүлэг ачаалахад алдаа гарлаа";
"Font Size: %dpt" = "Фонтын хэмжээ: %dpt";
"Line Spacing: %.1f" = "Мөр хоорондын зай: %.1f";
"Line Spacing" = "Мөр хоорондын зай";
"Margin: %dpx" = "Захын зай: %dpx";
"Margin" = "Захын зай";
"Auto Scroll Speed" = "Автомат гүйлгэх хурд";
"Speed" = "Хурд";
"Speed: %.1fx" = "Хурд: %.1fx";
"Matched %@: %@" = "Таарсан %@: %@";
"Enter the AniList ID for this series" = "Энэ цувралын AniList ID-г оруулна уу";
/* New additions */
"Create Collection" = "Цуглуулга үүсгэх";
"Collection Name" = "Цуглуулгын нэр";
"Rename Collection" = "Цуглуулгын нэр солих";
"Rename" = "Нэр солих";
"All Reading" = "Бүх унших зүйл";
"Recently Added" = "Саяхан нэмэгдсэн";
"Novel Title" = "Зохиолын гарчиг";
"Read Progress" = "Уншсан явц";
"Date Created" = "Үүсгэсэн огноо";
"Name" = "Нэр";
"Item Count" = "Зүйлийн тоо";
"Date Added" = "Нэмсэн огноо";
"Title" = "Гарчиг";
"Source" = "Эх сурвалж";
"Search reading..." = "Унж байгаа зүйл хайх...";
"Search collections..." = "Цуглуулга хайх...";
"Search bookmarks..." = "Хадгалсан зүйл хайх...";
"%d items" = "%d зүйл";
"Fetching Data" = "Өгөгдөл татаж байна";
"Please wait while fetching." = "Татаж байна, хүлээнэ үү.";
"Start Reading" = "Унж эхлэх";
"Chapters" = "Бүлгүүд";
"Completed" = "Дууссан";
"Drag to reorder" = "Дарааллаар байрлуулахын тулд чирнэ үү";
"Drag to reorder sections" = "Хэсгүүдийг дарааллаар байрлуулахын тулд чирнэ үү";
"Library View" = "Сангийн харагдац";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Сангийн харагдах хэсгүүдийг тохируулна уу. Хэсгүүдийг дахин эрэмбэлж эсвэл бүрэн идэвхгүй болгож болно.";
"Library Sections Order" = "Сангийн хэсгүүдийн дараалал";
"Completion Percentage" = "Дуусгах хувь";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Зарим үйлдлүүд нь зөвхөн Sora болон Үндсэн тоглуулагч дээр ажилладаг, тухайлбал Үргэлж хэвтээ байдлаар үзэх, удаан дарж хурд удирдах болон алгасах хугацаа нь тохиргоо\n\nДуусгах хувь нь бичлэгийн төгсгөлөөс хэдэн хувийн өмнө AniList болон Trakt дээр үзсэнээр тэмдэглэхээ тодорхойлно.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "Аппын кэш нь зургуудийг илүү хурдан ачаалахад тусалдаг.\n\nDocuments хавтасыг устгавал бүх татсан үзвэрүүдийг утгана.\n\nАпп датаг устгавал аппын бүх тохиргоо болон өгөгдөл устана.";
"Translators" = "Орчуулагчид";
"Paste URL" = "Холбоосыг буулгах";
/* Added missing localizations */
"Series Title" = "Цувралын гарчиг";
"Content Source" = "Агуулгын эх сурвалж";
"Watch Progress" = "Үзсэн явц";
"Recent searches" = "Саяхны хайлт";
"Collections" = "Цуглуулгууд";
"Continue Reading" = "Унших үргэлжлүүлэх";

View file

@ -0,0 +1,488 @@
/* General */
"About" = "Over";
"About Sora" = "Over Sora";
"Active" = "Actief";
"Active Downloads" = "Actieve Downloads";
"Actively downloading media can be tracked from here." = "Actief downloaden van media kan hier worden gevolgd.";
"Add Module" = "Module Toevoegen";
"Adjust the number of media items per row in portrait and landscape modes." = "Pas het aantal media-items per rij aan in staande en liggende modus.";
"Advanced" = "Geavanceerd";
"AKA Sulfur" = "AKA Sulfur";
"All Bookmarks" = "Alle Bladwijzers";
"All Prev" = "Alle vorige";
"All Watching" = "Alles Wat Ik Kijk";
"AniList" = "AniList";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Anonieme gegevens worden verzameld om de app te verbeteren. Er worden geen persoonlijke gegevens verzameld. Dit kan op elk moment worden uitgeschakeld.";
"App Info" = "App Info";
"App Language" = "App Taal";
"App Storage" = "App Opslag";
"Appearance" = "Uiterlijk";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Weet je zeker dat je alle gecachte gegevens wilt wissen? Dit helpt opslagruimte vrij te maken.";
"Are you sure you want to delete '%@'?" = "Weet je zeker dat je '%@' wilt verwijderen?";
"Are you sure you want to delete all %1$lld episodes in '%2$@'?" = "Weet je zeker dat je alle %1$lld afleveringen in '%2$@' wilt verwijderen?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Weet je zeker dat je alle gedownloade bestanden wilt verwijderen? Je kunt ervoor kiezen om alleen de bibliotheek te wissen terwijl je de gedownloade bestanden voor later gebruik bewaart.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Weet je zeker dat je alle app-gegevens wilt wissen? Deze actie kan niet ongedaan worden gemaakt.";
"Are you sure you want to remove all downloaded media files (.mov, .mp4, .pkg)?" = "Weet je zeker dat je alle gedownloade mediabestanden (.mov, .mp4, .pkg) wilt verwijderen?";
"Are you sure you want to remove all files in the Documents folder?" = "Weet je zeker dat je alle bestanden in de map Documenten wilt verwijderen?";
/* Features */
"Author" = "Auteur";
"Background Enabled" = "Achtergrond Ingeschakeld";
"Bookmark items for an easier access later." = "Bladwijzer items voor eenvoudigere toegang later.";
"Bookmarks" = "Bladwijzers";
"Bottom Padding" = "Onderste Padding";
"Cancel" = "Annuleren";
"Cellular Quality" = "Mobiele Kwaliteit";
"Check out some community modules here!" = "Bekijk hier enkele community modules!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Kies de gewenste videoresolutie voor WiFi- en mobiele verbindingen. Hogere resoluties gebruiken meer data maar bieden een betere kwaliteit. Als de exacte kwaliteit niet beschikbaar is, wordt automatisch de dichtstbijzijnde optie geselecteerd.\n\nLet op: Niet alle videobronnen en spelers ondersteunen kwaliteitsselectie. Deze functie werkt het beste met HLS-streams via de Sora-speler.";
"Clear" = "Wissen";
"Clear All Downloads" = "Alle Downloads Wissen";
"Clear Cache" = "Wis Cache";
"Clear Library Only" = "Alleen Bibliotheek Wissen";
"Clear Logs" = "Wis Logs";
"Click the plus button to add a module!" = "Klik op de plus-knop om een module toe te voegen!";
"Continue Watching" = "Verder Kijken";
"Continue Watching Episode %d" = "Verder Kijken Aflevering %d";
"Contributors" = "Bijdragers";
"Copied to Clipboard" = "Gekopieerd naar Klembord";
"Copy to Clipboard" = "Kopiëren naar Klembord";
"Copy URL" = "Kopieer URL";
/* Episodes */
"%lld Episodes" = "%lld Afleveringen";
"%lld of %lld" = "%1$lld van %2$lld";
"%lld-%lld" = "%1$lld-%2$lld";
"%lld%% seen" = "%lld%% gezien";
"Episode %lld" = "Aflevering %lld";
"Episodes" = "Afleveringen";
"Episodes might not be available yet or there could be an issue with the source." = "Afleveringen zijn mogelijk nog niet beschikbaar of er is een probleem met de bron.";
"Episodes Range" = "Afleveringen Bereik";
/* System */
"cranci1" = "cranci1";
"Dark" = "Donker";
"DATA & LOGS" = "DATA & LOGS";
"Debug" = "Debug";
"Debugging and troubleshooting." = "Debuggen en probleemoplossing.";
/* Actions */
"Delete" = "Verwijderen";
"Delete All" = "Alles Wissen";
"Delete All Downloads" = "Alle Downloads Verwijderen";
"Delete All Episodes" = "Alle Afleveringen Wissen";
"Delete Download" = "Downloads Wissen";
"Delete Episode" = "Afleveringen Wissen";
/* Player */
"Double Tap to Seek" = "Dubbel Tikken om te Zoeken";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Dubbel tikken op de zijkanten van het scherm zal overslaan met de korte tik instelling.";
/* Downloads */
"Download" = "Downloaden";
"Download Episode" = "Aflevering Downloaden";
"Download Summary" = "Download Samenvatting";
"Download This Episode" = "Download Deze Aflevering";
"Downloaded" = "Gedownload";
"Downloaded Shows" = "Gedownloade Series";
"Downloading" = "Downloaden";
"Downloads" = "Downloads";
/* Settings */
"Enable Analytics" = "Analytics Inschakelen";
"Enable Subtitles" = "Ondertiteling Inschakelen";
/* Data Management */
"Erase" = "Verwijden";
"Erase all App Data" = "Wis Alle App Data";
"Erase App Data" = "Verwijder App Data";
/* Errors */
"Error" = "Fout";
"Error Fetching Results" = "Fout bij Ophalen Resultaten";
"Errors and critical issues." = "Fouten en kritieke problemen.";
"Failed to load contributors" = "Laden van bijdragers mislukt";
/* Features */
"Fetch Episode metadata" = "Haal Aflevering Metadata op";
"Files Downloaded" = "Gedownloade Bestanden";
"Font Size" = "Lettergrootte";
/* Interface */
"Force Landscape" = "Forceer Landschap";
"General" = "Algemeen";
"General events and activities." = "Algemene gebeurtenissen en activiteiten.";
"General Preferences" = "Algemene Voorkeuren";
"Hide Splash Screen" = "Splash Screen Verbergen";
"HLS video downloading." = "HLS video downloaden.";
"Hold Speed" = "Vasthouden Snelheid";
/* Info */
"Info" = "Info";
"INFOS" = "INFO";
"Installed Modules" = "Geïnstalleerde Modules";
"Interface" = "Interface";
/* Social */
"Join the Discord" = "Word lid van de Discord";
/* Layout */
"Landscape Columns" = "Liggende Kolommen";
"Language" = "Taal";
"LESS" = "MINDER";
/* Library */
"Library" = "Bibliotheek";
"License (GPLv3.0)" = "Licentie (GPLv3.0)";
"Light" = "Licht";
/* Loading States */
"Loading Episode %lld..." = "Aflevering %lld laden...";
"Loading logs..." = "Logboeken laden...";
"Loading module information..." = "Module-informatie laden...";
"Loading Stream" = "Stream Laden";
/* Logging */
"Log Debug Info" = "Debug Info Loggen";
"Log Filters" = "Log Filters";
"Log In with AniList" = "Inloggen met AniList";
"Log In with Trakt" = "Inloggen met Trakt";
"Log Out from AniList" = "Uitloggen van AniList";
"Log Out from Trakt" = "Uitloggen van Trakt";
"Log Types" = "Logboek Types";
"Logged in as" = "Ingelogd als";
"Logged in as " = "Ingelogd als ";
/* Logs and Settings */
"Logs" = "Logboeken";
"Long press Skip" = "Lang Drukken Overslaan";
"MAIN" = "Hoofdinstellingen";
"Main Developer" = "Hoofdontwikkelaar";
"MAIN SETTINGS" = "HOOFDINSTELLINGEN";
/* Media Actions */
"Mark All Previous Watched" = "Markeer alles als gezien";
"Mark as Watched" = "Markeer als gezien";
"Mark Episode as Watched" = "Markeer aflevering als gezien";
"Mark Previous Episodes as Watched" = "Markeer vorige afleveringen als gezien";
"Mark watched" = "Markeer als gezien";
"Match with AniList" = "Match met AniList";
"Match with TMDB" = "Match met TMDB";
"Matched ID: %lld" = "Gematchte ID: %lld";
"Matched with: %@" = "Match met: %@";
"Max Concurrent Downloads" = "Maximaal gelijktijdige downloads";
/* Media Interface */
"Media Grid Layout" = "Media Raster Layout";
"Media Player" = "Media Speler";
"Media View" = "Mediaweergave";
"Metadata Provider" = "Metadata Provider";
"Metadata Providers Order" = "Metadata Providers Volgorde";
"Module Removed" = "Module Verwijderd";
"Modules" = "Modules";
/* Headers */
"MODULES" = "MODULES";
"MORE" = "MEER";
/* Status Messages */
"No Active Downloads" = "Geen Actieve Downloads";
"No AniList matches found" = "Geen AniList overeenkomsten gevonden";
"No Data Available" = "Geen Gegevens Beschikbaar";
"No Downloads" = "Geen Downloads";
"No episodes available" = "Geen afleveringen beschikbaar";
"No Episodes Available" = "Geen Afleveringen Beschikbaar";
"No items to continue watching." = "Geen items om verder te kijken.";
"No matches found" = "Geen overeenkomsten gevonden";
"No Module Selected" = "Geen Module Geselecteerd";
"No Modules" = "Geen Modules";
"No Results Found" = "Geen Resultaten Gevonden";
"No Search Results Found" = "Geen Zoekresultaten Gevonden";
"Nothing to Continue Watching" = "Niets om Verder te Kijken";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Let op: de modules worden alleen vervangen als er een andere versiestring in het JSON-bestand staat.";
/* Actions */
"OK" = "OK";
"Open Community Library" = "Open Community Bibliotheek";
/* External Services */
"Open in AniList" = "Openen in AniList";
"Original Poster" = "Originele Poster";
/* Playback */
"Paused" = "Gepauzeerd";
"Play" = "Afspelen";
"Player" = "Speler";
/* System Messages */
"Please restart the app to apply the language change." = "Herstart de app om de taalwijziging toe te passen.";
"Please select a module from settings" = "Selecteer een module uit de instellingen";
/* Interface */
"Portrait Columns" = "Staande Kolommen";
"Progress bar Marker Color" = "Voortgangsbalk Markeerkleur";
"Provider: %@" = "Provider: %@";
/* Queue */
"Queue" = "Wachtrij";
"Queued" = "In Wachtrij";
/* Content */
"Recently watched content will appear here." = "Recent bekeken inhoud verschijnt hier.";
/* Settings */
"Refresh Modules on Launch" = "Ververs Modules bij Opstarten";
"Refresh Storage Info" = "Opslaginformatie Vernieuwen";
"Remember Playback speed" = "Onthoud Afspeelsnelheid";
/* Actions */
"Remove" = "Verwijderen";
"Remove All Cache" = "Verwijder Alle Cache";
/* File Management */
"Remove All Documents" = "Verwijder Alle Documenten";
"Remove Documents" = "Documenten Verwijderen";
"Remove Downloaded Media" = "Gedownloade Media Verwijderen";
"Remove Downloads" = "Verwijder Downloads";
"Remove from Bookmarks" = "Verwijderen uit Bladwijzers";
"Remove Item" = "Item Verwijderen";
/* Support */
"Report an Issue" = "Rapporteer een Probleem";
/* Reset Options */
"Reset" = "Resetten";
"Reset AniList ID" = "AniList ID Resetten";
"Reset Episode Progress" = "Afleveringsvoortgang Resetten";
"Reset progress" = "Voortgang resetten";
"Reset Progress" = "Voortgang Resetten";
/* System */
"Restart Required" = "Herstart Vereist";
"Running Sora %@ - cranci1" = "Sora %@ draait - cranci1";
/* Actions */
"Save" = "Opslaan";
"Search" = "Zoeken";
/* Search */
"Search downloads" = "Downloads zoeken";
"Search for something..." = "Zoek naar iets...";
"Search..." = "Zoeken...";
/* Content */
"Season %d" = "Seizoen %d";
"Season %lld" = "Seizoen %lld";
"Segments Color" = "Segmenten Kleur";
/* Modules */
"Select Module" = "Module Selecteren";
"Set Custom AniList ID" = "Aangepaste AniList ID Instellen";
/* Interface */
"Settings" = "Instellingen";
"Shadow" = "Schaduw";
"Show More (%lld more characters)" = "Meer Tonen (%lld meer tekens)";
"Show PiP Button" = "Toon PiP Knop";
"Show Skip 85s Button" = "Toon Overslaan 85s Knop";
"Show Skip Intro / Outro Buttons" = "Toon Overslaan Intro / Outro Knoppen";
"Shows" = "Series";
"Size (%@)" = "Grootte (%@)";
"Skip Settings" = "Overslaan Instellingen";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Sommige functies zijn beperkt tot de Sora en Standaard speler, zoals ForceLandscape, holdSpeed en aangepaste tijd overslaan stappen.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ door cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate." = "Sora en cranci1 zijn op geen enkele manier verbonden met AniList of Trakt.\n\nHoud er ook rekening mee dat voortgangsupdates mogelijk niet 100% nauwkeurig zijn.";
"Sora GitHub Repository" = "Sora GitHub Repository";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur blijft altijd gratis zonder advertenties!";
/* Interface */
"Sort" = "Sorteren";
"Speed Settings" = "Snelheidsinstellingen";
/* Playback */
"Start Watching" = "Start met Kijken";
"Start Watching Episode %d" = "Start met Kijken Aflevering %d";
"Storage Used" = "Gebruikte Opslag";
"Stream" = "Stream";
"Streaming and video playback." = "Streaming en video afspelen.";
/* Subtitles */
"Subtitle Color" = "Ondertitelingskleur";
"Subtitle Settings" = "Ondertitelingsinstellingen";
/* Sync */
"Sync anime progress" = "Synchroniseer anime voortgang";
"Sync TV shows progress" = "Synchroniseer TV series voortgang";
/* System */
"System" = "Systeem";
/* Instructions */
"Tap a title to override the current match." = "Tik op een titel om de huidige match te overschrijven.";
"Tap Skip" = "Tik Overslaan";
"Tap to manage your modules" = "Tik om je modules te beheren";
"Tap to select a module" = "Tik om een module te selecteren";
/* App Information */
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "De app cache helpt de app om afbeeldingen sneller te laden.\n\nHet wissen van de Documents map zal alle gedownloade modules verwijderen.\n\nWis de App Data niet tenzij je de gevolgen begrijpt — het kan ervoor zorgen dat de app niet meer goed werkt.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "Het afleveringen bereik bepaalt hoeveel afleveringen er op elke pagina verschijnen. Afleveringen worden gegroepeerd in sets (zoals 1-25, 26-50, enzovoort), waardoor je er gemakkelijker doorheen kunt navigeren.\n\nVoor aflevering metadata verwijst dit naar de aflevering miniatuur en titel, aangezien deze soms spoilers kunnen bevatten.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "De module heeft slechts één aflevering geleverd, dit is waarschijnlijk een film, daarom hebben we aparte schermen gemaakt voor deze gevallen.";
/* Interface */
"Thumbnails Width" = "Miniatuur Breedte";
"TMDB Match" = "TMDB Match";
"Trackers" = "Trackers";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Probeer andere zoekwoorden";
"Try different search terms" = "Probeer andere zoektermen";
/* Player Controls */
"Two Finger Hold for Pause" = "Twee Vingers Vasthouden voor Pauze";
"Unable to fetch matches. Please try again later." = "Kan geen matches ophalen. Probeer het later opnieuw.";
"Use TMDB Poster Image" = "TMDB Poster Afbeelding Gebruiken";
/* Version */
"v%@" = "v%@";
"Video Player" = "Videospeler";
/* Video Settings */
"Video Quality Preferences" = "Video Kwaliteit Voorkeuren";
"View All" = "Alles Bekijken";
"Watched" = "Bekeken";
"Why am I not seeing any episodes?" = "Waarom zie ik geen afleveringen?";
"WiFi Quality" = "WiFi Kwaliteit";
/* User Status */
"You are not logged in" = "Je bent niet ingelogd";
"You have no items saved." = "Je hebt geen items opgeslagen.";
"Your downloaded episodes will appear here" = "Je gedownloade afleveringen verschijnen hier";
"Your recently watched content will appear here" = "Je recent bekeken inhoud verschijnt hier";
/* Download Settings */
"Download Settings" = "Download Instellingen";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Maximum gelijktijdige downloads bepaalt hoeveel afleveringen tegelijk kunnen worden gedownload. Hogere waarden kunnen meer bandbreedte en apparaatbronnen gebruiken.";
"Quality" = "Kwaliteit";
"Max Concurrent Downloads" = "Maximum Gelijktijdige Downloads";
"Allow Cellular Downloads" = "Downloads via Mobiel Netwerk Toestaan";
"Quality Information" = "Kwaliteitsinformatie";
/* Storage */
"Storage Management" = "Opslagbeheer";
"Storage Used" = "Gebruikte Opslag";
"Library cleared successfully" = "Bibliotheek succesvol gewist";
"All downloads deleted successfully" = "Alle downloads succesvol verwijderd";
/* New additions */
"Recent searches" = "Recente zoekopdrachten";
"me frfr" = "ik frfr";
"Data" = "Gegevens";
"Maximum Quality Available" = "Maximale beschikbare kwaliteit";
"DownloadCountFormat" = "%d van %d";
"Error loading chapter" = "Fout bij het laden van hoofdstuk";
"Font Size: %dpt" = "Lettergrootte: %dpt";
"Line Spacing: %.1f" = "Regelafstand: %.1f";
"Line Spacing" = "Regelafstand";
"Margin: %dpx" = "Marge: %dpx";
"Margin" = "Marge";
"Auto Scroll Speed" = "Automatische scrollsnelheid";
"Speed" = "Snelheid";
"Speed: %.1fx" = "Snelheid: %.1fx";
"Matched %@: %@" = "Overeenkomst %@: %@";
"Enter the AniList ID for this series" = "Voer de AniList-ID voor deze serie in";
/* Added missing localizations */
"Create Collection" = "Collectie aanmaken";
"Collection Name" = "Collectienaam";
"Rename Collection" = "Collectie hernoemen";
"Rename" = "Hernoemen";
"All Reading" = "Alles wat je leest";
"Recently Added" = "Recent toegevoegd";
"Novel Title" = "Roman titel";
"Read Progress" = "Leesvoortgang";
"Date Created" = "Aanmaakdatum";
"Name" = "Naam";
"Item Count" = "Aantal items";
"Date Added" = "Datum toegevoegd";
"Title" = "Titel";
"Source" = "Bron";
"Search reading..." = "Zoek in lezen...";
"Search collections..." = "Zoek in collecties...";
"Search bookmarks..." = "Zoek in bladwijzers...";
"%d items" = "%d items";
"Fetching Data" = "Gegevens ophalen";
"Please wait while fetching." = "Even geduld tijdens het ophalen.";
"Start Reading" = "Begin met lezen";
"Chapters" = "Hoofdstukken";
"Completed" = "Voltooid";
"Drag to reorder" = "Sleep om te herschikken";
"Drag to reorder sections" = "Sleep om secties te herschikken";
"Library View" = "Bibliotheekweergave";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Pas de secties aan die in je bibliotheek worden weergegeven. Je kunt secties herschikken of volledig uitschakelen.";
"Library Sections Order" = "Volgorde van bibliotheeksecties";
"Completion Percentage" = "Voltooiingspercentage";
"Translators" = "Vertalers";
"Paste URL" = "URL plakken";
/* Added missing localizations */
"Series Title" = "Serietitel";
"Content Source" = "Inhoudsbron";
"Watch Progress" = "Kijkvoortgang";
"Nothing to Continue Reading" = "Niets om verder te lezen";
"Your recently read novels will appear here" = "Je recent gelezen romans verschijnen hier";
"No Bookmarks" = "Geen bladwijzers";
"Add bookmarks to this collection" = "Voeg bladwijzers toe aan deze collectie";
"items" = "items";
"All Watching" = "Alles wat je kijkt";
"No Reading History" = "Geen leeshistorie";
"Books you're reading will appear here" = "Boeken die je leest verschijnen hier";
"Create Collection" = "Collectie aanmaken";
"Collection Name" = "Collectienaam";
"Rename Collection" = "Collectie hernoemen";
"Rename" = "Hernoemen";
"Novel Title" = "Roman titel";
"Read Progress" = "Leesvoortgang";
"Date Created" = "Aanmaakdatum";
"Name" = "Naam";
"Item Count" = "Aantal items";
"Date Added" = "Datum toegevoegd";
"Title" = "Titel";
"Source" = "Bron";
"Search reading..." = "Zoek in lezen...";
"Search collections..." = "Zoek in collecties...";
"Search bookmarks..." = "Zoek in bladwijzers...";
"%d items" = "%d items";
"Fetching Data" = "Gegevens ophalen";
"Please wait while fetching." = "Even geduld tijdens het ophalen.";
"Start Reading" = "Begin met lezen";
"Chapters" = "Hoofdstukken";
"Completed" = "Voltooid";
"Drag to reorder" = "Sleep om te herschikken";
"Drag to reorder sections" = "Sleep om secties te herschikken";
"Library View" = "Bibliotheekweergave";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Pas de secties aan die in je bibliotheek worden weergegeven. Je kunt secties herschikken of volledig uitschakelen.";
"Library Sections Order" = "Volgorde van bibliotheeksecties";
"Completion Percentage" = "Voltooiingspercentage";
"Translators" = "Vertalers";
"Paste URL" = "URL plakken";
/* Added missing localizations */
"Collections" = "Collecties";
"Continue Reading" = "Doorgaan met lezen";
/* Backup & Restore */
"Backup & Restore" = "Back-up & Herstellen";
"Export Backup" = "Back-up exporteren";
"Import Backup" = "Back-up importeren";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Let op: Deze functie is nog experimenteel. Controleer je gegevens na export/import.";
"Backup" = "Back-up";

View file

@ -0,0 +1,488 @@
/* General */
"About" = "Om oss";
"About Sora" = "Om Sora";
"Active" = "Aktiv";
"Active Downloads" = "Aktive nedlastinger";
"Actively downloading media can be tracked from here." = "Aktive nedlastninger av media kan spores her.";
"Add Module" = "Legg til Modul";
"Adjust the number of media items per row in portrait and landscape modes." = "Juster antall mediaelementer per rad i portrett- og landskapsmodus.";
"Advanced" = "Avansert";
"AKA Sulfur" = "AKA Sulfur";
"All Bookmarks" = "Alle bokmerker";
"All Watching" = "Alt du ser på";
"Also known as Sulfur" = "Også kjent som Sulfur";
"AniList" = "AniList";
"AniList ID" = "AniList-ID";
"AniList Match" = "AniList-treff";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Anonyme data samles inn for å forbedre appen. Ingen personlig informasjon samles inn. Dette kan deaktiveres når som helst.";
"App Info" = "App Informasjon";
"App Language" = "App Språk";
"App Storage" = "App Lagring";
"Appearance" = "Utseende";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Er du sikker på at du vil slette alle bufrede data? Dette vil hjelpe med å frigjøre lagringsplass.";
"Are you sure you want to delete '%@'?" = "Er du sikker på at du vil slette '%@'?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "Er du sikker på at du vil slette alle %1$d episodene i '%2$@'?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Er du sikker på at du vil slette alle nedlastede filer? Du kan velge å kun tømme biblioteket og beholde de nedlastede filene til senere bruk.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Er du sikker på at du vil slette alle appens data? Denne handlingen kan ikke angres.";
/* Features */
"Background Enabled" = "Bakgrunn Aktivert";
"Bookmark items for an easier access later." = "Bokmerk elementer for enklere tilgang senere.";
"Bookmarks" = "Bokmerker";
"Bottom Padding" = "Bunnutfylling";
"Cancel" = "Avbryt";
"Cellular Quality" = "Mobilnettkvalitet";
"Check out some community modules here!" = "Sjekk ut noen fellesskapsmoduler her!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality." = "Velg foretrukken videooppløsning for WiFi og mobilnett. Høyere oppløsninger bruker mer data, men gir bedre kvalitet.";
"Clear" = "Tøm";
"Clear All Downloads" = "Tøm alle nedlastinger";
"Clear Cache" = "Tøm buffer";
"Clear Library Only" = "Tøm kun bibliotek";
"Clear Logs" = "Tøm logger";
"Click the plus button to add a module!" = "Klikk på pluss-knappen for å legge til en modul!";
"Continue Watching" = "Fortsett å se";
"Continue Watching Episode %d" = "Fortsett å se Episode %d";
"Contributors" = "Prosjektdeltaker";
"Copied to Clipboard" = "Kopiert til Utklippstavlen";
"Copy to Clipboard" = "Kopier til Utklippstavlen";
"Copy URL" = "Kopier URL";
/* Episodes */
"%lld Episodes" = "%lld Episoder";
"%lld of %lld" = "%lld av %lld";
"%lld-%lld" = "%lld-%lld";
"%lld%% seen" = "%lld%% sett";
"Episode %lld" = "Episode %lld";
"Episodes" = "Episoder";
"Episodes might not be available yet or there could be an issue with the source." = "Det er mulig at episodene ikke er tilgjengelige ennå, eller at det er en feil med kilden.";
"Episodes Range" = "Episoderrekkevidde";
/* System */
"cranci1" = "cranci1";
"Dark" = "Mørk";
"DATA & LOGS" = "DATA & LOGGER";
"Debug" = "Feilsøking";
"Debugging and troubleshooting." = "Feilsøking og debugging.";
/* Actions */
"Delete" = "Slett";
"Delete All" = "Slett alle";
"Delete All Downloads" = "Slett Alle Nedlastinger";
"Delete All Episodes" = "Slett Alle Episoder";
"Delete Download" = "Slett Nedlasting";
"Delete Episode" = "Slett Episode";
/* Player */
"Double Tap to Seek" = "Dobbeltklikk for å Søke";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Ved å dobbeltklikke på sidene av skjermen vil spilleren hoppe som definert i kortklikk-innstillingen.";
/* Downloads */
"Download" = "Last ned";
"Download Episode" = "Last ned Episode";
"Download Summary" = "Nedlastingssammendrag";
"Download This Episode" = "Last ned Denne Episoden";
"Downloaded" = "Nedlastet";
"Downloaded Shows" = "Nedlastede Serier";
"Downloading" = "Laster ned";
"Downloads" = "Nedlastinger";
/* Settings */
"Enable Analytics" = "Aktiver Analyser";
"Enable Subtitles" = "Aktiver Undertekster";
/* Data Management */
"Erase" = "Slett";
"Erase all App Data" = "Slett alle App Data";
"Erase App Data" = "Slett App Data";
/* Errors */
"Error" = "Feil";
"Error Fetching Results" = "Feil ved henting av resultater";
"Errors and critical issues." = "Feil og kritiske problemer.";
"Failed to load contributors" = "Kunne ikke laste prosjektdeltakere";
/* Features */
"Fetch Episode metadata" = "Hent Episode metadata";
"Files Downloaded" = "Nedlastede Filer";
"Font Size" = "Skriftstørrelse";
/* Interface */
"Force Landscape" = "Tving Landskapsmodus";
"General" = "Generelt";
"General events and activities." = "Generelle hendelser og aktiviteter.";
"General Preferences" = "Generelle Instillinger";
"Hide Splash Screen" = "Skjul Velkomstskjerm";
"HLS video downloading." = "HLS videonedlasting.";
"Hold Speed" = "Midlertidig Holdehastighet";
/* Info */
"Info" = "Info";
"INFOS" = "INFO";
"Installed Modules" = "Installerte moduler";
"Interface" = "Grensesnitt";
/* Social */
"Join the Discord" = "Bli med i vår Discord";
/* Layout */
"Landscape Columns" = "Kolonner i Landskapsmodus";
"Language" = "Språk";
"LESS" = "MINDRE";
/* Library */
"Library" = "Bibliotek";
"License (GPLv3.0)" = "Lisens (GPLv3.0)";
"Light" = "Lys";
/* Loading States */
"Loading Episode %lld..." = "Laster Episode %lld...";
"Loading logs..." = "Laster logger...";
"Loading module information..." = "Laster modulinformasjon...";
"Loading Stream" = "Laster Videostrøm";
/* Logging */
"Log Debug Info" = "Logg Feilsøkingsinfo";
"Log Filters" = "Loggfiltre";
"Log In with AniList" = "Logg inn med AniList";
"Log In with Trakt" = "Logg inn med Trakt";
"Log Out from AniList" = "Logg ut fra AniList";
"Log Out from Trakt" = "Logg ut fra Trakt";
"Log Types" = "Loggtyper";
"Logged in as" = "Logget inn som";
"Logged in as " = "Logget inn som ";
/* Logs and Settings */
"Logs" = "Logger";
"Long press Skip" = "Langt trykk Skip";
"MAIN" = "HOVED";
"Main Developer" = "Hovedutvikler";
"MAIN SETTINGS" = "HOVEDINNSTILLINGER";
/* Media Actions */
"Mark All Previous Watched" = "Merk Alle Tidligere som Sett";
"Mark as Watched" = "Merk som Sett";
"Mark Episode as Watched" = "Merk Episode som Sett";
"Mark Previous Episodes as Watched" = "Merk Tidligere Episoder som Sett";
"Mark watched" = "Merk som Sett";
"Match with AniList" = "Match med AniList";
"Match with TMDB" = "Match med TMDB";
"Matched ID: %lld" = "Matchet ID: %lld";
"Matched with: %@" = "Matchet med: %@";
"Max Concurrent Downloads" = "Maks Antall Parallele Nedlastinger";
/* Media Interface */
"Media Grid Layout" = "Medierutenettlayout";
"Media Player" = "Mediaspiller";
"Media View" = "Mediavisning";
"Metadata Provider" = "Metadata Leverandør";
"Metadata Providers Order" = "Metadata Leverandørs Rekkefølge";
"Module Removed" = "Modul Fjernet";
"Modules" = "Moduler";
/* Headers */
"MODULES" = "MODULER";
"MORE" = "MER";
/* Status Messages */
"No Active Downloads" = "Ingen Aktive Nedlastinger";
"No AniList matches found" = "Ingen AniList-treff funnet";
"No Data Available" = "Ingen Data Tilgjengelig";
"No Downloads" = "Ingen Nedlastinger";
"No episodes available" = "Ingen episoder tilgjengelig";
"No Episodes Available" = "Ingen Episoder Tilgjengelig";
"No items to continue watching." = "Ingen elementer å fortsette å se på.";
"No matches found" = "Ingen treff funnet";
"No Module Selected" = "Ingen Modul Valgt";
"No Modules" = "Ingen Moduler";
"No Results Found" = "Ingen Resultater Funnet";
"No Search Results Found" = "Ingen Søkeresultater Funnet";
"Nothing to Continue Watching" = "Ingenting å fortsette å se på";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Merk at modulene erstattes kun hvis det er en annen versjonsstreng i JSON-filen.";
/* Actions */
"OK" = "OK";
"Open Community Library" = "Åpne Fellesskapsbibliotek";
/* External Services */
"Open in AniList" = "Åpne i AniList";
"Original Poster" = "Originalplakat";
/* Playback */
"Paused" = "Pauset";
"Play" = "Spill av";
"Player" = "Spiller";
/* System Messages */
"Please restart the app to apply the language change." = "Appen må startes på nytt for å aktivere språkendringen.";
"Please select a module from settings" = "Velg en modul fra innstillinger";
/* Interface */
"Portrait Columns" = "Kolonner i Portrettmodus";
"Progress bar Marker Color" = "Farge på Progresjonslinje";
"Provider: %@" = "Leverandør: %@";
/* Queue */
"Queue" = "Kø";
"Queued" = "I kø";
/* Content */
"Recently watched content will appear here." = "Nylig sett innhold vil vises her.";
/* Settings */
"Refresh Modules on Launch" = "Oppdater Moduler ved Oppstart";
"Refresh Storage Info" = "Oppdater Lagringsinformasjon";
"Remember Playback speed" = "Husk Avspillingshastighet";
/* Actions */
"Remove" = "Fjern";
"Remove All Cache" = "Fjern Alle Buffer";
/* File Management */
"Remove All Documents" = "Fjern Alle Dokumenter";
"Remove Documents" = "Fjern Dokumenter";
"Remove Downloaded Media" = "Fjern Nedlastet Media";
"Remove Downloads" = "Fjern Nedlastinger";
"Remove from Bookmarks" = "Fjern fra Bokmerker";
"Remove Item" = "Fjern Element";
/* Support */
"Report an Issue" = "Rapporter et Problem";
/* Reset Options */
"Reset" = "Tilbakestill";
"Reset AniList ID" = "Tilbakestill AniList-ID";
"Reset Episode Progress" = "Tilbakestill Episodeprogresjon";
"Reset progress" = "Tilbakestill Progresjon";
"Reset Progress" = "Tilbakestill Progresjon";
/* System */
"Restart Required" = "Omstart Kreves";
"Running Sora %@ - cranci1" = "Kjører Sora %@ - cranci1";
/* Actions */
"Save" = "Lagre";
"Search" = "Søk";
/* Search */
"Search downloads" = "Søk i nedlastinger";
"Search for something..." = "Søk etter noe...";
"Search..." = "Søk...";
/* Content */
"Season %d" = "Sesong %d";
"Season %lld" = "Sesong %lld";
"Segments Color" = "Segmentfarge";
/* Modules */
"Select Module" = "Velg Modul";
"Set Custom AniList ID" = "Sett Egendefinert AniList-ID";
/* Interface */
"Settings" = "Innstillinger";
"Shadow" = "Skygge";
"Show More (%lld more characters)" = "Vis mer (%lld flere tegn)";
"Show PiP Button" = "Vis PiP Knapp";
"Show Skip 85s Button" = "Vis Hopp 85s Knapp";
"Show Skip Intro / Outro Buttons" = "Vis hopp over Intro / Outro Knapper";
"Shows" = "Serier";
"Size (%@)" = "Størrelse (%@)";
"Skip Settings" = "Tidshopp Innstillinger";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Noen funksjoner er begrenset til Sora og standardavspiller, som Tving Landskapsmodus, Midlertidig Holdehastighet og tilpassede tidshopp.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ av cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate." = "Sora og cranci1 er ikke tilknyttet AniList eller Trakt på noen måte.\n\nVær også oppmerksom på at progresjonsoppdateringer ikke nødvendigvis er 100% nøyaktige.";
"Sora GitHub Repository" = "Sora GitHub Kodelager";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur vil alltid være gratis og uten reklame!";
/* Interface */
"Sort" = "Sorter";
"Speed Settings" = "Hastighetsinnstillinger";
/* Playback */
"Start Watching" = "Start å se";
"Start Watching Episode %d" = "Start å se Episode %d";
"Storage Used" = "Brukt Lagring";
"Stream" = "Strøm";
"Streaming and video playback." = "Strømming og videoavspilling.";
/* Subtitles */
"Subtitle Color" = "Undertekstfarge";
"Subtitle Settings" = "Undertekstinnstillinger";
/* Sync */
"Sync anime progress" = "Synkroniser anime progresjon";
"Sync TV shows progress" = "Synkroniser TV-serie progresjon";
/* System */
"System" = "System";
/* Instructions */
"Tap a title to override the current match." = "Trykk på en tittel for å overstyre gjeldende treff.";
"Tap Skip" = "Trykk for å hoppe Fram / Tilbake";
"Tap to manage your modules" = "Trykk for å administrere modulene dine";
"Tap to select a module" = "Trykk for å velge en modul";
/* App Information */
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "Appens buffer hjelper appen med å laste bilder raskere.\n\nÅ tømme dokumentmappen vil slette alle nedlastede moduler.\n\nIkke slett App Lagring med mindre du forstår konsekvensene — det kan føre til at appen ikke fungerer som den skal.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "Episoderekkevidden styrer hvor mange episoder som vises på hver side. Episoder er gruppert i sett (som 125, 2650, osv.), slik at du enklere kan navigere gjennom dem.\n\nEpisode-metadata refererer til episodens miniatyrbilde og tittel, da det noen ganger kan inneholde spoilers.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "Modulen leverte kun en episode, dette er mest sannsynlig en film, så vi bestemte oss for å lage separate sider for disse tilfellene.";
/* Interface */
"Thumbnails Width" = "Miniatyrbildebredde";
"TMDB Match" = "TMDB Treff";
"Trackers" = "Sporere";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Prøv andre nøkkelord";
"Try different search terms" = "Prøv andre søkeord";
/* Player Controls */
"Two Finger Hold for Pause" = "Hold to fingre for pause";
"Unable to fetch matches. Please try again later." = "Kunne ikke finne noen treff. Vennligst prøv igjen senere.";
"Use TMDB Poster Image" = "Bruk TMDB Plakatbilde";
/* Version */
"v%@" = "v%@";
"Video Player" = "Videospiller";
/* Video Settings */
"Video Quality Preferences" = "Videokvalitetspreferanser";
"View All" = "Se alle";
"Watched" = "Sett";
"Why am I not seeing any episodes?" = "Hvorfor ser jeg ingen episoder?";
"WiFi Quality" = "WiFi Kvalitet";
/* User Status */
"You are not logged in" = "Du er ikke logget inn";
"You have no items saved." = "Du har ingen lagrede elementer.";
"Your downloaded episodes will appear here" = "Dine nedlastede episoder vil vises her";
"Your recently watched content will appear here" = "Ditt nylig sette innhold vil vises her";
/* Download Settings */
"Download Settings" = "Nedlastingsinnstillinger";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Maks antall parallele nedlastinger styrer hvor mange episoder som kan lastes ned samtidig. Høyere verdier kan bruke mer båndbredde og enhetsressurser.";
"Quality" = "Kvalitet";
"Max Concurrent Downloads" = "Maks Antall Parallele Nedlastinger";
"Allow Cellular Downloads" = "Tillat Nedlastinger over Mobilnett";
"Quality Information" = "Kvalitetsinformasjon";
/* Storage */
"Storage Management" = "Lagringsadministrasjon";
"Storage Used" = "Brukt Lagring";
"Library cleared successfully" = "Bibliotek tømt";
"All downloads deleted successfully" = "Alle nedlastinger slettet";
/* New keys from English localization */
"DownloadCountFormat" = "%d av %d";
"Error loading chapter" = "Feil ved lasting av kapittel";
"Font Size: %dpt" = "Skriftstorleik: %dpt";
"Line Spacing: %.1f" = "Linjeavstand: %.1f";
"Line Spacing" = "Linjeavstand";
"Margin: %dpx" = "Marg: %dpx";
"Margin" = "Marg";
"Auto Scroll Speed" = "Fart på automatisk rulling";
"Speed" = "Fart";
"Speed: %.1fx" = "Fart: %.1fx";
"Matched %@: %@" = "Treff %@: %@";
"Enter the AniList ID for this series" = "Skriv inn AniList-ID for denne serien";
/* Added missing localizations */
"Create Collection" = "Opprett samling";
"Collection Name" = "Samlingens navn";
"Rename Collection" = "Gi nytt navn til samling";
"Rename" = "Gi nytt navn";
"All Reading" = "All lesing";
"Recently Added" = "Nylig lagt til";
"Novel Title" = "Roman tittel";
"Read Progress" = "Lesefremgang";
"Date Created" = "Opprettelsesdato";
"Name" = "Navn";
"Item Count" = "Antall elementer";
"Date Added" = "Dato lagt til";
"Title" = "Tittel";
"Source" = "Kilde";
"Search reading..." = "Søk i lesing...";
"Search collections..." = "Søk i samlinger...";
"Search bookmarks..." = "Søk i bokmerker...";
"%d items" = "%d elementer";
"Fetching Data" = "Henter data";
"Please wait while fetching." = "Vennligst vent mens det hentes.";
"Start Reading" = "Start lesing";
"Chapters" = "Kapitler";
"Completed" = "Fullført";
"Drag to reorder" = "Dra for å endre rekkefølge";
"Drag to reorder sections" = "Dra for å endre rekkefølge på seksjoner";
"Library View" = "Bibliotekvisning";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Tilpass seksjonene som vises i biblioteket ditt. Du kan endre rekkefølge eller deaktivere seksjoner helt.";
"Library Sections Order" = "Rekkefølge på bibliotekseksjoner";
"Completion Percentage" = "Fullføringsprosent";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Noen funksjoner er begrenset til Sora- og standardspilleren, som tvunget landskap, holdhastighet og tilpassede tidshopp.\n\nFullføringsprosenten bestemmer når før slutten av en video appen markerer den som fullført på AniList og Trakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "Appens cache hjelper med å laste inn bilder raskere.\n\nÅ tømme Dokumenter-mappen sletter alle nedlastede moduler.\n\nÅ slette appdata sletter alle innstillinger og data.";
"Translators" = "Oversettere";
"Paste URL" = "Lim inn URL";
/* Added missing localizations */
"Series Title" = "Serietittel";
"Content Source" = "Innhaldskjelde";
"Watch Progress" = "Framdrift for vising";
"Recent searches" = "Nylege søk";
"All Reading" = "Alt du les";
"Nothing to Continue Reading" = "Ingenting å fortsetje å lese";
"Your recently read novels will appear here" = "Dine nyleg lesne romanar vil visast her";
"No Bookmarks" = "Ingen bokmerke";
"Add bookmarks to this collection" = "Legg til bokmerke i denne samlinga";
"items" = "element";
"All Watching" = "Alt du ser på";
"No Reading History" = "Ingen leseloggar";
"Books you're reading will appear here" = "Bøker du les vil visast her";
"Create Collection" = "Opprett samling";
"Collection Name" = "Samlingnamn";
"Rename Collection" = "Endre namn på samling";
"Rename" = "Endre namn";
"Novel Title" = "Roman tittel";
"Read Progress" = "Leseframdrift";
"Date Created" = "Oppretta dato";
"Name" = "Namn";
"Item Count" = "Tal på element";
"Date Added" = "Dato lagt til";
"Title" = "Tittel";
"Source" = "Kjelde";
"Search reading..." = "Søk i lesing...";
"Search collections..." = "Søk i samlingar...";
"Search bookmarks..." = "Søk i bokmerke...";
"%d items" = "%d element";
"Fetching Data" = "Hentar data";
"Please wait while fetching." = "Vent medan data vert henta.";
"Start Reading" = "Start lesing";
"Chapters" = "Kapittel";
"Completed" = "Fullført";
"Drag to reorder" = "Dra for å endre rekkefølgje";
"Drag to reorder sections" = "Dra for å endre rekkefølgje på seksjonar";
"Library View" = "Bibliotekvising";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Tilpass seksjonane som vert viste i biblioteket ditt. Du kan endre rekkefølgje eller slå dei heilt av.";
"Library Sections Order" = "Rekkefølgje på bibliotekseksjonar";
"Completion Percentage" = "Fullføringsprosent";
"Translators" = "Omsetjarar";
"Paste URL" = "Lim inn URL";
/* Added missing localizations */
"Collections" = "Samlingar";
"Continue Reading" = "Hald fram med å lese";
/* Backup & Restore */
"Backup & Restore" = "Sikkerhetskopi og gjenoppretting";
"Export Backup" = "Eksporter sikkerhetskopi";
"Import Backup" = "Importer sikkerhetskopi";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Merk: Denne funksjonen er fortsatt eksperimentell. Vennligst dobbeltsjekk dataene dine etter eksport/import.";
"Backup" = "Sikkerhetskopi";

View file

@ -0,0 +1,512 @@
/* General */
"About" = "О программе";
"About Sora" = "О Sora";
"Active" = "Активные";
"Active Downloads" = "Активные загрузки";
"Actively downloading media can be tracked from here." = "Активно загружаемые медиафайлы можно отслеживать отсюда.";
"Add Module" = "Добавить модуль";
"Adjust the number of media items per row in portrait and landscape modes." = "Настройте количество медиаэлементов в строке в портретном и альбомном режимах.";
"Advanced" = "Расширенные";
"AKA Sulfur" = "Также известен как Sulfur";
"All Bookmarks" = "Все закладки";
"All Watching" = "Все просматриваемое";
"Also known as Sulfur" = "Также известен как Sulfur";
"AniList" = "AniList";
"AniList ID" = "AniList ID";
"AniList Match" = "Совпадение AniList";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Анонимные данные собираются для улучшения приложения. Личная информация не собирается. Это можно отключить в любое время.";
"App Info" = "Информация о приложении";
"App Language" = "Язык приложения";
"App Storage" = "Хранилище приложения";
"Appearance" = "Внешний вид";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Вы уверены, что хотите очистить все кэшированные данные? Это поможет освободить место в хранилище.";
"Are you sure you want to delete '%@'?" = "Вы уверены, что хотите удалить '%@'?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "Вы уверены, что хотите удалить все %1$d эпизодов в '%2$@'?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Вы уверены, что хотите удалить все загруженные файлы? Вы можете выбрать очистку только библиотеки, сохранив загруженные файлы для будущего использования.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Вы уверены, что хотите стереть все данные приложения? Это действие нельзя отменить.";
/* Features */
"Background Enabled" = "Фон включен";
"Bookmark items for an easier access later." = "Добавляйте элементы в закладки для более легкого доступа позже.";
"Bookmarks" = "Закладки";
"Bottom Padding" = "Отступ снизу";
"Cancel" = "Отмена";
"Cellular Quality" = "Качество мобильной сети";
"Check out some community modules here!" = "Посмотрите модули сообщества здесь!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Выберите предпочтительное разрешение видео для WiFi и мобильных соединений. Более высокие разрешения используют больше данных, но обеспечивают лучшее качество. Если точное качество недоступно, ближайший вариант будет выбран автоматически.\n\nПримечание: Не все источники видео и плееры поддерживают выбор качества. Эта функция работает лучше всего с HLS потоками, использующими плеер Sora.";
"Clear" = "Очистить";
"Clear All Downloads" = "Очистить все загрузки";
"Clear Cache" = "Очистить кэш";
"Clear Library Only" = "Очистить только библиотеку";
"Clear Logs" = "Очистить логи";
"Click the plus button to add a module!" = "Нажмите кнопку плюс, чтобы добавить модуль!";
"Continue Watching" = "Продолжить просмотр";
"Continue Watching Episode %d" = "Продолжить просмотр эпизода %d";
"Contributors" = "Участники разработки";
"Copied to Clipboard" = "Скопировано в буфер обмена";
"Copy to Clipboard" = "Скопировать в буфер обмена";
"Copy URL" = "Скопировать URL";
/* Episodes */
"%lld Episodes" = "%lld эпизодов";
"%lld of %lld" = "%lld из %lld";
"%lld-%lld" = "%lld-%lld";
"%lld%% seen" = "%lld%% просмотрено";
"Episode %lld" = "Эпизод %lld";
"Episodes" = "Эпизоды";
"Episodes might not be available yet or there could be an issue with the source." = "Эпизоды могут быть еще недоступны или могут быть проблемы с источником.";
"Episodes Range" = "Диапазон эпизодов";
/* System */
"cranci1" = "cranci1";
"Dark" = "Темная";
"DATA & LOGS" = "ДАННЫЕ И ЛОГИ";
"Debug" = "Отладка";
"Debugging and troubleshooting." = "Отладка и устранение неполадок.";
/* Actions */
"Delete" = "Удалить";
"Delete All" = "Удалить все";
"Delete All Downloads" = "Удалить все загрузки";
"Delete All Episodes" = "Удалить все эпизоды";
"Delete Download" = "Удалить загрузку";
"Delete Episode" = "Удалить эпизод";
/* Player */
"Double Tap to Seek" = "Двойное касание для перемотки";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Двойное касание по бокам экрана будет перематывать в соответствии с настройкой короткого касания.";
/* Downloads */
"Download" = "Загрузить";
"Download Episode" = "Загрузить эпизод";
"Download Summary" = "Сводка загрузок";
"Download This Episode" = "Загрузить этот эпизод";
"Downloaded" = "Загружено";
"Downloaded Shows" = "Загруженные шоу";
"Downloading" = "Загружается";
"Downloads" = "Загрузки";
/* Settings */
"Enable Analytics" = "Включить аналитику";
"Enable Subtitles" = "Включить субтитры";
/* Data Management */
"Erase" = "Стереть";
"Erase all App Data" = "Стереть все данные приложения";
"Erase App Data" = "Стереть данные приложения";
/* Errors */
"Error" = "Ошибка";
"Error Fetching Results" = "Ошибка получения результатов";
"Errors and critical issues." = "Ошибки и критические проблемы.";
"Failed to load contributors" = "Не удалось загрузить участников";
/* Features */
"Fetch Episode metadata" = "Получить метаданные эпизода";
"Files Downloaded" = "Файлы загружены";
"Font Size" = "Размер шрифта";
/* Interface */
"Force Landscape" = "Принудительный альбомный режим";
"General" = "Общие";
"General events and activities." = "Общие события и действия.";
"General Preferences" = "Общие настройки";
"Hide Splash Screen" = "Скрыть заставку";
"HLS video downloading." = "Загрузка HLS видео.";
"Hold Speed" = "Скорость при удержании";
/* Info */
"Info" = "Информация";
"INFOS" = "ИНФОРМАЦИЯ";
"Installed Modules" = "Установленные модули";
"Interface" = "Интерфейс";
/* Social */
"Join the Discord" = "Присоединиться к Discord";
/* Layout */
"Landscape Columns" = "Столбцы в альбомном режиме";
"Language" = "Язык";
"LESS" = "МЕНЬШЕ";
/* Library */
"Library" = "Библиотека";
"License (GPLv3.0)" = "Лицензия (GPLv3.0)";
"Light" = "Светлая";
/* Loading States */
"Loading Episode %lld..." = "Загрузка эпизода %lld...";
"Loading logs..." = "Загрузка логов...";
"Loading module information..." = "Загрузка информации о модуле...";
"Loading Stream" = "Загрузка потока";
/* Logging */
"Log Debug Info" = "Записывать отладочную информацию";
"Log Filters" = "Записывать фильтры";
"Log In with AniList" = "Войти с AniList";
"Log In with Trakt" = "Войти с Trakt";
"Log Out from AniList" = "Выйти из AniList";
"Log Out from Trakt" = "Выйти из Trakt";
"Log Types" = "Записывать Типы";
"Logged in as" = "Вошли как";
"Logged in as " = "Вошли как ";
/* Logs and Settings */
"Logs" = "Логи";
"Long press Skip" = "Долгое нажатие для пропуска";
"MAIN" = "ОСНОВНОЕ";
"Main Developer" = "Главный разработчик";
"MAIN SETTINGS" = "ОСНОВНЫЕ НАСТРОЙКИ";
/* Media Actions */
"Mark All Previous Watched" = "Отметить все предыдущие как просмотренные";
"Mark as Watched" = "Отметить как просмотренное";
"Mark Episode as Watched" = "Отметить эпизод как просмотренный";
"Mark Previous Episodes as Watched" = "Отметить предыдущие эпизоды как просмотренные";
"Mark watched" = "Отметить просмотренным";
"Match with AniList" = "Сопоставить с AniList";
"Match with TMDB" = "Сопоставить с TMDB";
"Matched ID: %lld" = "Сопоставленный ID: %lld";
"Matched with: %@" = "Сопоставлено с: %@";
"Max Concurrent Downloads" = "Максимальное количество одновременных загрузок";
/* Media Interface */
"Media Grid Layout" = "Макет сетки медиа";
"Media Player" = "Медиаплеер";
"Media View" = "Просмотр медиа";
"Metadata Provider" = "Поставщик метаданных";
"Metadata Providers Order" = "Порядок поставщиков метаданных";
"Module Removed" = "Модуль удален";
"Modules" = "Модули";
/* Headers */
"MODULES" = "МОДУЛИ";
"MORE" = "БОЛЬШЕ";
/* Status Messages */
"No Active Downloads" = "Нет активных загрузок";
"No AniList matches found" = "Совпадений AniList не найдено";
"No Data Available" = "Данные недоступны";
"No Downloads" = "Нет загрузок";
"No episodes available" = "Эпизоды недоступны";
"No Episodes Available" = "Эпизоды недоступны";
"No items to continue watching." = "Нет элементов для продолжения просмотра.";
"No matches found" = "Совпадений не найдено";
"No Module Selected" = "Модуль не выбран";
"No Modules" = "Нет модулей";
"No Results Found" = "Результаты не найдены";
"No Search Results Found" = "Результаты поиска не найдены";
"Nothing to Continue Watching" = "Нечего продолжать смотреть";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Обратите внимание, что модули будут заменены только в том случае, если в JSON-файле есть другая строка версии.";
/* Actions */
"OK" = "ОК";
"Open Community Library" = "Открыть библиотеку сообщества";
/* External Services */
"Open in AniList" = "Открыть в AniList";
"Original Poster" = "Оригинальный постер";
/* Playback */
"Paused" = "Приостановлено";
"Play" = "Воспроизвести";
"Player" = "Плеер";
/* System Messages */
"Please restart the app to apply the language change." = "Пожалуйста, перезапустите приложение, чтобы применить изменение языка.";
"Please select a module from settings" = "Пожалуйста, выберите модуль в настройках";
/* Interface */
"Portrait Columns" = "Столбцы в портретном режиме";
"Progress bar Marker Color" = "Цвет маркера полосы прогресса";
"Provider: %@" = "Поставщик: %@";
/* Queue */
"Queue" = "Очередь";
"Queued" = "В очереди";
/* Content */
"Recently watched content will appear here." = "Недавно просмотренный контент будет отображаться здесь.";
/* Settings */
"Refresh Modules on Launch" = "Обновлять модули при запуске";
"Refresh Storage Info" = "Обновить информацию о хранилище";
"Remember Playback speed" = "Запомнить скорость воспроизведения";
/* Actions */
"Remove" = "Удалить";
"Remove All Cache" = "Удалить весь кэш";
/* File Management */
"Remove All Documents" = "Удалить все документы";
"Remove Documents" = "Удалить документы";
"Remove Downloaded Media" = "Удалить загруженные медиафайлы";
"Remove Downloads" = "Удалить загрузки";
"Remove from Bookmarks" = "Удалить из закладок";
"Remove Item" = "Удалить элемент";
/* Support */
"Report an Issue" = "Сообщить о проблеме";
/* Reset Options */
"Reset" = "Сбросить";
"Reset AniList ID" = "Сбросить AniList ID";
"Reset Episode Progress" = "Сбросить прогресс эпизода";
"Reset progress" = "Сбросить прогресс";
"Reset Progress" = "Сбросить прогресс";
/* System */
"Restart Required" = "Требуется перезапуск";
"Running Sora %@ - cranci1" = "Запущена Sora %@ - cranci1";
/* Actions */
"Save" = "Сохранить";
"Search" = "Поиск";
/* Search */
"Search downloads" = "Поиск загрузок";
"Search for something..." = "Поиск чего-либо...";
"Search..." = "Поиск...";
/* Content */
"Season %d" = "Сезон %d";
"Season %lld" = "Сезон %lld";
"Segments Color" = "Цвет сегментов";
/* Modules */
"Select Module" = "Выбрать модуль";
"Set Custom AniList ID" = "Установить пользовательский AniList ID";
/* Interface */
"Settings" = "Настройки";
"Shadow" = "Тень";
"Show More (%lld more characters)" = "Показать больше (еще %lld символов)";
"Show PiP Button" = "Показать кнопку PiP";
"Show Skip 85s Button" = "Показать кнопку пропуска 85с";
"Show Skip Intro / Outro Buttons" = "Показать кнопки пропуска интро/аутро";
"Shows" = "Шоу";
"Size (%@)" = "Размер (%@)";
"Skip Settings" = "Настройки перемотки";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Некоторые функции ограничены плеерами Sora и Default, такие как принудительный альбомный режим, скорость при удержании и пользовательские интервалы перемотки времени.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ от cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.
Also note that progress updates may not be 100% accurate." = "Sora и cranci1 никак не связаны с AniList или Trakt.
Также обратите внимание, что обновления прогресса могут быть не на 100% точными.";
"Sora GitHub Repository" = "Репозиторий Sora на GitHub";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur всегда останется бесплатной без рекламы!";
/* Interface */
"Sort" = "Сортировка";
"Speed Settings" = "Настройки скорости";
/* Playback */
"Start Watching" = "Начать просмотр";
"Start Watching Episode %d" = "Начать просмотр эпизода %d";
"Storage Used" = "Использовано хранилища";
"Stream" = "Поток";
"Streaming and video playback." = "Потоковая передача и воспроизведение видео.";
/* Subtitles */
"Subtitle Color" = "Цвет субтитров";
"Subtitle Settings" = "Настройки субтитров";
/* Sync */
"Sync anime progress" = "Синхронизировать прогресс аниме";
"Sync TV shows progress" = "Синхронизировать прогресс ТВ-шоу";
/* System */
"System" = "Система";
/* Instructions */
"Tap a title to override the current match." = "Нажмите на название, чтобы переопределить текущее совпадение.";
"Tap Skip" = "Пропуск нажатием";
"Tap to manage your modules" = "Нажмите, чтобы управлять модулями";
"Tap to select a module" = "Нажмите, чтобы выбрать модуль";
/* App Information */
"The app cache helps the app load images faster.
Clearing the Documents folder will delete all downloaded modules.
Do not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "Кэш приложения помогает приложению загружать изображения быстрее.
Очистка папки документов удалит все загруженные модули.
Не стирайте данные приложения, если не понимаете последствий — это может привести к сбоям в работе приложения.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.
For episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "Диапазон эпизодов определяет, сколько эпизодов отображается на каждой странице. Эпизоды группируются в наборы (например, 125, 2650 и т. д.), что упрощает навигацию.
Для метаданных эпизода это относится к миниатюре и названию эпизода, поскольку иногда они могут содержать спойлеры.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "Модуль предоставил только один эпизод, это скорее всего фильм, поэтому мы решили сделать отдельные экраны для таких случаев.";
/* Interface */
"Thumbnails Width" = "Ширина миниатюр";
"TMDB Match" = "Совпадение TMDB";
"Trackers" = "Трекеры";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Попробуйте другие ключевые слова";
"Try different search terms" = "Попробуйте другие поисковые термины";
/* Player Controls */
"Two Finger Hold for Pause" = "Удержание двумя пальцами для паузы";
"Unable to fetch matches. Please try again later." = "Не удается получить совпадения. Пожалуйста, попробуйте позже.";
"Use TMDB Poster Image" = "Использовать изображение постера TMDB";
/* Version */
"v%@" = "v%@";
"Video Player" = "Видеоплеер";
/* Video Settings */
"Video Quality Preferences" = "Настройки качества видео";
"View All" = "Просмотреть все";
"Watched" = "Просмотрено";
"Why am I not seeing any episodes?" = "Почему я не вижу эпизодов?";
"WiFi Quality" = "Качество WiFi";
/* User Status */
"You are not logged in" = "Вы не вошли в";
"You have no items saved." = "У вас нет сохраненных элементов.";
"Your downloaded episodes will appear here" = "Ваши загруженные эпизоды будут отображаться здесь";
"Your recently watched content will appear here" = "Ваш недавно просмотренный контент будет отображаться здесь";
/* Download Settings */
"Download Settings" = "Настройки загрузки";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Максимальное количество одновременных загрузок контролирует, сколько эпизодов могут загружаться одновременно. Более высокие значения могут использовать больше пропускной способности и ресурсов устройства.";
"Quality" = "Качество";
"Max Concurrent Downloads" = "Максимальное количество одновременных загрузок";
"Allow Cellular Downloads" = "Разрешить загрузки по мобильной сети";
"Quality Information" = "Информация о качестве";
/* Storage */
"Storage Management" = "Управление хранилищем";
"Storage Used" = "Использовано хранилища";
"Library cleared successfully" = "Библиотека успешно очищена";
"All downloads deleted successfully" = "Все загрузки успешно удалены";
/* Recent searches */
"Recent searches" = "Недавние поиски";
"me frfr" = "я фрфр";
"Data" = "Данные";
/* New string */
"Maximum Quality Available" = "Максимальное доступное качество";
/* Additional translations */
"DownloadCountFormat" = "%d из %d";
"Error loading chapter" = "Ошибка загрузки главы";
"Font Size: %dpt" = "Размер шрифта: %dpt";
"Line Spacing: %.1f" = "Межстрочный интервал: %.1f";
"Line Spacing" = "Межстрочный интервал";
"Margin: %dpx" = "Поле: %dpx";
"Margin" = "Поле";
"Auto Scroll Speed" = "Скорость автопрокрутки";
"Speed" = "Скорость";
"Speed: %.1fx" = "Скорость: %.1fx";
"Matched %@: %@" = "Совпадение %@: %@";
"Enter the AniList ID for this series" = "Введите AniList ID для этой серии";
/* Added missing localizations */
"Create Collection" = "Создать коллекцию";
"Collection Name" = "Название коллекции";
"Rename Collection" = "Переименовать коллекцию";
"Rename" = "Переименовать";
"All Reading" = "Все чтения";
"Recently Added" = "Недавно добавленные";
"Novel Title" = "Название романа";
"Read Progress" = "Прогресс чтения";
"Date Created" = "Дата создания";
"Name" = "Имя";
"Item Count" = "Количество элементов";
"Date Added" = "Дата добавления";
"Title" = "Заголовок";
"Source" = "Источник";
"Search reading..." = "Поиск по чтению...";
"Search collections..." = "Поиск по коллекциям...";
"Search bookmarks..." = "Поиск по закладкам...";
"%d items" = "%d элементов";
"Fetching Data" = "Получение данных";
"Please wait while fetching." = "Пожалуйста, подождите, идет получение данных.";
"Start Reading" = "Начать чтение";
"Chapters" = "Главы";
"Completed" = "Завершено";
"Drag to reorder" = "Перетащите для изменения порядка";
"Drag to reorder sections" = "Перетащите для изменения порядка разделов";
"Library View" = "Вид библиотеки";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Настройте разделы, отображаемые в вашей библиотеке. Вы можете изменить порядок разделов или полностью их отключить.";
"Library Sections Order" = "Порядок разделов библиотеки";
"Completion Percentage" = "Процент завершения";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Некоторые функции доступны только в Sora и стандартном плеере, такие как принудительный ландшафт, удержание скорости и пользовательские интервалы пропуска.\n\nНастройка процента завершения определяет, в какой момент до конца видео приложение отметит его как завершенное на AniList и Trakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "Кэш приложения помогает быстрее загружать изображения.\n\nОчистка папки Documents удалит все загруженные модули.\n\nСтирание данных приложения удалит все ваши настройки и данные.";
"Translators" = "Переводчики";
"Paste URL" = "Вставить URL";
/* Added missing localizations */
"Series Title" = "Название серии";
"Content Source" = "Источник контента";
"Watch Progress" = "Прогресс просмотра";
"Recent searches" = "Недавние поиски";
"All Reading" = "Всё для чтения";
"Nothing to Continue Reading" = "Нечего продолжать читать";
"Your recently read novels will appear here" = "Ваши недавно прочитанные романы появятся здесь";
"No Bookmarks" = "Нет закладок";
"Add bookmarks to this collection" = "Добавьте закладки в эту коллекцию";
"items" = "элементы";
"All Watching" = "Всё для просмотра";
"No Reading History" = "Нет истории чтения";
"Books you're reading will appear here" = "Книги, которые вы читаете, появятся здесь";
"Create Collection" = "Создать коллекцию";
"Collection Name" = "Название коллекции";
"Rename Collection" = "Переименовать коллекцию";
"Rename" = "Переименовать";
"Novel Title" = "Название романа";
"Read Progress" = "Прогресс чтения";
"Date Created" = "Дата создания";
"Name" = "Имя";
"Item Count" = "Количество элементов";
"Date Added" = "Дата добавления";
"Title" = "Заголовок";
"Source" = "Источник";
"Search reading..." = "Поиск по чтению...";
"Search collections..." = "Поиск по коллекциям...";
"Search bookmarks..." = "Поиск по закладкам...";
"%d items" = "%d элементов";
"Fetching Data" = "Получение данных";
"Please wait while fetching." = "Пожалуйста, подождите, идет получение данных.";
"Start Reading" = "Начать чтение";
"Chapters" = "Главы";
"Completed" = "Завершено";
"Drag to reorder" = "Перетащите для изменения порядка";
"Drag to reorder sections" = "Перетащите для изменения порядка разделов";
"Library View" = "Вид библиотеки";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Настройте разделы, отображаемые в вашей библиотеке. Вы можете изменить порядок разделов или полностью их отключить.";
"Library Sections Order" = "Порядок разделов библиотеки";
"Completion Percentage" = "Процент завершения";
"Translators" = "Переводчики";
"Paste URL" = "Вставить URL";
/* Added missing localizations */
"Collections" = "Коллекции";
"Continue Reading" = "Продолжить чтение";
/* Backup & Restore */
"Backup & Restore" = "Резервное копирование и восстановление";
"Export Backup" = "Экспорт резервной копии";
"Import Backup" = "Импорт резервной копии";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Внимание: Эта функция все еще экспериментальная. Пожалуйста, проверьте свои данные после экспорта/импорта.";
"Backup" = "Резервная копия";

View file

@ -0,0 +1,508 @@
/* General */
"About" = "O aplikácii";
"About Sora" = "O aplikácii Sora";
"Active" = "Aktívne";
"Active Downloads" = "Aktívne sťahovania";
"Actively downloading media can be tracked from here." = "Aktívne preberané médiá môžete sledovať tu.";
"Add Module" = "Pridať modul";
"Adjust the number of media items per row in portrait and landscape modes." = "Nastavte počet mediálnych položiek na riadok v režime na výšku a na šírku.";
"Advanced" = "Pokročilé";
"AKA Sulfur" = "AKA Sulfur";
"All Bookmarks" = "Všetky záložky";
"All Watching" = "Všetky rozpozerané";
"Also known as Sulfur" = "Tiež známe ako Sulfur";
"AniList" = "AniList";
"AniList ID" = "ID AniList";
"AniList Match" = "AniList Match";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Anonymné údaje sa zbierajú na zlepšenie aplikácie. Žiadne osobné údaje sa nezbierajú. Túto možnosť môžete kedykoľvek vypnúť.";
"App Info" = "Informácie o aplikácii";
"App Language" = "Jazyk aplikácie";
"App Storage" = "Ukladací priestor aplikácie";
"Appearance" = "Vzhľad";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Naozaj chcete vymazať všetku medzipamäť? Pomôže to uvoľniť miesto na disku.";
"Are you sure you want to delete '%@'?" = "Naozaj chcete zmazať '%@'?";
"Are you sure you want to delete all %1\$d episodes in '%2\$@'?" = "Naozaj chcete zmazať všetkých %1\$d epizód v '%2\$@'?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Naozaj chcete zmazať všetky stiahnuté súbory? Môžete vymazať iba knižnicu a stiahnuté súbory si ponechať.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Naozaj chcete vymazať všetky dáta aplikácie? Túto akciu nie je možné vrátiť späť.";
/* Features */
"Background Enabled" = "V pozadí povolené";
"Bookmark items for an easier access later." = "Označte položky záložkou pre ľahší prístup neskôr.";
"Bookmarks" = "Záložky";
"Bottom Padding" = "Spodná výplň";
"Cancel" = "Zrušiť";
"Cellular Quality" = "Kvalita cez mobilné dáta";
"Check out some community modules here!" = "Prezrite si nejaké komunitou vytvorené moduly tu!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player." = "Vyberte preferované video rozlíšenie pre WiFi a mobilné pripojenia. Vyššie rozlíšenia používajú viac dát, ale poskytujú lepšiu kvalitu. Ak presné rozlíšenie nie je dostupné, automaticky sa vyberie najbližšia možnosť.\n\nPoznámka: Nie všetky video zdroje a prehrávače podporujú výber kvality. Táto funkcia funguje najlepšie s HLS streamami pomocou prehrávača Sora.";
"Clear" = "Vymazať";
"Clear All Downloads" = "Vymazať všetky sťiahnuté položky";
"Clear Cache" = "Vymazať medzipamäť";
"Clear Library Only" = "Vymazať len knižnicu";
"Clear Logs" = "Vymazať záznamy";
"Click the plus button to add a module!" = "Kliknite na tlačidlo plus pre pridanie modulu!";
"Continue Watching" = "Pokračovať v sledovaní";
"Continue Watching Episode %d" = "Pokračovať v sledovaní epizódy %d";
"Contributors" = "Prispievatelia";
"Copied to Clipboard" = "Skopírované do schránky";
"Copy to Clipboard" = "Kopírovať do schránky";
"Copy URL" = "Kopírovať URL";
/* Episodes */
"%lld Episodes" = "%lld epizód";
"%lld of %lld" = "%lld z %lld";
"%lld-%lld" = "%lld%lld";
"%lld%% seen" = "%lld%% zhliadnuté";
"Episode %lld" = "Epizóda %lld";
"Episodes" = "Epizódy";
"Episodes might not be available yet or there could be an issue with the source." = "Epizódy môžu byť ešte nedostupné alebo môže byť problém so zdrojom.";
"Episodes Range" = "Rozsah epizód";
/* System */
"cranci1" = "cranci1";
"Dark" = "Tmavé";
"DATA & LOGS" = "DÁTA A ZÁZNAMY";
"Debug" = "Ladenie";
"Debugging and troubleshooting." = "Ladenie a troubleshooting.";
/* Actions */
"Delete" = "Zmazať";
"Delete All" = "Zmazať všetko";
"Delete All Downloads" = "Zmazať všetky preberania";
"Delete All Episodes" = "Zmazať všetky epizódy";
"Delete Download" = "Zmazať preberanie";
"Delete Episode" = "Zmazať epizódu";
/* Player */
"Double Tap to Seek" = "Dvojité klepnutie pre skok";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Dvojité klepnutie na okraje obrazovky preskočí podľa nastavenia krátkeho klepnutia.";
/* Downloads */
"Download" = "Stiahnuť";
"Download Episode" = "Stiahnuť epizódu";
"Download Summary" = "Súhrn sťiahnutých";
"Download This Episode" = "Stiahnuť túto epizódu";
"Downloaded" = "Stiahnuté";
"Downloaded Shows" = "Stiahnuté shows";
"Downloading" = "Sťahuje sa";
"Downloads" = "Sťiahnuté";
/* Settings */
"Enable Analytics" = "Povoliť analytiku";
"Enable Subtitles" = "Povoliť titulky";
/* Data Management */
"Erase" = "Vymazať";
"Erase all App Data" = "Vymazať všetky dáta aplikácie";
"Erase App Data" = "Vymazať dáta aplikácie";
/* Errors */
"Error" = "Chyba";
"Error Fetching Results" = "Chyba pri získavaní výsledkov";
"Errors and critical issues." = "Chyby a kritické problémy.";
"Failed to load contributors" = "Nepodarilo sa načítať prispievateľov";
/* Features */
"Fetch Episode metadata" = "Získať metadata epizódy";
"Files Downloaded" = "Stiahnuté súbory";
"Font Size" = "Veľkosť písma";
/* Interface */
"Force Landscape" = "Vynútiť režim na šírku";
"General" = "Všeobecné";
"General events and activities." = "Všeobecné udalosti a aktivity.";
"General Preferences" = "Všeobecné nastavenia";
"Hide Splash Screen" = "Skryť uvítaciu obrazovku";
"HLS video downloading." = "Sťahovanie HLS videa.";
"Hold Speed" = "Rýchlosť podržania";
/* Info */
"Info" = "Informácie";
"INFOS" = "INFORMÁCIE";
"Installed Modules" = "Inštalované moduly";
"Interface" = "Rozhranie";
/* Social */
"Join the Discord" = "Pripojte sa k nášmu Discordu";
/* Layout */
"Landscape Columns" = "Stĺpce v režime na šírku";
"Language" = "Jazyk";
"LESS" = "MENEJ";
/* Library */
"Library" = "Knižnica";
"License (GPLv3.0)" = "Licencia (GPLv3.0)";
"Light" = "Svetlé";
/* Loading States */
"Loading Episode %lld..." = "Načítava sa epizóda %lld...";
"Loading logs..." = "Načítavajú sa záznamy...";
"Loading module information..." = "Načítavajú sa informácie o module...";
"Loading Stream" = "Načítava sa stream";
/* Logging */
"Log Debug Info" = "Zaznamenať ladacie informácie";
"Log Filters" = "Filtre záznamov";
"Log In with AniList" = "Prihlásiť sa cez AniList";
"Log In with Trakt" = "Prihlásiť sa cez Trakt";
"Log Out from AniList" = "Odhlásiť sa z AniList";
"Log Out from Trakt" = "Odhlásiť sa z Trakt";
"Log Types" = "Typy záznamov";
"Logged in as" = "Prihlásený ako";
"Logged in as " = "Prihlásený ako ";
/* Logs and Settings */
"Logs" = "Záznamy";
"Long press Skip" = "Dlhé stlačenie preskočí";
"MAIN" = "HLAVNÉ";
"Main Developer" = "Hlavný vývojár";
"MAIN SETTINGS" = "HLAVNÉ NASTAVENIA";
/* Media Actions */
"Mark All Previous Watched" = "Označiť všetky predchádzajúce ako zhliadnuté";
"Mark as Watched" = "Označiť ako zhliadnuté";
"Mark Episode as Watched" = "Označiť epizódu ako zhliadnutú";
"Mark Previous Episodes as Watched" = "Označiť predchádzajúce epizódy ako zhliadnuté";
"Mark watched" = "Označiť ako zhliadnuté";
"Match with AniList" = "Match with AniList";
"Match with TMDB" = "Match with TMDB";
"Matched ID: %lld" = "Matched ID: %lld";
"Matched with: %@" = "Matched with: %@";
"Max Concurrent Downloads" = "Maximálny počet súbežných šťahovaní";
/* Media Interface */
"Media Grid Layout" = "Rozloženie mriežky médií";
"Media Player" = "Prehrávač médií";
"Media View" = "Zobrazenie médií";
"Metadata Provider" = "Poskytovateľ metadát";
"Metadata Providers Order" = "Poradie poskytovateľov metadata";
"Module Removed" = "Modul odstránený";
"Modules" = "Moduly";
/* Headers */
"MODULES" = "MODULY";
"MORE" = "VIAC";
/* Status Messages */
"No Active Downloads" = "Žiadne aktívne sťahovania";
"No AniList matches found" = "No AniList matches found";
"No Data Available" = "Žiadne dostupné dáta";
"No Downloads" = "Žiadne sťahovania";
"No episodes available" = "Žiadne dostupné epizódy";
"No Episodes Available" = "Žiadne dostupné epizódy";
"No items to continue watching." = "Žiadne položky na pokračovanie v sledovaní.";
"No matches found" = "Nenašli sa žiadne zhody";
"No Module Selected" = "Nie je vybratý žiaden modul";
"No Modules" = "Žiadne moduly";
"No Results Found" = "Nenašli sa žiadne výsledky";
"No Search Results Found" = "Nenašli sa žiadne výsledky vyhľadávania";
"Nothing to Continue Watching" = "Niž nič na pokračovanie v sledovaní";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Moduly sa nahradia iba v prípade, že v JSON súbore je odlišná verzia.";
/* Actions */
"OK" = "OK";
"Open Community Library" = "Otvoriť komunitnú knižnicu";
/* External Services */
"Open in AniList" = "Otvoriť v AniList";
"Original Poster" = "Pôvodný plagát";
/* Playback */
"Paused" = "Pozastavené";
"Play" = "Prehrať";
"Player" = "Prehrávač";
/* System Messages */
"Please restart the app to apply the language change." = "Reštartujte aplikáciu pre zmenu jazyka.";
"Please select a module from settings" = "Vyberte modul v nastaveniach";
/* Interface */
"Portrait Columns" = "Stĺpce na výšku";
"Progress bar Marker Color" = "Farba značky na časového úseku";
"Provider: %@" = "Poskytovateľ: %@";
/* Queue */
"Queue" = "Fronta";
"Queued" = "Vo fronte";
/* Content */
"Recently watched content will appear here." = "Nedávno sledovaný obsah sa zobrazí tu.";
/* Settings */
"Refresh Modules on Launch" = "Obnoviť moduly pri spustení";
"Refresh Storage Info" = "Obnoviť informácie o úložisku";
"Remember Playback speed" = "Zapamätať rýchlosť prehrávania";
/* Actions */
"Remove" = "Odstrániť";
"Remove All Cache" = "Odstrániť všetku medzipamäť";
/* File Management */
"Remove All Documents" = "Odstrániť všetky dokumenty";
"Remove Documents" = "Odstrániť dokumenty";
"Remove Downloaded Media" = "Odstrániť stiahnuté médiá";
"Remove Downloads" = "Odstrániť preberania";
"Remove from Bookmarks" = "Odstrániť zo záložiek";
"Remove Item" = "Odstrániť položku";
/* Support */
"Report an Issue" = "Nahlásiť problém";
/* Reset Options */
"Reset" = "Obnoviť";
"Reset AniList ID" = "Obnoviť ID AniList";
"Reset Episode Progress" = "Obnoviť pokrok epizódy";
"Reset progress" = "Obnoviť pokrok";
"Reset Progress" = "Obnoviť pokrok";
/* System */
"Restart Required" = "Vyžaduje sa reštart";
"Running Sora %@ - cranci1" = "Spustená Sora %@ - cranci1";
/* Actions */
"Save" = "Uložiť";
"Search" = "Vyhľadať";
/* Search */
"Search downloads" = "Vyhľadávať preberania";
"Search for something..." = "Hľadať niečo...";
"Search..." = "Hľadať...";
/* Content */
"Season %d" = "Séria %d";
"Season %lld" = "Séria %lld";
"Segments Color" = "Farba segmentov";
/* Modules */
"Select Module" = "Vybrať modul";
"Set Custom AniList ID" = "Nastaviť vlastné AniList ID";
/* Interface */
"Settings" = "Nastavenia";
"Shadow" = "Tieň";
"Show More (%lld more characters)" = "Zobraziť viac (%lld znakov)";
"Show PiP Button" = "Zobraziť tlačidlo PiP";
"Show Skip 85s Button" = "Zobraziť tlačidlo preskočiť 85s";
"Show Skip Intro / Outro Buttons" = "Zobraziť tlačidlá preskočiť úvod / záver";
"Shows" = "Relácie";
"Size (%@)" = "Veľkosť (%@)";
"Skip Settings" = "Nastavenia preskočenia";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Niektoré funkcie sú obmedzené na prehrávač Sora a predvolený prehrávač, ako ForceLandscape, holdSpeed a vlastné inkrementy preskočenia času.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ od cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.
Also note that progress updates may not be 100% accurate." = "Sora a cranci1 nie sú v žiadnom prípade spojené s AniList alebo Trakt.
Upozorňujeme, že aktualizácie pozerania nemusia byť stopercentne presné.";
"Sora GitHub Repository" = "GitHub repozitár Sora";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur bude vždy zadarmo bez reklám!";
/* Interface */
"Sort" = "Zoradiť";
"Speed Settings" = "Nastavenia rýchlosti";
/* Playback */
"Start Watching" = "Začať pozerať";
"Start Watching Episode %d" = "Začať pozerať epizódu %d";
"Storage Used" = "Použitá pamäť";
"Stream" = "Stream";
"Streaming and video playback." = "Streamovanie a prehrávanie videa.";
/* Subtitles */
"Subtitle Color" = "Farba tituliek";
"Subtitle Settings" = "Nastavenia tituliek";
/* Sync */
"Sync anime progress" = "Synchronizovať pozeranie anime";
"Sync TV shows progress" = "Synchronizovať pozeranie TV relácií";
/* System */
"System" = "Systém";
/* Instructions */
"Tap a title to override the current match." = "Klepnite na názov prepíšte aktuálnu zhodu.";
"Tap Skip" = "Klepnite na Preskočiť";
"Tap to manage your modules" = "Klepnite pre správu modulov";
"Tap to select a module" = "Klepnite na výber modulu";
/* App Information */
"The app cache helps the app load images faster.
Clearing the Documents folder will delete all downloaded modules.
Do not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "Medzipamäť aplikácie pomáha aplikácii rýchlejšie načítať obrázky.
Vyprázdnením priečinka Dokumenty sa odstránia všetky stiahnuté moduly.
Neodstraňujte dáta aplikácie, ak nerozumiete následkom — môže to spôsobiť nesprávne fungovanie aplikácie.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.
For episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "Rozsah epizód riadi, koľko epizód sa zobrazí na každej stránke. Epizódy sú zoskupené do sád (napr. 125, 2650 atď.), čo vám umožňuje ľahšie sa medzi nimi pohybovať.
Čo sa týka metadata epizódy, ide o náhľad epizódy a názov, pretože niekedy môže obsahovať spoilery.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "Modul poskytol iba jednu epizódu; pravdepodobne ide o film, preto sme sa rozhodli vytvoriť pre tieto prípady samostatné obrazovky.";
/* Interface */
"Thumbnails Width" = "Šírka náhľadov";
"TMDB Match" = "Match s TMDB";
"Trackers" = "Sledovače";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Skúste iné kľúčové slová";
"Try different search terms" = "Skúste iné vyhľadávacie výrazy";
/* Player Controls */
"Two Finger Hold for Pause" = "Podržanie dvoma prstami na pozastavenie";
"Unable to fetch matches. Please try again later." = "Nie je možné načítať zhody. Skúste to prosím neskôr.";
"Use TMDB Poster Image" = "Použiť plagát z TMDB";
/* Version */
"v%@" = "v%@";
"Video Player" = "Video prehrávač";
/* Video Settings */
"Video Quality Preferences" = "Preferencie kvality videa";
"View All" = "Zobraziť všetko";
"Watched" = "Zhliadnuté";
"Why am I not seeing any episodes?" = "Prečo nevidím žiadne epizódy?";
"WiFi Quality" = "Kvalita WiFi";
/* User Status */
"You are not logged in" = "Nie ste prihlásený";
"You have no items saved." = "Nemáte uložené žiadne položky.";
"Your downloaded episodes will appear here" = "Stiahnuté epizódy sa zobrazia tu";
"Your recently watched content will appear here" = "Nedávno zhliadnutý obsah sa zobrazí tu";
/* Download Settings */
"Download Settings" = "Nastavenia sťahovania";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Maximálny počet súbežných sťahovaní určuje, koľko epizód sa môže sťahovať v jeden moment. Vyššie hodnoty môžu využívať viacej internetu/dát a zaťažovať zariadenie.";
"Quality" = "Kvalita";
"Max Concurrent Downloads" = "Maximálne súbežné sťahovania";
"Allow Cellular Downloads" = "Povoliť sťahovanie cez mobilné dáta";
"Quality Information" = "Informácie o kvalite";
/* Storage */
"Storage Management" = "Správa ukladacieho priestoru";
"Storage Used" = "Použitý priestor";
"Library cleared successfully" = "Knižnica bola úspešne vymazaná";
"All downloads deleted successfully" = "Všetky sťahovania boli úspešne zmazané";
/* New additions */
"Recent searches" = "Nedávne vyhľadávania";
"me frfr" = "me frfr";
"Data" = "Dáta";
"Maximum Quality Available" = "Maximálna dostupná kvalita";
/* New additions */
"DownloadCountFormat" = "%d z %d";
"Error loading chapter" = "Chyba pri načítaní kapitoly";
"Font Size: %dpt" = "Veľkosť písma: %dpt";
"Line Spacing: %.1f" = "Riadkovanie: %.1f";
"Line Spacing" = "Riadkovanie";
"Margin: %dpx" = "Okraj: %dpx";
"Margin" = "Okraj";
"Auto Scroll Speed" = "Rýchlosť automatického posúvania";
"Speed" = "Rýchlosť";
"Speed: %.1fx" = "Rýchlosť: %.1fx";
"Matched %@: %@" = "Zhoda %@: %@";
"Enter the AniList ID for this series" = "Zadajte AniList ID pre túto sériu";
/* Added missing localizations */
"Create Collection" = "Vytvoriť kolekciu";
"Collection Name" = "Názov kolekcie";
"Rename Collection" = "Premenovať kolekciu";
"Rename" = "Premenovať";
"All Reading" = "Všetko na čítanie";
"Recently Added" = "Nedávno pridané";
"Novel Title" = "Názov románu";
"Read Progress" = "Priebeh čítania";
"Date Created" = "Dátum vytvorenia";
"Name" = "Meno";
"Item Count" = "Počet položiek";
"Date Added" = "Dátum pridania";
"Title" = "Názov";
"Source" = "Zdroj";
"Search reading..." = "Hľadať v čítaní...";
"Search collections..." = "Hľadať v kolekciách...";
"Search bookmarks..." = "Hľadať v záložkách...";
"%d items" = "%d položiek";
"Fetching Data" = "Načítavanie údajov";
"Please wait while fetching." = "Počkajte, kým sa údaje načítajú.";
"Start Reading" = "Začať čítať";
"Chapters" = "Kapitoly";
"Completed" = "Dokončené";
"Drag to reorder" = "Potiahnite na zmenu poradia";
"Drag to reorder sections" = "Potiahnite na zmenu poradia sekcií";
"Library View" = "Zobrazenie knižnice";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Prispôsobte sekcie zobrazené vo vašej knižnici. Môžete zmeniť ich poradie alebo ich úplne vypnúť.";
"Library Sections Order" = "Poradie sekcií knižnice";
"Completion Percentage" = "Percento dokončenia";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Niektoré funkcie sú obmedzené na Sora a predvolený prehrávač, ako je vynútená krajina, podržanie rýchlosti a vlastné intervaly preskočenia.\n\nNastavenie percenta dokončenia určuje, v ktorom bode pred koncom videa aplikácia označí ako dokončené na AniList a Trakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "Vyrovnávacia pamäť aplikácie pomáha rýchlejšiemu načítaniu obrázkov.\n\nVymazanie priečinka Documents odstráni všetky stiahnuté moduly.\n\nVymazanie údajov aplikácie odstráni všetky vaše nastavenia a údaje.";
"Translators" = "Prekladatelia";
"Paste URL" = "Vložiť URL";
/* New additions */
"Series Title" = "Názov série";
"Content Source" = "Zdroj obsahu";
"Watch Progress" = "Priebeh sledovania";
"Nothing to Continue Reading" = "Nič na pokračovanie v čítaní";
"Your recently read novels will appear here" = "Vaše nedávno čítané romány sa zobrazia tu";
"No Bookmarks" = "Žiadne záložky";
"Add bookmarks to this collection" = "Pridajte záložky do tejto kolekcie";
"items" = "položky";
"All Watching" = "Všetko na sledovanie";
"No Reading History" = "Žiadna história čítania";
"Books you're reading will appear here" = "Knihy, ktoré čítate, sa zobrazia tu";
"Create Collection" = "Vytvoriť kolekciu";
"Collection Name" = "Názov kolekcie";
"Rename Collection" = "Premenovať kolekciu";
"Rename" = "Premenovať";
"Novel Title" = "Názov románu";
"Read Progress" = "Priebeh čítania";
"Date Created" = "Dátum vytvorenia";
"Name" = "Meno";
"Item Count" = "Počet položiek";
"Date Added" = "Dátum pridania";
"Title" = "Názov";
"Source" = "Zdroj";
"Search reading..." = "Hľadať v čítaní...";
"Search collections..." = "Hľadať v kolekciách...";
"Search bookmarks..." = "Hľadať v záložkách...";
"%d items" = "%d položiek";
"Fetching Data" = "Načítavanie údajov";
"Please wait while fetching." = "Počkajte, kým sa údaje načítajú.";
"Start Reading" = "Začať čítať";
"Chapters" = "Kapitoly";
"Completed" = "Dokončené";
"Drag to reorder" = "Potiahnite na zmenu poradia";
"Drag to reorder sections" = "Potiahnite na zmenu poradia sekcií";
"Library View" = "Zobrazenie knižnice";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Prispôsobte sekcie zobrazené vo vašej knižnici. Môžete zmeniť ich poradie alebo ich úplne vypnúť.";
"Library Sections Order" = "Poradie sekcií knižnice";
"Completion Percentage" = "Percento dokončenia";
"Translators" = "Prekladatelia";
"Paste URL" = "Vložiť URL";
/* New additions */
"Collections" = "Kolekcie";
"Continue Reading" = "Pokračovať v čítaní";
/* Backup & Restore */
"Backup & Restore" = "Záloha a obnovenie";
"Export Backup" = "Exportovať zálohu";
"Import Backup" = "Importovať zálohu";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Upozornenie: Táto funkcia je stále experimentálna. Po exporte/importe si prosím skontrolujte svoje údaje.";
"Backup" = "Záloha";

View file

@ -0,0 +1,487 @@
/* General */
"About" = "Om oss";
"About Sora" = "Om Sora";
"Active" = "Aktiv";
"Active Downloads" = "Aktiva nedladdningar";
"Actively downloading media can be tracked from here." = "Aktiva nedladdningar av media kan spåras här.";
"Add Module" = "Lägg till Modul";
"Adjust the number of media items per row in portrait and landscape modes." = "Justera antalet mediaelement per rad i porträtt- och landskapsläge.";
"Advanced" = "Avancerat";
"AKA Sulfur" = "AKA Sulfur";
"All Bookmarks" = "Alla bokmärken";
"All Watching" = "Allt du tittar på";
"Also known as Sulfur" = "Även känd som Sulfur";
"AniList" = "AniList";
"AniList ID" = "AniList-ID";
"AniList Match" = "AniList-matchning";
"AniList.co" = "AniList.co";
"Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time." = "Anonym data samlas in för att förbättra appen. Ingen personlig information samlas in. Detta kan inaktiveras när som helst.";
"App Info" = "Appinformation";
"App Language" = "Appspråk";
"App Storage" = "Applagring";
"Appearance" = "Utseende";
/* Alerts and Actions */
"Are you sure you want to clear all cached data? This will help free up storage space." = "Är du säker på att du vill rensa all cachad data? Detta hjälper till att frigöra lagringsutrymme.";
"Are you sure you want to delete '%@'?" = "Är du säker på att du vill radera '%@'?";
"Are you sure you want to delete all %1$d episodes in '%2$@'?" = "Är du säker på att du vill radera alla %1$d avsnitt i '%2$@'?";
"Are you sure you want to delete all downloaded assets? You can choose to clear only the library while preserving the downloaded files for future use." = "Är du säker på att du vill radera alla nedladdade filer? Du kan välja att endast rensa biblioteket och behålla de nedladdade filerna för framtida bruk.";
"Are you sure you want to erase all app data? This action cannot be undone." = "Är du säker på att du vill radera all appdata? Denna åtgärd kan inte ångras.";
/* Features */
"Background Enabled" = "Bakgrund Aktiverad";
"Bookmark items for an easier access later." = "Bokmärk objekt för enklare åtkomst senare.";
"Bookmarks" = "Bokmärken";
"Bottom Padding" = "Bottenutfyllnad";
"Cancel" = "Avbryt";
"Cellular Quality" = "Mobilnätskvalitet";
"Check out some community modules here!" = "Kolla in några communitymoduler här!";
"Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality." = "Välj önskad videoupplösning för WiFi och mobilnät. Högre upplösningar använder mer data men ger bättre kvalitet.";
"Clear" = "Rensa";
"Clear All Downloads" = "Rensa alla nedladdningar";
"Clear Cache" = "Rensa cache";
"Clear Library Only" = "Rensa endast bibliotek";
"Clear Logs" = "Rensa loggar";
"Click the plus button to add a module!" = "Klicka på plusknappen för att lägga till en modul!";
"Continue Watching" = "Fortsätt titta";
"Continue Watching Episode %d" = "Fortsätt titta på Avsnitt %d";
"Contributors" = "Medverkande";
"Copied to Clipboard" = "Kopierat till Urklipp";
"Copy to Clipboard" = "Kopiera till Urklipp";
"Copy URL" = "Kopiera URL";
/* Episodes */
"%lld Episodes" = "%lld Avsnitt";
"%lld of %lld" = "%lld av %lld";
"%lld-%lld" = "%lld-%lld";
"%lld%% seen" = "%lld%% sedda";
"Episode %lld" = "Avsnitt %lld";
"Episodes" = "Avsnitt";
"Episodes might not be available yet or there could be an issue with the source." = "Avsnitten kanske inte är tillgängliga än eller så kan det vara ett problem med källan.";
"Episodes Range" = "Avsnittsintervall";
/* System */
"cranci1" = "cranci1";
"Dark" = "Mörk";
"DATA & LOGS" = "DATA & LOGGAR";
"Debug" = "Felsökning";
"Debugging and troubleshooting." = "Felsökning och problemlösning.";
/* Actions */
"Delete" = "Radera";
"Delete All" = "Radera alla";
"Delete All Downloads" = "Radera alla nedladdningar";
"Delete All Episodes" = "Radera alla avsnitt";
"Delete Download" = "Radera nedladdning";
"Delete Episode" = "Radera avsnitt";
/* Player */
"Double Tap to Seek" = "Dubbeltryck för att söka";
"Double tapping the screen on it's sides will skip with the short tap setting." = "Genom att dubbeltrycka på skärmens sidor hoppar spelaren enligt korttrycksinställningen.";
/* Downloads */
"Download" = "Ladda ner";
"Download Episode" = "Ladda ner avsnitt";
"Download Summary" = "Nedladdningssammanfattning";
"Download This Episode" = "Ladda ner detta avsnitt";
"Downloaded" = "Nedladdat";
"Downloaded Shows" = "Nedladdade serier";
"Downloading" = "Laddar ner";
"Downloads" = "Nedladdningar";
/* Settings */
"Enable Analytics" = "Aktivera analys";
"Enable Subtitles" = "Aktivera undertexter";
/* Data Management */
"Erase" = "Radera";
"Erase all App Data" = "Radera all appdata";
"Erase App Data" = "Radera appdata";
/* Errors */
"Error" = "Fel";
"Error Fetching Results" = "Fel vid hämtning av resultat";
"Errors and critical issues." = "Fel och kritiska problem.";
"Failed to load contributors" = "Kunde inte ladda medverkande";
/* Features */
"Fetch Episode metadata" = "Hämta avsnittsmetadata";
"Files Downloaded" = "Nedladdade filer";
"Font Size" = "Fontstorlek";
/* Interface */
"Force Landscape" = "Tvinga landskapsläge";
"General" = "Allmänt";
"General events and activities." = "Allmänna händelser och aktiviteter.";
"General Preferences" = "Allmänna inställningar";
"Hide Splash Screen" = "Dölj välkomstskärm";
"HLS video downloading." = "HLS-videonedladdning.";
"Hold Speed" = "Tillfällig hållhastighet";
/* Info */
"Info" = "Info";
"INFOS" = "INFO";
"Installed Modules" = "Installerade moduler";
"Interface" = "Gränssnitt";
/* Social */
"Join the Discord" = "Gå med i vår Discord";
/* Layout */
"Landscape Columns" = "Kolumner i landskapsläge";
"Language" = "Språk";
"LESS" = "MINDRE";
/* Library */
"Library" = "Bibliotek";
"License (GPLv3.0)" = "Licens (GPLv3.0)";
"Light" = "Ljust";
/* Loading States */
"Loading Episode %lld..." = "Laddar avsnitt %lld...";
"Loading logs..." = "Laddar loggar...";
"Loading module information..." = "Laddar modulinformation...";
"Loading Stream" = "Laddar videoström";
/* Logging */
"Log Debug Info" = "Logga felsökningsinfo";
"Log Filters" = "Loggfilter";
"Log In with AniList" = "Logga in med AniList";
"Log In with Trakt" = "Logga in med Trakt";
"Log Out from AniList" = "Logga ut från AniList";
"Log Out from Trakt" = "Logga ut från Trakt";
"Log Types" = "Loggtyper";
"Logged in as" = "Inloggad som";
"Logged in as " = "Inloggad som ";
/* Logs and Settings */
"Logs" = "Loggar";
"Long press Skip" = "Långtryck för att hoppa";
"MAIN" = "HUVUD";
"Main Developer" = "Huvudutvecklare";
"MAIN SETTINGS" = "HUVUDINSTÄLLNINGAR";
/* Media Actions */
"Mark All Previous Watched" = "Markera alla tidigare som sedda";
"Mark as Watched" = "Markera som sedd";
"Mark Episode as Watched" = "Markera avsnitt som sedd";
"Mark Previous Episodes as Watched" = "Markera tidigare avsnitt som sedda";
"Mark watched" = "Markera som sedd";
"Match with AniList" = "Matcha med AniList";
"Match with TMDB" = "Matcha med TMDB";
"Matched ID: %lld" = "Matchat ID: %lld";
"Matched with: %@" = "Matchad med: %@";
"Max Concurrent Downloads" = "Max antal parallella nedladdningar";
/* Media Interface */
"Media Grid Layout" = "Medierutnätslayout";
"Media Player" = "Mediauppspelare";
"Media View" = "Mediavisning";
"Metadata Provider" = "Metadata Leverantör";
"Metadata Providers Order" = "Metadata Leverantörers Ordning";
"Module Removed" = "Modul Borttagen";
"Modules" = "Moduler";
/* Headers */
"MODULES" = "MODULER";
"MORE" = "MER";
/* Status Messages */
"No Active Downloads" = "Inga Aktiva Nedladdningar";
"No AniList matches found" = "Inga AniList matchningar hittades";
"No Data Available" = "Ingen Data Tillgänglig";
"No Downloads" = "Inga Nedladdningar";
"No episodes available" = "Inga avsnitt tillgängliga";
"No Episodes Available" = "Inga Avsnitt Tillgängliga";
"No items to continue watching." = "Inga objekt att fortsätta titta på.";
"No matches found" = "Inga matchningar hittades";
"No Module Selected" = "Ingen Modul Vald";
"No Modules" = "Inga Moduler";
"No Results Found" = "Inga Resultat Hittades";
"No Search Results Found" = "Inga Sökresultat Hittades";
"Nothing to Continue Watching" = "Inget att fortsätta titta på";
/* Notes and Messages */
"Note that the modules will be replaced only if there is a different version string inside the JSON file." = "Observera att modulerna ersätts endast om det finns en annan versionssträng i JSON-filen.";
/* Actions */
"OK" = "OK";
"Open Community Library" = "Öppna Community Bibliotek";
/* External Services */
"Open in AniList" = "Öppna i AniList";
"Original Poster" = "Original Miniatyrbild";
/* Playback */
"Paused" = "Pausad";
"Play" = "Spela";
"Player" = "Spelare";
/* System Messages */
"Please restart the app to apply the language change." = "Starta om appen för att språkändringen ska träda i kraft.";
"Please select a module from settings" = "Välj en modul från inställningar";
/* Interface */
"Portrait Columns" = "Kolumner i porträttläge";
"Progress bar Marker Color" = "Färg på Förloppsindikator";
"Provider: %@" = "Leverantör: %@";
/* Queue */
"Queue" = "Kö";
"Queued" = "I kö";
/* Content */
"Recently watched content will appear here." = "Nyligen sedda innehåll visas här.";
/* Settings */
"Refresh Modules on Launch" = "Uppdatera Moduler vid Start";
"Refresh Storage Info" = "Uppdatera Lagringsinformation";
"Remember Playback speed" = "Kom ihåg uppspelningshastighet";
/* Actions */
"Remove" = "Radera";
"Remove All Cache" = "Radera all cache";
/* File Management */
"Remove All Documents" = "Radera alla dokument";
"Remove Documents" = "Radera dokument";
"Remove Downloaded Media" = "Radera nedladdad media";
"Remove Downloads" = "Radera nedladdningar";
"Remove from Bookmarks" = "Radera från bokmärken";
"Remove Item" = "Radera objekt";
/* Support */
"Report an Issue" = "Rapportera ett Problem";
/* Reset Options */
"Reset" = "Återställ";
"Reset AniList ID" = "Återställ AniList-ID";
"Reset Episode Progress" = "Återställ Avsnittsprogress";
"Reset progress" = "Återställ progress";
"Reset Progress" = "Återställ Progress";
/* System */
"Restart Required" = "Omstart Krävs";
"Running Sora %@ - cranci1" = "Körar Sora %@ - cranci1";
/* Actions */
"Save" = "Spara";
"Search" = "Sök";
/* Search */
"Search downloads" = "Sök i nedladdningar";
"Search for something..." = "Sök efter något...";
"Search..." = "Sök...";
/* Content */
"Season %d" = "Säsong %d";
"Season %lld" = "Säsong %lld";
"Segments Color" = "Segmentfärg";
/* Modules */
"Select Module" = "Välj Modul";
"Set Custom AniList ID" = "Ange Anpassat AniList-ID";
/* Interface */
"Settings" = "Inställningar";
"Shadow" = "Skugga";
"Show More (%lld more characters)" = "Visa Mer (%lld fler tecken)";
"Show PiP Button" = "Visa PiP Knapp";
"Show Skip 85s Button" = "Visa Hoppa 85s Knapp";
"Show Skip Intro / Outro Buttons" = "Visa Hoppa över Intro / Outro Knappar";
"Shows" = "Serier";
"Size (%@)" = "Storlek (%@)";
"Skip Settings" = "Hoppa Inställningar";
/* Player Features */
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments." = "Vissa funktioner är begränsade till Sora och standarduppspelaren, som Tvinga landskapsläge, tillfällig hållhastighet och anpassade hoppintervall.";
/* App Info */
"Sora" = "Sora";
"Sora %@ by cranci1" = "Sora %@ av cranci1";
"Sora and cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate." = "Sora och cranci1 är på inget sätt knutna till AniList eller Trakt.\n\nObservera också att progressuppdateringar kanske inte är 100% exakta.";
"Sora GitHub Repository" = "Sora GitHub-förråd";
"Sora/Sulfur will always remain free with no ADs!" = "Sora/Sulfur kommer alltid att vara gratis och utan reklam!";
/* Interface */
"Sort" = "Sortera";
"Speed Settings" = "Hastighetsinställningar";
/* Playback */
"Start Watching" = "Börja Titta";
"Start Watching Episode %d" = "Börja Titta på Avsnitt %d";
"Storage Used" = "Använt Lagringsutrymme";
"Stream" = "Strömma";
"Streaming and video playback." = "Strömning och videouppspelning.";
/* Subtitles */
"Subtitle Color" = "Undertextfärg";
"Subtitle Settings" = "Undertextinställningar";
/* Sync */
"Sync anime progress" = "Synkronisera animeprogress";
"Sync TV shows progress" = "Synkronisera TV-serieprogress";
/* System */
"System" = "System";
/* Instructions */
"Tap a title to override the current match." = "Tryck på en titel för att åsidosätta aktuell matchning.";
"Tap Skip" = "Tryck för att hoppa Fram / Tillbaka";
"Tap to manage your modules" = "Tryck för att hantera dina moduler";
"Tap to select a module" = "Tryck för att välja en modul";
/* App Information */
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction." = "Appens cache hjälper appen att ladda bilder snabbare.\n\nAtt rensa dokumentmappen tar bort alla nedladdade moduler.\n\nRadera inte appdata om du inte förstår konsekvenserna - det kan få appen att fungera fel.";
"The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 125, 2650, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata, it refers to the episode thumbnail and title, since sometimes it can contain spoilers." = "Avsnittsintervallet styr hur många avsnitt som visas på varje sida. Avsnitten är grupperade i uppsättningar (som 125, 2650, osv.), vilket gör det enklare att navigera mellan dem.\n\nFör avsnittsmetadata avses miniatyrbild och titel, eftersom dessa ibland kan innehålla spoilers.";
"The module provided only a single episode, this is most likely a movie, so we decided to make separate screens for these cases." = "Modulen tillhandahöll endast ett avsnitt, detta är troligen en film, så vi beslutade att skapa separata skärmar för dessa fall.";
/* Interface */
"Thumbnails Width" = "Miniatyrbildsbredd";
"TMDB Match" = "TMDB Matchning";
"Trackers" = "Spårare";
"Trakt" = "Trakt";
"Trakt.tv" = "Trakt.tv";
/* Search */
"Try different keywords" = "Prova andra sökord";
"Try different search terms" = "Prova andra söktermer";
/* Player Controls */
"Two Finger Hold for Pause" = "Håll med två fingrar för paus";
"Unable to fetch matches. Please try again later." = "Kunde inte hämta matchningar. Försök igen senare.";
"Use TMDB Poster Image" = "Använd TMDB Miniatyrbild";
/* Version */
"v%@" = "v%@";
"Video Player" = "Videouppspelare";
/* Video Settings */
"Video Quality Preferences" = "Videokvalitetsinställningar";
"View All" = "Visa alla";
"Watched" = "Sedda";
"Why am I not seeing any episodes?" = "Varför ser jag inga avsnitt?";
"WiFi Quality" = "WiFi Kvalitet";
/* User Status */
"You are not logged in" = "Du är inte inloggad";
"You have no items saved." = "Du har inga sparade objekt.";
"Your downloaded episodes will appear here" = "Dina nedladdade avsnitt visas här";
"Your recently watched content will appear here" = "Ditt nyligen sedda innehåll visas här";
/* Download Settings */
"Download Settings" = "Nedladdningsinställningar";
"Max concurrent downloads controls how many episodes can download simultaneously. Higher values may use more bandwidth and device resources." = "Max antal parallella nedladdningar styr hur många avsnitt som kan laddas ner samtidigt. Högre värden kan använda mer bandbredd och enhetsresurser.";
"Quality" = "Kvalitet";
"Max Concurrent Downloads" = "Max antal Parallella Nedladdningar";
"Allow Cellular Downloads" = "Tillåt Nedladdningar via Mobilnät";
"Quality Information" = "Kvalitetsinformation";
/* Storage */
"Storage Management" = "Lagringshantering";
"Storage Used" = "Använt Lagringsutrymme";
"Library cleared successfully" = "Biblioteket rensat";
"All downloads deleted successfully" = "Alla nedladdningar borttagna";
/* New keys from English localization */
"DownloadCountFormat" = "%d av %d";
"Error loading chapter" = "Fel vid inläsning av kapitel";
"Font Size: %dpt" = "Teckenstorlek: %dpt";
"Line Spacing: %.1f" = "Radavstånd: %.1f";
"Line Spacing" = "Radavstånd";
"Margin: %dpx" = "Marginal: %dpx";
"Margin" = "Marginal";
"Auto Scroll Speed" = "Automatisk rullningshastighet";
"Speed" = "Hastighet";
"Speed: %.1fx" = "Hastighet: %.1fx";
"Matched %@: %@" = "Matchning %@: %@";
"Enter the AniList ID for this series" = "Ange AniList-ID för denna serie";
/* Added missing localizations */
"Create Collection" = "Skapa samling";
"Collection Name" = "Samlingens namn";
"Rename Collection" = "Byt namn på samling";
"Rename" = "Byt namn";
"All Reading" = "All läsning";
"Recently Added" = "Nyligen tillagda";
"Novel Title" = "Romanens titel";
"Read Progress" = "Läsningsframsteg";
"Date Created" = "Skapad datum";
"Name" = "Namn";
"Item Count" = "Antal objekt";
"Date Added" = "Datum tillagt";
"Title" = "Titel";
"Source" = "Källa";
"Search reading..." = "Sök i läsning...";
"Search collections..." = "Sök i samlingar...";
"Search bookmarks..." = "Sök i bokmärken...";
"%d items" = "%d objekt";
"Fetching Data" = "Hämtar data";
"Please wait while fetching." = "Vänligen vänta under hämtning.";
"Start Reading" = "Börja läsa";
"Chapters" = "Kapitel";
"Completed" = "Slutförd";
"Drag to reorder" = "Dra för att ändra ordning";
"Drag to reorder sections" = "Dra för att ändra ordning på sektioner";
"Library View" = "Biblioteksvisning";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Anpassa sektionerna som visas i ditt bibliotek. Du kan ändra ordning eller inaktivera sektioner helt.";
"Library Sections Order" = "Ordning på bibliotekets sektioner";
"Completion Percentage" = "Slutförandeprocent";
"Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.\n\nThe completion percentage setting determines at what point before the end of a video the app will mark it as completed on AniList and Trakt." = "Vissa funktioner är begränsade till Sora- och standardspelaren, såsom tvingat landskap, hållhastighet och anpassade tidshopp.\n\nInställningen för slutförandeprocent avgör vid vilken punkt före slutet av en video appen markerar den som slutförd på AniList och Trakt.";
"The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nErasing the App Data will clears all your settings and data of the app." = "Appens cache hjälper till att ladda bilder snabbare.\n\nAtt rensa mappen Dokument tar bort alla nedladdade moduler.\n\nAtt radera appdata tar bort alla dina inställningar och data.";
"Translators" = "Översättare";
"Paste URL" = "Klistra in URL";
/* Added missing localizations */
"Series Title" = "Serietitel";
"Content Source" = "Innehållskälla";
"Watch Progress" = "Tittarframsteg";
"Recent searches" = "Senaste sökningar";
"Nothing to Continue Reading" = "Inget att fortsätta läsa";
"Your recently read novels will appear here" = "Dina nyligen lästa romaner visas här";
"No Bookmarks" = "Inga bokmärken";
"Add bookmarks to this collection" = "Lägg till bokmärken i denna samling";
"items" = "objekt";
"All Watching" = "Allt du tittar på";
"No Reading History" = "Ingen läshistorik";
"Books you're reading will appear here" = "Böcker du läser visas här";
"Create Collection" = "Skapa samling";
"Collection Name" = "Samlingens namn";
"Rename Collection" = "Byt namn på samling";
"Rename" = "Byt namn";
"Novel Title" = "Roman titel";
"Read Progress" = "Läsframsteg";
"Date Created" = "Skapad datum";
"Name" = "Namn";
"Item Count" = "Antal objekt";
"Date Added" = "Datum tillagt";
"Title" = "Titel";
"Source" = "Källa";
"Search reading..." = "Sök i läsning...";
"Search collections..." = "Sök i samlingar...";
"Search bookmarks..." = "Sök i bokmärken...";
"%d items" = "%d objekt";
"Fetching Data" = "Hämtar data";
"Please wait while fetching." = "Vänligen vänta medan data hämtas.";
"Start Reading" = "Börja läsa";
"Chapters" = "Kapitel";
"Completed" = "Slutförd";
"Drag to reorder" = "Dra för att ändra ordning";
"Drag to reorder sections" = "Dra för att ändra ordning på sektioner";
"Library View" = "Biblioteksvy";
"Customize the sections shown in your library. You can reorder sections or disable them completely." = "Anpassa sektionerna som visas i ditt bibliotek. Du kan ändra ordning eller inaktivera dem helt.";
"Library Sections Order" = "Bibliotekssektioners ordning";
"Completion Percentage" = "Slutförandeprocent";
"Translators" = "Översättare";
"Paste URL" = "Klistra in URL";
/* Added missing localizations */
"Collections" = "Samlingar";
"Continue Reading" = "Fortsätt läsa";
/* Backup & Restore */
"Backup & Restore" = "Säkerhetskopiera & Återställ";
"Export Backup" = "Exportera säkerhetskopia";
"Import Backup" = "Importera säkerhetskopia";
"Notice: This feature is still experimental. Please double-check your data after import/export." = "Observera: Denna funktion är fortfarande experimentell. Kontrollera dina data efter export/import.";
"Backup" = "Säkerhetskopia";

View file

@ -0,0 +1,48 @@
//
// ContinueReadingItem.swift
// Sora
//
// Created by paul on 26/06/25.
//
import Foundation
struct ContinueReadingItem: Identifiable, Codable {
let id: UUID
let mediaTitle: String
let chapterTitle: String
let chapterNumber: Int
let imageUrl: String
let href: String
let moduleId: String
let progress: Double
let totalChapters: Int
let lastReadDate: Date
let cachedHtml: String?
init(
id: UUID = UUID(),
mediaTitle: String,
chapterTitle: String,
chapterNumber: Int,
imageUrl: String,
href: String,
moduleId: String,
progress: Double = 0.0,
totalChapters: Int = 0,
lastReadDate: Date = Date(),
cachedHtml: String? = nil
) {
self.id = id
self.mediaTitle = mediaTitle
self.chapterTitle = chapterTitle
self.chapterNumber = chapterNumber
self.imageUrl = imageUrl
self.href = href
self.moduleId = moduleId
self.progress = progress
self.totalChapters = totalChapters
self.lastReadDate = lastReadDate
self.cachedHtml = cachedHtml
}
}

View file

@ -0,0 +1,275 @@
//
// ContinueReadingManager.swift
// Sora
//
// Created by paul on 26/06/25.
//
import Foundation
class ContinueReadingManager {
static let shared = ContinueReadingManager()
private let userDefaults = UserDefaults.standard
private let continueReadingKey = "continueReadingItems"
private init() {}
func extractTitleFromURL(_ url: String) -> String? {
guard let url = URL(string: url) else { return nil }
let pathComponents = url.pathComponents
for (index, component) in pathComponents.enumerated() {
if component == "book" || component == "novel" {
if index + 1 < pathComponents.count {
let bookTitle = pathComponents[index + 1]
.replacingOccurrences(of: "-", with: " ")
.replacingOccurrences(of: "_", with: " ")
.capitalized
if !bookTitle.isEmpty {
return bookTitle
}
}
}
}
return nil
}
func fetchItems() -> [ContinueReadingItem] {
guard let data = userDefaults.data(forKey: continueReadingKey) else {
Logger.shared.log("No continue reading items found in UserDefaults", type: "Debug")
return []
}
do {
let items = try JSONDecoder().decode([ContinueReadingItem].self, from: data)
Logger.shared.log("Fetched \(items.count) continue reading items", type: "Debug")
for (index, item) in items.enumerated() {
Logger.shared.log("Item \(index): \(item.mediaTitle), Image URL: \(item.imageUrl)", type: "Debug")
}
return items.sorted(by: { $0.lastReadDate > $1.lastReadDate })
} catch {
Logger.shared.log("Error decoding continue reading items: \(error)", type: "Error")
return []
}
}
func save(item: ContinueReadingItem, htmlContent: String? = nil) {
var items = fetchItems()
items.removeAll { $0.href == item.href }
if item.progress >= 0.98 {
userDefaults.set(item.progress, forKey: "readingProgress_\(item.href)")
do {
let data = try JSONEncoder().encode(items)
userDefaults.set(data, forKey: continueReadingKey)
} catch {
Logger.shared.log("Error encoding continue reading items: \(error)", type: "Error")
}
return
}
var updatedItem = item
if item.mediaTitle.contains("-") && item.mediaTitle.count >= 30 || item.mediaTitle.contains("Unknown") {
if let betterTitle = extractTitleFromURL(item.href) {
updatedItem = ContinueReadingItem(
id: item.id,
mediaTitle: betterTitle,
chapterTitle: item.chapterTitle,
chapterNumber: item.chapterNumber,
imageUrl: item.imageUrl,
href: item.href,
moduleId: item.moduleId,
progress: item.progress,
totalChapters: item.totalChapters,
lastReadDate: item.lastReadDate,
cachedHtml: htmlContent ?? item.cachedHtml
)
}
}
Logger.shared.log("Incoming item image URL: \(updatedItem.imageUrl)", type: "Debug")
if updatedItem.imageUrl.isEmpty {
let defaultImageUrl = "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/novel_cover.jpg"
updatedItem = ContinueReadingItem(
id: updatedItem.id,
mediaTitle: updatedItem.mediaTitle,
chapterTitle: updatedItem.chapterTitle,
chapterNumber: updatedItem.chapterNumber,
imageUrl: defaultImageUrl,
href: updatedItem.href,
moduleId: updatedItem.moduleId,
progress: updatedItem.progress,
totalChapters: updatedItem.totalChapters,
lastReadDate: updatedItem.lastReadDate,
cachedHtml: htmlContent ?? updatedItem.cachedHtml
)
Logger.shared.log("Using default image URL: \(defaultImageUrl)", type: "Debug")
}
if !updatedItem.imageUrl.isEmpty {
if URL(string: updatedItem.imageUrl) == nil {
Logger.shared.log("Invalid image URL format: \(updatedItem.imageUrl)", type: "Warning")
if let encodedUrl = updatedItem.imageUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let _ = URL(string: encodedUrl) {
updatedItem = ContinueReadingItem(
id: updatedItem.id,
mediaTitle: updatedItem.mediaTitle,
chapterTitle: updatedItem.chapterTitle,
chapterNumber: updatedItem.chapterNumber,
imageUrl: encodedUrl,
href: updatedItem.href,
moduleId: updatedItem.moduleId,
progress: updatedItem.progress,
totalChapters: updatedItem.totalChapters,
lastReadDate: updatedItem.lastReadDate,
cachedHtml: htmlContent ?? updatedItem.cachedHtml
)
Logger.shared.log("Fixed image URL with encoding: \(encodedUrl)", type: "Debug")
} else {
let defaultImageUrl = "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/novel_cover.jpg"
updatedItem = ContinueReadingItem(
id: updatedItem.id,
mediaTitle: updatedItem.mediaTitle,
chapterTitle: updatedItem.chapterTitle,
chapterNumber: updatedItem.chapterNumber,
imageUrl: defaultImageUrl,
href: updatedItem.href,
moduleId: updatedItem.moduleId,
progress: updatedItem.progress,
totalChapters: updatedItem.totalChapters,
lastReadDate: updatedItem.lastReadDate,
cachedHtml: htmlContent ?? updatedItem.cachedHtml
)
Logger.shared.log("Using default image URL after encoding failed: \(defaultImageUrl)", type: "Debug")
}
}
}
Logger.shared.log("Saving item with image URL: \(updatedItem.imageUrl)", type: "Debug")
items.append(updatedItem)
if items.count > 20 {
items = Array(items.sorted(by: { $0.lastReadDate > $1.lastReadDate }).prefix(20))
}
do {
let data = try JSONEncoder().encode(items)
userDefaults.set(data, forKey: continueReadingKey)
Logger.shared.log("Successfully saved continue reading item", type: "Debug")
} catch {
Logger.shared.log("Error encoding continue reading items: \(error)", type: "Error")
}
}
func remove(item: ContinueReadingItem) {
var items = fetchItems()
items.removeAll { $0.id == item.id }
do {
let data = try JSONEncoder().encode(items)
userDefaults.set(data, forKey: continueReadingKey)
} catch {
Logger.shared.log("Error encoding continue reading items: \(error)", type: "Error")
}
}
func updateProgress(for href: String, progress: Double, htmlContent: String? = nil) {
var items = fetchItems()
if let index = items.firstIndex(where: { $0.href == href }) {
let updatedItem = items[index]
if progress >= 0.98 {
let cachedHtml = htmlContent ?? updatedItem.cachedHtml
if let html = cachedHtml, !html.isEmpty && !html.contains("undefined") && html.count > 50 {
let completedChapterKey = "completedChapterHtml_\(href)"
UserDefaults.standard.set(html, forKey: completedChapterKey)
Logger.shared.log("Saved HTML content for completed chapter \(href)", type: "Debug")
}
items.remove(at: index)
userDefaults.set(progress, forKey: "readingProgress_\(href)")
do {
let data = try JSONEncoder().encode(items)
userDefaults.set(data, forKey: continueReadingKey)
} catch {
Logger.shared.log("Error encoding continue reading items: \(error)", type: "Error")
}
return
}
var mediaTitle = updatedItem.mediaTitle
if mediaTitle.contains("-") && mediaTitle.count >= 30 || mediaTitle.contains("Unknown") {
if let betterTitle = extractTitleFromURL(href) {
mediaTitle = betterTitle
}
}
let newItem = ContinueReadingItem(
id: updatedItem.id,
mediaTitle: mediaTitle,
chapterTitle: updatedItem.chapterTitle,
chapterNumber: updatedItem.chapterNumber,
imageUrl: updatedItem.imageUrl,
href: updatedItem.href,
moduleId: updatedItem.moduleId,
progress: progress,
totalChapters: updatedItem.totalChapters,
lastReadDate: Date(),
cachedHtml: htmlContent ?? updatedItem.cachedHtml
)
Logger.shared.log("Updating item with image URL: \(newItem.imageUrl)", type: "Debug")
items[index] = newItem
do {
let data = try JSONEncoder().encode(items)
userDefaults.set(data, forKey: continueReadingKey)
} catch {
Logger.shared.log("Error encoding continue reading items: \(error)", type: "Error")
}
}
}
func isChapterCompleted(href: String) -> Bool {
let progress = UserDefaults.standard.double(forKey: "readingProgress_\(href)")
if progress >= 0.98 {
return true
}
let items = fetchItems()
if let item = items.first(where: { $0.href == href }) {
return item.progress >= 0.98
}
return false
}
func getCachedHtml(for href: String) -> String? {
let completedChapterKey = "completedChapterHtml_\(href)"
if let completedHtml = UserDefaults.standard.string(forKey: completedChapterKey),
!completedHtml.isEmpty && !completedHtml.contains("undefined") && completedHtml.count > 50 {
Logger.shared.log("Using cached HTML for completed chapter \(href)", type: "Debug")
return completedHtml
}
let items = fetchItems()
if let item = items.first(where: { $0.href == href }) {
return item.cachedHtml
}
return nil
}
}

View file

@ -0,0 +1,23 @@
//
// ContinueWatchingItem.swift
// Sora
//
// Created by Francesco on 14/02/25.
//
import Foundation
struct ContinueWatchingItem: Codable, Identifiable {
let id: UUID
let imageUrl: String
let episodeNumber: Int
let mediaTitle: String
var progress: Double
let streamUrl: String
let fullUrl: String
let subtitles: String?
let aniListID: Int?
let module: ScrapingModule
let headers: [String:String]?
let totalEpisodes: Int
}

View file

@ -0,0 +1,146 @@
//
// ContinueWatchingManager.swift
// Sora
//
// Created by Francesco on 14/02/25.
//
import Foundation
class ContinueWatchingManager {
static let shared = ContinueWatchingManager()
private let storageKey = "continueWatchingItems"
private let lastCleanupKey = "lastContinueWatchingCleanup"
private init() {
NotificationCenter.default.addObserver(self, selector: #selector(handleiCloudSync), name: .iCloudSyncDidComplete, object: nil)
performCleanupIfNeeded()
}
@objc private func handleiCloudSync() {
NotificationCenter.default.post(name: .ContinueWatchingDidUpdate, object: nil)
}
private func performCleanupIfNeeded() {
let lastCleanup = UserDefaults.standard.double(forKey: lastCleanupKey)
let currentTime = Date().timeIntervalSince1970
if currentTime - lastCleanup > 86400 {
cleanupOldEpisodes()
UserDefaults.standard.set(currentTime, forKey: lastCleanupKey)
}
}
private func cleanupOldEpisodes() {
var items = fetchItems()
var itemsToRemove: Set<UUID> = []
let groupedItems = Dictionary(grouping: items) { item in
let title = item.mediaTitle.replacingOccurrences(of: "Episode \\d+.*$", with: "", options: .regularExpression)
.trimmingCharacters(in: .whitespacesAndNewlines)
return title
}
for (_, showEpisodes) in groupedItems {
let sortedEpisodes = showEpisodes.sorted { $0.episodeNumber < $1.episodeNumber }
for i in 0..<sortedEpisodes.count - 1 {
let currentEpisode = sortedEpisodes[i]
let nextEpisode = sortedEpisodes[i + 1]
if currentEpisode.progress >= 0.8 && nextEpisode.episodeNumber > currentEpisode.episodeNumber {
itemsToRemove.insert(currentEpisode.id)
}
}
}
if !itemsToRemove.isEmpty {
items.removeAll { itemsToRemove.contains($0.id) }
if let data = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(data, forKey: storageKey)
}
}
}
func save(item: ContinueWatchingItem) {
// Use real playback times
let lastKey = "lastPlayedTime_\(item.fullUrl)"
let totalKey = "totalTime_\(item.fullUrl)"
let lastPlayed = UserDefaults.standard.double(forKey: lastKey)
let totalTime = UserDefaults.standard.double(forKey: totalKey)
// Compute up-to-date progress
let actualProgress: Double
if totalTime > 0 {
actualProgress = min(max(lastPlayed / totalTime, 0), 1)
} else {
actualProgress = item.progress
}
// If watched 90%, remove it
if actualProgress >= 0.9 {
remove(item: item)
return
}
// Otherwise update progress and remove old episodes from the same show
var updatedItem = item
updatedItem.progress = actualProgress
var items = fetchItems()
let showTitle = item.mediaTitle.replacingOccurrences(of: "Episode \\d+.*$", with: "", options: .regularExpression)
.trimmingCharacters(in: .whitespacesAndNewlines)
items.removeAll { existingItem in
let existingShowTitle = existingItem.mediaTitle.replacingOccurrences(of: "Episode \\d+.*$", with: "", options: .regularExpression)
.trimmingCharacters(in: .whitespacesAndNewlines)
return showTitle == existingShowTitle &&
existingItem.episodeNumber < item.episodeNumber &&
existingItem.progress >= 0.8
}
items.removeAll { existing in
existing.fullUrl == item.fullUrl &&
existing.episodeNumber == item.episodeNumber &&
existing.module.metadata.sourceName == item.module.metadata.sourceName
}
items.append(updatedItem)
if let data = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(data, forKey: storageKey)
}
}
func fetchItems() -> [ContinueWatchingItem] {
guard
let data = UserDefaults.standard.data(forKey: storageKey),
let raw = try? JSONDecoder().decode([ContinueWatchingItem].self, from: data)
else {
return []
}
var seen = Set<String>()
let unique = raw.reversed().filter { item in
let key = "\(item.fullUrl)|\(item.module.metadata.sourceName)|\(item.episodeNumber)"
if seen.contains(key) {
return false
} else {
seen.insert(key)
return true
}
}.reversed()
return Array(unique)
}
func remove(item: ContinueWatchingItem) {
var items = fetchItems()
items.removeAll { $0.id == item.id }
if let data = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(data, forKey: storageKey)
}
}
}

View file

@ -0,0 +1,73 @@
//
// Double+Extension.swift
// AppleMusicSlider
//
// Created by Pratik on 14/01/23.
//
// Thanks to pratikg29 for this code inside his open source project "https://github.com/pratikg29/Custom-Slider-Control?ref=iosexample.com"
//
import Foundation
import Combine
extension Double {
func asTimeString(style: DateComponentsFormatter.UnitsStyle) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
formatter.unitsStyle = style
formatter.zeroFormattingBehavior = .pad
return formatter.string(from: self) ?? ""
}
}
extension BinaryFloatingPoint {
func asTimeString(style: TimeStringStyle, showHours: Bool = false) -> String {
let totalSeconds = Int(self)
let hours = totalSeconds / 3600
let minutes = (totalSeconds % 3600) / 60
let seconds = totalSeconds % 60
if showHours || hours > 0 {
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
} else {
return String(format: "%02d:%02d", minutes, seconds)
}
}
}
enum TimeStringStyle {
case positional
case standard
}
class VolumeViewModel: ObservableObject {
@Published var value: Double = 0.0
}
class SliderViewModel: ObservableObject {
@Published var sliderValue: Double = 0.0
@Published var introSegments: [ClosedRange<Double>] = []
@Published var outroSegments: [ClosedRange<Double>] = []
}
struct AniListMediaResponse: Decodable {
struct DataField: Decodable {
struct Media: Decodable { let idMal: Int? }
let Media: Media?
}
let data: DataField
}
struct AniSkipResponse: Decodable {
struct Result: Decodable {
struct Interval: Decodable {
let startTime: Double
let endTime: Double
}
let interval: Interval
let skipType: String
}
let found: Bool
let results: [Result]
let statusCode: Int
}

View file

@ -0,0 +1,143 @@
//
// MusicProgressSlider.swift
// Custom Seekbar
//
// Created by Pratik on 08/01/23.
//
// Thanks to pratikg29 for this code inside his open source project "https://github.com/pratikg29/Custom-Slider-Control?ref=iosexample.com"
// I did edit some of the code for my liking (added a buffer indicator, etc.)
import SwiftUI
struct MusicProgressSlider<T: BinaryFloatingPoint>: View {
@Binding var value: T
let inRange: ClosedRange<T>
let activeFillColor: Color
let fillColor: Color
let textColor: Color
let emptyColor: Color
let height: CGFloat
let onEditingChanged: (Bool) -> Void
let introSegments: [ClosedRange<T>]
let outroSegments: [ClosedRange<T>]
let introColor: Color
let outroColor: Color
@State private var localRealProgress: T = 0
@State private var localTempProgress: T = 0
@GestureState private var isActive: Bool = false
var body: some View {
GeometryReader { bounds in
ZStack {
VStack(spacing: 8) {
ZStack(alignment: .center) {
ZStack(alignment: .center) {
Capsule()
.fill(.ultraThinMaterial)
}
.clipShape(Capsule())
Capsule()
.fill(isActive ? activeFillColor : fillColor)
.mask({
HStack {
Rectangle()
.frame(
width: max(
bounds.size.width * CGFloat(localRealProgress + localTempProgress),
0
),
alignment: .leading
)
Spacer(minLength: 0)
}
})
ForEach(introSegments, id: \.self) { segment in
HStack(spacing: 0) {
Spacer()
.frame(width: bounds.size.width * CGFloat(segment.lowerBound))
Rectangle()
.fill(introColor.opacity(0.5))
.frame(width: bounds.size.width * CGFloat(segment.upperBound - segment.lowerBound))
Spacer()
}
}
ForEach(outroSegments, id: \.self) { segment in
HStack(spacing: 0) {
Spacer()
.frame(width: bounds.size.width * CGFloat(segment.lowerBound))
Rectangle()
.fill(outroColor.opacity(0.5))
.frame(width: bounds.size.width * CGFloat(segment.upperBound - segment.lowerBound))
Spacer()
}
}
}
HStack {
let shouldShowHours = inRange.upperBound >= 3600
Text(value.asTimeString(style: .positional, showHours: shouldShowHours))
Spacer(minLength: 0)
Text("-" + (inRange.upperBound - value)
.asTimeString(style: .positional, showHours: shouldShowHours))
}
.font(.system(size: 12.5))
.foregroundColor(textColor)
}
.frame(width: isActive ? bounds.size.width * 1.04 : bounds.size.width, alignment: .center)
.animation(animation, value: isActive)
}
.frame(width: bounds.size.width, height: bounds.size.height, alignment: .center)
.contentShape(Rectangle())
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.updating($isActive) { _, state, _ in
state = true
}
.onChanged { gesture in
localTempProgress = T(gesture.translation.width / bounds.size.width)
value = max(min(getPrgValue(), inRange.upperBound), inRange.lowerBound)
}
.onEnded { _ in
localRealProgress = max(min(localRealProgress + localTempProgress, 1), 0)
localTempProgress = 0
}
)
.onChange(of: isActive) { newValue in
value = max(min(getPrgValue(), inRange.upperBound), inRange.lowerBound)
onEditingChanged(newValue)
}
.onAppear {
localRealProgress = getPrgPercentage(value)
}
.onChange(of: value) { newValue in
if !isActive {
localRealProgress = getPrgPercentage(newValue)
}
}
}
.frame(height: isActive ? height * 1.25 : height, alignment: .center)
}
private var animation: Animation {
if isActive {
return .spring()
} else {
return .spring(response: 0.5, dampingFraction: 0.5, blendDuration: 0.6)
}
}
private func getPrgPercentage(_ value: T) -> T {
let range = inRange.upperBound - inRange.lowerBound
let correctedStartValue = value - inRange.lowerBound
let percentage = correctedStartValue / range
return percentage
}
private func getPrgValue() -> T {
return ((localRealProgress + localTempProgress) * (inRange.upperBound - inRange.lowerBound)) + inRange.lowerBound
}
}

View file

@ -0,0 +1,189 @@
//
// VolumeSlider.swift
// Custom Seekbar
//
// Created by Pratik on 08/01/23.
// Credits to Pratik https://github.com/pratikg29/Custom-Slider-Control/blob/main/AppleMusicSlider/AppleMusicSlider/VolumeSlider.swift
//
import SwiftUI
struct VolumeSlider<T: BinaryFloatingPoint>: View {
@Binding var value: T
let inRange: ClosedRange<T>
let activeFillColor: Color
let fillColor: Color
let emptyColor: Color
let height: CGFloat
let onEditingChanged: (Bool) -> Void
@State private var localRealProgress: T = 0
@State private var localTempProgress: T = 0
@State private var lastVolumeValue: T = 0
@GestureState private var isActive: Bool = false
@State private var isAtEnd: Bool = false
var body: some View {
GeometryReader { bounds in
ZStack {
HStack {
GeometryReader { geo in
ZStack(alignment: .center) {
Capsule().fill(emptyColor)
Capsule().fill(isActive ? activeFillColor : fillColor)
.mask {
HStack {
Rectangle()
.frame(
width: max(geo.size.width * CGFloat(localRealProgress + localTempProgress), 0),
alignment: .leading
)
Spacer(minLength: 0)
}
}
}
}
Image(systemName: getIconName)
.font(.system(size: 20, weight: .bold, design: .rounded))
.frame(width: 30)
.foregroundColor(isActive ? activeFillColor : fillColor)
.onTapGesture {
handleIconTap()
}
}
.frame(width: getStretchWidth(bounds: bounds), alignment: .center)
.animation(animation, value: isActive)
.animation(animation, value: isAtEnd)
}
.frame(width: bounds.size.width, height: bounds.size.height)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.updating($isActive) { _, state, _ in state = true }
.onChanged { gesture in
let delta = gesture.translation.width / bounds.size.width
localTempProgress = T(delta)
let totalProgress = localRealProgress + localTempProgress
if totalProgress <= 0.0 || totalProgress >= 1.0 {
isAtEnd = true
} else {
isAtEnd = false
}
value = sliderValueInRange()
}
.onEnded { _ in
localRealProgress = max(min(localRealProgress + localTempProgress, 1), 0)
localTempProgress = 0
isAtEnd = false
}
)
.onChange(of: isActive) { newValue in
if !newValue {
value = sliderValueInRange()
isAtEnd = false
}
onEditingChanged(newValue)
}
.onAppear {
localRealProgress = progress(for: value)
if value > 0 {
lastVolumeValue = value
}
}
.onChange(of: value) { newVal in
if !isActive {
withAnimation(.easeInOut(duration: 0.3)) {
localRealProgress = progress(for: newVal)
}
if newVal > 0 {
lastVolumeValue = newVal
}
}
}
}
.frame(height: getStretchHeight())
}
private var getIconName: String {
let p = max(0, min(localRealProgress + localTempProgress, 1))
let muteThreshold: T = 0
let lowThreshold: T = 0.2
let midThreshold: T = 0.35
let highThreshold: T = 0.7
switch p {
case muteThreshold:
return "speaker.slash.fill"
case muteThreshold..<lowThreshold:
return "speaker.fill"
case lowThreshold..<midThreshold:
return "speaker.wave.1.fill"
case midThreshold..<highThreshold:
return "speaker.wave.2.fill"
default:
return "speaker.wave.3.fill"
}
}
private func handleIconTap() {
let currentProgress = localRealProgress + localTempProgress
withAnimation {
if currentProgress <= 0 {
value = lastVolumeValue
localRealProgress = progress(for: lastVolumeValue)
localTempProgress = 0
} else {
lastVolumeValue = sliderValueInRange()
value = T(0)
localRealProgress = 0
localTempProgress = 0
}
}
}
private var animation: Animation {
.interpolatingSpring(
mass: 1.0,
stiffness: 100,
damping: 15,
initialVelocity: 0.0
)
}
private func progress(for val: T) -> T {
let totalRange = inRange.upperBound - inRange.lowerBound
let adjustedVal = val - inRange.lowerBound
return adjustedVal / totalRange
}
private func sliderValueInRange() -> T {
let totalProgress = localRealProgress + localTempProgress
let rawVal = totalProgress * (inRange.upperBound - inRange.lowerBound)
+ inRange.lowerBound
return max(min(rawVal, inRange.upperBound), inRange.lowerBound)
}
private func getStretchWidth(bounds: GeometryProxy) -> CGFloat {
let baseWidth = bounds.size.width
if isAtEnd {
return baseWidth * 1.08
} else if isActive {
return baseWidth * 1.04
} else {
return baseWidth
}
}
private func getStretchHeight() -> CGFloat {
if isAtEnd {
return height * 1.35
} else if isActive {
return height * 1.25
} else {
return height
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,45 @@
//
// SubtitleSettingsManager.swift
// Sulfur
//
// Created by Francesco on 09/03/25.
//
import UIKit
struct SubtitleSettings: Codable {
var enabled: Bool = true
var foregroundColor: String = "white"
var fontSize: Double = 20.0
var shadowRadius: Double = 1.0
var backgroundEnabled: Bool = true
var bottomPadding: CGFloat = 20.0
var subtitleDelay: Double = 0.0
}
class SubtitleSettingsManager {
static let shared = SubtitleSettingsManager()
private let userDefaultsKey = "SubtitleSettings"
var settings: SubtitleSettings {
get {
if let data = UserDefaults.standard.data(forKey: userDefaultsKey),
let savedSettings = try? JSONDecoder().decode(SubtitleSettings.self, from: data) {
return savedSettings
}
return SubtitleSettings()
}
set {
if let data = try? JSONEncoder().encode(newValue) {
UserDefaults.standard.set(data, forKey: userDefaultsKey)
}
}
}
func update(_ updateBlock: (inout SubtitleSettings) -> Void) {
var currentSettings = settings
updateBlock(&currentSettings)
settings = currentSettings
}
}

View file

@ -0,0 +1,168 @@
//
// VTTSubtitlesLoader.swift
// Sora
//
// Created by Francesco on 15/02/25.
//
import Combine
import Foundation
struct SubtitleCue: Identifiable {
let id = UUID()
let startTime: Double
let endTime: Double
let text: String
}
class VTTSubtitlesLoader: ObservableObject {
@Published var cues: [SubtitleCue] = []
enum SubtitleFormat {
case vtt
case srt
case unknown
}
func load(from urlString: String) {
guard let url = URL(string: urlString) else { return }
let format = determineSubtitleFormat(from: url)
URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data,
let content = String(data: data, encoding: .utf8),
error == nil else { return }
DispatchQueue.main.async {
switch format {
case .vtt:
self.cues = self.parseVTT(content: content)
case .srt:
self.cues = self.parseSRT(content: content)
case .unknown:
if content.trimmed.hasPrefix("WEBVTT") {
self.cues = self.parseVTT(content: content)
} else {
self.cues = self.parseSRT(content: content)
}
}
}
}.resume()
}
private func determineSubtitleFormat(from url: URL) -> SubtitleFormat {
let fileExtension = url.pathExtension.lowercased()
switch fileExtension {
case "vtt", "webvtt":
return .vtt
case "srt":
return .srt
default:
return .unknown
}
}
private func parseVTT(content: String) -> [SubtitleCue] {
var cues: [SubtitleCue] = []
let lines = content.components(separatedBy: .newlines)
var index = 0
while index < lines.count {
let line = lines[index].trimmingCharacters(in: .whitespaces)
if line.isEmpty || line == "WEBVTT" {
index += 1
continue
}
if !line.contains("-->") {
index += 1
if index >= lines.count { break }
}
let timeLine = lines[index]
let times = timeLine.components(separatedBy: "-->")
if times.count < 2 {
index += 1
continue
}
let startTime = parseTimecode(times[0].trimmingCharacters(in: .whitespaces))
let adjustedStartTime = max(startTime - 0.5, 0)
let endTime = parseTimecode(times[1].trimmingCharacters(in: .whitespaces))
let adjusteEndTime = max(endTime - 0.5, 0)
index += 1
var cueText = ""
while index < lines.count && !lines[index].trimmingCharacters(in: .whitespaces).isEmpty {
cueText += lines[index] + "\n"
index += 1
}
cues.append(SubtitleCue(startTime: adjustedStartTime, endTime: adjusteEndTime, text: cueText.trimmingCharacters(in: .whitespacesAndNewlines)))
}
return cues
}
private func parseSRT(content: String) -> [SubtitleCue] {
var cues: [SubtitleCue] = []
let normalizedContent = content.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")
let blocks = normalizedContent.components(separatedBy: "\n\n")
for block in blocks {
let lines = block.components(separatedBy: "\n").filter { !$0.isEmpty }
guard lines.count >= 2 else { continue }
let timeLine = lines[1]
let times = timeLine.components(separatedBy: "-->")
guard times.count >= 2 else { continue }
let startTime = parseSRTTimecode(times[0].trimmingCharacters(in: .whitespaces))
let adjustedStartTime = max(startTime - 0.5, 0)
let endTime = parseSRTTimecode(times[1].trimmingCharacters(in: .whitespaces))
let adjustedEndTime = max(endTime - 0.5, 0)
var textLines = [String]()
if lines.count > 2 {
textLines = Array(lines[2...])
}
let text = textLines.joined(separator: "\n")
cues.append(SubtitleCue(startTime: adjustedStartTime, endTime: adjustedEndTime, text: text))
}
return cues
}
private func parseTimecode(_ timeString: String) -> Double {
let parts = timeString.components(separatedBy: ":")
var seconds = 0.0
if parts.count == 3,
let h = Double(parts[0]),
let m = Double(parts[1]),
let s = Double(parts[2].replacingOccurrences(of: ",", with: ".")) {
seconds = h * 3600 + m * 60 + s
} else if parts.count == 2,
let m = Double(parts[0]),
let s = Double(parts[1].replacingOccurrences(of: ",", with: ".")) {
seconds = m * 60 + s
}
return seconds
}
private func parseSRTTimecode(_ timeString: String) -> Double {
let parts = timeString.components(separatedBy: ":")
guard parts.count == 3 else { return 0 }
let secondsParts = parts[2].components(separatedBy: ",")
guard secondsParts.count == 2,
let hours = Double(parts[0]),
let minutes = Double(parts[1]),
let seconds = Double(secondsParts[0]),
let milliseconds = Double(secondsParts[1]) else {
return 0
}
return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000
}
}

View file

@ -6,6 +6,7 @@
//
import AVKit
import GroupActivities
class NormalPlayer: AVPlayerViewController {
private var originalRate: Float = 1.0
@ -17,22 +18,6 @@ class NormalPlayer: AVPlayerViewController {
setupAudioSession()
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UserDefaults.standard.bool(forKey: "AlwaysLandscape") {
return .landscape
} else {
return .all
}
}
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
private func setupHoldGesture() {
holdGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleHoldGesture(_:)))
holdGesture?.minimumPressDuration = 0.5
@ -56,13 +41,13 @@ class NormalPlayer: AVPlayerViewController {
guard let player = player else { return }
originalRate = player.rate
let holdSpeed = UserDefaults.standard.float(forKey: "holdSpeedPlayer")
player.rate = holdSpeed
player.rate = holdSpeed > 0 ? holdSpeed : 2.0
}
private func endHoldSpeed() {
player?.rate = originalRate
}
func setupAudioSession() {
do {
let audioSession = AVAudioSession.sharedInstance()
@ -71,8 +56,7 @@ class NormalPlayer: AVPlayerViewController {
try audioSession.overrideOutputAudioPort(.speaker)
} catch {
print("Failed to set up AVAudioSession: \(error)")
Logger.shared.log("Didn't set up AVAudioSession: \(error)", type: "Debug")
}
}
}

View file

@ -0,0 +1,419 @@
//
// VideoPlayer.swift
// Sora
//
// Created by Francesco on 09/01/25.
//
import UIKit
import AVKit
import Combine
import GroupActivities
class VideoPlayerViewController: UIViewController {
let module: ScrapingModule
var player: AVPlayer?
var playerViewController: NormalPlayer?
var timeObserverToken: Any?
var streamUrl: String?
var fullUrl: String = ""
var subtitles: String = ""
var aniListID: Int = 0
var headers: [String:String]? = nil
var totalEpisodes: Int = 0
var tmdbID: Int? = nil
var isMovie: Bool = false
var seasonNumber: Int = 1
var episodeNumber: Int = 0
var episodeImageUrl: String = ""
var mediaTitle: String = ""
private var groupSession: GroupSession<VideoWatchingActivity>?
private var subscriptions = Set<AnyCancellable>()
private var isLaunchedFromSharePlay = false
private var aniListUpdateSent = false
private var aniListUpdatedSuccessfully = false
private var traktUpdateSent = false
private var traktUpdatedSuccessfully = false
init(module: ScrapingModule) {
self.module = module
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
configureGroupSession()
if isLaunchedFromSharePlay {
return
}
setupVideoPlayer()
}
private func setupVideoPlayer() {
guard let streamUrl = streamUrl, let url = URL(string: streamUrl) else {
return
}
var request = URLRequest(url: url)
if let mydict = headers, !mydict.isEmpty {
for (key,value) in mydict {
request.addValue(value, forHTTPHeaderField: key)
}
} else {
request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer")
request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Origin")
}
request.addValue("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", forHTTPHeaderField: "User-Agent")
let asset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": request.allHTTPHeaderFields ?? [:]])
let playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playerItem)
playerViewController = NormalPlayer()
playerViewController?.player = player
if let playerViewController = playerViewController {
addChild(playerViewController)
playerViewController.view.frame = view.bounds
playerViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(playerViewController.view)
playerViewController.didMove(toParent: self)
}
addPeriodicTimeObserver(fullURL: fullUrl)
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(fullUrl)")
if lastPlayedTime > 0 {
let seekTime = CMTime(seconds: lastPlayedTime, preferredTimescale: 1)
self.player?.seek(to: seekTime) { _ in
self.player?.play()
}
} else {
self.player?.play()
}
}
private func configureGroupSession() {
Task {
for await groupSession in VideoWatchingActivity.sessions() {
await configureGroupSession(groupSession)
}
}
}
@MainActor
private func configureGroupSession(_ groupSession: GroupSession<VideoWatchingActivity>) async {
self.groupSession = groupSession
let activity = groupSession.activity
if streamUrl == nil {
streamUrl = activity.streamUrl
fullUrl = activity.fullUrl
subtitles = activity.subtitles
aniListID = activity.aniListID
headers = activity.headers
totalEpisodes = activity.totalEpisodes
tmdbID = activity.tmdbID
isMovie = activity.isMovie
seasonNumber = activity.seasonNumber
episodeNumber = activity.episodeNumber
episodeImageUrl = activity.episodeImageUrl
mediaTitle = activity.mediaTitle
isLaunchedFromSharePlay = true
setupVideoPlayer()
}
groupSession.$state
.receive(on: DispatchQueue.main)
.sink { [weak self] state in
switch state {
case .joined:
self?.coordinatePlayback()
Logger.shared.log("Joined SharePlay session", type: "SharePlay")
case .invalidated:
self?.groupSession = nil
Logger.shared.log("SharePlay session invalidated", type: "SharePlay")
default:
break
}
}
.store(in: &subscriptions)
groupSession.join()
Logger.shared.log("Automatically joining SharePlay session for: \(activity.mediaTitle)", type: "SharePlay")
}
private func coordinatePlayback() {
guard let player = player, let groupSession = groupSession else { return }
player.playbackCoordinator.coordinateWithSession(groupSession)
}
@MainActor
func startSharePlay() async {
guard let streamUrl = streamUrl else { return }
var episodeImageData: Data?
if !episodeImageUrl.isEmpty, let imageUrl = URL(string: episodeImageUrl) {
do {
episodeImageData = try await URLSession.shared.data(from: imageUrl).0
} catch {
Logger.shared.log("Failed to load episode image: \(error)", type: "Error")
}
}
let activity = VideoWatchingActivity(
mediaTitle: mediaTitle,
episodeNumber: episodeNumber,
streamUrl: streamUrl,
subtitles: subtitles,
aniListID: aniListID,
fullUrl: fullUrl,
headers: headers,
episodeImageUrl: episodeImageUrl,
episodeImageData: episodeImageData,
totalEpisodes: totalEpisodes,
tmdbID: tmdbID,
isMovie: isMovie,
seasonNumber: seasonNumber
)
do {
_ = try await activity.activate()
Logger.shared.log("SharePlay session started successfully", type: "SharePlay")
} catch {
Logger.shared.log("Failed to start SharePlay: \(error)", type: "Error")
let alert = UIAlertController(
title: "SharePlay Unavailable",
message: "SharePlay is not available right now. Make sure you're connected to FaceTime or have SharePlay enabled in Control Center.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isLaunchedFromSharePlay {
player?.play()
setInitialPlayerRate()
Task {
await checkForFaceTimeAndPromptSharePlay()
}
} else {
setInitialPlayerRate()
}
}
@MainActor
private func checkForFaceTimeAndPromptSharePlay() async {
let activity = VideoWatchingActivity(
mediaTitle: mediaTitle,
episodeNumber: episodeNumber,
streamUrl: streamUrl ?? "",
subtitles: subtitles,
aniListID: aniListID,
fullUrl: fullUrl,
headers: headers,
episodeImageUrl: episodeImageUrl,
episodeImageData: nil,
totalEpisodes: totalEpisodes,
tmdbID: tmdbID,
isMovie: isMovie,
seasonNumber: seasonNumber
)
let result = await activity.prepareForActivation()
if result == .activationPreferred {
showSharePlayPrompt()
}
}
@MainActor
private func showSharePlayPrompt() {
let alert = UIAlertController(
title: "Watch Together?",
message: "You're in a FaceTime call. Would you like to share this video with everyone?",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Share Video", style: .default) { [weak self] _ in
Task {
await self?.startSharePlay()
}
})
alert.addAction(UIAlertAction(title: "Watch Alone", style: .cancel))
present(alert, animated: true)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if let playbackSpeed = player?.rate {
UserDefaults.standard.set(playbackSpeed, forKey: "lastPlaybackSpeed")
}
player?.pause()
if let timeObserverToken = timeObserverToken {
player?.removeTimeObserver(timeObserverToken)
self.timeObserverToken = nil
}
}
private func setInitialPlayerRate() {
if UserDefaults.standard.bool(forKey: "rememberPlaySpeed") {
let lastPlayedSpeed = UserDefaults.standard.float(forKey: "lastPlaybackSpeed")
player?.rate = lastPlayedSpeed > 0 ? lastPlayedSpeed : 1.0
}
}
func addPeriodicTimeObserver(fullURL: String) {
guard let player = self.player else { return }
let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
guard let self = self,
let currentItem = player.currentItem,
currentItem.duration.seconds.isFinite else {
return
}
let currentTime = time.seconds
let duration = currentItem.duration.seconds
UserDefaults.standard.set(currentTime, forKey: "lastPlayedTime_\(fullURL)")
UserDefaults.standard.set(duration, forKey: "totalTime_\(fullURL)")
if let streamUrl = self.streamUrl {
let progress = min(max(currentTime / duration, 0), 1.0)
let item = ContinueWatchingItem(
id: UUID(),
imageUrl: self.episodeImageUrl,
episodeNumber: self.episodeNumber,
mediaTitle: self.mediaTitle,
progress: progress,
streamUrl: streamUrl,
fullUrl: self.fullUrl,
subtitles: self.subtitles,
aniListID: self.aniListID,
module: self.module,
headers: self.headers,
totalEpisodes: self.totalEpisodes
)
ContinueWatchingManager.shared.save(item: item)
}
let remainingPercentage = (duration - currentTime) / duration
let remainingTimePercentage = UserDefaults.standard.object(forKey: "remainingTimePercentage") != nil ? UserDefaults.standard.double(forKey: "remainingTimePercentage") : 90.0
let threshold = (100.0 - remainingTimePercentage) / 100.0
if remainingPercentage <= threshold {
if self.aniListID != 0 && !self.aniListUpdateSent {
self.sendAniListUpdate()
}
if let tmdbId = self.tmdbID, tmdbId > 0, !self.traktUpdateSent {
self.sendTraktUpdate(tmdbId: tmdbId)
}
}
}
}
private func sendAniListUpdate() {
guard !aniListUpdateSent else { return }
aniListUpdateSent = true
let aniListMutation = AniListMutation()
aniListMutation.updateAnimeProgress(animeId: self.aniListID, episodeNumber: self.episodeNumber) { [weak self] result in
switch result {
case .success:
self?.aniListUpdatedSuccessfully = true
Logger.shared.log("Successfully updated AniList progress for Episode \(self?.episodeNumber ?? 0)", type: "General")
case .failure(let error):
Logger.shared.log("Failed to update AniList progress: \(error.localizedDescription)", type: "Error")
}
}
}
private func sendTraktUpdate(tmdbId: Int) {
guard !traktUpdateSent else { return }
traktUpdateSent = true
let traktMutation = TraktMutation()
if self.isMovie {
traktMutation.markAsWatched(type: "movie", tmdbID: tmdbId) { [weak self] result in
switch result {
case .success:
self?.traktUpdatedSuccessfully = true
Logger.shared.log("Successfully updated Trakt progress for movie (TMDB: \(tmdbId))", type: "General")
case .failure(let error):
Logger.shared.log("Failed to update Trakt progress for movie: \(error.localizedDescription)", type: "Error")
}
}
} else {
guard self.episodeNumber > 0 && self.seasonNumber > 0 else {
Logger.shared.log("Invalid episode (\(self.episodeNumber)) or season (\(self.seasonNumber)) number for Trakt update", type: "Error")
return
}
traktMutation.markAsWatched(
type: "episode",
tmdbID: tmdbId,
episodeNumber: self.episodeNumber,
seasonNumber: self.seasonNumber
) { [weak self] result in
switch result {
case .success:
self?.traktUpdatedSuccessfully = true
Logger.shared.log("Successfully updated Trakt progress for Episode \(self?.episodeNumber ?? 0) (TMDB: \(tmdbId))", type: "General")
case .failure(let error):
Logger.shared.log("Failed to update Trakt progress for episode: \(error.localizedDescription)", type: "Error")
}
}
}
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UserDefaults.standard.bool(forKey: "alwaysLandscape") {
return .landscape
} else {
return .all
}
}
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
deinit {
player?.pause()
if let timeObserverToken = timeObserverToken {
player?.removeTimeObserver(timeObserverToken)
}
groupSession?.leave()
subscriptions.removeAll()
}
}

View file

@ -0,0 +1,45 @@
//
// VideoWatchingActivity.swift
// Sora
//
// Created by Francesco on 15/06/25.
//
import UIKit
import Foundation
import GroupActivities
struct VideoWatchingActivity: GroupActivity {
var metadata: GroupActivityMetadata {
var metadata = GroupActivityMetadata()
metadata.title = mediaTitle
if isMovie {
metadata.subtitle = "Movie"
} else {
metadata.subtitle = "Episode \(episodeNumber)"
}
if let imageData = episodeImageData,
let uiImage = UIImage(data: imageData) {
metadata.previewImage = uiImage.cgImage
}
metadata.type = .watchTogether
return metadata
}
let mediaTitle: String
let episodeNumber: Int
let streamUrl: String
let subtitles: String
let aniListID: Int
let fullUrl: String
let headers: [String: String]?
let episodeImageUrl: String
let episodeImageData: Data?
let totalEpisodes: Int
let tmdbID: Int?
let isMovie: Bool
let seasonNumber: Int
}

View file

@ -2,9 +2,19 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.group-session</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.movies.read-write</key>
<true/>
<key>com.apple.security.assets.music.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View file

@ -2,7 +2,7 @@
// SoraApp.swift
// Sora
//
// Created by Francesco on 18/12/24.
// Created by Francesco on 06/01/25.
//
import SwiftUI
@ -10,40 +10,125 @@ import SwiftUI
@main
struct SoraApp: App {
@StateObject private var settings = Settings()
@StateObject private var modulesManager = ModulesManager()
@StateObject private var moduleManager = ModuleManager()
@StateObject private var libraryManager = LibraryManager()
@StateObject private var downloadManager = DownloadManager()
@StateObject private var jsController = JSController.shared
init() {
if let userAccentColor = UserDefaults.standard.color(forKey: "accentColor") {
UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = userAccentColor
}
clearTmpFolder()
TraktToken.checkAuthenticationStatus { isAuthenticated in
if isAuthenticated {
Logger.shared.log("Trakt authentication is valid", type: "Debug")
} else {
Logger.shared.log("Trakt authentication required", type: "Debug")
}
}
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(settings)
.environmentObject(modulesManager)
.accentColor(settings.accentColor)
.onAppear {
settings.updateAppearance()
Group {
if !UserDefaults.standard.bool(forKey: "hideSplashScreen") {
SplashScreenView()
} else {
ContentView()
}
.onOpenURL { url in
handleURL(url)
}
.environment(\.layoutDirection, .leftToRight)
.environmentObject(moduleManager)
.environmentObject(settings)
.environmentObject(libraryManager)
.environmentObject(downloadManager)
.environmentObject(jsController)
.accentColor(settings.accentColor)
.onAppear {
settings.updateAppearance()
Task {
if UserDefaults.standard.bool(forKey: "refreshModulesOnLaunch") {
await moduleManager.refreshModules()
}
}
}
.onOpenURL { url in
handleURL(url)
}
}
}
private func handleURL(_ url: URL) {
guard url.scheme == "sora",
url.host == "module",
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let moduleURL = components.queryItems?.first(where: { $0.name == "url" })?.value else {
return
}
modulesManager.addModule(from: moduleURL) { result in
switch result {
case .success:
NotificationCenter.default.post(name: .moduleAdded, object: nil)
Logger.shared.log("Successfully added module from URL scheme: \(moduleURL)")
case .failure(let error):
Logger.shared.log("Failed to add module from URL scheme: \(error.localizedDescription)")
guard url.scheme == "sora", let host = url.host else { return }
switch host {
case "default_page":
if let comps = URLComponents(url: url, resolvingAgainstBaseURL: true),
let libraryURL = comps.queryItems?.first(where: { $0.name == "url" })?.value {
UserDefaults.standard.set(libraryURL, forKey: "lastCommunityURL")
UserDefaults.standard.set(true, forKey: "didReceiveDefaultPageLink")
DropManager.shared.showDrop(
title: "Module Library Added",
subtitle: "You can browse the community library in settings.",
duration: 2,
icon: UIImage(systemName: "books.vertical.circle.fill")
)
}
case "module":
guard url.scheme == "sora",
url.host == "module",
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let moduleURL = components.queryItems?.first(where: { $0.name == "url" })?.value
else {
return
}
let addModuleView = ModuleAdditionSettingsView(moduleUrl: moduleURL).environmentObject(moduleManager)
let hostingController = UIHostingController(rootView: addModuleView)
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first {
window.rootViewController?.present(hostingController, animated: true)
} else {
Logger.shared.log(
"Failed to present module addition view: No window scene found",
type: "Error"
)
}
default:
break
}
}
private func clearTmpFolder() {
let fileManager = FileManager.default
let tmpDirectory = NSTemporaryDirectory()
do {
let tmpURL = URL(fileURLWithPath: tmpDirectory)
let tmpContents = try fileManager.contentsOfDirectory(at: tmpURL, includingPropertiesForKeys: nil)
for url in tmpContents {
try fileManager.removeItem(at: url)
}
let parentURL = tmpURL.deletingLastPathComponent()
let parentContents = try fileManager.contentsOfDirectory(at: parentURL, includingPropertiesForKeys: [.isDirectoryKey])
for url in parentContents {
if url.lastPathComponent.hasPrefix("com.apple.UserManagedAssets") {
var isDir: ObjCBool = false
if fileManager.fileExists(atPath: url.path, isDirectory: &isDir), isDir.boolValue {
try fileManager.removeItem(at: url)
}
}
}
} catch {
Logger.shared.log("Failed to clear tmp folder: \(error.localizedDescription)", type: "Error")
}
}
}

View file

@ -0,0 +1,43 @@
//
// Login.swift
// Ryu
//
// Created by Francesco on 08/08/24.
//
import UIKit
class AniListLogin {
static let clientID = "19551"
static let redirectURI = "sora://anilist"
static let authorizationEndpoint = "https://anilist.co/api/v2/oauth/authorize"
static func authenticate() {
let urlString = "\(authorizationEndpoint)?client_id=\(clientID)&redirect_uri=\(redirectURI)&response_type=code"
guard let url = URL(string: urlString) else {
Logger.shared.log("Invalid authorization URL", type: "Error")
return
}
WebAuthenticationManager.shared.authenticate(url: url, callbackScheme: "sora") { result in
switch result {
case .success(let callbackURL):
if let params = callbackURL.queryParameters,
let code = params["code"] {
AniListToken.exchangeAuthorizationCodeForToken(code: code) { success in
if success {
Logger.shared.log("AniList token exchange successful", type: "Debug")
} else {
Logger.shared.log("AniList token exchange failed", type: "Error")
}
}
} else {
Logger.shared.log("No authorization code in callback URL", type: "Error")
}
case .failure(let error):
Logger.shared.log("Authentication failed: \(error.localizedDescription)", type: "Error")
}
}
}
}

View file

@ -0,0 +1,106 @@
//
// Token.swift
// Ryu
//
// Created by Francesco on 08/08/24.
//
import UIKit
import Security
class AniListToken {
static let clientID = "19551"
static let clientSecret = "fk8EgkyFbXk95TbPwLYQLaiMaNIryMpDBwJsPXoX"
static let redirectURI = "sora://anilist"
static let tokenEndpoint = "https://anilist.co/api/v2/oauth/token"
static let serviceName = "me.cranci.sora.AniListToken"
static let accountName = "AniListAccessToken"
static let authSuccessNotification = Notification.Name("AniListAuthenticationSuccess")
static let authFailureNotification = Notification.Name("AniListAuthenticationFailure")
static func saveTokenToKeychain(token: String) -> Bool {
let tokenData = token.data(using: .utf8)!
let deleteQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: accountName
]
SecItemDelete(deleteQuery as CFDictionary)
let addQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: accountName,
kSecValueData as String: tokenData
]
let status = SecItemAdd(addQuery as CFDictionary, nil)
return status == errSecSuccess
}
static func exchangeAuthorizationCodeForToken(code: String, completion: @escaping (Bool) -> Void) {
Logger.shared.log("Exchanging authorization code for access token...")
guard let url = URL(string: tokenEndpoint) else {
Logger.shared.log("Invalid token endpoint URL", type: "Error")
DispatchQueue.main.async {
NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": "Invalid token endpoint URL"])
completion(false)
}
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let bodyString = "grant_type=authorization_code&client_id=\(clientID)&client_secret=\(clientSecret)&redirect_uri=\(redirectURI)&code=\(code)"
request.httpBody = bodyString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
Logger.shared.log("Error: \(error.localizedDescription)", type: "Error")
NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": error.localizedDescription])
completion(false)
return
}
guard let data = data else {
Logger.shared.log("No data received", type: "Error")
NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": "No data received"])
completion(false)
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
if let accessToken = json["access_token"] as? String {
let success = saveTokenToKeychain(token: accessToken)
if success {
NotificationCenter.default.post(name: authSuccessNotification, object: nil)
} else {
NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": "Failed to save token to keychain"])
}
completion(success)
} else {
let errorMessage = (json["error"] as? String) ?? "Unexpected response"
Logger.shared.log("Authentication error: \(errorMessage)", type: "Error")
NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": errorMessage])
completion(false)
}
}
} catch {
Logger.shared.log("Failed to parse JSON: \(error.localizedDescription)", type: "Error")
NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": "Failed to parse response: \(error.localizedDescription)"])
completion(false)
}
}
}
task.resume()
}
}

View file

@ -0,0 +1,256 @@
//
// AniListPushUpdates.swift
// Sulfur
//
// Created by Francesco on 07/04/25.
//
import UIKit
import Security
class AniListMutation {
let apiURL = URL(string: "https://graphql.anilist.co")!
func getTokenFromKeychain() -> String? {
let serviceName = "me.cranci.sora.AniListToken"
let accountName = "AniListAccessToken"
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: accountName,
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitOne
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess, let tokenData = item as? Data else {
return nil
}
return String(data: tokenData, encoding: .utf8)
}
func updateAnimeProgress(
animeId: Int,
episodeNumber: Int,
status: String = "CURRENT",
completion: @escaping (Result<Void, Error>) -> Void
) {
if let sendPushUpdates = UserDefaults.standard.object(forKey: "sendPushUpdates") as? Bool,
sendPushUpdates == false {
return
}
guard let userToken = getTokenFromKeychain() else {
completion(.failure(NSError(
domain: "",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Access token not found"]
)))
return
}
let query = """
mutation ($mediaId: Int, $progress: Int, $status: MediaListStatus) {
SaveMediaListEntry(mediaId: $mediaId, progress: $progress, status: $status) {
id
progress
status
}
}
"""
let variables: [String: Any] = [
"mediaId": animeId,
"progress": episodeNumber,
"status": status
]
let requestBody: [String: Any] = [
"query": query,
"variables": variables
]
guard let jsonData = try? JSONSerialization.data(withJSONObject: requestBody, options: []) else {
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to serialize JSON"])))
return
}
var request = URLRequest(url: apiURL)
request.httpMethod = "POST"
request.setValue("Bearer \(userToken)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1
completion(.failure(NSError(domain: "", code: statusCode, userInfo: [NSLocalizedDescriptionKey: "Unexpected response or status code"])))
return
}
if let data = data {
do {
_ = try JSONSerialization.jsonObject(with: data, options: [])
Logger.shared.log("Successfully updated anime progress", type: "Debug")
completion(.success(()))
} catch {
completion(.failure(error))
}
} else {
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "No data received"])))
}
}
task.resume()
}
func fetchMediaStatus(
mediaId: Int,
completion: @escaping (Result<String, Error>) -> Void
) {
guard let token = getTokenFromKeychain() else {
completion(.failure(NSError(
domain: "", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Access token not found"]
)))
return
}
let query = """
query ($mediaId: Int) {
Media(id: $mediaId) {
status
}
}
"""
let vars = ["mediaId": mediaId]
var req = URLRequest(url: URL(string: "https://graphql.anilist.co")!)
req.httpMethod = "POST"
req.addValue("application/json", forHTTPHeaderField: "Content-Type")
req.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
req.httpBody = try? JSONSerialization.data(
withJSONObject: ["query": query, "variables": vars]
)
URLSession.shared.dataTask(with: req) { data, _, error in
if let e = error { return completion(.failure(e)) }
guard
let d = data,
let json = try? JSONSerialization.jsonObject(with: d) as? [String: Any],
let md = (json["data"] as? [String: Any])?["Media"] as? [String: Any],
let status = md["status"] as? String
else {
return completion(.failure(NSError(
domain: "", code: -2,
userInfo: [NSLocalizedDescriptionKey: "Invalid response"]
)))
}
completion(.success(status))
}.resume()
}
func fetchMalID(animeId: Int, completion: @escaping (Result<Int, Error>) -> Void) {
let query = """
query ($id: Int) {
Media(id: $id) {
idMal
}
}
"""
let variables: [String: Any] = ["id": animeId]
let requestBody: [String: Any] = [
"query": query,
"variables": variables
]
guard let jsonData = try? JSONSerialization.data(withJSONObject: requestBody, options: []) else {
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to serialize GraphQL request"])))
return
}
var request = URLRequest(url: apiURL)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
URLSession.shared.dataTask(with: request) { data, resp, error in
if let e = error {
return completion(.failure(e))
}
guard let data = data,
let json = try? JSONDecoder().decode(AniListMediaResponse.self, from: data),
let mal = json.data.Media?.idMal else {
return completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to decode AniList response or idMal missing"])))
}
completion(.success(mal))
}.resume()
}
func fetchCoverImage(
animeId: Int,
completion: @escaping (Result<String, Error>) -> Void
) {
let query = """
query ($id: Int) {
Media(id: $id, type: ANIME) {
coverImage { large }
}
}
"""
let variables = ["id": animeId]
let body: [String: Any] = ["query": query, "variables": variables]
guard let url = URL(string: "https://graphql.anilist.co"),
let httpBody = try? JSONSerialization.data(withJSONObject: body)
else {
completion(.failure(NSError(domain: "AniList", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL or payload"])))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = httpBody
URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
return completion(.failure(error))
}
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let dataDict = json["data"] as? [String: Any],
let media = dataDict["Media"] as? [String: Any],
let cover = media["coverImage"] as? [String: Any],
let imageUrl = cover["large"] as? String
else {
return completion(.failure(NSError(domain: "AniList", code: 1, userInfo: [NSLocalizedDescriptionKey: "Malformed response"])))
}
completion(.success(imageUrl))
}
.resume()
}
private struct AniListMediaResponse: Decodable {
struct DataField: Decodable {
struct Media: Decodable { let idMal: Int? }
let Media: Media?
}
let data: DataField
}
}
struct EpisodeMetadataInfo: Codable, Equatable {
let title: [String: String]
let imageUrl: String
let anilistId: Int
let episodeNumber: Int
}

View file

@ -0,0 +1,104 @@
//
// TMDB-FetchID.swift
// Sulfur
//
// Created by Francesco on 01/06/25.
//
import Foundation
class TMDBFetcher {
enum MediaType: String, CaseIterable {
case tv, movie
}
struct TMDBResult: Decodable {
let id: Int
let name: String?
let title: String?
let popularity: Double
}
struct TMDBResponse: Decodable {
let results: [TMDBResult]
}
let apiKey = "738b4edd0a156cc126dc4a4b8aea4aca"
private let session = URLSession.custom
func fetchBestMatchID(for title: String, completion: @escaping (Int?, MediaType?) -> Void) {
let group = DispatchGroup()
var bestResults: [(id: Int, score: Double, type: MediaType)] = []
for type in MediaType.allCases {
group.enter()
fetchBestMatchID(for: title, type: type) { id, score in
if let id = id, let score = score {
bestResults.append((id, score, type))
}
group.leave()
}
}
group.notify(queue: .main) {
let best = bestResults.max { $0.score < $1.score }
completion(best?.id, best?.type)
}
}
private func fetchBestMatchID(for title: String, type: MediaType, completion: @escaping (Int?, Double?) -> Void) {
let query = title.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let urlString = "https://api.themoviedb.org/3/search/\(type.rawValue)?api_key=\(apiKey)&query=\(query)"
guard let url = URL(string: urlString) else {
completion(nil, nil)
return
}
session.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else {
completion(nil, nil)
return
}
do {
let response = try JSONDecoder().decode(TMDBResponse.self, from: data)
let scored = response.results.map { result -> (Int, Double) in
let candidateTitle = type == .tv ? result.name ?? "" : result.title ?? ""
let similarity = TMDBFetcher.titleSimilarity(title, candidateTitle)
let score = (similarity * 0.7) + ((result.popularity / 100.0) * 0.3)
return (result.id, score)
}
let best = scored.max { $0.1 < $1.1 }
completion(best?.0, best?.1)
} catch {
completion(nil, nil)
}
}.resume()
}
static func titleSimilarity(_ a: String, _ b: String) -> Double {
let lowerA = a.lowercased()
let lowerB = b.lowercased()
let distance = Double(levenshtein(lowerA, lowerB))
let maxLen = Double(max(lowerA.count, lowerB.count))
if maxLen == 0 { return 1.0 }
return 1.0 - (distance / maxLen)
}
static func levenshtein(_ a: String, _ b: String) -> Int {
let a = Array(a)
let b = Array(b)
var dist = Array(repeating: Array(repeating: 0, count: b.count + 1), count: a.count + 1)
for i in 0...a.count { dist[i][0] = i }
for j in 0...b.count { dist[0][j] = j }
for i in 1...a.count {
for j in 1...b.count {
if a[i-1] == b[j-1] {
dist[i][j] = dist[i-1][j-1]
} else {
dist[i][j] = min(dist[i-1][j-1], dist[i][j-1], dist[i-1][j]) + 1
}
}
}
return dist[a.count][b.count]
}
}

View file

@ -0,0 +1,43 @@
//
// Trakt-Login.swift
// Sulfur
//
// Created by Francesco on 13/04/25.
//
import UIKit
class TraktLogin {
static let clientID = "6ec81bf19deb80fdfa25652eef101576ca6aaa0dc016d36079b2de413d71c369"
static let redirectURI = "sora://trakt"
static let authorizationEndpoint = "https://trakt.tv/oauth/authorize"
static func authenticate() {
let urlString = "\(authorizationEndpoint)?client_id=\(clientID)&redirect_uri=\(redirectURI)&response_type=code"
guard let url = URL(string: urlString) else {
Logger.shared.log("Invalid authorization URL", type: "Error")
return
}
WebAuthenticationManager.shared.authenticate(url: url, callbackScheme: "sora") { result in
switch result {
case .success(let callbackURL):
if let params = callbackURL.queryParameters,
let code = params["code"] {
TraktToken.exchangeAuthorizationCodeForToken(code: code) { success in
if success {
Logger.shared.log("Trakt token exchange successful", type: "Debug")
} else {
Logger.shared.log("Trakt token exchange failed", type: "Error")
}
}
} else {
Logger.shared.log("No authorization code in callback URL", type: "Error")
}
case .failure(let error):
Logger.shared.log("Authentication failed: \(error.localizedDescription)", type: "Error")
}
}
}
}

View file

@ -0,0 +1,246 @@
//
// Trakt-Token.swift
// Sulfur
//
// Created by Francesco on 13/04/25.
//
import UIKit
import Security
class TraktToken {
static let clientID = "6ec81bf19deb80fdfa25652eef101576ca6aaa0dc016d36079b2de413d71c369"
static let clientSecret = "17cd92f71da3be9d755e2d8a6506fb3c3ecee19a247a6f0120ce2fb1f359850b"
static let redirectURI = "sora://trakt"
static let tokenEndpoint = "https://api.trakt.tv/oauth/token"
static let serviceName = "me.cranci.sora.TraktToken"
static let accessTokenKey = "TraktAccessToken"
static let refreshTokenKey = "TraktRefreshToken"
static let authSuccessNotification = Notification.Name("TraktAuthenticationSuccess")
static let authFailureNotification = Notification.Name("TraktAuthenticationFailure")
private static func saveToKeychain(key: String, data: String) -> Bool {
let tokenData = data.data(using: .utf8)!
let deleteQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: key
]
SecItemDelete(deleteQuery as CFDictionary)
let addQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: key,
kSecValueData as String: tokenData
]
return SecItemAdd(addQuery as CFDictionary, nil) == errSecSuccess
}
static func exchangeAuthorizationCodeForToken(code: String, completion: @escaping (Bool) -> Void) {
guard let url = URL(string: tokenEndpoint) else {
Logger.shared.log("Invalid token endpoint URL", type: "Error")
handleFailure(error: "Invalid token endpoint URL", completion: completion)
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let bodyData: [String: Any] = [
"code": code,
"client_id": clientID,
"client_secret": clientSecret,
"redirect_uri": redirectURI,
"grant_type": "authorization_code"
]
processTokenRequest(request: request, bodyData: bodyData, completion: completion)
}
static func refreshAccessToken(completion: @escaping (Bool) -> Void) {
guard let refreshToken = getRefreshToken() else {
handleFailure(error: "No refresh token available", completion: completion)
return
}
guard let url = URL(string: tokenEndpoint) else {
handleFailure(error: "Invalid token endpoint URL", completion: completion)
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let bodyData: [String: Any] = [
"refresh_token": refreshToken,
"client_id": clientID,
"client_secret": clientSecret,
"redirect_uri": redirectURI,
"grant_type": "refresh_token"
]
processTokenRequest(request: request, bodyData: bodyData, completion: completion)
}
private static func processTokenRequest(request: URLRequest, bodyData: [String: Any], completion: @escaping (Bool) -> Void) {
var request = request
do {
request.httpBody = try JSONSerialization.data(withJSONObject: bodyData)
} catch {
handleFailure(error: "Failed to create request body", completion: completion)
return
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
handleFailure(error: error.localizedDescription, completion: completion)
return
}
guard let data = data else {
handleFailure(error: "No data received", completion: completion)
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
if let accessToken = json["access_token"] as? String,
let refreshToken = json["refresh_token"] as? String {
let accessSuccess = saveToKeychain(key: accessTokenKey, data: accessToken)
let refreshSuccess = saveToKeychain(key: refreshTokenKey, data: refreshToken)
if accessSuccess && refreshSuccess {
NotificationCenter.default.post(name: authSuccessNotification, object: nil)
completion(true)
} else {
handleFailure(error: "Failed to save tokens to keychain", completion: completion)
}
} else {
let errorMessage = (json["error"] as? String) ?? "Unexpected response"
handleFailure(error: errorMessage, completion: completion)
}
}
} catch {
handleFailure(error: "Failed to parse response: \(error.localizedDescription)", completion: completion)
}
}
}
task.resume()
}
private static func handleFailure(error: String, completion: @escaping (Bool) -> Void) {
Logger.shared.log(error, type: "Error")
NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": error])
completion(false)
}
private static func getRefreshToken() -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: refreshTokenKey,
kSecReturnData as String: true
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess,
let tokenData = result as? Data,
let token = String(data: tokenData, encoding: .utf8) else {
return nil
}
return token
}
static func getAccessToken() -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: accessTokenKey,
kSecReturnData as String: true
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess,
let tokenData = result as? Data,
let token = String(data: tokenData, encoding: .utf8) else {
return nil
}
return token
}
static func validateToken(completion: @escaping (Bool) -> Void) {
guard let token = getAccessToken() else {
completion(false)
return
}
guard let url = URL(string: "https://api.trakt.tv/users/settings") else {
completion(false)
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("2", forHTTPHeaderField: "trakt-api-version")
request.setValue(clientID, forHTTPHeaderField: "trakt-api-key")
let task = URLSession.shared.dataTask(with: request) { _, response, _ in
DispatchQueue.main.async {
if let httpResponse = response as? HTTPURLResponse {
let isValid = httpResponse.statusCode == 200
completion(isValid)
} else {
completion(false)
}
}
}
task.resume()
}
static func validateAndRefreshTokenIfNeeded(completion: @escaping (Bool) -> Void) {
if getAccessToken() == nil {
if getRefreshToken() != nil {
refreshAccessToken(completion: completion)
} else {
completion(false)
}
return
}
validateToken { isValid in
if isValid {
completion(true)
} else {
if getRefreshToken() != nil {
refreshAccessToken(completion: completion)
} else {
completion(false)
}
}
}
}
static func checkAuthenticationStatus(completion: @escaping (Bool) -> Void) {
validateAndRefreshTokenIfNeeded(completion: completion)
}
}

View file

@ -0,0 +1,153 @@
//
// TraktPushUpdates.swift
// Sulfur
//
// Created by Francesco on 13/04/25.
//
import UIKit
import Security
class TraktMutation {
let apiURL = URL(string: "https://api.trakt.tv")!
func getTokenFromKeychain() -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: TraktToken.serviceName,
kSecAttrAccount as String: TraktToken.accessTokenKey,
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitOne
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess,
let tokenData = item as? Data,
let token = String(data: tokenData, encoding: .utf8) else {
return nil
}
return token
}
func markAsWatched(type: String, tmdbID: Int, episodeNumber: Int? = nil, seasonNumber: Int? = nil, completion: @escaping (Result<Void, Error>) -> Void) {
let sendTraktUpdates = UserDefaults.standard.object(forKey: "sendTraktUpdates") as? Bool ?? true
if !sendTraktUpdates {
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Trakt updates disabled by user"])))
return
}
guard let userToken = getTokenFromKeychain() else {
Logger.shared.log("Trakt access token not found in keychain", type: "Error")
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Access token not found"])))
return
}
let endpoint = "/sync/history"
let watchedAt = ISO8601DateFormatter().string(from: Date())
let body: [String: Any]
switch type {
case "movie":
body = [
"movies": [
[
"ids": ["tmdb": tmdbID],
"watched_at": watchedAt
]
]
]
case "episode":
guard let episode = episodeNumber, let season = seasonNumber else {
let errorMsg = "Missing episode (\(episodeNumber ?? -1)) or season (\(seasonNumber ?? -1)) number"
Logger.shared.log(errorMsg, type: "Error")
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: errorMsg])))
return
}
Logger.shared.log("Preparing episode watch request - TMDB ID: \(tmdbID), Season: \(season), Episode: \(episode)", type: "Debug")
body = [
"shows": [
[
"ids": ["tmdb": tmdbID],
"seasons": [
[
"number": season,
"episodes": [
[
"number": episode,
"watched_at": watchedAt
]
]
]
]
]
]
]
default:
Logger.shared.log("Invalid content type: \(type)", type: "Error")
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid content type"])))
return
}
var request = URLRequest(url: apiURL.appendingPathComponent(endpoint))
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(userToken)", forHTTPHeaderField: "Authorization")
request.setValue("2", forHTTPHeaderField: "trakt-api-version")
request.setValue(TraktToken.clientID, forHTTPHeaderField: "trakt-api-key")
do {
let jsonData = try JSONSerialization.data(withJSONObject: body, options: [.prettyPrinted])
request.httpBody = jsonData
if let jsonString = String(data: jsonData, encoding: .utf8) {
Logger.shared.log("Trakt API Request Body: \(jsonString)", type: "Debug")
}
} catch {
Logger.shared.log("Failed to serialize request body: \(error.localizedDescription)", type: "Error")
completion(.failure(error))
return
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
Logger.shared.log("Trakt API network error: \(error.localizedDescription)", type: "Error")
completion(.failure(error))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
Logger.shared.log("Trakt API: No HTTP response received", type: "Error")
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "No HTTP response"])))
return
}
if let data = data, let responseString = String(data: data, encoding: .utf8) {
Logger.shared.log("Trakt API Response Body: \(responseString)", type: "Debug")
}
if (200...299).contains(httpResponse.statusCode) {
Logger.shared.log("Successfully updated watch status on Trakt for \(type)", type: "General")
completion(.success(()))
} else {
var errorMessage = "HTTP \(httpResponse.statusCode)"
if let data = data,
let errorJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
if let error = errorJson["error"] as? String {
errorMessage = "\(errorMessage): \(error)"
}
if let errorDescription = errorJson["error_description"] as? String {
errorMessage = "\(errorMessage) - \(errorDescription)"
}
}
Logger.shared.log("Trakt API Error: \(errorMessage)", type: "Error")
completion(.failure(NSError(domain: "", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: errorMessage])))
}
}
task.resume()
}
}

View file

@ -1,30 +0,0 @@
//
// Double+Extension.swift
// AppleMusicSlider
//
// Created by Pratik on 14/01/23.
//
// Thanks to pratikg29 for this code inside his open source project "https://github.com/pratikg29/Custom-Slider-Control?ref=iosexample.com"
//
import Foundation
extension Double {
func asTimeString(style: DateComponentsFormatter.UnitsStyle) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
formatter.unitsStyle = style
formatter.zeroFormattingBehavior = .pad
return formatter.string(from: self) ?? ""
}
}
extension BinaryFloatingPoint {
func asTimeString(style: DateComponentsFormatter.UnitsStyle) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
formatter.unitsStyle = style
formatter.zeroFormattingBehavior = .pad
return formatter.string(from: TimeInterval(self)) ?? ""
}
}

View file

@ -1,102 +0,0 @@
//
// MusicProgressSlider.swift
// Custom Seekbar
//
// Created by Pratik on 08/01/23.
//
// Thanks to pratikg29 for this code inside his open source project "https://github.com/pratikg29/Custom-Slider-Control?ref=iosexample.com"
// I did edit just a little bit the code for my liking
//
import SwiftUI
struct MusicProgressSlider<T: BinaryFloatingPoint>: View {
@Binding var value: T
let inRange: ClosedRange<T>
let activeFillColor: Color
let fillColor: Color
let emptyColor: Color
let height: CGFloat
let onEditingChanged: (Bool) -> Void
@State private var localRealProgress: T = 0
@State private var localTempProgress: T = 0
@GestureState private var isActive: Bool = false
var body: some View {
GeometryReader { bounds in
ZStack {
VStack {
ZStack(alignment: .center) {
Capsule()
.fill(emptyColor)
Capsule()
.fill(isActive ? activeFillColor : fillColor)
.mask({
HStack {
Rectangle()
.frame(width: max(bounds.size.width * CGFloat((localRealProgress + localTempProgress)), 0), alignment: .leading)
Spacer(minLength: 0)
}
})
}
HStack {
Text(value.asTimeString(style: .positional))
Spacer(minLength: 0)
Text("-" + (inRange.upperBound - value).asTimeString(style: .positional))
}
.font(.system(size: 12))
.foregroundColor(isActive ? fillColor : emptyColor)
}
.frame(width: isActive ? bounds.size.width * 1.04 : bounds.size.width, alignment: .center)
.animation(animation, value: isActive)
}
.frame(width: bounds.size.width, height: bounds.size.height, alignment: .center)
.contentShape(Rectangle())
.gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local)
.updating($isActive) { value, state, transaction in
state = true
}
.onChanged { gesture in
localTempProgress = T(gesture.translation.width / bounds.size.width)
value = max(min(getPrgValue(), inRange.upperBound), inRange.lowerBound)
}.onEnded { value in
localRealProgress = max(min(localRealProgress + localTempProgress, 1), 0)
localTempProgress = 0
})
.onChange(of: isActive) { newValue in
value = max(min(getPrgValue(), inRange.upperBound), inRange.lowerBound)
onEditingChanged(newValue)
}
.onAppear {
localRealProgress = getPrgPercentage(value)
}
.onChange(of: value) { newValue in
if !isActive {
localRealProgress = getPrgPercentage(newValue)
}
}
}
.frame(height: isActive ? height * 1.25 : height, alignment: .center)
}
private var animation: Animation {
if isActive {
return .spring()
} else {
return .spring(response: 0.5, dampingFraction: 0.5, blendDuration: 0.6)
}
}
private func getPrgPercentage(_ value: T) -> T {
let range = inRange.upperBound - inRange.lowerBound
let correctedStartValue = value - inRange.lowerBound
let percentage = correctedStartValue / range
return percentage
}
private func getPrgValue() -> T {
return ((localRealProgress + localTempProgress) * (inRange.upperBound - inRange.lowerBound)) + inRange.lowerBound
}
}

View file

@ -1,271 +0,0 @@
//
// ContentView.swift
// test2
//
// Created by Francesco on 20/12/24.
//
import SwiftUI
import AVKit
struct CustomVideoPlayer: UIViewControllerRepresentable {
let player: AVPlayer
func makeUIViewController(context: Context) -> AVPlayerViewController {
let controller = NormalPlayer()
controller.player = player
controller.showsPlaybackControls = false
player.play()
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
// yes? Like the plural of the famous american rapper ye? -IBHRAD
}
}
struct CustomMediaPlayer: View {
@State private var player: AVPlayer
@State private var isPlaying = true
@State private var currentTime: Double = 0.0
@State private var duration: Double = 0.0
@State private var showControls = false
@State private var inactivityTimer: Timer?
@State private var timeObserverToken: Any?
@Environment(\.presentationMode) var presentationMode
let module: ModuleStruct
let fullUrl: String
let title: String
let episodeNumber: Int
let onWatchNext: () -> Void
init(module: ModuleStruct, urlString: String, fullUrl: String, title: String, episodeNumber: Int, onWatchNext: @escaping () -> Void) {
guard let url = URL(string: urlString) else {
fatalError("Invalid URL string")
}
var request = URLRequest(url: url)
if urlString.contains("ascdn") {
request.addValue("\(module.module[0].details.baseURL)", forHTTPHeaderField: "Referer")
}
let asset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": request.allHTTPHeaderFields ?? [:]])
_player = State(initialValue: AVPlayer(playerItem: AVPlayerItem(asset: asset)))
self.module = module
self.fullUrl = fullUrl
self.title = title
self.episodeNumber = episodeNumber
self.onWatchNext = onWatchNext
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(fullUrl)")
if lastPlayedTime > 0 {
let seekTime = CMTime(seconds: lastPlayedTime, preferredTimescale: 1)
self._player.wrappedValue.seek(to: seekTime)
}
}
var body: some View {
ZStack {
VStack {
ZStack {
CustomVideoPlayer(player: player)
.onAppear {
player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 600), queue: .main) { time in
currentTime = time.seconds
if let itemDuration = player.currentItem?.duration.seconds, itemDuration.isFinite && !itemDuration.isNaN {
duration = itemDuration
}
}
startUpdatingCurrentTime()
addPeriodicTimeObserver(fullURL: fullUrl)
}
.edgesIgnoringSafeArea(.all)
.overlay(
Group {
if showControls {
Color.black.opacity(0.5)
.edgesIgnoringSafeArea(.all)
HStack(spacing: 20) {
Button(action: {
currentTime = max(currentTime - 10, 0)
player.seek(to: CMTime(seconds: currentTime, preferredTimescale: 600))
}) {
Image(systemName: "gobackward.10")
}
.foregroundColor(.white)
.font(.system(size: 25))
.contentShape(Rectangle())
.frame(width: 60, height: 60)
Button(action: {
if isPlaying {
player.pause()
} else {
player.play()
}
isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
}
.foregroundColor(.white)
.font(.system(size: 45))
.contentShape(Rectangle())
.frame(width: 80, height: 80)
Button(action: {
currentTime = min(currentTime + 10, duration)
player.seek(to: CMTime(seconds: currentTime, preferredTimescale: 600))
}) {
Image(systemName: "goforward.10")
}
.foregroundColor(.white)
.font(.system(size: 25))
.contentShape(Rectangle())
.frame(width: 60, height: 60)
}
}
}
.animation(.easeInOut(duration: 0.2), value: showControls),
alignment: .center
)
.onTapGesture {
withAnimation {
showControls.toggle()
}
}
VStack {
Spacer()
VStack {
HStack(alignment: .bottom) {
if showControls {
VStack(alignment: .leading) {
Text("Episode \(episodeNumber)")
.font(.subheadline)
.foregroundColor(.gray)
Text(title)
.font(.headline)
.foregroundColor(.white)
}
.padding(.horizontal, 32)
}
Spacer()
if duration - currentTime <= duration * 0.10 && currentTime != duration {
Button(action: {
player.pause()
presentationMode.wrappedValue.dismiss()
onWatchNext()
}) {
HStack {
Image(systemName: "forward.fill")
.foregroundColor(Color.black)
Text("Watch Next")
.font(.headline)
.foregroundColor(Color.black)
}
.padding()
.background(Color.white)
.cornerRadius(32)
}
.padding(.trailing, 10)
}
if showControls {
Menu {
Menu("Playback Speed") {
ForEach([0.5, 1.0, 1.25, 1.5, 1.75, 2.0], id: \.self) { speed in
Button(action: {
player.rate = Float(speed)
if player.timeControlStatus != .playing {
player.pause()
}
}) {
Text("\(speed, specifier: "%.2f")")
}
}
}
} label: {
Image(systemName: "ellipsis.circle")
.foregroundColor(.white)
.font(.system(size: 15))
}
}
}
.padding(.trailing, 32)
if showControls {
MusicProgressSlider(
value: $currentTime,
inRange: 0...duration,
activeFillColor: .white,
fillColor: .white.opacity(0.5),
emptyColor: .white.opacity(0.3),
height: 28,
onEditingChanged: { editing in
if !editing {
player.seek(to: CMTime(seconds: currentTime, preferredTimescale: 600))
}
}
)
.padding(.horizontal, 32)
.padding(.bottom, 10)
}
}
}
.onAppear {
startUpdatingCurrentTime()
}
.onDisappear {
player.pause()
inactivityTimer?.invalidate()
if let timeObserverToken = timeObserverToken {
player.removeTimeObserver(timeObserverToken)
self.timeObserverToken = nil
}
}
}
}
VStack {
if showControls {
HStack {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "xmark")
.foregroundColor(.white)
.font(.system(size: 20))
}
.frame(width: 60, height: 60)
.contentShape(Rectangle())
.padding()
Spacer()
}
Spacer()
}
}
}
}
private func startUpdatingCurrentTime() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
currentTime = player.currentTime().seconds
}
}
private func addPeriodicTimeObserver(fullURL: String) {
let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
guard let currentItem = player.currentItem,
currentItem.duration.seconds.isFinite else {
return
}
let currentTime = time.seconds
let duration = currentItem.duration.seconds
UserDefaults.standard.set(currentTime, forKey: "lastPlayedTime_\(fullURL)")
UserDefaults.standard.set(duration, forKey: "totalTime_\(fullURL)")
}
}
}

View file

@ -1,13 +0,0 @@
//
// Notification.swift
// Sora
//
// Created by Francesco on 18/12/24.
//
import Foundation
extension Notification.Name {
static let moduleAdded = Notification.Name("moduleAdded")
static let moduleRemoved = Notification.Name("moduleRemoved")
}

View file

@ -1,18 +0,0 @@
//
// URLSession.swift
// Sora
//
// Created by Francesco on 24/12/24.
//
import Foundation
extension URLSession {
static let custom: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = [
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
]
return URLSession(configuration: configuration)
}()
}

View file

@ -1,38 +0,0 @@
//
// GitHubAPI.swift
// Sora
//
// Created by Francesco on 31/12/24.
//
import Foundation
struct GitHubReleases: Codable {
let tagName: String
let body: String
let htmlUrl: String
enum CodingKeys: String, CodingKey {
case tagName = "tag_name"
case body
case htmlUrl = "html_url"
}
}
class GitHubAPI {
static let shared = GitHubAPI()
func fetchReleases(completion: @escaping ([GitHubReleases]?) -> Void) {
let url = URL(string: "https://api.github.com/repos/cranci1/Sora/releases")!
URLSession.custom.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
let releases = try? JSONDecoder().decode([GitHubReleases].self, from: data)
completion(releases)
}.resume()
}
}

View file

@ -1,124 +0,0 @@
//
// GitHubRelease.swift
// Sora
//
// Created by Francesco on 29/12/24.
//
import Foundation
struct GitHubRelease: Codable {
let url: String
let assetsUrl: String
let uploadUrl: String
let htmlUrl: String
let id: Int
let author: Author
let nodeId: String
let tagName: String
let targetCommitish: String
let name: String
let draft: Bool
let prerelease: Bool
let createdAt: String
let publishedAt: String
let assets: [Asset]
let tarballUrl: String
let zipballUrl: String
let body: String
enum CodingKeys: String, CodingKey {
case url
case assetsUrl = "assets_url"
case uploadUrl = "upload_url"
case htmlUrl = "html_url"
case id
case author
case nodeId = "node_id"
case tagName = "tag_name"
case targetCommitish = "target_commitish"
case name
case draft
case prerelease
case createdAt = "created_at"
case publishedAt = "published_at"
case assets
case tarballUrl = "tarball_url"
case zipballUrl = "zipball_url"
case body
}
struct Author: Codable {
let login: String
let id: Int
let nodeId: String
let avatarUrl: String
let gravatarId: String
let url: String
let htmlUrl: String
let followersUrl: String
let followingUrl: String
let gistsUrl: String
let starredUrl: String
let subscriptionsUrl: String
let organizationsUrl: String
let reposUrl: String
let eventsUrl: String
let receivedEventsUrl: String
let type: String
let siteAdmin: Bool
enum CodingKeys: String, CodingKey {
case login
case id
case nodeId = "node_id"
case avatarUrl = "avatar_url"
case gravatarId = "gravatar_id"
case url
case htmlUrl = "html_url"
case followersUrl = "followers_url"
case followingUrl = "following_url"
case gistsUrl = "gists_url"
case starredUrl = "starred_url"
case subscriptionsUrl = "subscriptions_url"
case organizationsUrl = "organizations_url"
case reposUrl = "repos_url"
case eventsUrl = "events_url"
case receivedEventsUrl = "received_events_url"
case type
case siteAdmin = "site_admin"
}
}
struct Asset: Codable {
let url: String
let id: Int
let nodeId: String
let name: String
let label: String?
let uploader: Author
let contentType: String
let state: String
let size: Int
let downloadCount: Int
let createdAt: String
let updatedAt: String
let browserDownloadUrl: String
enum CodingKeys: String, CodingKey {
case url
case id
case nodeId = "node_id"
case name
case label
case uploader
case contentType = "content_type"
case state
case size
case downloadCount = "download_count"
case createdAt = "created_at"
case updatedAt = "updated_at"
case browserDownloadUrl = "browser_download_url"
}
}
}

View file

@ -1,37 +0,0 @@
//
// HistoryManager.swift
// Sora
//
// Created by Francesco on 18/12/24.
//
import Foundation
import Combine
class HistoryManager: ObservableObject {
@Published var searchHistory: [String] = UserDefaults.standard.stringArray(forKey: "SearchHistory") ?? []
private var cancellables = Set<AnyCancellable>()
init() {
NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification)
.sink { [weak self] _ in
DispatchQueue.main.async {
self?.searchHistory = UserDefaults.standard.stringArray(forKey: "SearchHistory") ?? []
}
}
.store(in: &cancellables)
}
func addSearchHistory(_ item: String) {
if !searchHistory.contains(item) {
searchHistory.insert(item, at: 0)
UserDefaults.standard.set(searchHistory, forKey: "SearchHistory")
}
}
func deleteHistoryItem(at offsets: IndexSet) {
searchHistory.remove(atOffsets: offsets)
UserDefaults.standard.set(searchHistory, forKey: "SearchHistory")
}
}

View file

@ -1,26 +0,0 @@
//
// MiruDataStruct.swift
// Sora
//
// Created by Francesco on 18/12/24.
//
import Foundation
struct MiruDataStruct: Codable {
var likes: [Like]
struct Like: Codable {
let anilistID: Int
var gogoSlug: String
let title: String
let cover: String
enum CodingKeys: String, CodingKey {
case anilistID = "anilist_id"
case gogoSlug = "gogo_slug"
case title
case cover
}
}
}

View file

@ -1,77 +0,0 @@
//
// ModuleStruct.swift
// Sora
//
// Created by Francesco on 18/12/24.
//
import Foundation
struct ModuleStruct: Codable {
let name: String
let version: String
let author: Author
let iconURL: String
let stream: String
let language: String
let extractor: String
let module: [Module]
struct Author: Codable {
let name: String
let website: String
}
struct Module: Codable, Hashable {
let search: Search
let featured: Featured
let details: Details
let episodes: Episodes
struct Search: Codable, Hashable {
let url: String
let parameter: String
let documentSelector: String
let title: String
let image: Image
let href: String
struct Image: Codable, Hashable {
let url: String
let attribute: String
}
}
struct Featured: Codable, Hashable {
let url: String
let documentSelector: String
let title: String
let image: Image
let href: String
struct Image: Codable, Hashable {
let url: String
let attribute: String
}
}
struct Details: Codable, Hashable {
let baseURL: String
let aliases: Aliases
let synopsis: String
let airdate: String
let stars: String
struct Aliases: Codable, Hashable {
let selector: String
let attribute: String
}
}
struct Episodes: Codable, Hashable {
let selector: String
let order: String
let pattern: String
}
}
}

View file

@ -1,155 +0,0 @@
//
// ModulesManager.swift
// Sora
//
// Created by Francesco on 18/12/24.
//
import Foundation
class ModulesManager: ObservableObject {
@Published var modules: [ModuleStruct] = []
@Published var isLoading = true
var moduleURLs: [String: String] = [:]
private let modulesFileName = "modules.json"
private let moduleURLsFileName = "moduleURLs.json"
init() {
loadModules()
}
func loadModules() {
isLoading = true
loadModuleURLs()
loadModuleData()
isLoading = false
}
func addModule(from urlString: String, completion: @escaping (Result<Void, Error>) -> Void) {
guard let url = URL(string: urlString) else {
completion(.failure(ModuleError.invalidURL))
return
}
let task = URLSession.custom.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(.failure(error ?? ModuleError.unknown))
return
}
do {
let module = try JSONDecoder().decode(ModuleStruct.self, from: data)
DispatchQueue.main.async {
if !self.modules.contains(where: { $0.name == module.name }) {
self.modules.append(module)
self.moduleURLs[module.name] = urlString
self.saveModuleData()
self.saveModuleURLs()
NotificationCenter.default.post(name: .moduleAdded, object: nil)
completion(.success(()))
} else {
completion(.failure(ModuleError.duplicateModule))
}
}
} catch {
completion(.failure(error))
}
}
task.resume()
}
func deleteModule(named name: String) {
if let index = modules.firstIndex(where: { $0.name == name }) {
modules.remove(at: index)
moduleURLs.removeValue(forKey: name)
saveModuleData()
saveModuleURLs()
NotificationCenter.default.post(name: .moduleRemoved, object: nil)
}
}
func refreshModules() {
for (name, urlString) in moduleURLs {
guard let url = URL(string: urlString) else { continue }
let task = URLSession.custom.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
do {
let updatedModule = try JSONDecoder().decode(ModuleStruct.self, from: data)
DispatchQueue.main.async {
if let index = self.modules.firstIndex(where: { $0.name == name }) {
self.modules[index] = updatedModule
self.saveModuleData()
}
}
} catch {
print("Failed to decode module during refresh: \(error.localizedDescription)")
Logger.shared.log("Failed to decode module during refresh: \(error.localizedDescription)")
}
}
task.resume()
}
}
private func loadModuleURLs() {
let fileURL = getDocumentsDirectory().appendingPathComponent(moduleURLsFileName)
do {
let data = try Data(contentsOf: fileURL)
moduleURLs = try JSONDecoder().decode([String: String].self, from: data)
} catch {
print("Failed to load module URLs: \(error.localizedDescription)")
Logger.shared.log("Failed to load module URLs: \(error.localizedDescription)")
}
}
private func loadModuleData() {
let fileURL = getDocumentsDirectory().appendingPathComponent(modulesFileName)
do {
let data = try Data(contentsOf: fileURL)
modules = try JSONDecoder().decode([ModuleStruct].self, from: data)
} catch {
print("Failed to load modules: \(error.localizedDescription)")
Logger.shared.log("Failed to load modules: \(error.localizedDescription)")
}
}
private func saveModuleData() {
let fileURL = getDocumentsDirectory().appendingPathComponent(modulesFileName)
do {
let data = try JSONEncoder().encode(modules)
try data.write(to: fileURL)
} catch {
print("Failed to save modules: \(error.localizedDescription)")
Logger.shared.log("Failed to save modules: \(error.localizedDescription)")
}
}
private func saveModuleURLs() {
let fileURL = getDocumentsDirectory().appendingPathComponent(moduleURLsFileName)
do {
let data = try JSONEncoder().encode(moduleURLs)
try data.write(to: fileURL)
} catch {
print("Failed to save module URLs: \(error.localizedDescription)")
Logger.shared.log("Failed to save module URLs: \(error.localizedDescription)")
}
}
private func getDocumentsDirectory() -> URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}
enum ModuleError: LocalizedError {
case invalidURL
case duplicateModule
case unknown
var errorDescription: String? {
switch self {
case .invalidURL:
return "The provided URL is invalid."
case .duplicateModule:
return "This module already exists."
case .unknown:
return "An unknown error occurred."
}
}
}
}

View file

@ -1,98 +0,0 @@
//
// VideoPlayerView.swift
// Sora
//
// Created by Francesco on 18/12/24.
//
import UIKit
import AVKit
class VideoPlayerViewController: UIViewController {
let module: ModuleStruct
var player: AVPlayer?
var playerViewController: AVPlayerViewController?
var timeObserverToken: Any?
var streamUrl: String?
var fullUrl: String = ""
init(module: ModuleStruct) {
self.module = module
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
guard let streamUrl = streamUrl, let url = URL(string: streamUrl) else {
return
}
var request = URLRequest(url: url)
if streamUrl.contains("ascdn") {
request.addValue("\(module.module[0].details.baseURL)", forHTTPHeaderField: "Referer")
}
let asset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": request.allHTTPHeaderFields ?? [:]])
let playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playerItem)
playerViewController = NormalPlayer()
playerViewController?.player = player
addPeriodicTimeObserver(fullURL: fullUrl)
if let playerViewController = playerViewController {
playerViewController.view.frame = self.view.frame
self.view.addSubview(playerViewController.view)
self.addChild(playerViewController)
playerViewController.didMove(toParent: self)
}
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(fullUrl)")
if lastPlayedTime > 0 {
let seekTime = CMTime(seconds: lastPlayedTime, preferredTimescale: 1)
self.player?.seek(to: seekTime) { _ in
self.player?.play()
}
} else {
self.player?.play()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
player?.play()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
player?.pause()
if let timeObserverToken = timeObserverToken {
player?.removeTimeObserver(timeObserverToken)
self.timeObserverToken = nil
}
}
func addPeriodicTimeObserver(fullURL: String) {
guard let player = self.player else { return }
let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
guard let currentItem = player.currentItem,
currentItem.duration.seconds.isFinite else {
return
}
let currentTime = time.seconds
let duration = currentItem.duration.seconds
UserDefaults.standard.set(currentTime, forKey: "lastPlayedTime_\(fullURL)")
UserDefaults.standard.set(duration, forKey: "totalTime_\(fullURL)")
}
}
}

View file

@ -0,0 +1,112 @@
//
// Analytics.swift
// Sora
//
// Created by Hamzo on 28.02.25.
//
import Foundation
import UIKit
struct AnalyticsResponse: Codable {
let status: String
let message: String
let event: String?
let timestamp: String?
}
@MainActor
class AnalyticsManager {
static let shared = AnalyticsManager()
private let analyticsURL = URL(string: "http://151.106.3.14:47474/analytics")!
private let moduleManager = ModuleManager()
private init() {}
func sendEvent(event: String, additionalData: [String: Any] = [:]) {
let defaults = UserDefaults.standard
if defaults.object(forKey: "analyticsEnabled") == nil {
defaults.setValue(false, forKey: "analyticsEnabled")
}
let analyticsEnabled = UserDefaults.standard.bool(forKey: "analyticsEnabled")
guard analyticsEnabled else {
Logger.shared.log("Analytics is disabled, skipping event: \(event)", type: "Debug")
return
}
guard let selectedModule = getSelectedModule() else {
Logger.shared.log("No selected module found", type: "Debug")
return
}
var safeAdditionalData = additionalData
if let errorValue = additionalData["error"] as? NSError {
safeAdditionalData["error"] = errorValue.localizedDescription
}
let analyticsData: [String: Any] = [
"event": event,
"device": getDeviceModel(),
"app_version": getAppVersion(),
"module_name": selectedModule.metadata.sourceName,
"module_version": selectedModule.metadata.version,
"data": safeAdditionalData
]
sendRequest(with: analyticsData)
}
private func sendRequest(with data: [String: Any]) {
var request = URLRequest(url: analyticsURL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: data, options: [])
} catch {
Logger.shared.log("Failed to encode JSON: \(error.localizedDescription)", type: "Debug")
return
}
URLSession.shared.dataTask(with: request) { (data, _, error) in
if let error = error {
Logger.shared.log("Request failed: \(error.localizedDescription)", type: "Debug")
return
}
guard let data = data else {
Logger.shared.log("No data received from server", type: "Debug")
return
}
do {
let decodedResponse = try JSONDecoder().decode(AnalyticsResponse.self, from: data)
if decodedResponse.status == "success" {
Logger.shared.log("Analytics saved: \(decodedResponse.event ?? "unknown event") at \(decodedResponse.timestamp ?? "unknown time")", type: "Debug")
} else {
Logger.shared.log("Server error: \(decodedResponse.message)", type: "Debug")
}
} catch {
Logger.shared.log("Failed to decode response: \(error.localizedDescription)", type: "Debug")
}
}.resume()
}
private func getAppVersion() -> String {
return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown_version"
}
private func getDeviceModel() -> String {
return UIDevice.modelName
}
private func getSelectedModule() -> ScrapingModule? {
guard let selectedModuleId = UserDefaults.standard.string(forKey: "selectedModuleId") else { return nil }
return moduleManager.modules.first { $0.id.uuidString == selectedModuleId }
}
}

View file

@ -0,0 +1,100 @@
//
// DownloadManager.swift
// Sulfur
//
// Created by Francesco on 29/04/25.
//
import SwiftUI
import AVKit
import AVFoundation
class DownloadManager: NSObject, ObservableObject {
@Published var activeDownloads: [(URL, Double)] = []
@Published var localPlaybackURL: URL?
private var assetDownloadURLSession: AVAssetDownloadURLSession!
private var activeDownloadTasks: [URLSessionTask: URL] = [:]
override init() {
super.init()
initializeDownloadSession()
loadLocalContent()
}
private func initializeDownloadSession() {
#if targetEnvironment(simulator)
Logger.shared.log("Download Sessions are not available on Simulator", type: "Error")
#else
let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader")
assetDownloadURLSession = AVAssetDownloadURLSession(
configuration: configuration,
assetDownloadDelegate: self,
delegateQueue: .main
)
#endif
}
func downloadAsset(from url: URL) {
let asset = AVURLAsset(url: url)
let task = assetDownloadURLSession.makeAssetDownloadTask(
asset: asset,
assetTitle: "Offline Video",
assetArtworkData: nil,
options: nil
)
task?.resume()
activeDownloadTasks[task!] = url
}
private func loadLocalContent() {
guard let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
do {
let contents = try FileManager.default.contentsOfDirectory(
at: documents,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles
)
if let localURL = contents.first(where: { ["movpkg", "mp4"].contains($0.pathExtension.lowercased()) }) {
localPlaybackURL = localURL
}
} catch {
Logger.shared.log("Could not load local content: \(error)", type: "Error")
}
}
}
extension DownloadManager: AVAssetDownloadDelegate {
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
activeDownloadTasks.removeValue(forKey: assetDownloadTask)
localPlaybackURL = location
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let error = error else { return }
Logger.shared.log("Download failed: \(error.localizedDescription)", type: "Error")
activeDownloadTasks.removeValue(forKey: task)
}
func urlSession(_ session: URLSession,
assetDownloadTask: AVAssetDownloadTask,
didLoad timeRange: CMTimeRange,
totalTimeRangesLoaded loadedTimeRanges: [NSValue],
timeRangeExpectedToLoad: CMTimeRange) {
guard let url = activeDownloadTasks[assetDownloadTask] else { return }
let progress = loadedTimeRanges
.map { $0.timeRangeValue.duration.seconds / timeRangeExpectedToLoad.duration.seconds }
.reduce(0, +)
if let index = activeDownloads.firstIndex(where: { $0.0 == url }) {
activeDownloads[index].1 = progress
} else {
activeDownloads.append((url, progress))
}
}
}

View file

@ -0,0 +1,648 @@
//
// DownloadModels.swift
// Sora
//
// Created by Francesco on 30/04/25.
//
import Foundation
// MARK: - Quality Preference Constants
enum DownloadQualityPreference: String, CaseIterable {
case best = "Best"
case high = "High"
case medium = "Medium"
case low = "Low"
static var defaultPreference: DownloadQualityPreference {
return .best
}
static var userDefaultsKey: String {
return "downloadQuality"
}
/// Returns the current user preference for download quality
static var current: DownloadQualityPreference {
let storedValue = UserDefaults.standard.string(forKey: userDefaultsKey) ?? defaultPreference.rawValue
return DownloadQualityPreference(rawValue: storedValue) ?? defaultPreference
}
/// Description of what each quality preference means
var description: String {
switch self {
case .best:
return "Maximum quality available (largest file size)"
case .high:
return "High quality (720p or better)"
case .medium:
return "Medium quality (480p to 720p)"
case .low:
return "Minimum quality available (smallest file size)"
}
}
}
// MARK: - Download Types
enum DownloadType: String, Codable {
case movie
case episode
var description: String {
switch self {
case .movie:
return "Movie"
case .episode:
return "Episode"
}
}
}
// MARK: - Downloaded Asset Model
struct DownloadedAsset: Identifiable, Codable, Equatable {
let id: UUID
var name: String
let downloadDate: Date
let originalURL: URL
let localURL: URL
let type: DownloadType
let metadata: AssetMetadata?
// New fields for subtitle support
let subtitleURL: URL?
let localSubtitleURL: URL?
// For caching purposes, but not stored as part of the codable object
private var _cachedFileSize: Int64? = nil
// Implement Equatable
static func == (lhs: DownloadedAsset, rhs: DownloadedAsset) -> Bool {
return lhs.id == rhs.id
}
/// Returns the combined file size of the video file and subtitle file (if exists)
var fileSize: Int64 {
// This implementation calculates file size without caching it in the struct property
// Instead we'll use a static cache dictionary
let subtitlePathString = localSubtitleURL?.path ?? ""
let cacheKey = localURL.path + ":" + subtitlePathString
// Check the static cache first
if let size = DownloadedAsset.fileSizeCache[cacheKey] {
return size
}
// Check if this asset is currently being downloaded (avoid expensive calculations during active downloads)
if isCurrentlyBeingDownloaded() {
// Return cached size if available, otherwise return 0 and schedule background calculation
if let lastKnownSize = DownloadedAsset.lastKnownSizes[cacheKey] {
// Schedule a background update for when download completes
scheduleBackgroundSizeCalculation(cacheKey: cacheKey)
return lastKnownSize
} else {
// Return 0 for actively downloading files that we haven't calculated yet
return 0
}
}
// For non-active downloads, calculate the size normally
let calculatedSize = calculateFileSizeInternal()
// Store in both caches
DownloadedAsset.fileSizeCache[cacheKey] = calculatedSize
DownloadedAsset.lastKnownSizes[cacheKey] = calculatedSize
return calculatedSize
}
/// Check if this asset is currently being downloaded
public func isCurrentlyBeingDownloaded() -> Bool {
// Access JSController to check active downloads
let activeDownloads = JSController.shared.activeDownloads
// Check if any active download matches this asset's path
for download in activeDownloads {
// Compare based on the file name or title
if let downloadTitle = download.title, downloadTitle == name {
return true
}
// Also compare based on URL path if titles don't match
if download.originalURL.lastPathComponent.contains(name) ||
name.contains(download.originalURL.lastPathComponent) {
return true
}
}
return false
}
/// Schedule a background calculation for when the download completes
private func scheduleBackgroundSizeCalculation(cacheKey: String) {
DispatchQueue.global(qos: .background).async {
// Check if download is still active before calculating
if !self.isCurrentlyBeingDownloaded() {
let size = self.calculateFileSizeInternal()
DispatchQueue.main.async {
// Update caches on main thread
DownloadedAsset.fileSizeCache[cacheKey] = size
DownloadedAsset.lastKnownSizes[cacheKey] = size
// Post a notification that file size has been updated
NotificationCenter.default.post(
name: NSNotification.Name("fileSizeUpdated"),
object: nil,
userInfo: ["assetId": self.id.uuidString]
)
}
}
}
}
/// Internal method to calculate file size (separated for reuse)
public func calculateFileSizeInternal() -> Int64 {
var totalSize: Int64 = 0
let fileManager = FileManager.default
// Get video file or directory size
if fileManager.fileExists(atPath: localURL.path) {
// Check if it's a .movpkg directory or a regular file
var isDirectory: ObjCBool = false
fileManager.fileExists(atPath: localURL.path, isDirectory: &isDirectory)
if isDirectory.boolValue {
// If it's a directory (like .movpkg), calculate size of all contained files
totalSize += calculateDirectorySize(localURL)
Logger.shared.log("Calculated directory size for .movpkg: \(totalSize) bytes", type: "Info")
} else {
// If it's a single file, get its size
do {
let attributes = try fileManager.attributesOfItem(atPath: localURL.path)
if let size = attributes[.size] as? Int64 {
totalSize += size
} else if let size = attributes[.size] as? Int {
totalSize += Int64(size)
} else if let size = attributes[.size] as? NSNumber {
totalSize += size.int64Value
} else {
Logger.shared.log("Could not get file size as Int64 for: \(localURL.path)", type: "Warning")
}
} catch {
Logger.shared.log("Error getting file size: \(error.localizedDescription) for \(localURL.path)", type: "Error")
}
}
} else {
Logger.shared.log("Video file does not exist at path: \(localURL.path)", type: "Warning")
}
// Add subtitle file size if it exists
if let subtitlePath = localSubtitleURL?.path, fileManager.fileExists(atPath: subtitlePath) {
do {
let attributes = try fileManager.attributesOfItem(atPath: subtitlePath)
if let size = attributes[.size] as? Int64 {
totalSize += size
} else if let size = attributes[.size] as? Int {
totalSize += Int64(size)
} else if let size = attributes[.size] as? NSNumber {
totalSize += size.int64Value
}
} catch {
Logger.shared.log("Error getting subtitle file size: \(error.localizedDescription)", type: "Warning")
}
}
return totalSize
}
/// Calculates the size of all files in a directory recursively
private func calculateDirectorySize(_ directoryURL: URL) -> Int64 {
let fileManager = FileManager.default
var totalSize: Int64 = 0
do {
// Get all content URLs
let contents = try fileManager.contentsOfDirectory(at: directoryURL, includingPropertiesForKeys: [.fileSizeKey, .isDirectoryKey], options: [])
// Calculate size for each item
for url in contents {
let resourceValues = try url.resourceValues(forKeys: [.isDirectoryKey, .fileSizeKey])
if let isDirectory = resourceValues.isDirectory, isDirectory {
// If it's a directory, recursively calculate its size
totalSize += calculateDirectorySize(url)
} else {
// If it's a file, add its size
if let fileSize = resourceValues.fileSize {
totalSize += Int64(fileSize)
}
}
}
} catch {
Logger.shared.log("Error calculating directory size: \(error.localizedDescription)", type: "Error")
}
return totalSize
}
/// Global file size cache for performance
private static var fileSizeCache: [String: Int64] = [:]
/// Global last known sizes cache for performance
private static var lastKnownSizes: [String: Int64] = [:]
/// Clears the global file size cache
static func clearFileSizeCache() {
fileSizeCache.removeAll()
lastKnownSizes.removeAll()
}
/// Returns true if the main video file exists
var fileExists: Bool {
return FileManager.default.fileExists(atPath: localURL.path)
}
// MARK: - New Grouping Properties
/// Returns the anime title to use for grouping (show title for episodes, name for movies)
var groupTitle: String {
if type == .episode, let showTitle = metadata?.showTitle, !showTitle.isEmpty {
return showTitle
}
// For movies or episodes without show title, use the asset name
return name
}
/// Returns a display name suitable for showing in a list of episodes
var episodeDisplayName: String {
guard type == .episode else { return name }
// Return the name directly since titles typically already contain episode information
return name
}
/// Returns order priority for episodes within a show (by season and episode)
var episodeOrderPriority: Int {
guard type == .episode else { return 0 }
// Calculate priority: Season number * 1000 + episode number
let seasonValue = metadata?.season ?? 0
let episodeValue = metadata?.episode ?? 0
return (seasonValue * 1000) + episodeValue
}
// Add coding keys to ensure backward compatibility
enum CodingKeys: String, CodingKey {
case id, name, downloadDate, originalURL, localURL, type, metadata
case subtitleURL, localSubtitleURL
}
// Custom decoding to handle optional new fields
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Decode required fields
id = try container.decode(UUID.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
downloadDate = try container.decode(Date.self, forKey: .downloadDate)
originalURL = try container.decode(URL.self, forKey: .originalURL)
localURL = try container.decode(URL.self, forKey: .localURL)
type = try container.decode(DownloadType.self, forKey: .type)
metadata = try container.decodeIfPresent(AssetMetadata.self, forKey: .metadata)
// Decode new optional fields
subtitleURL = try container.decodeIfPresent(URL.self, forKey: .subtitleURL)
localSubtitleURL = try container.decodeIfPresent(URL.self, forKey: .localSubtitleURL)
// Initialize cache
_cachedFileSize = nil
}
init(
id: UUID = UUID(),
name: String,
downloadDate: Date,
originalURL: URL,
localURL: URL,
type: DownloadType = .movie,
metadata: AssetMetadata? = nil,
subtitleURL: URL? = nil,
localSubtitleURL: URL? = nil
) {
self.id = id
self.name = name
self.downloadDate = downloadDate
self.originalURL = originalURL
self.localURL = localURL
self.type = type
self.metadata = metadata
self.subtitleURL = subtitleURL
self.localSubtitleURL = localSubtitleURL
}
}
// MARK: - Active Download Model
struct ActiveDownload: Identifiable, Equatable {
let id: UUID
let originalURL: URL
var progress: Double
let task: URLSessionTask
let type: DownloadType
let metadata: AssetMetadata?
// Implement Equatable
static func == (lhs: ActiveDownload, rhs: ActiveDownload) -> Bool {
return lhs.id == rhs.id
}
// Add the same grouping properties as DownloadedAsset for consistency
var groupTitle: String {
if type == .episode,
let showTitle = metadata?.showTitle,
!showTitle.isEmpty {
return showTitle
}
return metadata?.title ?? originalURL.lastPathComponent
}
var episodeDisplayName: String {
guard type == .episode else {
return metadata?.title ?? originalURL.lastPathComponent
}
// Extract base episode number from metadata or default to 1
let episodeNumber = metadata?.episode ?? 1
let base = "Episode \(episodeNumber)"
// Check if we have a valid title that's different from the base
if let title = metadata?.title, !title.isEmpty, title != base {
return "\(base): \(title)"
} else {
return base
}
}
init(
id: UUID = UUID(),
originalURL: URL,
progress: Double = 0,
task: URLSessionTask,
type: DownloadType = .movie,
metadata: AssetMetadata? = nil
) {
self.id = id
self.originalURL = originalURL
self.progress = progress
self.task = task
self.type = type
self.metadata = metadata
}
}
// MARK: - Asset Metadata
struct AssetMetadata: Codable {
let title: String
let overview: String?
let posterURL: URL?
let backdropURL: URL?
let releaseDate: String?
// Additional fields for episodes
let showTitle: String?
let season: Int?
let episode: Int?
let showPosterURL: URL? // Main show poster URL (distinct from episode-specific images)
init(
title: String,
overview: String? = nil,
posterURL: URL? = nil,
backdropURL: URL? = nil,
releaseDate: String? = nil,
showTitle: String? = nil,
season: Int? = nil,
episode: Int? = nil,
showPosterURL: URL? = nil
) {
self.title = title
self.overview = overview
self.posterURL = posterURL
self.backdropURL = backdropURL
self.releaseDate = releaseDate
self.showTitle = showTitle
self.season = season
self.episode = episode
self.showPosterURL = showPosterURL
}
}
// MARK: - New Group Model
/// Represents a group of downloads (anime/show or movies)
struct DownloadGroup: Identifiable {
var id = UUID()
let title: String // Anime title for shows
let type: DownloadType
var assets: [DownloadedAsset]
var posterURL: URL?
// Cache key for this group
private var cacheKey: String {
return "\(id)-\(title)-\(assets.count)"
}
// Static file size cache
private static var fileSizeCache: [String: Int64] = [:]
// Static last known group sizes cache for performance during active downloads
private static var lastKnownGroupSizes: [String: Int64] = [:]
var assetCount: Int {
return assets.count
}
var isShow: Bool {
return type == .episode
}
var isAnime: Bool {
return isShow
}
/// Returns the total file size of all assets in the group
var totalFileSize: Int64 {
// Check if we have a cached size for this group
let key = cacheKey
if let cachedSize = DownloadGroup.fileSizeCache[key] {
return cachedSize
}
// Check if any assets in this group are currently being downloaded
let hasActiveDownloads = assets.contains { asset in
return asset.isCurrentlyBeingDownloaded()
}
if hasActiveDownloads {
// If any downloads are active, return last known size or schedule background calculation
if let lastKnownSize = DownloadGroup.lastKnownGroupSizes[key] {
// Schedule a background update for when downloads complete
scheduleBackgroundGroupSizeCalculation(cacheKey: key)
return lastKnownSize
} else {
// Return 0 for groups with active downloads that we haven't calculated yet
return 0
}
}
// For groups without active downloads, calculate the size normally
let total = assets.reduce(0) { runningTotal, asset in
return runningTotal + asset.fileSize
}
// Store in both caches
DownloadGroup.fileSizeCache[key] = total
DownloadGroup.lastKnownGroupSizes[key] = total
return total
}
/// Schedule a background calculation for when downloads complete
private func scheduleBackgroundGroupSizeCalculation(cacheKey: String) {
DispatchQueue.global(qos: .background).async {
// Check if any assets are still being downloaded
let stillHasActiveDownloads = self.assets.contains { asset in
return asset.isCurrentlyBeingDownloaded()
}
if !stillHasActiveDownloads {
// Calculate total size
let total = self.assets.reduce(0) { runningTotal, asset in
return runningTotal + asset.calculateFileSizeInternal()
}
DispatchQueue.main.async {
// Update caches on main thread
DownloadGroup.fileSizeCache[cacheKey] = total
DownloadGroup.lastKnownGroupSizes[cacheKey] = total
// Post a notification that group size has been updated
NotificationCenter.default.post(
name: NSNotification.Name("groupSizeUpdated"),
object: nil,
userInfo: ["groupId": self.id.uuidString]
)
}
}
}
}
/// Returns the count of assets that actually exist on disk
var existingAssetsCount: Int {
return assets.filter { $0.fileExists }.count
}
/// Returns true if all assets in this group exist
var allAssetsExist: Bool {
return existingAssetsCount == assets.count
}
/// Clear the file size cache for all groups
static func clearFileSizeCache() {
fileSizeCache.removeAll()
lastKnownGroupSizes.removeAll()
}
// For anime/TV shows, organize episodes by season then episode number
func organizedEpisodes() -> [DownloadedAsset] {
guard isShow else { return assets }
return assets.sorted { $0.episodeOrderPriority < $1.episodeOrderPriority }
}
/// Refresh the calculated size for this group
mutating func refreshFileSize() {
DownloadGroup.fileSizeCache.removeValue(forKey: cacheKey)
_ = totalFileSize
}
init(title: String, type: DownloadType, assets: [DownloadedAsset], posterURL: URL? = nil) {
self.title = title
self.type = type
self.assets = assets
self.posterURL = posterURL
}
}
// MARK: - Grouping Extensions
extension Array where Element == DownloadedAsset {
/// Groups assets by anime title or movie
func groupedByTitle() -> [DownloadGroup] {
// First group by the anime title (show title for episodes, name for movies)
let groupedDict = Dictionary(grouping: self) { asset in
// For episodes, prioritize the showTitle from metadata
if asset.type == .episode, let showTitle = asset.metadata?.showTitle, !showTitle.isEmpty {
return showTitle
}
// For movies or episodes without proper metadata, use the asset name
return asset.name
}
// Convert to array of DownloadGroup objects
return groupedDict.map { (title, assets) in
// Determine group type (if any asset is an episode, it's a show)
let isShow = assets.contains { $0.type == .episode }
let type: DownloadType = isShow ? .episode : .movie
// Find poster URL - prioritize show-level posters over episode-specific ones
let posterURL: URL? = {
// First priority: Use dedicated showPosterURL if available
if let showPosterURL = assets.compactMap({ $0.metadata?.showPosterURL }).first {
return showPosterURL
}
// Second priority: For anime/TV shows, look for consistent poster URLs that appear across multiple episodes
// These are more likely to be show posters rather than episode-specific images
if isShow && assets.count > 1 {
let posterURLs = assets.compactMap { $0.metadata?.posterURL }
let urlCounts = Dictionary(grouping: posterURLs, by: { $0 })
// Find the most common poster URL (likely the show poster)
if let mostCommonPoster = urlCounts.max(by: { $0.value.count < $1.value.count })?.key {
return mostCommonPoster
}
}
// Fallback to first available poster
return assets.compactMap { $0.metadata?.posterURL }.first
}()
return DownloadGroup(
title: title,
type: type,
assets: assets,
posterURL: posterURL
)
}.sorted { $0.title < $1.title }
}
/// Sorts assets in a way suitable for flat list display
func sortedForDisplay(by sortOption: DownloadView.SortOption) -> [DownloadedAsset] {
switch sortOption {
case .newest:
return sorted { $0.downloadDate > $1.downloadDate }
case .oldest:
return sorted { $0.downloadDate < $1.downloadDate }
case .title:
return sorted { $0.name < $1.name }
}
}
}
// MARK: - Active Downloads Grouping
extension Array where Element == ActiveDownload {
/// Groups active downloads by show title
func groupedByTitle() -> [String: [ActiveDownload]] {
let grouped = Dictionary(grouping: self) { download in
return download.groupTitle
}
return grouped
}
}

View file

@ -0,0 +1,337 @@
//
// M3U8StreamExtractor.swift
// Sora
//
// Created by Francesco on 30/04/25.
//
import Foundation
enum M3U8StreamExtractorError: Error {
case networkError(Error)
case parsingError(String)
case noStreamFound
case invalidURL
var localizedDescription: String {
switch self {
case .networkError(let error):
return "Connection error: \(error.localizedDescription)"
case .parsingError(let message):
return "Stream parsing error: \(message)"
case .noStreamFound:
return "No compatible stream found in playlist"
case .invalidURL:
return "Stream URL is invalid"
}
}
}
class M3U8StreamExtractor {
// Enable verbose logging for development/testing
static var verboseLogging: Bool = true
/// Logs messages with a consistent format if verbose logging is enabled
/// - Parameters:
/// - message: The message to log
/// - function: The calling function (auto-filled)
/// - line: The line number (auto-filled)
private static func log(_ message: String, function: String = #function, line: Int = #line) {
if verboseLogging {
print("[M3U8Extractor:\(function):\(line)] \(message)")
}
}
/// Extracts the appropriate stream URL from a master M3U8 playlist based on quality preference
/// - Parameters:
/// - masterURL: The URL of the master M3U8 playlist
/// - headers: HTTP headers to use for the request
/// - preferredQuality: User's preferred quality ("Best", "High", "Medium", "Low")
/// - jsController: Optional reference to the JSController for header management
/// - completion: Completion handler with the result containing the selected stream URL and headers
static func extractStreamURL(
from masterURL: URL,
headers: [String: String],
preferredQuality: String,
jsController: JSController? = nil,
completion: @escaping (Result<(streamURL: URL, headers: [String: String]), Error>) -> Void
) {
log("Starting extraction from master playlist: \(masterURL.absoluteString)")
log("Preferred quality: \(preferredQuality)")
var requestHeaders = headers
// Use header manager if available
if let controller = jsController {
log("Using JSController for header management")
requestHeaders = controller.ensureStreamingHeaders(headers: headers, for: masterURL)
controller.logHeadersForRequest(headers: requestHeaders, url: masterURL, operation: "Extracting streams from")
} else {
log("JSController not provided, using original headers")
}
var request = URLRequest(url: masterURL)
// Add headers to the request
for (key, value) in requestHeaders {
request.addValue(value, forHTTPHeaderField: key)
}
// Add a unique request ID for tracking in logs
let requestID = UUID().uuidString.prefix(8)
log("Request ID: \(requestID)")
// Fetch the master playlist
log("Sending request to fetch master playlist")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// Handle network errors
if let error = error {
log("Network error: \(error.localizedDescription)")
completion(.failure(M3U8StreamExtractorError.networkError(error)))
return
}
// Log HTTP status for debugging
if let httpResponse = response as? HTTPURLResponse {
let statusCode = httpResponse.statusCode
log("HTTP Status: \(statusCode) for \(masterURL.absoluteString)")
if statusCode == 403 {
log("HTTP Error 403: Access Forbidden")
// Try to extract domain from URL for logging
let domain = masterURL.host ?? "unknown domain"
log("Access denied by server: \(domain)")
// Check if we have essential headers that might be missing/incorrect
let missingCriticalHeaders = ["Origin", "Referer", "User-Agent"].filter { requestHeaders[$0] == nil }
if !missingCriticalHeaders.isEmpty {
log("Missing critical headers: \(missingCriticalHeaders.joined(separator: ", "))")
}
// Since we got a 403, just fall back to the master URL directly
log("403 error - Falling back to master URL")
completion(.success((streamURL: masterURL, headers: requestHeaders)))
return
} else if statusCode >= 400 {
log("HTTP Error: \(statusCode)")
completion(.failure(M3U8StreamExtractorError.parsingError("HTTP Error: \(statusCode)")))
return
}
// Log response headers for debugging
log("Response Headers:")
for (key, value) in httpResponse.allHeaderFields {
log(" \(key): \(value)")
}
}
// Ensure we have data
guard let data = data else {
log("No data received")
completion(.failure(M3U8StreamExtractorError.parsingError("No data received")))
return
}
// Try to parse as string
guard let content = String(data: data, encoding: .utf8) else {
log("Failed to decode playlist content")
completion(.failure(M3U8StreamExtractorError.parsingError("Failed to decode playlist content")))
return
}
// Log a sample of the content (first 200 chars)
let contentPreview = String(content.prefix(200))
log("Playlist Content (preview): \(contentPreview)...")
// Count the number of lines in the content
let lineCount = content.components(separatedBy: .newlines).count
log("Playlist has \(lineCount) lines")
// Parse the M3U8 content to extract available streams
log("Parsing M3U8 content")
let streams = parseM3U8Content(content: content, baseURL: masterURL)
// Log the extracted streams
log("Extracted \(streams.count) streams from M3U8 playlist")
for (index, stream) in streams.enumerated() {
log("Stream #\(index + 1): \(stream.name), \(stream.resolution.width)x\(stream.resolution.height), URL: \(stream.url)")
}
if streams.isEmpty {
log("No streams found in playlist")
}
// Select the appropriate stream based on quality preference
log("Selecting stream with quality preference: \(preferredQuality)")
if let selectedURL = selectStream(streams: streams, preferredQuality: preferredQuality),
let url = URL(string: selectedURL) {
log("Selected stream URL: \(url.absoluteString)")
var finalHeaders = requestHeaders
// Use header manager to optimize headers for the selected stream if available
if let controller = jsController {
log("Optimizing headers for selected stream")
finalHeaders = controller.ensureStreamingHeaders(headers: requestHeaders, for: url)
controller.logHeadersForRequest(headers: finalHeaders, url: url, operation: "Selected stream")
}
// Return the selected stream URL along with the headers
log("Extraction successful")
completion(.success((streamURL: url, headers: finalHeaders)))
} else if !streams.isEmpty, let fallbackStream = streams.first, let url = URL(string: fallbackStream.url) {
// Fallback to first stream if preferred quality not found
log("Preferred quality '\(preferredQuality)' not found, falling back to: \(fallbackStream.name)")
var finalHeaders = requestHeaders
// Use header manager for fallback stream
if let controller = jsController {
log("Optimizing headers for fallback stream")
finalHeaders = controller.ensureStreamingHeaders(headers: requestHeaders, for: url)
controller.logHeadersForRequest(headers: finalHeaders, url: url, operation: "Fallback stream")
}
log("Fallback extraction successful")
completion(.success((streamURL: url, headers: finalHeaders)))
} else if streams.isEmpty {
// If the playlist doesn't contain any streams, use the master URL as fallback
log("No streams found in the playlist, using master URL as fallback")
log("Using master URL as fallback")
completion(.success((streamURL: masterURL, headers: requestHeaders)))
} else {
log("No suitable stream found")
completion(.failure(M3U8StreamExtractorError.noStreamFound))
}
}
task.resume()
log("Request started")
}
/// Parses M3U8 content to extract available streams
/// - Parameters:
/// - content: The M3U8 playlist content as string
/// - baseURL: The base URL of the playlist for resolving relative URLs
/// - Returns: Array of extracted streams with name, URL, and resolution
private static func parseM3U8Content(
content: String,
baseURL: URL
) -> [(name: String, url: String, resolution: (width: Int, height: Int))] {
let lines = content.components(separatedBy: .newlines)
var streams: [(name: String, url: String, resolution: (width: Int, height: Int))] = []
for (index, line) in lines.enumerated() {
// Look for the stream info tag
if line.contains("#EXT-X-STREAM-INF"), index + 1 < lines.count {
// Extract resolution information
if let resolutionRange = line.range(of: "RESOLUTION="),
let resolutionEndRange = line[resolutionRange.upperBound...].range(of: ",")
?? line[resolutionRange.upperBound...].range(of: "\n") {
let resolutionPart = String(line[resolutionRange.upperBound..<resolutionEndRange.lowerBound])
let dimensions = resolutionPart.components(separatedBy: "x")
if dimensions.count == 2,
let width = Int(dimensions[0]),
let height = Int(dimensions[1]) {
// Get the URL from the next line
let nextLine = lines[index + 1].trimmingCharacters(in: .whitespacesAndNewlines)
// Generate a quality name
let qualityName = getQualityName(for: height)
// Handle relative URLs
var streamURL = nextLine
if !nextLine.hasPrefix("http") && nextLine.contains(".m3u8") {
let baseURLString = baseURL.deletingLastPathComponent().absoluteString
streamURL = URL(string: nextLine, relativeTo: baseURL)?.absoluteString
?? baseURLString + "/" + nextLine
}
// Add the stream to our list
streams.append((
name: qualityName,
url: streamURL,
resolution: (width: width, height: height)
))
}
}
}
}
return streams
}
/// Selects a stream based on the user's quality preference
/// - Parameters:
/// - streams: Array of available streams
/// - preferredQuality: User's preferred quality
/// - Returns: URL of the selected stream, or nil if no suitable stream was found
private static func selectStream(
streams: [(name: String, url: String, resolution: (width: Int, height: Int))],
preferredQuality: String
) -> String? {
guard !streams.isEmpty else { return nil }
// Sort streams by resolution (height) in descending order
let sortedStreams = streams.sorted { $0.resolution.height > $1.resolution.height }
switch preferredQuality {
case "Best":
// Return the highest quality stream
return sortedStreams.first?.url
case "High":
// Return a high quality stream (720p or higher, but not the highest)
let highStreams = sortedStreams.filter { $0.resolution.height >= 720 }
if highStreams.count > 1 {
return highStreams[1].url // Second highest if available
} else if !highStreams.isEmpty {
return highStreams[0].url // Highest if only one high quality stream
} else if !sortedStreams.isEmpty {
return sortedStreams.first?.url // Fallback to highest available
}
case "Medium":
// Return a medium quality stream (between 480p and 720p)
let mediumStreams = sortedStreams.filter {
$0.resolution.height >= 480 && $0.resolution.height < 720
}
if !mediumStreams.isEmpty {
return mediumStreams.first?.url
} else if sortedStreams.count > 1 {
let medianIndex = sortedStreams.count / 2
return sortedStreams[medianIndex].url // Return median quality as fallback
} else if !sortedStreams.isEmpty {
return sortedStreams.first?.url // Fallback to highest available
}
case "Low":
// Return the lowest quality stream
return sortedStreams.last?.url
default:
// Default to best quality
return sortedStreams.first?.url
}
return nil
}
/// Generates a quality name based on resolution height
/// - Parameter height: The vertical resolution (height) of the stream
/// - Returns: A human-readable quality name
private static func getQualityName(for height: Int) -> String {
switch height {
case 1080...: return "\(height)p (FHD)"
case 720..<1080: return "\(height)p (HD)"
case 480..<720: return "\(height)p (SD)"
default: return "\(height)p"
}
}
}

View file

@ -0,0 +1,82 @@
//
// DropManager.swift
// Sora
//
// Created by Francesco on 25/01/25.
//
import Drops
import UIKit
import SwiftUI
class DropManager {
static let shared = DropManager()
private var notificationQueue: [(title: String, subtitle: String, duration: TimeInterval, icon: UIImage?)] = []
private var isProcessingQueue = false
private init() {}
func showDrop(title: String, subtitle: String, duration: TimeInterval, icon: UIImage?) {
notificationQueue.append((title: title, subtitle: subtitle, duration: duration, icon: icon))
if !isProcessingQueue {
processQueue()
}
}
private func processQueue() {
guard !notificationQueue.isEmpty else {
isProcessingQueue = false
return
}
isProcessingQueue = true
let notification = notificationQueue.removeFirst()
let drop = Drop(
title: notification.title,
subtitle: notification.subtitle,
icon: notification.icon,
position: .top,
duration: .seconds(notification.duration)
)
Drops.show(drop)
DispatchQueue.main.asyncAfter(deadline: .now() + notification.duration) { [weak self] in
self?.processQueue()
}
}
func success(_ message: String, duration: TimeInterval = 2.0) {
let icon = UIImage(systemName: "checkmark.circle.fill")?.withTintColor(.green, renderingMode: .alwaysOriginal)
showDrop(title: "Success", subtitle: message, duration: duration, icon: icon)
}
func error(_ message: String, duration: TimeInterval = 2.0) {
let icon = UIImage(systemName: "xmark.circle.fill")?.withTintColor(.red, renderingMode: .alwaysOriginal)
showDrop(title: "Error", subtitle: message, duration: duration, icon: icon)
}
func info(_ message: String, duration: TimeInterval = 2.0) {
let accentColor = UIColor(Color.accentColor)
let icon = UIImage(systemName: "info.circle.fill")?.withTintColor(accentColor, renderingMode: .alwaysOriginal)
showDrop(title: "Info", subtitle: message, duration: duration, icon: icon)
}
func downloadStarted(episodeNumber: Int) {
let willStartImmediately = JSController.shared.willDownloadStartImmediately()
let message = willStartImmediately
? "Episode \(episodeNumber) is now downloading"
: "Episode \(episodeNumber) added to download queue"
showDrop(
title: willStartImmediately ? "Download Started" : "Download Queued",
subtitle: message,
duration: 1.5,
icon: UIImage(systemName: willStartImmediately ? "arrow.down.circle.fill" : "clock.arrow.circlepath")
)
}
}

View file

@ -0,0 +1,343 @@
//
// JSContext+Extensions.swift
// Sora
//
// Created by Hamzo on 19/03/25.
//
import SoraCore
import JavaScriptCore
extension JSContext {
func setupConsoleLogging() {
let consoleObject = JSValue(newObjectIn: self)
let consoleLogFunction: @convention(block) (String) -> Void = { message in
Logger.shared.log(message, type: "Debug")
}
consoleObject?.setObject(consoleLogFunction, forKeyedSubscript: "log" as NSString)
let consoleErrorFunction: @convention(block) (String) -> Void = { message in
Logger.shared.log(message, type: "Error")
}
consoleObject?.setObject(consoleErrorFunction, forKeyedSubscript: "error" as NSString)
self.setObject(consoleObject, forKeyedSubscript: "console" as NSString)
let logFunction: @convention(block) (String) -> Void = { message in
Logger.shared.log("JavaScript log: \(message)", type: "Debug")
}
self.setObject(logFunction, forKeyedSubscript: "log" as NSString)
}
func setupNativeFetch() {
let fetchNativeFunction: @convention(block) (String, [String: String]?, JSValue, JSValue) -> Void = { urlString, headers, resolve, reject in
guard let url = URL(string: urlString) else {
Logger.shared.log("Invalid URL", type: "Error")
reject.call(withArguments: ["Invalid URL"])
return
}
var request = URLRequest(url: url)
if let headers = headers {
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
}
let task = URLSession.custom.dataTask(with: request) { data, _, error in
if let error = error {
Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)", type: "Error")
reject.call(withArguments: [error.localizedDescription])
return
}
guard let data = data else {
Logger.shared.log("No data in response", type: "Error")
reject.call(withArguments: ["No data"])
return
}
if let text = String(data: data, encoding: .utf8) {
resolve.call(withArguments: [text])
} else {
Logger.shared.log("Unable to decode data to text", type: "Error")
reject.call(withArguments: ["Unable to decode data"])
}
}
task.resume()
}
self.setObject(fetchNativeFunction, forKeyedSubscript: "fetchNative" as NSString)
let fetchDefinition = """
function fetch(url, headers) {
return new Promise(function(resolve, reject) {
fetchNative(url, headers, resolve, reject);
});
}
"""
self.evaluateScript(fetchDefinition)
}
func setupFetchV2() {
let fetchV2NativeFunction: @convention(block) (String, Any?, String?, String?, ObjCBool, String?, JSValue, JSValue) -> Void = { urlString, headersAny, method, body, redirect, encoding, resolve, reject in
guard let url = URL(string: urlString) else {
Logger.shared.log("Invalid URL", type: "Error")
DispatchQueue.main.async {
reject.call(withArguments: ["Invalid URL"])
}
return
}
var headers: [String: String]? = nil
if let headersAny = headersAny {
if headersAny is NSNull {
headers = nil
} else if let headersDict = headersAny as? [String: Any] {
var safeHeaders: [String: String] = [:]
for (key, value) in headersDict {
let stringValue: String
if let str = value as? String {
stringValue = str
} else if let num = value as? NSNumber {
stringValue = num.stringValue
} else if value is NSNull {
continue
} else {
stringValue = String(describing: value)
}
safeHeaders[key] = stringValue
}
headers = safeHeaders.isEmpty ? nil : safeHeaders
} else if let headersDict = headersAny as? [AnyHashable: Any] {
var safeHeaders: [String: String] = [:]
for (key, value) in headersDict {
let stringKey = String(describing: key)
let stringValue: String
if let str = value as? String {
stringValue = str
} else if let num = value as? NSNumber {
stringValue = num.stringValue
} else if value is NSNull {
continue
} else {
stringValue = String(describing: value)
}
safeHeaders[stringKey] = stringValue
}
headers = safeHeaders.isEmpty ? nil : safeHeaders
} else {
Logger.shared.log("Headers argument is not a dictionary, type: \(type(of: headersAny))", type: "Warning")
headers = nil
}
}
let httpMethod = method ?? "GET"
var request = URLRequest(url: url)
request.httpMethod = httpMethod
Logger.shared.log("FetchV2 Request: URL=\(url), Method=\(httpMethod), Body=\(body ?? "nil"), Encoding=\(encoding ?? "utf-8")", type: "Debug")
func getEncoding(from encodingString: String?) -> String.Encoding {
guard let encodingString = encodingString?.lowercased() else {
return .utf8
}
switch encodingString {
case "utf-8", "utf8":
return .utf8
case "windows-1251", "cp1251":
return .windowsCP1251
case "windows-1252", "cp1252":
return .windowsCP1252
case "iso-8859-1", "latin1":
return .isoLatin1
case "ascii":
return .ascii
case "utf-16", "utf16":
return .utf16
default:
Logger.shared.log("Unknown encoding '\(encodingString)', defaulting to UTF-8", type: "Warning")
return .utf8
}
}
let textEncoding = getEncoding(from: encoding)
let bodyIsEmpty = body == nil || (body)?.isEmpty == true || body == "null" || body == "undefined"
if httpMethod == "GET" && !bodyIsEmpty {
Logger.shared.log("GET request must not have a body", type: "Error")
DispatchQueue.main.async {
reject.call(withArguments: ["GET request must not have a body"])
}
return
}
if httpMethod != "GET" && !bodyIsEmpty {
if let bodyString = body {
request.httpBody = bodyString.data(using: .utf8)
} else {
let bodyString = String(describing: body!)
request.httpBody = bodyString.data(using: .utf8)
}
}
if let headers = headers {
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
}
Logger.shared.log("Redirect value is \(redirect.boolValue)", type: "Debug")
let session = URLSession.fetchData(allowRedirects: redirect.boolValue)
let task = session.downloadTask(with: request) { tempFileURL, response, error in
defer { session.finishTasksAndInvalidate() }
let callReject: (String) -> Void = { message in
DispatchQueue.main.async {
reject.call(withArguments: [message])
}
}
let callResolve: ([String: Any]) -> Void = { dict in
DispatchQueue.main.async {
resolve.call(withArguments: [dict])
}
}
if let error = error {
Logger.shared.log("Network error in fetchV2NativeFunction: \(error.localizedDescription)", type: "Error")
callReject(error.localizedDescription)
return
}
guard let tempFileURL = tempFileURL else {
Logger.shared.log("No data in response", type: "Error")
callReject("No data")
return
}
var safeHeaders: [String: String] = [:]
if let httpResponse = response as? HTTPURLResponse {
for (key, value) in httpResponse.allHeaderFields {
if let keyString = key as? String {
let valueString: String
if let str = value as? String {
valueString = str
} else {
valueString = String(describing: value)
}
safeHeaders[keyString] = valueString
}
}
}
var responseDict: [String: Any] = [
"status": (response as? HTTPURLResponse)?.statusCode ?? 0,
"headers": safeHeaders,
"body": ""
]
do {
let data = try Data(contentsOf: tempFileURL)
if data.count > 10_000_000 {
Logger.shared.log("Response exceeds maximum size", type: "Error")
callReject("Response exceeds maximum size")
return
}
if let text = String(data: data, encoding: textEncoding) {
responseDict["body"] = text
callResolve(responseDict)
} else {
Logger.shared.log("Unable to decode data with encoding \(encoding ?? "utf-8"), trying UTF-8 fallback", type: "Warning")
if let fallbackText = String(data: data, encoding: .utf8) {
responseDict["body"] = fallbackText
callResolve(responseDict)
} else {
Logger.shared.log("Unable to decode data to text with any encoding", type: "Error")
callResolve(responseDict)
}
}
} catch {
Logger.shared.log("Error reading downloaded file: \(error.localizedDescription)", type: "Error")
callReject("Error reading downloaded file")
}
}
task.resume()
}
self.setObject(fetchV2NativeFunction, forKeyedSubscript: "fetchV2Native" as NSString)
let fetchv2Definition = """
function fetchv2(url, headers = {}, method = "GET", body = null, redirect = true, encoding) {
var processedBody = null;
if(method != "GET") {
processedBody = (body && (typeof body === 'object')) ? JSON.stringify(body) : (body || null)
}
var finalEncoding = encoding || "utf-8";
// Ensure headers is an object and not null/undefined
var processedHeaders = {};
if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
processedHeaders = headers;
}
return new Promise(function(resolve, reject) {
fetchV2Native(url, processedHeaders, method, processedBody, redirect, finalEncoding, function(rawText) {
const responseObj = {
headers: rawText.headers,
status: rawText.status,
_data: rawText.body,
text: function() {
return Promise.resolve(this._data);
},
json: function() {
try {
return Promise.resolve(JSON.parse(this._data));
} catch (e) {
return Promise.reject("JSON parse error: " + e.message);
}
}
};
resolve(responseObj);
}, reject);
});
}
"""
self.evaluateScript(fetchv2Definition)
}
func setupBase64Functions() {
let btoaFunction: @convention(block) (String) -> String? = { data in
guard let data = data.data(using: .utf8) else {
Logger.shared.log("btoa: Failed to encode input as UTF-8", type: "Error")
return nil
}
return data.base64EncodedString()
}
let atobFunction: @convention(block) (String) -> String? = { base64String in
guard let data = Data(base64Encoded: base64String) else {
Logger.shared.log("atob: Invalid base64 input", type: "Error")
return nil
}
return String(data: data, encoding: .utf8)
}
self.setObject(btoaFunction, forKeyedSubscript: "btoa" as NSString)
self.setObject(atobFunction, forKeyedSubscript: "atob" as NSString)
}
func setupJavaScriptEnvironment() {
setupWeirdCode()
setupConsoleLogging()
setupNativeFetch()
setupFetchV2()
setupBase64Functions()
}
}

View file

@ -0,0 +1,26 @@
//
// Notification+Name.swift
// Sulfur
//
// Created by Francesco on 17/04/25.
//
import Foundation
import UIKit
extension Notification.Name {
static let iCloudSyncDidComplete = Notification.Name("iCloudSyncDidComplete")
static let iCloudSyncDidFail = Notification.Name("iCloudSyncDidFail")
static let ContinueWatchingDidUpdate = Notification.Name("ContinueWatchingDidUpdate")
static let DownloadManagerStatusUpdate = Notification.Name("DownloadManagerStatusUpdate")
static let modulesSyncDidComplete = Notification.Name("modulesSyncDidComplete")
static let moduleRemoved = Notification.Name("moduleRemoved")
static let didReceiveNewModule = Notification.Name("didReceiveNewModule")
static let didUpdateModules = Notification.Name("didUpdateModules")
static let didUpdateDownloads = Notification.Name("didUpdateDownloads")
static let didUpdateBookmarks = Notification.Name("didUpdateBookmarks")
static let hideTabBar = Notification.Name("hideTabBar")
static let showTabBar = Notification.Name("showTabBar")
static let searchQueryChanged = Notification.Name("searchQueryChanged")
static let tabBarSearchQueryUpdated = Notification.Name("tabBarSearchQueryUpdated")
}

View file

@ -0,0 +1,24 @@
//
// String.swift
// Sora
//
// Created by Francesco on 14/02/25.
//
import Foundation
extension String {
var strippedHTML: String {
guard let data = self.data(using: .utf8) else { return self }
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
]
let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil)
return attributedString?.string ?? self
}
var trimmed: String {
return self.trimmingCharacters(in: .whitespacesAndNewlines)
}
}

View file

@ -0,0 +1,226 @@
//
// UIDevice+Model.swift
// Sora
//
// Created by Hamzo on 02.03.25.
//
import UIKit
public extension UIDevice {
static let modelName: String = {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
func mapToDevice(identifier: String) -> String {
#if os(iOS)
switch identifier {
case "iPod5,1":
return "iPod touch (5th generation)"
case "iPod7,1":
return "iPod touch (6th generation)"
case "iPod9,1":
return "iPod touch (7th generation)"
case "iPhone3,1", "iPhone3,2", "iPhone3,3":
return "iPhone 4"
case "iPhone4,1":
return "iPhone 4s"
case "iPhone5,1", "iPhone5,2":
return "iPhone 5"
case "iPhone5,3", "iPhone5,4":
return "iPhone 5c"
case "iPhone6,1", "iPhone6,2":
return "iPhone 5s"
case "iPhone7,2":
return "iPhone 6"
case "iPhone7,1":
return "iPhone 6 Plus"
case "iPhone8,1":
return "iPhone 6s"
case "iPhone8,2":
return "iPhone 6s Plus"
case "iPhone9,1", "iPhone9,3":
return "iPhone 7"
case "iPhone9,2", "iPhone9,4":
return "iPhone 7 Plus"
case "iPhone10,1", "iPhone10,4":
return "iPhone 8"
case "iPhone10,2", "iPhone10,5":
return "iPhone 8 Plus"
case "iPhone10,3", "iPhone10,6":
return "iPhone X"
case "iPhone11,2":
return "iPhone XS"
case "iPhone11,4", "iPhone11,6":
return "iPhone XS Max"
case "iPhone11,8":
return "iPhone XR"
case "iPhone12,1":
return "iPhone 11"
case "iPhone12,3":
return "iPhone 11 Pro"
case "iPhone12,5":
return "iPhone 11 Pro Max"
case "iPhone13,1":
return "iPhone 12 mini"
case "iPhone13,2":
return "iPhone 12"
case "iPhone13,3":
return "iPhone 12 Pro"
case "iPhone13,4":
return "iPhone 12 Pro Max"
case "iPhone14,4":
return "iPhone 13 mini"
case "iPhone14,5":
return "iPhone 13"
case "iPhone14,2":
return "iPhone 13 Pro"
case "iPhone14,3":
return "iPhone 13 Pro Max"
case "iPhone14,7":
return "iPhone 14"
case "iPhone14,8":
return "iPhone 14 Plus"
case "iPhone15,2":
return "iPhone 14 Pro"
case "iPhone15,3":
return "iPhone 14 Pro Max"
case "iPhone15,4":
return "iPhone 15"
case "iPhone15,5":
return "iPhone 15 Plus"
case "iPhone16,1":
return "iPhone 15 Pro"
case "iPhone16,2":
return "iPhone 15 Pro Max"
case "iPhone17,3":
return "iPhone 16"
case "iPhone17,4":
return "iPhone 16 Plus"
case "iPhone17,1":
return "iPhone 16 Pro"
case "iPhone17,2":
return "iPhone 16 Pro Max"
case "iPhone8,4":
return "iPhone SE"
case "iPhone12,8":
return "iPhone SE (2nd generation)"
case "iPhone14,6":
return "iPhone SE (3rd generation)"
case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":
return "iPad 2"
case "iPad3,1", "iPad3,2", "iPad3,3":
return "iPad (3rd generation)"
case "iPad3,4", "iPad3,5", "iPad3,6":
return "iPad (4th generation)"
case "iPad6,11", "iPad6,12":
return "iPad (5th generation)"
case "iPad7,5", "iPad7,6":
return "iPad (6th generation)"
case "iPad7,11", "iPad7,12":
return "iPad (7th generation)"
case "iPad11,6", "iPad11,7":
return "iPad (8th generation)"
case "iPad12,1", "iPad12,2":
return "iPad (9th generation)"
case "iPad13,18", "iPad13,19":
return "iPad (10th generation)"
case "iPad4,1", "iPad4,2", "iPad4,3":
return "iPad Air"
case "iPad5,3", "iPad5,4":
return "iPad Air 2"
case "iPad11,3", "iPad11,4":
return "iPad Air (3rd generation)"
case "iPad13,1", "iPad13,2":
return "iPad Air (4th generation)"
case "iPad13,16", "iPad13,17":
return "iPad Air (5th generation)"
case "iPad14,8", "iPad14,9":
return "iPad Air (11-inch) (M2)"
case "iPad14,10", "iPad14,11":
return "iPad Air (13-inch) (M2)"
case "iPad2,5", "iPad2,6", "iPad2,7":
return "iPad mini"
case "iPad4,4", "iPad4,5", "iPad4,6":
return "iPad mini 2"
case "iPad4,7", "iPad4,8", "iPad4,9":
return "iPad mini 3"
case "iPad5,1", "iPad5,2":
return "iPad mini 4"
case "iPad11,1", "iPad11,2":
return "iPad mini (5th generation)"
case "iPad14,1", "iPad14,2":
return "iPad mini (6th generation)"
case "iPad16,1", "iPad16,2":
return "iPad mini (A17 Pro)"
case "iPad6,3", "iPad6,4":
return "iPad Pro (9.7-inch)"
case "iPad7,3", "iPad7,4":
return "iPad Pro (10.5-inch)"
case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4":
return "iPad Pro (11-inch) (1st generation)"
case "iPad8,9", "iPad8,10":
return "iPad Pro (11-inch) (2nd generation)"
case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7":
return "iPad Pro (11-inch) (3rd generation)"
case "iPad14,3", "iPad14,4":
return "iPad Pro (11-inch) (4th generation)"
case "iPad16,3", "iPad16,4":
return "iPad Pro (11-inch) (M4)"
case "iPad6,7", "iPad6,8":
return "iPad Pro (12.9-inch) (1st generation)"
case "iPad7,1", "iPad7,2":
return "iPad Pro (12.9-inch) (2nd generation)"
case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8":
return "iPad Pro (12.9-inch) (3rd generation)"
case "iPad8,11", "iPad8,12":
return "iPad Pro (12.9-inch) (4th generation)"
case "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11":
return "iPad Pro (12.9-inch) (5th generation)"
case "iPad14,5", "iPad14,6":
return "iPad Pro (12.9-inch) (6th generation)"
case "iPad16,5", "iPad16,6":
return "iPad Pro (13-inch) (M4)"
case "AppleTV5,3":
return "Apple TV"
case "AppleTV6,2":
return "Apple TV 4K"
case "AudioAccessory1,1":
return "HomePod"
case "AudioAccessory5,1":
return "HomePod mini"
case "i386", "x86_64", "arm64":
return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))"
default:
return identifier
}
#elseif os(tvOS)
switch identifier {
case "AppleTV5,3":
return "Apple TV 4"
case "AppleTV6,2", "AppleTV11,1", "AppleTV14,1":
return "Apple TV 4K"
case "i386", "x86_64":
return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))"
default:
return identifier
}
#elseif os(visionOS)
switch identifier {
case "RealityDevice14,1":
return "Apple Vision Pro"
default:
return identifier
}
#endif
}
return mapToDevice(identifier: identifier)
}()
}

View file

@ -0,0 +1,25 @@
//
// URL.swift
// Sulfur
//
// Created by Francesco on 23/03/25.
//
import Foundation
extension URL {
var queryParameters: [String: String]? {
guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true),
let queryItems = components.queryItems else { return nil }
var params = [String: String]()
for queryItem in queryItems {
params[queryItem.name] = queryItem.value
}
return params
}
static func isValidHLSURL(string: String) -> Bool {
guard let url = URL(string: string), url.pathExtension == "m3u8" else { return false }
return true
}
}

View file

@ -0,0 +1,121 @@
//
// URLSession.swift
// Sora-JS
//
// Created by Francesco on 05/01/25.
//
import Network
import Foundation
class FetchDelegate: NSObject, URLSessionTaskDelegate {
private let allowRedirects: Bool
init(allowRedirects: Bool) {
self.allowRedirects = allowRedirects
}
deinit { Logger.shared.log("FetchDelegate deallocated", type: "Debug")
}
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
if(allowRedirects) {
completionHandler(request)
} else {
completionHandler(nil)
}
}
}
extension URLSession {
static let userAgents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.2903.86",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.2849.80",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.2 Safari/605.1.15",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15_1_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.1 Safari/605.1.15",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15.1; rv:132.0) Gecko/20100101 Firefox/132.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15.0; rv:131.0) Gecko/20100101 Firefox/131.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64; rv:132.0) Gecko/20100101 Firefox/132.0",
"Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0",
"Mozilla/5.0 (Linux; Android 15; SM-S928B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.135 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 15; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.135 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.2 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPad; CPU OS 18_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.2 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Android 15; Mobile; rv:132.0) Gecko/132.0 Firefox/132.0",
"Mozilla/5.0 (Android 14; Mobile; rv:131.0) Gecko/131.0 Firefox/131.0"
]
static var randomUserAgent: String = {
userAgents.randomElement() ?? userAgents[0]
}()
static let custom: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = ["User-Agent": randomUserAgent]
return URLSession(configuration: configuration)
}()
static func fetchData(allowRedirects:Bool) -> URLSession
{
let delegate = FetchDelegate(allowRedirects:allowRedirects)
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = ["User-Agent": randomUserAgent]
return URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
}
}
enum NetworkType {
case wifi
case cellular
case unknown
}
class NetworkMonitor: ObservableObject {
static let shared = NetworkMonitor()
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "NetworkMonitor")
@Published var currentNetworkType: NetworkType = .unknown
@Published var isConnected: Bool = false
private init() {
startMonitoring()
}
private func startMonitoring() {
monitor.pathUpdateHandler = { [weak self] path in
DispatchQueue.main.async {
self?.isConnected = path.status == .satisfied
self?.currentNetworkType = self?.getNetworkType(from: path) ?? .unknown
}
}
monitor.start(queue: queue)
}
private func getNetworkType(from path: NWPath) -> NetworkType {
if path.usesInterfaceType(.wifi) {
return .wifi
} else if path.usesInterfaceType(.cellular) {
return .cellular
} else {
return .unknown
}
}
static func getCurrentNetworkType() -> NetworkType {
return shared.currentNetworkType
}
deinit {
monitor.cancel()
}
}

View file

@ -0,0 +1,105 @@
//
// UserDefaults.swift
// Sulfur
//
// Created by Francesco on 23/05/25.
//
import UIKit
enum VideoQualityPreference: String, CaseIterable {
case best = "Best"
case p1080 = "1080p"
case p720 = "720p"
case p420 = "420p"
case p360 = "360p"
case worst = "Worst"
static let wifiDefaultKey = "videoQualityWiFi"
static let cellularDefaultKey = "videoQualityCellular"
static let defaultWiFiPreference: VideoQualityPreference = .best
static let defaultCellularPreference: VideoQualityPreference = .p720
static let qualityPriority: [VideoQualityPreference] = [.best, .p1080, .p720, .p420, .p360, .worst]
static func findClosestQuality(preferred: VideoQualityPreference, availableQualities: [(String, String)]) -> (String, String)? {
for (name, url) in availableQualities {
if isQualityMatch(preferred: preferred, qualityName: name) {
return (name, url)
}
}
let preferredIndex = qualityPriority.firstIndex(of: preferred) ?? qualityPriority.count
for i in 0..<qualityPriority.count {
let candidate = qualityPriority[i]
for (name, url) in availableQualities {
if isQualityMatch(preferred: candidate, qualityName: name) {
return (name, url)
}
}
}
return availableQualities.first
}
private static func isQualityMatch(preferred: VideoQualityPreference, qualityName: String) -> Bool {
let lowercaseName = qualityName.lowercased()
switch preferred {
case .best:
return lowercaseName.contains("best") || lowercaseName.contains("highest") || lowercaseName.contains("max")
case .p1080:
return lowercaseName.contains("1080") || lowercaseName.contains("1920")
case .p720:
return lowercaseName.contains("720") || lowercaseName.contains("1280")
case .p420:
return lowercaseName.contains("420") || lowercaseName.contains("480")
case .p360:
return lowercaseName.contains("360") || lowercaseName.contains("640")
case .worst:
return lowercaseName.contains("worst") || lowercaseName.contains("lowest") || lowercaseName.contains("min")
}
}
}
extension UserDefaults {
func color(forKey key: String) -> UIColor? {
guard let colorData = data(forKey: key) else { return nil }
do {
return try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: colorData)
} catch {
return nil
}
}
func set(_ color: UIColor?, forKey key: String) {
guard let color = color else {
removeObject(forKey: key)
return
}
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: false)
set(data, forKey: key)
} catch {
Logger.shared.log("Error archiving color: \(error)", type: "Error")
}
}
static func getVideoQualityPreference() -> VideoQualityPreference {
let networkType = NetworkMonitor.getCurrentNetworkType()
switch networkType {
case .wifi:
let rawValue = UserDefaults.standard.string(forKey: VideoQualityPreference.wifiDefaultKey)
return VideoQualityPreference(rawValue: rawValue ?? "") ?? VideoQualityPreference.defaultWiFiPreference
case .cellular:
let rawValue = UserDefaults.standard.string(forKey: VideoQualityPreference.cellularDefaultKey)
return VideoQualityPreference(rawValue: rawValue ?? "") ?? VideoQualityPreference.defaultCellularPreference
case .unknown:
return .p720
}
}
}

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