Build 8 (1.0.0)

This commit is contained in:
Aiden Vigue 2021-05-22 11:14:07 -04:00
parent b49f796a72
commit ca61e0dd4c
12 changed files with 258 additions and 128 deletions

View File

@ -188,6 +188,7 @@ struct ContentView: View {
@State private var librariesShowRecentlyAdded: [String] = [];
@State private var libraryPrefillID: String = "";
@State private var showSettingsPopover: Bool = false;
@State private var viewDidLoad: Bool = false;
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
@ -198,6 +199,10 @@ struct ContentView: View {
}
func startup() {
if(_viewDidLoad.wrappedValue) {
return
}
_viewDidLoad.wrappedValue = true;
SentrySDK.start { options in
options.dsn = "https://75ac77d6af4d406eb989f3d8ef0f119f@o513670.ingest.sentry.io/5778242"
options.debug = false // Enabled debug when first installing is always helpful
@ -274,6 +279,18 @@ struct ContentView: View {
_librariesShowRecentlyAdded.wrappedValue = _libraries.wrappedValue.filter { element in
return !array2.contains(element)
}
_libraries.wrappedValue.forEach { library in
if(_library_names.wrappedValue[library] == nil) {
_libraries.wrappedValue.removeAll { ele in
if(library == ele) {
return true
} else {
return false
}
}
}
}
} catch {
}
@ -301,84 +318,95 @@ struct ContentView: View {
}
var body: some View {
if(!jsi.did) {
LoadingView(isShowing: $isLoading) {
TabView(selection: $tabSelection) {
NavigationView() {
VStack {
NavigationLink(destination: ConnectToServerView(isActive: $needsToSelectServer), isActive: $needsToSelectServer) {
EmptyView()
}.isDetailLink(false)
NavigationLink(destination: ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored), isActive: $isSignInErrored) {
EmptyView()
}.isDetailLink(false)
if(!needsToSelectServer && !isSignInErrored) {
VStack(alignment: .leading) {
ScrollView() {
Spacer().frame(height: self.isPortrait ? 0 : 15)
ContinueWatchingView()
NextUpView().padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in
VStack(alignment: .leading) {
HStack() {
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
Spacer()
NavigationLink(destination: LibraryView(prefill: library_id, names: library_names, libraries: libraries, filter: "&SortBy=DateCreated&SortOrder=Descending")) {
Text("See All").font(.subheadline).fontWeight(.bold)
}
}.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
LatestMediaView(library: library_id)
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
if(needsToSelectServer) {
NavigationView() {
ConnectToServerView(isActive: $needsToSelectServer)
}
} else if(isSignInErrored) {
NavigationView() {
ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored)
}
} else {
if(!jsi.did) {
LoadingView(isShowing: $isLoading) {
TabView(selection: $tabSelection) {
NavigationView() {
VStack {
NavigationLink(destination: ConnectToServerView(isActive: $needsToSelectServer), isActive: $needsToSelectServer) {
EmptyView()
}.isDetailLink(false)
NavigationLink(destination: ConnectToServerView(skip_server: true, skip_server_prefill: globalData.server, reauth_deviceId: globalData.user?.device_uuid ?? "", isActive: $isSignInErrored), isActive: $isSignInErrored) {
EmptyView()
}.isDetailLink(false)
if(!needsToSelectServer && !isSignInErrored) {
VStack(alignment: .leading) {
ScrollView() {
Spacer().frame(height: self.isPortrait ? 0 : 15)
ContinueWatchingView()
NextUpView().padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in
VStack(alignment: .leading) {
HStack() {
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold).padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
Spacer()
NavigationLink(destination: LibraryView(prefill: library_id, names: library_names, libraries: libraries, filter: "&SortBy=DateCreated&SortOrder=Descending")) {
Text("See All").font(.subheadline).fontWeight(.bold)
}
}.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
LatestMediaView(library: library_id)
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
}
Spacer().frame(height: 7)
}
Spacer().frame(height: 7)
}
}
}
}
.navigationTitle("Home")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
showSettingsPopover = true;
} label: {
Image(systemName: "gear")
.navigationTitle("Home")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
showSettingsPopover = true;
} label: {
Image(systemName: "gear")
}
}
}
}.popover( isPresented: self.$showSettingsPopover, arrowEdge: .bottom) { SettingsView(close: $showSettingsPopover).environmentObject(self.globalData) }
}
.tabItem({
Text("Home")
Image(systemName: "house")
})
.tag("Home")
NavigationView() {
LibraryView(prefill: "", names: library_names, libraries: libraries)
.navigationTitle("Library")
}
.tabItem({
Text("All Media")
Image(systemName: "folder")
})
.tag("All Media")
}.edgesIgnoringSafeArea(isPortrait ? [] : [.leading,.trailing])
}.environmentObject(globalData)
.edgesIgnoringSafeArea(isPortrait ? [] : [.leading,.trailing])
.onAppear(perform: startup)
.navigationViewStyle(StackNavigationViewStyle())
.alert(isPresented: $isNetworkErrored) {
Alert(title: Text("Network Error"), message: Text("Couldn't connect to Jellyfin"), dismissButton: .default(Text("Ok")))
}.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = false
}
} else {
Text("Signing in...")
.onAppear(perform: {
DispatchQueue.main.async { [self] in
usleep(500000);
self.jsi.did = false;
}.popover( isPresented: self.$showSettingsPopover, arrowEdge: .bottom) { SettingsView(close: $showSettingsPopover).environmentObject(self.globalData) }
}
.tabItem({
Text("Home")
Image(systemName: "house")
})
.tag("Home")
NavigationView() {
LibraryView(prefill: "", names: library_names, libraries: libraries)
.navigationTitle("Library")
}
.tabItem({
Text("All Media")
Image(systemName: "folder")
})
.tag("All Media")
}.edgesIgnoringSafeArea(isPortrait ? [] : [.leading,.trailing])
}.environmentObject(globalData)
.edgesIgnoringSafeArea(isPortrait ? [] : [.leading,.trailing])
.onAppear(perform: startup)
.navigationViewStyle(StackNavigationViewStyle())
.alert(isPresented: $isNetworkErrored) {
Alert(title: Text("Network Error"), message: Text("Couldn't connect to Jellyfin"), dismissButton: .default(Text("Ok")))
}.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = false
}
})
} else {
Text("Signing in...")
.onAppear(perform: {
DispatchQueue.main.async { [self] in
_viewDidLoad.wrappedValue = false
usleep(500000);
self.jsi.did = false;
}
})
}
}
}
}

View File

@ -10,6 +10,30 @@ import SwiftyRequest
import SwiftyJSON
import SDWebImageSwiftUI
struct CustomShape: Shape {
let radius: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
let tl = CGPoint(x: rect.minX, y: rect.minY)
let tr = CGPoint(x: rect.maxX, y: rect.minY)
let br = CGPoint(x: rect.maxX, y: rect.maxY)
let bls = CGPoint(x: rect.minX + radius, y: rect.maxY)
let blc = CGPoint(x: rect.minX + radius, y: rect.maxY - radius)
path.move(to: tl)
path.addLine(to: tr)
path.addLine(to: br)
path.addLine(to: bls)
path.addRelativeArc(center: blc, radius: radius,
startAngle: Angle.degrees(90), delta: Angle.degrees(90))
return path
}
}
struct ContinueWatchingView: View {
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var globalData: GlobalData
@ -44,12 +68,18 @@ struct ContinueWatchingView: View {
//portrait; use backdrop instead
itemObj.Image = item["BackdropImageTags"][0].string ?? ""
itemObj.ImageType = "Backdrop"
if(itemObj.Image == "") {
itemObj.Image = item["ParentBackdropImageTags"][0].string ?? ""
}
itemObj.BlurHash = item["ImageBlurHashes"]["Backdrop"][itemObj.Image].string ?? ""
} else {
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
itemObj.ImageType = "Primary"
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
}
itemObj.Name = item["Name"].string ?? ""
itemObj.Type = item["Type"].string ?? ""
itemObj.IndexNumber = item["IndexNumber"].int ?? nil
@ -108,10 +138,11 @@ struct ContinueWatchingView: View {
.padding(6), alignment: .topTrailing
)
.overlay(
RoundedRectangle(cornerRadius: 10, style: .circular)
.fill(Color(red: 172/255, green: 92/255, blue: 195/255).opacity(0.4))
.frame(width: CGFloat((item.ItemProgress/100)*320), height: 180)
.padding(0), alignment: .bottomLeading
Rectangle()
.fill(Color(red: 172/255, green: 92/255, blue: 195/255))
.mask(CustomShape(radius: 10))
.frame(width: CGFloat((item.ItemProgress/100)*320), height: 7)
.padding(0), alignment: .bottomLeading
)
.shadow(radius: 5)
} else {
@ -126,9 +157,10 @@ struct ContinueWatchingView: View {
.frame(width: 320, height: 180)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10, style: .circular)
.fill(Color(red: 172/255, green: 92/255, blue: 195/255).opacity(0.4))
.frame(width: CGFloat((item.ItemProgress/100)*320), height: 180)
Rectangle()
.fill(Color(red: 172/255, green: 92/255, blue: 195/255))
.mask(CustomShape(radius: 10))
.frame(width: CGFloat((item.ItemProgress/100)*320), height: 7)
.padding(0), alignment: .bottomLeading
)
.shadow(radius: 5)
@ -137,6 +169,8 @@ struct ContinueWatchingView: View {
.font(.callout)
.fontWeight(.semibold)
.foregroundColor(.primary)
.lineLimit(1)
.frame(width: 320, alignment: .leading)
Spacer().frame(height: 5)
}.padding(.trailing, 5)
}

View File

@ -265,7 +265,7 @@ struct EpisodeItemView: View {
.stroke(Color.secondary, lineWidth: 1)
)
}
if(fullItem.CommunityRating != "") {
if(fullItem.CommunityRating != "0") {
HStack() {
Image(systemName: "star").foregroundColor(.secondary)
Text(fullItem.CommunityRating).font(.subheadline)
@ -275,9 +275,8 @@ struct EpisodeItemView: View {
.offset(x: -7, y: 0.7)
}
}
}
}.offset(x: 0, y: -46).padding(.trailing, 16)
}.frame(maxWidth: .infinity, alignment: .leading)
}.frame(maxWidth: .infinity, alignment: .leading).offset(x: 0, y: -46).padding(.trailing, 16)
}.offset(x: 16, y: 40)
, alignment: .bottomLeading)
VStack(alignment: .leading) {
@ -466,7 +465,7 @@ struct EpisodeItemView: View {
.stroke(Color.secondary, lineWidth: 1)
)
}
if(fullItem.CommunityRating != "") {
if(fullItem.CommunityRating != "0") {
HStack() {
Image(systemName: "star").foregroundColor(.secondary)
Text(fullItem.CommunityRating).font(.subheadline)

View File

@ -20,6 +20,14 @@
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>7</string>
<key>DTXApplicationID</key>
<string>8c1f6941-ec78-480c-b589-b41aca29a52e</string>
<key>DTXBeaconURL</key>
<string>https://bf64941kgh.bf.dynatrace.com/mbeacon</string>
<key>DTXStartupLoadBalancing</key>
<true/>
<key>DTXUserOptIn</key>
<true/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
@ -42,6 +50,8 @@
<array>
<string>armv7</string>
</array>
<key>UIRequiresFullScreen</key>
<true/>
<key>UIStatusBarHidden</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
@ -53,17 +63,8 @@
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>DTXApplicationID</key>
<string>8c1f6941-ec78-480c-b589-b41aca29a52e</string>
<key>DTXBeaconURL</key>
<string>https://bf64941kgh.bf.dynatrace.com/mbeacon</string>
<key>DTXUserOptIn</key>
<true/>
<key>DTXStartupLoadBalancing</key>
<true/>
</dict>
</plist>

View File

@ -103,10 +103,17 @@ struct LatestMediaView: View {
.cornerRadius(10)
.overlay(
ZStack {
Text("\(String(item.ItemBadge ?? 0))")
.font(.caption)
.padding(3)
.foregroundColor(.white)
if(item.ItemBadge == 0) {
Image(systemName: "checkmark")
.font(.caption)
.padding(3)
.foregroundColor(.white)
} else {
Text("\(String(item.ItemBadge ?? 0))")
.font(.caption)
.padding(3)
.foregroundColor(.white)
}
}.background(Color.black)
.opacity(0.8)
.cornerRadius(10.0)

View File

@ -34,6 +34,10 @@ struct LibraryFilterView: View {
@Binding var close: Bool;
func onAppear() {
if(_viewDidLoad.wrappedValue == true) {
return
}
_viewDidLoad.wrappedValue = true;
if(_output.wrappedValue.contains("&Filters=IsUnplayed")) {
_onlyUnplayed.wrappedValue = true;
}
@ -55,10 +59,6 @@ struct LibraryFilterView: View {
_sortOrder.wrappedValue = sortOrder;
recalculateFilters()
if(_viewDidLoad.wrappedValue == true) {
return
}
_viewDidLoad.wrappedValue = true;
_allGenres.wrappedValue = []
let url = "/Items/Filters?UserId=\(globalData.user?.user_id ?? "")&ParentId=\(library)"
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + url)
@ -98,6 +98,7 @@ struct LibraryFilterView: View {
}
func recalculateFilters() {
print("recalcFilters running");
output = "";
if(_onlyUnplayed.wrappedValue) {
output = "&Filters=IsUnPlayed";

View File

@ -32,7 +32,7 @@ struct LibrarySearchView: View {
func onAppear() {
_isLoading.wrappedValue = true;
_items.wrappedValue = [];
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + _url.wrappedValue + "&searchTerm=" + searchQuery.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
let request = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + _url.wrappedValue + "&searchTerm=" + searchQuery.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + (_url.wrappedValue.contains("SortBy") ? "" : "&SortBy=Name&SortOrder=Descending"))
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json"
request.acceptType = "application/json"
@ -151,10 +151,17 @@ struct LibrarySearchView: View {
.frame(width:100, height: 150)
.cornerRadius(10).overlay(
ZStack {
Text("\(String(item.ItemBadge ?? 0))")
.font(.caption)
.padding(3)
.foregroundColor(.white)
if(item.ItemBadge == 0) {
Image(systemName: "checkmark")
.font(.caption)
.padding(3)
.foregroundColor(.white)
} else {
Text("\(String(item.ItemBadge ?? 0))")
.font(.caption)
.padding(3)
.foregroundColor(.white)
}
}.background(Color.black)
.opacity(0.8)
.cornerRadius(10.0)

View File

@ -71,7 +71,7 @@ struct LibraryView: View {
_library_names.wrappedValue["favorites"] = "Favorites"
_library_ids.wrappedValue.append("genres")
_library_names.wrappedValue["genres"] = "Genres"
_library_names.wrappedValue["genres"] = "Genres - WIP"
}
}
@ -196,10 +196,17 @@ struct LibraryView: View {
.frame(width:100, height: 150)
.cornerRadius(10).overlay(
ZStack {
Text("\(String(item.ItemBadge ?? 0))")
.font(.caption)
.padding(3)
.foregroundColor(.white)
if(item.ItemBadge == 0) {
Image(systemName: "checkmark")
.font(.caption)
.padding(3)
.foregroundColor(.white)
} else {
Text("\(String(item.ItemBadge ?? 0))")
.font(.caption)
.padding(3)
.foregroundColor(.white)
}
}.background(Color.black)
.opacity(0.8)
.cornerRadius(10.0)

View File

@ -312,7 +312,7 @@ struct MovieItemView: View {
.stroke(Color.secondary, lineWidth: 1)
)
}
if(fullItem.CommunityRating != "") {
if(fullItem.CommunityRating != "0") {
HStack() {
Image(systemName: "star").foregroundColor(.secondary)
Text(fullItem.CommunityRating).font(.subheadline)
@ -322,8 +322,7 @@ struct MovieItemView: View {
.offset(x: -7, y: 0.7)
}
}
}
}.frame(maxWidth: .infinity, alignment: .leading)
}.offset(x: 0, y: -46).padding(.trailing, 16)
}.offset(x: 16, y: 40)
, alignment: .bottomLeading)
@ -512,7 +511,7 @@ struct MovieItemView: View {
.stroke(Color.secondary, lineWidth: 1)
)
}
if(fullItem.CommunityRating != "") {
if(fullItem.CommunityRating != "0") {
HStack() {
Image(systemName: "star").foregroundColor(.secondary)
Text(fullItem.CommunityRating).font(.subheadline)
@ -523,9 +522,9 @@ struct MovieItemView: View {
}
}
Spacer()
}.frame(maxWidth: .infinity)
}.frame(maxWidth: .infinity, alignment: .leading)
.offset(x: 14)
}.frame(maxWidth: .infinity)
}.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
HStack() {
Button() {

View File

@ -218,10 +218,12 @@ struct SeasonItemView: View {
.foregroundColor(.primary)
.fixedSize(horizontal: false, vertical: true)
.offset(y: -4)
Text(String(fullItem.ProductionYear)).font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.secondary)
.lineLimit(1)
if(fullItem.ProductionYear != 0) {
Text(String(fullItem.ProductionYear)).font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.secondary)
.lineLimit(1)
}
}.offset(x: 0, y: 45)
}.offset(x: 16, y: 22)
, alignment: .bottomLeading)
@ -324,9 +326,11 @@ struct SeasonItemView: View {
.frame(width: 120, height: 180)
.cornerRadius(10)
Spacer().frame(height: 4)
Text(String(fullItem.ProductionYear)).font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.secondary)
if(fullItem.ProductionYear != 0) {
Text(String(fullItem.ProductionYear)).font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.secondary)
}
Spacer()
}
ScrollView() {

View File

@ -43,6 +43,11 @@ struct SeriesItemView: View {
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
if(itemObj.Image == "") {
itemObj.Image = item["ParentBackdropImageTags"][0].string ?? ""
}
itemObj.ImageType = "Primary"
itemObj.SeasonImage = item["ParentBackdropImageTags"][0].string ?? ""
itemObj.SeasonImageType = "Backdrop"
@ -93,10 +98,17 @@ struct SeriesItemView: View {
.cornerRadius(10)
}.overlay(
ZStack {
Text("\(String(item.ItemBadge ?? 0))")
.font(.caption)
.padding(3)
.foregroundColor(.white)
if(item.ItemBadge == 0) {
Image(systemName: "checkmark")
.font(.caption)
.padding(3)
.foregroundColor(.white)
} else {
Text("\(String(item.ItemBadge ?? 0))")
.font(.caption)
.padding(3)
.foregroundColor(.white)
}
}.background(Color.black)
.opacity(0.8)
.cornerRadius(10.0)

31
Release Notes.rtf Normal file
View File

@ -0,0 +1,31 @@
{\rtf1\ansi\ansicpg1252\cocoartf2580
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;;}
{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1}
{\list\listtemplateid2\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{hyphen\}}{\leveltext\leveltemplateid101\'01\uc0\u8259 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid2}}
{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}}
\margl1440\margr1440\vieww11520\viewh8400\viewkind0
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
\f0\b\fs26 \cf0 1.0.0 (Build: 8)
\f1\b0\fs24 \
\
\pard\tx220\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li720\fi-720\pardirnatural\partightenfactor0
\ls1\ilvl0\cf0 {\listtext \uc0\u8226 }Fix progress bar on ContinueWatchingView\
{\listtext \uc0\u8226 }Sort search if no sorting active\
{\listtext \uc0\u8226 }Limit text length on ContinueWatchingView\
{\listtext \uc0\u8226 }Fix \'930\'94 for some production years on SeasonItemView\
{\listtext \uc0\u8226 }Fix \'930\'94 for some CommunityRatings on EpisodeItemView & MovieItemView\
{\listtext \uc0\u8226 }Fix placeholder image for some episodes on ContinueWatchingView\
{\listtext \uc0\u8226 }Fix placeholder image for some seasons w/o their own images in SeriesItemView\
{\listtext \uc0\u8226 }Show checkmarks instead of \'930\'94 on ItemBadges\
{\listtext \uc0\u8226 }Fix having to click twice on selections in LibraryFilterView\
{\listtext \uc0\u8226 }Fix bug where sign in page would disappear on orientation change or app relaunch\
{\listtext \uc0\u8226 }Fix bug where some users would have access to libraries they were not supposed to see.\
{\listtext \uc0\u8226 }Adds Dynatrace session tracking to try and find the cause for random crashes (app is FOOMing)\
{\listtext \uc0\u8226 }Fix memory leak when viewing the main page multiple times\
\pard\tx220\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li720\fi-720\pardirnatural\partightenfactor0
\ls2\ilvl0\cf0 {\listtext \uc0\u8259 }Squashed some commits in the repo as sensitive tokens were exposed for Sentry & Dynatrace\
{\listtext \uc0\u8259 }Also adds Fastlane\
}