mirror of
https://github.com/yattee/yattee.git
synced 2025-12-13 19:48:14 +00:00
Compare commits
9 Commits
v1.2-beta.
...
v1.2-beta.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b090fcd51 | ||
|
|
12eb4401b5 | ||
|
|
170f2ee94e | ||
|
|
fe56739211 | ||
|
|
759a942426 | ||
|
|
8d9bbf647a | ||
|
|
eeb7b1f151 | ||
|
|
62bff9283c | ||
|
|
3624c9619a |
@@ -29,6 +29,12 @@ final class CommentsModel: ObservableObject {
|
|||||||
!Defaults[.commentsInstanceID].isNil && !Defaults[.commentsInstanceID]!.isEmpty
|
!Defaults[.commentsInstanceID].isNil && !Defaults[.commentsInstanceID]!.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
|
static var placement: CommentsPlacement {
|
||||||
|
Defaults[.commentsPlacement]
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
var nextPageAvailable: Bool {
|
var nextPageAvailable: Bool {
|
||||||
!(nextPage?.isEmpty ?? true)
|
!(nextPage?.isEmpty ?? true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func playNow(_ video: Video, at time: TimeInterval? = nil) {
|
func playNow(_ video: Video, at time: TimeInterval? = nil) {
|
||||||
|
player.replaceCurrentItem(with: nil)
|
||||||
addCurrentItemToHistory()
|
addCurrentItemToHistory()
|
||||||
|
|
||||||
enqueueVideo(video, prepending: true) { _, item in
|
enqueueVideo(video, prepending: true) { _, item in
|
||||||
@@ -92,6 +93,7 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func advanceToItem(_ newItem: PlayerQueueItem, at time: TimeInterval? = nil) {
|
func advanceToItem(_ newItem: PlayerQueueItem, at time: TimeInterval? = nil) {
|
||||||
|
player.replaceCurrentItem(with: nil)
|
||||||
addCurrentItemToHistory()
|
addCurrentItemToHistory()
|
||||||
|
|
||||||
remove(newItem)
|
remove(newItem)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ final class SearchModel: ObservableObject {
|
|||||||
@Published var query = SearchQuery()
|
@Published var query = SearchQuery()
|
||||||
@Published var queryText = ""
|
@Published var queryText = ""
|
||||||
@Published var querySuggestions = Store<[String]>()
|
@Published var querySuggestions = Store<[String]>()
|
||||||
|
@Published var suggestionsText = ""
|
||||||
|
|
||||||
@Published var fieldIsFocused = false
|
@Published var fieldIsFocused = false
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ final class SearchModel: ObservableObject {
|
|||||||
|
|
||||||
suggestionsDebounceTimer?.invalidate()
|
suggestionsDebounceTimer?.invalidate()
|
||||||
|
|
||||||
suggestionsDebounceTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
|
suggestionsDebounceTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { _ in
|
||||||
let resource = self.accounts.api.searchSuggestions(query: query)
|
let resource = self.accounts.api.searchSuggestions(query: query)
|
||||||
|
|
||||||
resource.addObserver(self.querySuggestions)
|
resource.addObserver(self.querySuggestions)
|
||||||
@@ -99,9 +100,11 @@ final class SearchModel: ObservableObject {
|
|||||||
if let suggestions: [String] = response.typedContent() {
|
if let suggestions: [String] = response.typedContent() {
|
||||||
self.querySuggestions = Store<[String]>(suggestions)
|
self.querySuggestions = Store<[String]>(suggestions)
|
||||||
}
|
}
|
||||||
|
self.suggestionsText = query
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.querySuggestions = Store<[String]>(self.querySuggestions.collection)
|
self.querySuggestions = Store<[String]>(self.querySuggestions.collection)
|
||||||
|
self.suggestionsText = query
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
README.md
109
README.md
@@ -2,16 +2,14 @@
|
|||||||
|
|
||||||
Video player for [Invidious](https://github.com/iv-org/invidious) and [Piped](https://github.com/TeamPiped/Piped) instances built for iOS, tvOS and macOS.
|
Video player for [Invidious](https://github.com/iv-org/invidious) and [Piped](https://github.com/TeamPiped/Piped) instances built for iOS, tvOS and macOS.
|
||||||
|
|
||||||
|
|
||||||
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||||
[](https://github.com/yattee/yattee/issues)
|
[](https://github.com/yattee/yattee/issues)
|
||||||
[](https://github.com/yattee/yattee/pulls)
|
[](https://github.com/yattee/yattee/pulls)
|
||||||
[](https://matrix.to/#/#yattee:matrix.org)
|
[](https://matrix.to/#/#yattee:matrix.org)
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Major Features
|
||||||
* Native user interface built with [SwiftUI](https://developer.apple.com/xcode/swiftui/)
|
* Native user interface built with [SwiftUI](https://developer.apple.com/xcode/swiftui/)
|
||||||
* Multiple instances and accounts, fast switching
|
* Multiple instances and accounts, fast switching
|
||||||
* [SponsorBlock](https://sponsor.ajay.app/), configurable categories to skip
|
* [SponsorBlock](https://sponsor.ajay.app/), configurable categories to skip
|
||||||
@@ -37,104 +35,15 @@ Video player for [Invidious](https://github.com/iv-org/invidious) and [Piped](ht
|
|||||||
| Subtitles | 🔴 | ✅ |
|
| Subtitles | 🔴 | ✅ |
|
||||||
| Comments | 🔴 | ✅ |
|
| Comments | 🔴 | ✅ |
|
||||||
|
|
||||||
## Installation
|
You can browse and use accounts from one app and play videos with another (for example: use Invidious account for subscriptions and use Piped as playback source). Comments can be displayed from Piped even when Invidious is used for browsing/playing.
|
||||||
### Requirements
|
|
||||||
System requirements:
|
|
||||||
* iOS 14 (or newer)
|
|
||||||
* tvOS 15 (or newer)
|
|
||||||
* macOS Big Sur (or newer)
|
|
||||||
|
|
||||||
### How to install?
|
## Documentation
|
||||||
|
* [Installation Instructions](https://github.com/yattee/yattee/wiki/Installation-instructions)
|
||||||
#### macOS
|
* [Integrations](https://github.com/yattee/yattee/wiki/Integrations)
|
||||||
Download and run latest version from the [Releases](https://github.com/yattee/yattee/releases) page.
|
* [Screenshots Gallery](https://github.com/yattee/yattee/wiki/Screenshots-Gallery)
|
||||||
|
* [Tips](https://github.com/yattee/yattee/wiki/Tips)
|
||||||
#### iOS/tvOS: [AltStore](https://altstore.io/) (free)
|
* [FAQ](https://github.com/yattee/yattee/wiki)
|
||||||
You can sideload IPA files downloaded from the [Releases](https://github.com/yattee/yattee/releases) page to your iOS or tvOS device - check [AltStore FAQ](https://altstore.io/faq/) for more information.
|
* [Donations](https://github.com/yattee/yattee/wiki/Donations)
|
||||||
|
|
||||||
If you have to access to the beta AltStore version (v1.5, for Patreons only), you can add the following repository in `Browse > Sources` screen:
|
|
||||||
|
|
||||||
`https://alt.yattee.stream`
|
|
||||||
|
|
||||||
#### iOS/tvOS: Signing IPA files online (paid)
|
|
||||||
[UDID Registrations](https://www.udidregistrations.com/) provides services to sign IPA files for your devices. Refer to: ***Break free from the App Store*** section of the website for more information.
|
|
||||||
|
|
||||||
#### iOS/tvOS: Manual installation
|
|
||||||
Download sources and compile them on a Mac using Xcode, install to your devices. Please note that if you are not registered in Apple Developer Program you will need to reinstall every 7 days.
|
|
||||||
|
|
||||||
## Integrations
|
|
||||||
### macOS
|
|
||||||
With [Finicky](https://github.com/johnste/finicky) you can configure your system to open all the video links in the app. Example configuration:
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
match: [
|
|
||||||
finicky.matchDomains(/(.*\.)?youtube.com/),
|
|
||||||
finicky.matchDomains(/(.*\.)?youtu.be/)
|
|
||||||
],
|
|
||||||
browser: "/Applications/Yattee.app"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
### iOS
|
|
||||||
| Player | Search | Playlists |
|
|
||||||
| - | - | - |
|
|
||||||
| [](https://r.yattee.stream/screenshots/iOS/player.png) | [](https://r.yattee.stream/screenshots/iOS/search-suggestions.png) | [](https://r.yattee.stream/screenshots/iOS/playlists.png) |
|
|
||||||
### iPadOS
|
|
||||||
| Settings | Player | Subscriptions |
|
|
||||||
| - | - | - |
|
|
||||||
| [](https://r.yattee.stream/screenshots/iPadOS/settings.png) | [](https://r.yattee.stream/screenshots/iPadOS/player.png) | [](https://r.yattee.stream/screenshots/iPadOS/subscriptions.png) |
|
|
||||||
### tvOS
|
|
||||||
| Player | Popular | Search | Now Playing | Settings |
|
|
||||||
| - | - | - | - | - |
|
|
||||||
| [](https://r.yattee.stream/screenshots/tvOS/player.png) | [](https://r.yattee.stream/screenshots/tvOS/popular.png) | [](https://r.yattee.stream/screenshots/tvOS/search.png) | [](https://r.yattee.stream/screenshots/tvOS/now-playing.png) | [](https://r.yattee.stream/screenshots/tvOS/settings.png) |
|
|
||||||
### macOS
|
|
||||||
| Player | Channel | Search | Settings |
|
|
||||||
| - | - | - | - |
|
|
||||||
| [](https://r.yattee.stream/screenshots/macOS/player.png) | [](https://r.yattee.stream/screenshots/macOS/channel.png) | [](https://r.yattee.stream/screenshots/macOS/search.png) | [](https://r.yattee.stream/screenshots/macOS/settings.png) |
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
### Settings
|
|
||||||
* [tvOS] To open settings, press Play/Pause button while hovering over navigation menu or video
|
|
||||||
### Navigation
|
|
||||||
* Use videos context menus to add to queue, open or subscribe channel and add to playlist
|
|
||||||
* [tvOS] Pressing buttons in the app trigger switch to next available option (for example: next account in Settings). If you want to access list of all options, press and hold to open the context menu.
|
|
||||||
* [iOS] Swipe the player/title bar: up to open fullscreen details view, bottom to close fullscreen details or hide player
|
|
||||||
### Favorites
|
|
||||||
* Add more sections using ❤️ button in views channels, playlists, searches, subscriptions and popular
|
|
||||||
* [iOS/macOS] Reorganize with dragging and dropping
|
|
||||||
* [iOS/macOS] Remove section with right click/press and hold on section name
|
|
||||||
* [tvOS] Reorganize and remove from `Settings > Edit Favorites...`
|
|
||||||
### Keyboard shortcuts
|
|
||||||
* `Command+1` - Favorites
|
|
||||||
* `Command+2` - Subscriptions
|
|
||||||
* `Command+3` - Popular
|
|
||||||
* `Command+4` - Trending
|
|
||||||
* `Command+F` - Search
|
|
||||||
* `Command+P` - Play/Pause
|
|
||||||
* `Command+S` - Play Next
|
|
||||||
* `Command+O` - Toggle Player
|
|
||||||
|
|
||||||
|
|
||||||
## Donations
|
|
||||||
|
|
||||||
You can support development of this app with
|
|
||||||
[Patreon](https://www.patreon.com/arekf) or cryptocurrencies:
|
|
||||||
|
|
||||||
**Monero (XMR)**
|
|
||||||
```
|
|
||||||
48zfKjLmnXs21PinU2ucMiUPwhiKt5d7WJKiy3ACVS28BKqSn52c1TX8L337oESHJ5TZCyGkozjfWZG11h6C46mN9n4NPrD
|
|
||||||
```
|
|
||||||
**Bitcoin (BTC)**
|
|
||||||
```
|
|
||||||
bc1qe24zz5a5hm0trc7glwckz93py274eycxzju3mv
|
|
||||||
```
|
|
||||||
**Ethereum (ETH)**
|
|
||||||
```
|
|
||||||
0xa2f81A58Ec5E550132F03615c8d91954A4E37423
|
|
||||||
```
|
|
||||||
|
|
||||||
Donations will be used to cover development program access and domain renewal costs.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
If you're interestred in contributing, you can browse the [issues](https://github.com/yattee/yattee/issues) list or create a new one to discuss your feature idea. Every contribution is very welcome.
|
If you're interestred in contributing, you can browse the [issues](https://github.com/yattee/yattee/issues) list or create a new one to discuss your feature idea. Every contribution is very welcome.
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ extension Defaults.Keys {
|
|||||||
static let playerInstanceID = Key<Instance.ID?>("playerInstance")
|
static let playerInstanceID = Key<Instance.ID?>("playerInstance")
|
||||||
static let showKeywords = Key<Bool>("showKeywords", default: false)
|
static let showKeywords = Key<Bool>("showKeywords", default: false)
|
||||||
static let commentsInstanceID = Key<Instance.ID?>("commentsInstance", default: kavinPipedInstanceID)
|
static let commentsInstanceID = Key<Instance.ID?>("commentsInstance", default: kavinPipedInstanceID)
|
||||||
|
#if !os(tvOS)
|
||||||
|
static let commentsPlacement = Key<CommentsPlacement>("commentsPlacement", default: .separate)
|
||||||
|
#endif
|
||||||
|
|
||||||
static let recentlyOpened = Key<[RecentItem]>("recentlyOpened", default: [])
|
static let recentlyOpened = Key<[RecentItem]>("recentlyOpened", default: [])
|
||||||
|
|
||||||
@@ -129,3 +132,9 @@ enum VisibleSection: String, CaseIterable, Comparable, Defaults.Serializable {
|
|||||||
lhs.sortOrder < rhs.sortOrder
|
lhs.sortOrder < rhs.sortOrder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
|
enum CommentsPlacement: String, CaseIterable, Defaults.Serializable {
|
||||||
|
case info, separate
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ struct FavoriteItemView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: accounts.current) { _ in
|
||||||
|
resource?.addObserver(store)
|
||||||
|
resource?.load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isVisible: Bool {
|
private var isVisible: Bool {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ struct AppTabNavigation: View {
|
|||||||
NavigationView {
|
NavigationView {
|
||||||
ChannelPlaylistView(playlist: playlist)
|
ChannelPlaylistView(playlist: playlist)
|
||||||
.environment(\.inNavigationView, true)
|
.environment(\.inNavigationView, true)
|
||||||
|
.environmentObject(subscriptions)
|
||||||
.background(playerNavigationLink)
|
.background(playerNavigationLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,11 +45,16 @@ final class PlayerViewController: UIViewController {
|
|||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
playerModel.avPlayerViewController = playerViewController
|
playerModel.avPlayerViewController = playerViewController
|
||||||
playerViewController.customInfoViewControllers = [
|
var infoViewControllers = [UIHostingController<AnyView>]()
|
||||||
infoViewController([.comments], title: "Comments"),
|
if CommentsModel.enabled {
|
||||||
|
infoViewControllers.append(infoViewController([.comments], title: "Comments"))
|
||||||
|
}
|
||||||
|
infoViewControllers.append(contentsOf: [
|
||||||
infoViewController([.related], title: "Related"),
|
infoViewController([.related], title: "Related"),
|
||||||
infoViewController([.playingNext, .playedPreviously], title: "Playing Next")
|
infoViewController([.playingNext, .playedPreviously], title: "Playing Next")
|
||||||
]
|
])
|
||||||
|
|
||||||
|
playerViewController.customInfoViewControllers = infoViewControllers
|
||||||
#else
|
#else
|
||||||
embedViewController()
|
embedViewController()
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ struct VideoDetails: View {
|
|||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
if CommentsModel.enabled {
|
if CommentsModel.enabled, CommentsModel.placement == .separate {
|
||||||
pagePicker
|
pagePicker
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
@@ -245,13 +245,13 @@ struct VideoDetails: View {
|
|||||||
Picker("Page", selection: $currentPage) {
|
Picker("Page", selection: $currentPage) {
|
||||||
if !video.isNil {
|
if !video.isNil {
|
||||||
Text("Info").tag(Page.info)
|
Text("Info").tag(Page.info)
|
||||||
if !sidebarQueue {
|
if CommentsModel.enabled, CommentsModel.placement == .separate {
|
||||||
Text("Related").tag(Page.related)
|
|
||||||
}
|
|
||||||
if CommentsModel.enabled {
|
|
||||||
Text("Comments")
|
Text("Comments")
|
||||||
.tag(Page.comments)
|
.tag(Page.comments)
|
||||||
}
|
}
|
||||||
|
if !sidebarQueue {
|
||||||
|
Text("Related").tag(Page.related)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !sidebarQueue {
|
if !sidebarQueue {
|
||||||
Text("Queue").tag(Page.queue)
|
Text("Queue").tag(Page.queue)
|
||||||
@@ -365,6 +365,7 @@ struct VideoDetails: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var detailsPage: some View {
|
var detailsPage: some View {
|
||||||
|
Group {
|
||||||
Group {
|
Group {
|
||||||
if let video = player.currentItem?.video {
|
if let video = player.currentItem?.video {
|
||||||
Group {
|
Group {
|
||||||
@@ -393,7 +394,6 @@ struct VideoDetails: View {
|
|||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.font(.system(size: 14))
|
.font(.system(size: 14))
|
||||||
.lineSpacing(3)
|
.lineSpacing(3)
|
||||||
.padding(.bottom, 4)
|
|
||||||
} else {
|
} else {
|
||||||
Text("No description")
|
Text("No description")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
@@ -423,8 +423,24 @@ struct VideoDetails: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !video.isNil, CommentsModel.placement == .info {
|
||||||
|
Divider()
|
||||||
|
#if os(macOS)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
#else
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
Group {
|
||||||
|
if !video.isNil, CommentsModel.placement == .info {
|
||||||
|
CommentsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func videoDetail(label: String, value: String, symbol: String) -> some View {
|
func videoDetail(label: String, value: String, symbol: String) -> some View {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ struct SearchTextField: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
.frame(maxWidth: 190)
|
||||||
.textFieldStyle(.plain)
|
.textFieldStyle(.plain)
|
||||||
#else
|
#else
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
@@ -52,6 +53,11 @@ struct SearchTextField: View {
|
|||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
#endif
|
#endif
|
||||||
clearButton
|
clearButton
|
||||||
|
} else {
|
||||||
|
#if os(macOS)
|
||||||
|
clearButton
|
||||||
|
.opacity(0)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ struct SearchSuggestions: View {
|
|||||||
|
|
||||||
recents.addQuery(state.queryText)
|
recents.addQuery(state.queryText)
|
||||||
} label: {
|
} label: {
|
||||||
HStack(spacing: 5) {
|
HStack {
|
||||||
Label(state.queryText, systemImage: "magnifyingglass")
|
Image(systemName: "magnifyingglass")
|
||||||
|
Text(state.queryText)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,8 +28,11 @@ struct SearchSuggestions: View {
|
|||||||
Button {
|
Button {
|
||||||
state.queryText = suggestion
|
state.queryText = suggestion
|
||||||
} label: {
|
} label: {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "arrow.up.left.circle")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Label(state.queryText, systemImage: "arrow.up.left.circle")
|
Text(state.suggestionsText)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.layoutPriority(2)
|
.layoutPriority(2)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
@@ -38,6 +42,7 @@ struct SearchSuggestions: View {
|
|||||||
.layoutPriority(1)
|
.layoutPriority(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.onHover(perform: onHover(_:))
|
.onHover(perform: onHover(_:))
|
||||||
#endif
|
#endif
|
||||||
@@ -55,7 +60,7 @@ struct SearchSuggestions: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func querySuffix(_ suggestion: String) -> String {
|
private func querySuffix(_ suggestion: String) -> String {
|
||||||
suggestion.replacingFirstOccurrence(of: state.queryText.lowercased(), with: "")
|
suggestion.replacingFirstOccurrence(of: state.suggestionsText.lowercased(), with: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
|||||||
@@ -6,9 +6,17 @@ struct ServicesSettings: View {
|
|||||||
@Default(.sponsorBlockCategories) private var sponsorBlockCategories
|
@Default(.sponsorBlockCategories) private var sponsorBlockCategories
|
||||||
@Default(.commentsInstanceID) private var commentsInstanceID
|
@Default(.commentsInstanceID) private var commentsInstanceID
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
|
@Default(.commentsPlacement) private var commentsPlacement
|
||||||
|
#endif
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Section(header: SettingsHeader(text: "Comments")) {
|
Section(header: SettingsHeader(text: "Comments")) {
|
||||||
commentsInstancePicker
|
commentsInstancePicker
|
||||||
|
#if !os(tvOS)
|
||||||
|
commentsPlacementPicker
|
||||||
|
.disabled(!CommentsModel.enabled)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: SettingsHeader(text: "SponsorBlock API")) {
|
Section(header: SettingsHeader(text: "SponsorBlock API")) {
|
||||||
@@ -58,7 +66,7 @@ struct ServicesSettings: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var commentsInstancePicker: some View {
|
private var commentsInstancePicker: some View {
|
||||||
Picker("Comments", selection: $commentsInstanceID) {
|
Picker("Source", selection: $commentsInstanceID) {
|
||||||
Text("Disabled").tag(Optional(""))
|
Text("Disabled").tag(Optional(""))
|
||||||
|
|
||||||
ForEach(InstancesModel.all.filter { $0.app.supportsComments }) { instance in
|
ForEach(InstancesModel.all.filter { $0.app.supportsComments }) { instance in
|
||||||
@@ -73,6 +81,19 @@ struct ServicesSettings: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
|
private var commentsPlacementPicker: some View {
|
||||||
|
Picker("Placement", selection: $commentsPlacement) {
|
||||||
|
Text("Below video description").tag(CommentsPlacement.info)
|
||||||
|
Text("Separate tab").tag(CommentsPlacement.separate)
|
||||||
|
}
|
||||||
|
.labelsHidden()
|
||||||
|
#if os(iOS)
|
||||||
|
.pickerStyle(.automatic)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
func toggleCategory(_ category: String, value: Bool) {
|
func toggleCategory(_ category: String, value: Bool) {
|
||||||
if let index = sponsorBlockCategories.firstIndex(where: { $0 == category }), !value {
|
if let index = sponsorBlockCategories.firstIndex(where: { $0 == category }), !value {
|
||||||
sponsorBlockCategories.remove(at: index)
|
sponsorBlockCategories.remove(at: index)
|
||||||
|
|||||||
@@ -2277,7 +2277,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -2311,7 +2311,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -2343,7 +2343,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||||
@@ -2375,7 +2375,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||||
@@ -2538,7 +2538,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -2569,7 +2569,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -2604,7 +2604,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -2637,7 +2637,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -2768,7 +2768,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -2800,7 +2800,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|||||||
Reference in New Issue
Block a user