Merge branch 'video' into carplay
This commit is contained in:
commit
759597dfb2
11 files changed
+6251
-102
No files matched your search
@@ -268,7 +268,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 = 5;
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 2P4CP47H76;
|
DEVELOPMENT_TEAM = 2P4CP47H76;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -300,7 +300,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 = 5;
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 2P4CP47H76;
|
DEVELOPMENT_TEAM = 2P4CP47H76;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
<key>Pocket Poster.xcscheme_^#shared#^_</key>
|
<key>Pocket Poster.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>1</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "POEditor@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -8,44 +8,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import ZIPFoundation
|
import ZIPFoundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreTransferable
|
|
||||||
|
|
||||||
struct Movie: Transferable {
|
|
||||||
let url: URL
|
|
||||||
|
|
||||||
static var transferRepresentation: some TransferRepresentation {
|
|
||||||
FileRepresentation(contentType: .movie) { movie in
|
|
||||||
SentTransferredFile(movie.url)
|
|
||||||
} importing: { received in
|
|
||||||
let videoFolder = SymHandler.getDocumentsDirectory().appendingPathComponent("Videos", conformingTo: .directory)
|
|
||||||
if !FileManager.default.fileExists(atPath: videoFolder.path()) {
|
|
||||||
try? FileManager.default.createDirectory(at: videoFolder, withIntermediateDirectories: true)
|
|
||||||
}
|
|
||||||
let copy = videoFolder.appending(path: "\(UUID()).mp4")
|
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: copy.path()) {
|
|
||||||
try FileManager.default.removeItem(at: copy)
|
|
||||||
}
|
|
||||||
|
|
||||||
try FileManager.default.copyItem(at: received.file, to: copy)
|
|
||||||
return Self.init(url: copy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum LoadState {
|
|
||||||
case unknown, loading, loaded(Movie), failed
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LoadInfo: Identifiable, Equatable {
|
|
||||||
static func == (lhs: LoadInfo, rhs: LoadInfo) -> Bool {
|
|
||||||
lhs.id == rhs.id
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = UUID()
|
|
||||||
var autoReverses: Bool = false
|
|
||||||
var loadState: LoadState
|
|
||||||
}
|
|
||||||
|
|
||||||
class PosterBoardManager: ObservableObject {
|
class PosterBoardManager: ObservableObject {
|
||||||
static let ShortcutURL = "https://www.icloud.com/shortcuts/a28d2c02ca11453cb5b8f91c12cfa692"
|
static let ShortcutURL = "https://www.icloud.com/shortcuts/a28d2c02ca11453cb5b8f91c12cfa692"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import CoreTransferable
|
||||||
|
|
||||||
extension CGImage {
|
extension CGImage {
|
||||||
var png: Data? {
|
var png: Data? {
|
||||||
@@ -23,7 +24,55 @@ extension CGImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Movie: Transferable {
|
||||||
|
let url: URL
|
||||||
|
|
||||||
|
static var transferRepresentation: some TransferRepresentation {
|
||||||
|
FileRepresentation(contentType: .movie) { movie in
|
||||||
|
SentTransferredFile(movie.url)
|
||||||
|
} importing: { received in
|
||||||
|
let videoFolder = SymHandler.getDocumentsDirectory().appendingPathComponent("Videos", conformingTo: .directory)
|
||||||
|
if !FileManager.default.fileExists(atPath: videoFolder.path()) {
|
||||||
|
try? FileManager.default.createDirectory(at: videoFolder, withIntermediateDirectories: true)
|
||||||
|
}
|
||||||
|
let copy = videoFolder.appending(path: "\(UUID()).mp4")
|
||||||
|
|
||||||
|
if FileManager.default.fileExists(atPath: copy.path()) {
|
||||||
|
try FileManager.default.removeItem(at: copy)
|
||||||
|
}
|
||||||
|
|
||||||
|
try FileManager.default.copyItem(at: received.file, to: copy)
|
||||||
|
return Self.init(url: copy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LoadState {
|
||||||
|
case unknown, loading, loaded(Movie), failed
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoadInfo: Identifiable, Equatable {
|
||||||
|
static func == (lhs: LoadInfo, rhs: LoadInfo) -> Bool {
|
||||||
|
lhs.id == rhs.id
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = UUID()
|
||||||
|
var autoReverses: Bool = false
|
||||||
|
var loadState: LoadState
|
||||||
|
}
|
||||||
|
|
||||||
class VideoHandler {
|
class VideoHandler {
|
||||||
|
static let MaxDurationSecs = 12.0
|
||||||
|
|
||||||
|
static func isVideoTooLong(at url: URL) -> Bool {
|
||||||
|
let asset = AVAsset(url: url)
|
||||||
|
|
||||||
|
let duration = asset.duration
|
||||||
|
let durationTime = CMTimeGetSeconds(duration)
|
||||||
|
|
||||||
|
return durationTime > MaxDurationSecs
|
||||||
|
}
|
||||||
|
|
||||||
static func createCaml(from url: URL, autoReverses: Bool) throws -> URL {
|
static func createCaml(from url: URL, autoReverses: Bool) throws -> URL {
|
||||||
// copy the resources
|
// copy the resources
|
||||||
let descrURL = SymHandler.getDocumentsDirectory().appendingPathComponent(UUID().uuidString, conformingTo: .directory)
|
let descrURL = SymHandler.getDocumentsDirectory().appendingPathComponent(UUID().uuidString, conformingTo: .directory)
|
||||||
|
|||||||
+5947
-37
File diff suppressed because it is too large.
Load diff
@@ -5,6 +5,36 @@
|
|||||||
"comment" : "Bundle name",
|
"comment" : "Bundle name",
|
||||||
"extractionState" : "extracted_with_value",
|
"extractionState" : "extracted_with_value",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
"ar" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ar-AE" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster Ar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ar-SA" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cs" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "new",
|
"state" : "new",
|
||||||
@@ -17,12 +47,60 @@
|
|||||||
"value" : "Pocket Poster"
|
"value" : "Pocket Poster"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es-MX" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
"fr" : {
|
"fr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Pocket Poster"
|
"value" : "Pocket Poster"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"th" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Pocket Poster"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vi" : {
|
"vi" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
@@ -33,18 +111,90 @@
|
|||||||
},
|
},
|
||||||
"Nugget Wallpaper" : {
|
"Nugget Wallpaper" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
"ar" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "خلفيات Nugget"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ar-AE" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "خلفيات Nugget"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cs" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Nugget Tapeta"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Nugget Hintergrundbilder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"es" : {
|
"es" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Nugget Wallpaper"
|
"value" : "Nugget Wallpaper"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es-MX" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Nugget Wallpaper"
|
||||||
|
}
|
||||||
|
},
|
||||||
"fr" : {
|
"fr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Fond d'écran Nugget"
|
"value" : "Fond d'écran Nugget"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Nugget Háttérkép"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Nugget Wallpaper"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Nugget Achtergrond"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Tapeta Nugget"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Обои Nugget"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"th" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "วอลเปเปอร์ Nugget"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Nugget Duvar Kağıdı"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vi" : {
|
"vi" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
|||||||
@@ -11,14 +11,33 @@ import AVKit
|
|||||||
|
|
||||||
struct VideoView: View {
|
struct VideoView: View {
|
||||||
@ObservedObject var pbManager = PosterBoardManager.shared
|
@ObservedObject var pbManager = PosterBoardManager.shared
|
||||||
|
@AppStorage("ignoreDurationLimit") var ignoreDurationLimit: Bool = false
|
||||||
|
|
||||||
@State var selectedVideo: PhotosPickerItem?
|
@State var selectedVideo: PhotosPickerItem?
|
||||||
|
@State private var currentPage: Int = 0
|
||||||
|
|
||||||
|
@State private var currentDate = Date()
|
||||||
|
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() // update every second
|
||||||
|
private var dateFormatter: DateFormatter {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateFormat = "EEEE, MMMM d"
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
|
private var timeFormatter: DateFormatter {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
if Locale.current.hourCycle == .zeroToEleven || Locale.current.hourCycle == .oneToTwelve {
|
||||||
|
formatter.dateFormat = "h:mm"
|
||||||
|
} else {
|
||||||
|
formatter.dateFormat = "HH:mm"
|
||||||
|
}
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
GeometryReader { geom in
|
GeometryReader { geom in
|
||||||
TabView {
|
TabView(selection: $currentPage) {
|
||||||
ForEach(pbManager.videos) { vid in
|
ForEach(Array(pbManager.videos.enumerated()), id: \.offset) { idx, vid in
|
||||||
ZStack {
|
ZStack {
|
||||||
switch vid.loadState {
|
switch vid.loadState {
|
||||||
case .unknown:
|
case .unknown:
|
||||||
@@ -30,6 +49,17 @@ struct VideoView: View {
|
|||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(width: geom.size.width * 0.7, height: geom.size.height * 0.8)
|
.frame(width: geom.size.width * 0.7, height: geom.size.height * 0.8)
|
||||||
.clipped()
|
.clipped()
|
||||||
|
VStack {
|
||||||
|
Text(dateFormatter.string(from: currentDate))
|
||||||
|
.shadow(radius: 10)
|
||||||
|
|
||||||
|
Text(timeFormatter.string(from: currentDate))
|
||||||
|
.font(.system(size: 75, weight: .semibold, design: .rounded))
|
||||||
|
.shadow(radius: 10)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.top, 45)
|
||||||
|
.foregroundStyle(.white)
|
||||||
case .failed:
|
case .failed:
|
||||||
Text("Failed")
|
Text("Failed")
|
||||||
}
|
}
|
||||||
@@ -38,6 +68,7 @@ struct VideoView: View {
|
|||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
.background(Color.black.opacity(0.6))
|
.background(Color.black.opacity(0.6))
|
||||||
.cornerRadius(24)
|
.cornerRadius(24)
|
||||||
|
.tag(idx)
|
||||||
.overlay {
|
.overlay {
|
||||||
RoundedRectangle(cornerRadius: 24)
|
RoundedRectangle(cornerRadius: 24)
|
||||||
.stroke(.gray, lineWidth: 4)
|
.stroke(.gray, lineWidth: 4)
|
||||||
@@ -50,22 +81,30 @@ struct VideoView: View {
|
|||||||
.overlay(
|
.overlay(
|
||||||
// Loops Button
|
// Loops Button
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if let index = pbManager.videos.firstIndex(of: vid) {
|
pbManager.videos[idx].autoReverses = !vid.autoReverses
|
||||||
pbManager.videos[index].autoReverses = !vid.autoReverses
|
|
||||||
}
|
|
||||||
}) {
|
}) {
|
||||||
Image(systemName: "arrow.left.arrow.right.circle")
|
Image(systemName: "arrow.left.arrow.right.circle")
|
||||||
.font(.title)
|
.font(.title)
|
||||||
.foregroundStyle(.black)
|
.foregroundStyle(Color(uiColor: .label))
|
||||||
.background {
|
.background {
|
||||||
Circle()
|
if vid.autoReverses {
|
||||||
.foregroundStyle(vid.autoReverses ? .blue : .white)
|
Circle()
|
||||||
|
.foregroundStyle(.blue.opacity(0.8))
|
||||||
|
.shadow(radius: 5)
|
||||||
|
} else {
|
||||||
|
Circle()
|
||||||
|
.foregroundStyle(.regularMaterial)
|
||||||
|
.shadow(radius: 5)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.offset(x: 8, y: -8),
|
}.offset(x: 8, y: -8),
|
||||||
alignment: .topTrailing
|
alignment: .topTrailing
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onDelete(perform: removeVideo)
|
.onDelete(perform: removeVideo)
|
||||||
|
.onReceive(timer) { input in
|
||||||
|
currentDate = input
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Select Photo Option
|
// MARK: Select Photo Option
|
||||||
if selectedVideo == nil && pbManager.videos.count < 5 {
|
if selectedVideo == nil && pbManager.videos.count < 5 {
|
||||||
@@ -89,6 +128,7 @@ struct VideoView: View {
|
|||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
.background(Color.black.opacity(0.6))
|
.background(Color.black.opacity(0.6))
|
||||||
.cornerRadius(24)
|
.cornerRadius(24)
|
||||||
|
.tag(pbManager.videos.count)
|
||||||
.overlay {
|
.overlay {
|
||||||
RoundedRectangle(cornerRadius: 24)
|
RoundedRectangle(cornerRadius: 24)
|
||||||
.stroke(.gray, lineWidth: 4)
|
.stroke(.gray, lineWidth: 4)
|
||||||
@@ -103,7 +143,13 @@ struct VideoView: View {
|
|||||||
do {
|
do {
|
||||||
if let movie = try await selectedVideo?.loadTransferable(type: Movie.self) {
|
if let movie = try await selectedVideo?.loadTransferable(type: Movie.self) {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
pbManager.videos[id].loadState = .loaded(movie)
|
if !ignoreDurationLimit && VideoHandler.isVideoTooLong(at: movie.url) {
|
||||||
|
pbManager.videos.remove(at: id)
|
||||||
|
UIApplication.shared.alert(title: NSLocalizedString("Failed to Import Video", comment: ""), body: String(format: NSLocalizedString("The video you imported is too long! Your video must be %@ seconds or less.", comment: ""), "\(Int(VideoHandler.MaxDurationSecs))"))
|
||||||
|
} else {
|
||||||
|
pbManager.videos[id].loadState = .loaded(movie)
|
||||||
|
currentPage = id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
@@ -118,7 +164,7 @@ struct VideoView: View {
|
|||||||
selectedVideo = nil
|
selectedVideo = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tabViewStyle(.page)
|
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +195,8 @@ struct DeleteButton: View {
|
|||||||
.font(.title)
|
.font(.title)
|
||||||
.background {
|
.background {
|
||||||
Circle()
|
Circle()
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.regularMaterial)
|
||||||
|
.shadow(radius: 5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.offset(x: -8, y: -8)
|
.offset(x: -8, y: -8)
|
||||||
|
|||||||
@@ -29,30 +29,30 @@ struct OnBoardingPage: Identifiable {
|
|||||||
|
|
||||||
let onBoardingCards: [OnBoardingPage] = [
|
let onBoardingCards: [OnBoardingPage] = [
|
||||||
.init(
|
.init(
|
||||||
title: "Welcome to Pocket Poster!",
|
title: NSLocalizedString("Welcome to Pocket Poster!", comment: ""),
|
||||||
description: "Here is a tutorial to help you get started with the app.",
|
description: NSLocalizedString("Here is a tutorial to help you get started with the app.", comment: ""),
|
||||||
image: "Logo"
|
image: "Logo"
|
||||||
),
|
),
|
||||||
.init(
|
.init(
|
||||||
title: "Install the Fallback Shortcut (Optional)",
|
title: NSLocalizedString("Install the Fallback Shortcut (Optional)", comment: ""),
|
||||||
description: "To apply, PosterBoard will need to open.\n\nYou can install an optional shortcut if the original method fails.",
|
description: NSLocalizedString("To apply, PosterBoard will need to open.", comment: "") + "\n\n" + NSLocalizedString("You can install an optional shortcut if the original method fails.", comment: ""),
|
||||||
image: "Shortcuts",
|
image: "Shortcuts",
|
||||||
link: URL(string: PosterBoardManager.ShortcutURL),
|
link: URL(string: PosterBoardManager.ShortcutURL),
|
||||||
linkName: "Get Shortcut"
|
linkName: NSLocalizedString("Get Shortcut", comment: "")
|
||||||
),
|
),
|
||||||
.init(
|
.init(
|
||||||
title: "Install Nugget",
|
title: NSLocalizedString("Install Nugget", comment: ""),
|
||||||
description: "To get the app bundle id, Nugget is required.\n\nOn your computer, download Nugget from the GitHub.",
|
description: NSLocalizedString("To get the app bundle id, Nugget is required.", comment: "") + "\n\n" + NSLocalizedString("On your computer, download Nugget from the GitHub.", comment: ""),
|
||||||
image: "Nugget",
|
image: "Nugget",
|
||||||
link: URL(string: "https://github.com/leminlimez/Nugget/releases/latest"),
|
link: URL(string: "https://github.com/leminlimez/Nugget/releases/latest"),
|
||||||
linkName: "Open GitHub"
|
linkName: NSLocalizedString("Open GitHub", comment: "")
|
||||||
),
|
),
|
||||||
.init(
|
.init(
|
||||||
title: "Enjoy!",
|
title: NSLocalizedString("Enjoy!", comment: ""),
|
||||||
description: "You can find wallpapers on the official Cowabun.ga website.",
|
description: NSLocalizedString("You can find wallpapers on the official Cowabun.ga website.", comment: ""),
|
||||||
image: "Cowabunga",
|
image: "Cowabunga",
|
||||||
link: URL(string: PosterBoardManager.WallpapersURL),
|
link: URL(string: PosterBoardManager.WallpapersURL),
|
||||||
linkName: "Find Wallpapers"
|
linkName: NSLocalizedString("Find Wallpapers", comment: "")
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import SwiftUI
|
|||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
// Prefs
|
// Prefs
|
||||||
@AppStorage("pbHash") var pbHash: String = ""
|
@AppStorage("pbHash") var pbHash: String = "" // PosterBoard hash
|
||||||
@AppStorage("cpHash") var cpHash: String = ""
|
@AppStorage("cpHash") var cpHash: String = "" // CarPlay hash
|
||||||
|
@AppStorage("ignoreDurationLimit") var ignoreDurationLimit: Bool = false
|
||||||
|
|
||||||
@State var checkingForHash: Bool = false
|
@State var checkingForHash: Bool = false
|
||||||
@State var hashCheckTask: Task<Void, any Error>? = nil
|
@State var hashCheckTask: Task<Void, any Error>? = nil
|
||||||
@@ -49,6 +50,14 @@ struct SettingsView: View {
|
|||||||
Label("App Hash", systemImage: "lock.app.dashed")
|
Label("App Hash", systemImage: "lock.app.dashed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Toggle(isOn: $ignoreDurationLimit, label: {
|
||||||
|
Label("Disable Video Duration Limit", systemImage: "ruler")
|
||||||
|
})
|
||||||
|
} header: {
|
||||||
|
Label("Preferences", systemImage: "gear")
|
||||||
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
||||||
@@ -128,6 +137,7 @@ struct SettingsView: View {
|
|||||||
LinkCell(imageName: "Nathan", url: "https://github.com/verygenericname", title: "Nathan", contribution: NSLocalizedString("Exploit", comment: ""), circle: true)
|
LinkCell(imageName: "Nathan", url: "https://github.com/verygenericname", title: "Nathan", contribution: NSLocalizedString("Exploit", comment: ""), circle: true)
|
||||||
LinkCell(imageName: "duy", url: "https://github.com/khanhduytran0", title: "DuyKhanhTran", contribution: NSLocalizedString("Exploit", comment: ""), circle: true)
|
LinkCell(imageName: "duy", url: "https://github.com/khanhduytran0", title: "DuyKhanhTran", contribution: NSLocalizedString("Exploit", comment: ""), circle: true)
|
||||||
LinkCell(imageName: "sky", url: "https://bsky.app/profile/did:plc:xykfeb7ieeo335g3aly6vev4", title: "dootskyre", contribution: NSLocalizedString("Fallback Shortcut Creator", comment: ""), circle: true)
|
LinkCell(imageName: "sky", url: "https://bsky.app/profile/did:plc:xykfeb7ieeo335g3aly6vev4", title: "dootskyre", contribution: NSLocalizedString("Fallback Shortcut Creator", comment: ""), circle: true)
|
||||||
|
LinkCell(imageName: "POEditor", url: "https://poeditor.com/join/project/MPZOsunwVj", title: NSLocalizedString("Community Translators", comment: ""), contribution: "POEditor")
|
||||||
} header: {
|
} header: {
|
||||||
Label("Credits", systemImage: "wrench.and.screwdriver")
|
Label("Credits", systemImage: "wrench.and.screwdriver")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user