diff --git a/PodcastsClone.xcodeproj/project.pbxproj b/PodcastsClone.xcodeproj/project.pbxproj index 3facd1d..5127148 100644 --- a/PodcastsClone.xcodeproj/project.pbxproj +++ b/PodcastsClone.xcodeproj/project.pbxproj @@ -7,18 +7,40 @@ objects = { /* Begin PBXBuildFile section */ + EC37FB2A2A1A3231005C78D6 /* Episode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC37FB292A1A3231005C78D6 /* Episode.swift */; }; + EC37FB2C2A1A3E03005C78D6 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC37FB2B2A1A3E03005C78D6 /* Color.swift */; }; + EC37FB322A1A49EB005C78D6 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC37FB312A1A49EB005C78D6 /* Strings.swift */; }; + EC48FBBA2A1A826800CD7B83 /* PodcastViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC48FBB92A1A826800CD7B83 /* PodcastViewCell.swift */; }; + EC48FBBC2A1A890D00CD7B83 /* Stub.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC48FBBB2A1A890D00CD7B83 /* Stub.swift */; }; + EC48FBC32A1ABA2000CD7B83 /* NowPlayingBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC48FBC22A1ABA2000CD7B83 /* NowPlayingBar.swift */; }; + EC8CF6202A13A4F200BE6FD5 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC8CF61F2A13A4F200BE6FD5 /* Colors.xcassets */; }; + EC8CF6232A13A59400BE6FD5 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8CF6222A13A59400BE6FD5 /* LibraryView.swift */; }; + EC8CF6252A13A7F000BE6FD5 /* Podcast.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8CF6242A13A7F000BE6FD5 /* Podcast.swift */; }; ECB23B932A0E33B000A1C62B /* PodcastsCloneApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB23B922A0E33B000A1C62B /* PodcastsCloneApp.swift */; }; - ECB23B952A0E33B000A1C62B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB23B942A0E33B000A1C62B /* ContentView.swift */; }; ECB23B972A0E33B200A1C62B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ECB23B962A0E33B200A1C62B /* Assets.xcassets */; }; ECB23B9A2A0E33B200A1C62B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ECB23B992A0E33B200A1C62B /* Preview Assets.xcassets */; }; + ECB23BA12A0E3FDF00A1C62B /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB23BA02A0E3FDF00A1C62B /* MainView.swift */; }; + ECB23BA32A0E455300A1C62B /* PodcastDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB23BA22A0E455300A1C62B /* PodcastDetailView.swift */; }; + ECB23BA62A0E4C0B00A1C62B /* EpisodeViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB23BA52A0E4C0B00A1C62B /* EpisodeViewCell.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + EC37FB292A1A3231005C78D6 /* Episode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Episode.swift; sourceTree = ""; }; + EC37FB2B2A1A3E03005C78D6 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; + EC37FB312A1A49EB005C78D6 /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; + EC48FBB92A1A826800CD7B83 /* PodcastViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodcastViewCell.swift; sourceTree = ""; }; + EC48FBBB2A1A890D00CD7B83 /* Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stub.swift; sourceTree = ""; }; + EC48FBC22A1ABA2000CD7B83 /* NowPlayingBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingBar.swift; sourceTree = ""; }; + EC8CF61F2A13A4F200BE6FD5 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; + EC8CF6222A13A59400BE6FD5 /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = ""; }; + EC8CF6242A13A7F000BE6FD5 /* Podcast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Podcast.swift; sourceTree = ""; }; ECB23B8F2A0E33B000A1C62B /* PodcastsClone.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PodcastsClone.app; sourceTree = BUILT_PRODUCTS_DIR; }; ECB23B922A0E33B000A1C62B /* PodcastsCloneApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodcastsCloneApp.swift; sourceTree = ""; }; - ECB23B942A0E33B000A1C62B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; ECB23B962A0E33B200A1C62B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; ECB23B992A0E33B200A1C62B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + ECB23BA02A0E3FDF00A1C62B /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + ECB23BA22A0E455300A1C62B /* PodcastDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodcastDetailView.swift; sourceTree = ""; }; + ECB23BA52A0E4C0B00A1C62B /* EpisodeViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeViewCell.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -32,6 +54,72 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + EC37FB2D2A1A3FA4005C78D6 /* Extensions */ = { + isa = PBXGroup; + children = ( + EC37FB2B2A1A3E03005C78D6 /* Color.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + EC37FB2E2A1A4941005C78D6 /* Strings */ = { + isa = PBXGroup; + children = ( + EC37FB312A1A49EB005C78D6 /* Strings.swift */, + ); + path = Strings; + sourceTree = ""; + }; + EC48FBBD2A1A93C300CD7B83 /* Podcast */ = { + isa = PBXGroup; + children = ( + ECB23BA22A0E455300A1C62B /* PodcastDetailView.swift */, + EC48FBB92A1A826800CD7B83 /* PodcastViewCell.swift */, + ); + path = Podcast; + sourceTree = ""; + }; + EC48FBBE2A1A93EB00CD7B83 /* Episode */ = { + isa = PBXGroup; + children = ( + ECB23BA52A0E4C0B00A1C62B /* EpisodeViewCell.swift */, + ); + path = Episode; + sourceTree = ""; + }; + EC48FBBF2A1A93F600CD7B83 /* Library */ = { + isa = PBXGroup; + children = ( + EC8CF6222A13A59400BE6FD5 /* LibraryView.swift */, + ); + path = Library; + sourceTree = ""; + }; + EC48FBC02A1A93FF00CD7B83 /* Stubs */ = { + isa = PBXGroup; + children = ( + EC48FBBB2A1A890D00CD7B83 /* Stub.swift */, + ); + path = Stubs; + sourceTree = ""; + }; + EC48FBC12A1ABA0700CD7B83 /* Components */ = { + isa = PBXGroup; + children = ( + EC48FBC22A1ABA2000CD7B83 /* NowPlayingBar.swift */, + ); + path = Components; + sourceTree = ""; + }; + EC8CF6212A13A57400BE6FD5 /* Model */ = { + isa = PBXGroup; + children = ( + EC8CF6242A13A7F000BE6FD5 /* Podcast.swift */, + EC37FB292A1A3231005C78D6 /* Episode.swift */, + ); + path = Model; + sourceTree = ""; + }; ECB23B862A0E33B000A1C62B = { isa = PBXGroup; children = ( @@ -51,9 +139,11 @@ ECB23B912A0E33B000A1C62B /* PodcastsClone */ = { isa = PBXGroup; children = ( + EC8CF6212A13A57400BE6FD5 /* Model */, + ECB23BA42A0E45CC00A1C62B /* View */, ECB23B922A0E33B000A1C62B /* PodcastsCloneApp.swift */, - ECB23B942A0E33B000A1C62B /* ContentView.swift */, ECB23B962A0E33B200A1C62B /* Assets.xcassets */, + EC8CF61F2A13A4F200BE6FD5 /* Colors.xcassets */, ECB23B982A0E33B200A1C62B /* Preview Content */, ); path = PodcastsClone; @@ -67,6 +157,21 @@ path = "Preview Content"; sourceTree = ""; }; + ECB23BA42A0E45CC00A1C62B /* View */ = { + isa = PBXGroup; + children = ( + EC48FBC12A1ABA0700CD7B83 /* Components */, + EC37FB2E2A1A4941005C78D6 /* Strings */, + EC37FB2D2A1A3FA4005C78D6 /* Extensions */, + EC48FBC02A1A93FF00CD7B83 /* Stubs */, + EC48FBBF2A1A93F600CD7B83 /* Library */, + EC48FBBE2A1A93EB00CD7B83 /* Episode */, + EC48FBBD2A1A93C300CD7B83 /* Podcast */, + ECB23BA02A0E3FDF00A1C62B /* MainView.swift */, + ); + path = View; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -125,6 +230,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EC8CF6202A13A4F200BE6FD5 /* Colors.xcassets in Resources */, ECB23B9A2A0E33B200A1C62B /* Preview Assets.xcassets in Resources */, ECB23B972A0E33B200A1C62B /* Assets.xcassets in Resources */, ); @@ -137,8 +243,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ECB23B952A0E33B000A1C62B /* ContentView.swift in Sources */, + ECB23BA12A0E3FDF00A1C62B /* MainView.swift in Sources */, + EC48FBBC2A1A890D00CD7B83 /* Stub.swift in Sources */, + EC48FBBA2A1A826800CD7B83 /* PodcastViewCell.swift in Sources */, + EC37FB322A1A49EB005C78D6 /* Strings.swift in Sources */, + EC37FB2A2A1A3231005C78D6 /* Episode.swift in Sources */, + ECB23BA62A0E4C0B00A1C62B /* EpisodeViewCell.swift in Sources */, + EC8CF6252A13A7F000BE6FD5 /* Podcast.swift in Sources */, + ECB23BA32A0E455300A1C62B /* PodcastDetailView.swift in Sources */, + EC37FB2C2A1A3E03005C78D6 /* Color.swift in Sources */, + EC8CF6232A13A59400BE6FD5 /* LibraryView.swift in Sources */, ECB23B932A0E33B000A1C62B /* PodcastsCloneApp.swift in Sources */, + EC48FBC32A1ABA2000CD7B83 /* NowPlayingBar.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/PodcastsClone/Assets.xcassets/bdnpColor.colorset/Contents.json b/PodcastsClone/Assets.xcassets/bdnpColor.colorset/Contents.json new file mode 100644 index 0000000..56df92b --- /dev/null +++ b/PodcastsClone/Assets.xcassets/bdnpColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.620", + "green" : "0.870", + "red" : "0.910" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.620", + "green" : "0.870", + "red" : "0.910" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/bdnp_logo.imageset/Contents.json b/PodcastsClone/Assets.xcassets/bdnp_logo.imageset/Contents.json new file mode 100644 index 0000000..b428999 --- /dev/null +++ b/PodcastsClone/Assets.xcassets/bdnp_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "beefdairysquare_36.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/bdnp_logo.imageset/beefdairysquare_36.png b/PodcastsClone/Assets.xcassets/bdnp_logo.imageset/beefdairysquare_36.png new file mode 100644 index 0000000..da2749b Binary files /dev/null and b/PodcastsClone/Assets.xcassets/bdnp_logo.imageset/beefdairysquare_36.png differ diff --git a/PodcastsClone/Assets.xcassets/bewjtColor.colorset/Contents.json b/PodcastsClone/Assets.xcassets/bewjtColor.colorset/Contents.json new file mode 100644 index 0000000..5547f36 --- /dev/null +++ b/PodcastsClone/Assets.xcassets/bewjtColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.560", + "green" : "0.230", + "red" : "0.080" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.560", + "green" : "0.230", + "red" : "0.080" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/bewjt_logo.imageset/Contents.json b/PodcastsClone/Assets.xcassets/bewjt_logo.imageset/Contents.json new file mode 100644 index 0000000..5b0a5b9 --- /dev/null +++ b/PodcastsClone/Assets.xcassets/bewjt_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bullseye-cover2-1024x1024.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/bewjt_logo.imageset/bullseye-cover2-1024x1024.jpg b/PodcastsClone/Assets.xcassets/bewjt_logo.imageset/bullseye-cover2-1024x1024.jpg new file mode 100644 index 0000000..2b53de3 Binary files /dev/null and b/PodcastsClone/Assets.xcassets/bewjt_logo.imageset/bullseye-cover2-1024x1024.jpg differ diff --git a/PodcastsClone/Assets.xcassets/dgsColor.colorset/Contents.json b/PodcastsClone/Assets.xcassets/dgsColor.colorset/Contents.json new file mode 100644 index 0000000..f68554d --- /dev/null +++ b/PodcastsClone/Assets.xcassets/dgsColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.810", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.810", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/dgs_logo.imageset/Contents.json b/PodcastsClone/Assets.xcassets/dgs_logo.imageset/Contents.json new file mode 100644 index 0000000..9bf8707 --- /dev/null +++ b/PodcastsClone/Assets.xcassets/dgs_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "DGS-podcastArt.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/dgs_logo.imageset/DGS-podcastArt.png b/PodcastsClone/Assets.xcassets/dgs_logo.imageset/DGS-podcastArt.png new file mode 100644 index 0000000..120eb9b Binary files /dev/null and b/PodcastsClone/Assets.xcassets/dgs_logo.imageset/DGS-podcastArt.png differ diff --git a/PodcastsClone/Assets.xcassets/jjgoColor.colorset/Contents.json b/PodcastsClone/Assets.xcassets/jjgoColor.colorset/Contents.json new file mode 100644 index 0000000..bd14b20 --- /dev/null +++ b/PodcastsClone/Assets.xcassets/jjgoColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.700", + "green" : "0.670", + "red" : "0.010" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.700", + "green" : "0.670", + "red" : "0.010" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/jjgo_logo.imageset/Contents.json b/PodcastsClone/Assets.xcassets/jjgo_logo.imageset/Contents.json new file mode 100644 index 0000000..cc17d95 --- /dev/null +++ b/PodcastsClone/Assets.xcassets/jjgo_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "jjgo_logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/jjgo_logo.imageset/jjgo_logo.png b/PodcastsClone/Assets.xcassets/jjgo_logo.imageset/jjgo_logo.png new file mode 100644 index 0000000..ed55f65 Binary files /dev/null and b/PodcastsClone/Assets.xcassets/jjgo_logo.imageset/jjgo_logo.png differ diff --git a/PodcastsClone/Assets.xcassets/jjhoColor.colorset/Contents.json b/PodcastsClone/Assets.xcassets/jjhoColor.colorset/Contents.json new file mode 100644 index 0000000..c6b0321 --- /dev/null +++ b/PodcastsClone/Assets.xcassets/jjhoColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.350", + "green" : "0.040", + "red" : "0.080" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.350", + "green" : "0.040", + "red" : "0.080" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/jjho_logo.imageset/Contents.json b/PodcastsClone/Assets.xcassets/jjho_logo.imageset/Contents.json new file mode 100644 index 0000000..324835e --- /dev/null +++ b/PodcastsClone/Assets.xcassets/jjho_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Screenshot 2023-05-21 at 18.58.17.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/jjho_logo.imageset/Screenshot 2023-05-21 at 18.58.17.png b/PodcastsClone/Assets.xcassets/jjho_logo.imageset/Screenshot 2023-05-21 at 18.58.17.png new file mode 100644 index 0000000..1855be3 Binary files /dev/null and b/PodcastsClone/Assets.xcassets/jjho_logo.imageset/Screenshot 2023-05-21 at 18.58.17.png differ diff --git a/PodcastsClone/Assets.xcassets/onracColor.colorset/Contents.json b/PodcastsClone/Assets.xcassets/onracColor.colorset/Contents.json new file mode 100644 index 0000000..6a153bc --- /dev/null +++ b/PodcastsClone/Assets.xcassets/onracColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.340", + "green" : "0.130", + "red" : "0.050" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.340", + "green" : "0.130", + "red" : "0.050" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/onrac_logo.imageset/Contents.json b/PodcastsClone/Assets.xcassets/onrac_logo.imageset/Contents.json new file mode 100644 index 0000000..efa58c7 --- /dev/null +++ b/PodcastsClone/Assets.xcassets/onrac_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "oh-no-ross-and-carrie-cover-1024x1024.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/onrac_logo.imageset/oh-no-ross-and-carrie-cover-1024x1024.jpg b/PodcastsClone/Assets.xcassets/onrac_logo.imageset/oh-no-ross-and-carrie-cover-1024x1024.jpg new file mode 100644 index 0000000..f505b09 Binary files /dev/null and b/PodcastsClone/Assets.xcassets/onrac_logo.imageset/oh-no-ross-and-carrie-cover-1024x1024.jpg differ diff --git a/PodcastsClone/Assets.xcassets/spyColor.colorset/Contents.json b/PodcastsClone/Assets.xcassets/spyColor.colorset/Contents.json new file mode 100644 index 0000000..23dc698 --- /dev/null +++ b/PodcastsClone/Assets.xcassets/spyColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.750", + "green" : "0.080", + "red" : "0.030" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.750", + "green" : "0.080", + "red" : "0.030" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/spy_logo.imageset/Contents.json b/PodcastsClone/Assets.xcassets/spy_logo.imageset/Contents.json new file mode 100644 index 0000000..d39a2a6 --- /dev/null +++ b/PodcastsClone/Assets.xcassets/spy_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "spy_logo.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Assets.xcassets/spy_logo.imageset/spy_logo.jpg b/PodcastsClone/Assets.xcassets/spy_logo.imageset/spy_logo.jpg new file mode 100644 index 0000000..7371de6 Binary files /dev/null and b/PodcastsClone/Assets.xcassets/spy_logo.imageset/spy_logo.jpg differ diff --git a/PodcastsClone/Colors.xcassets/Contents.json b/PodcastsClone/Colors.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/PodcastsClone/Colors.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/accent.colorset/Contents.json b/PodcastsClone/Colors.xcassets/accent.colorset/Contents.json new file mode 100644 index 0000000..0546015 --- /dev/null +++ b/PodcastsClone/Colors.xcassets/accent.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.871", + "green" : "0.314", + "red" : "0.490" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.871", + "green" : "0.314", + "red" : "0.490" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/background.colorset/Contents.json b/PodcastsClone/Colors.xcassets/background.colorset/Contents.json new file mode 100644 index 0000000..9cf3e9e --- /dev/null +++ b/PodcastsClone/Colors.xcassets/background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.980", + "green" : "0.980", + "red" : "0.980" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.020", + "green" : "0.020", + "red" : "0.020" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/backgroundSecondary.colorset/Contents.json b/PodcastsClone/Colors.xcassets/backgroundSecondary.colorset/Contents.json new file mode 100644 index 0000000..8f5067f --- /dev/null +++ b/PodcastsClone/Colors.xcassets/backgroundSecondary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.900", + "green" : "0.900", + "red" : "0.900" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.250", + "green" : "0.250", + "red" : "0.250" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/primary.colorset/Contents.json b/PodcastsClone/Colors.xcassets/primary.colorset/Contents.json new file mode 100644 index 0000000..561d0f0 --- /dev/null +++ b/PodcastsClone/Colors.xcassets/primary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.090", + "green" : "0.090", + "red" : "0.090" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.910", + "green" : "0.910", + "red" : "0.910" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/secondary.colorset/Contents.json b/PodcastsClone/Colors.xcassets/secondary.colorset/Contents.json new file mode 100644 index 0000000..1dccaa8 --- /dev/null +++ b/PodcastsClone/Colors.xcassets/secondary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.666", + "green" : "0.666", + "red" : "0.666" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.833", + "green" : "0.833", + "red" : "0.833" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/shadow.colorset/Contents.json b/PodcastsClone/Colors.xcassets/shadow.colorset/Contents.json new file mode 100644 index 0000000..4e332df --- /dev/null +++ b/PodcastsClone/Colors.xcassets/shadow.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.833", + "green" : "0.833", + "red" : "0.833" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.333", + "green" : "0.333", + "red" : "0.333" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/unchanging/Contents.json b/PodcastsClone/Colors.xcassets/unchanging/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/PodcastsClone/Colors.xcassets/unchanging/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/unchanging/backgroundDark.colorset/Contents.json b/PodcastsClone/Colors.xcassets/unchanging/backgroundDark.colorset/Contents.json new file mode 100644 index 0000000..6af7322 --- /dev/null +++ b/PodcastsClone/Colors.xcassets/unchanging/backgroundDark.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.100", + "green" : "0.100", + "red" : "0.100" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.100", + "green" : "0.100", + "red" : "0.100" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/unchanging/backgroundLight.colorset/Contents.json b/PodcastsClone/Colors.xcassets/unchanging/backgroundLight.colorset/Contents.json new file mode 100644 index 0000000..0119781 --- /dev/null +++ b/PodcastsClone/Colors.xcassets/unchanging/backgroundLight.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.900", + "green" : "0.900", + "red" : "0.900" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.900", + "green" : "0.900", + "red" : "0.900" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/unchanging/primaryDark.colorset/Contents.json b/PodcastsClone/Colors.xcassets/unchanging/primaryDark.colorset/Contents.json new file mode 100644 index 0000000..f5ed17a --- /dev/null +++ b/PodcastsClone/Colors.xcassets/unchanging/primaryDark.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.910", + "green" : "0.910", + "red" : "0.910" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.910", + "green" : "0.910", + "red" : "0.910" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/unchanging/primaryLight.colorset/Contents.json b/PodcastsClone/Colors.xcassets/unchanging/primaryLight.colorset/Contents.json new file mode 100644 index 0000000..d6445cb --- /dev/null +++ b/PodcastsClone/Colors.xcassets/unchanging/primaryLight.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.090", + "green" : "0.090", + "red" : "0.090" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.090", + "green" : "0.090", + "red" : "0.090" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/unchanging/secondaryDark.colorset/Contents.json b/PodcastsClone/Colors.xcassets/unchanging/secondaryDark.colorset/Contents.json new file mode 100644 index 0000000..36f354c --- /dev/null +++ b/PodcastsClone/Colors.xcassets/unchanging/secondaryDark.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.800", + "blue" : "0.800", + "green" : "0.800", + "red" : "0.800" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.800", + "blue" : "0.800", + "green" : "0.800", + "red" : "0.800" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/Colors.xcassets/unchanging/secondaryLight.colorset/Contents.json b/PodcastsClone/Colors.xcassets/unchanging/secondaryLight.colorset/Contents.json new file mode 100644 index 0000000..a5f421f --- /dev/null +++ b/PodcastsClone/Colors.xcassets/unchanging/secondaryLight.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.800", + "blue" : "0.250", + "green" : "0.250", + "red" : "0.250" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.800", + "blue" : "0.250", + "green" : "0.250", + "red" : "0.250" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PodcastsClone/ContentView.swift b/PodcastsClone/ContentView.swift deleted file mode 100644 index 2f0a219..0000000 --- a/PodcastsClone/ContentView.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// ContentView.swift -// PodcastsClone -// -// Created by etudiant on 2023-05-12. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundColor(.accentColor) - Text("Hello, World!") - } - .padding() - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/PodcastsClone/Model/Episode.swift b/PodcastsClone/Model/Episode.swift new file mode 100644 index 0000000..44f7d08 --- /dev/null +++ b/PodcastsClone/Model/Episode.swift @@ -0,0 +1,15 @@ +// +// PodcastEpisode.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-21. +// + +import Foundation +struct Episode { + var id: UUID + var publicationDate: Date + var title: String + var description: String + var duration: TimeInterval // duration in seconds +} diff --git a/PodcastsClone/Model/Podcast.swift b/PodcastsClone/Model/Podcast.swift new file mode 100644 index 0000000..1c72367 --- /dev/null +++ b/PodcastsClone/Model/Podcast.swift @@ -0,0 +1,59 @@ +// +// Podcast.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-16. +// + +import Foundation +import SwiftUI + +struct Podcast { + var id: UUID + var image: UIImage + var title: String + var by: String + private var _episodes: [Episode] + var rating: Double + var reviews: Int + var genre: String + var frequency: String + var backgroundColor: Color + var backgroundIsDark: Bool + + init( + id: UUID, + image: UIImage, + title: String, + by: String, + episodes: [Episode], + rating: Double, + reviews: Int, + genre: String, + frequency: String = "Unknown", + backgroundColor: Color = Color.theme.background, + backgroundIsDark: Bool = false + ) { + self.id = id + self.image = image + self.title = title + self.by = by + self._episodes = episodes + self.rating = rating + self.reviews = reviews + self.genre = genre + self.frequency = frequency + self.backgroundColor = backgroundColor + self.backgroundIsDark = backgroundIsDark + } + + + var episodes: [Episode] { + _episodes.sorted(by: { $0.publicationDate > $1.publicationDate }) + } + + var latestEpisodeDescription: String { + guard !episodes.isEmpty else { return "No episodes available" } + return episodes.first?.description ?? "No description available" + } +} diff --git a/PodcastsClone/PodcastsCloneApp.swift b/PodcastsClone/PodcastsCloneApp.swift index aca480c..b9969da 100644 --- a/PodcastsClone/PodcastsCloneApp.swift +++ b/PodcastsClone/PodcastsCloneApp.swift @@ -11,7 +11,7 @@ import SwiftUI struct PodcastsCloneApp: App { var body: some Scene { WindowGroup { - ContentView() + MainView() } } } diff --git a/PodcastsClone/View/Components/NowPlayingBar.swift b/PodcastsClone/View/Components/NowPlayingBar.swift new file mode 100644 index 0000000..7c6ba70 --- /dev/null +++ b/PodcastsClone/View/Components/NowPlayingBar.swift @@ -0,0 +1,77 @@ +// +// NowPlayingBar.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-21. +// + +// Thanks to Luca Jonscher for this tutorial: https://itnext.io/add-a-now-playing-bar-with-swiftui-to-your-app-d515b03f05e3 +// This bar is almost all made by them. The mistakes are all mine. +import SwiftUI + +struct NowPlayingBar: View { + var content: Content + + @ViewBuilder var body: some View { + ZStack { + Blur(style: .systemMaterial) // Use Blur view as background + .frame(width: UIScreen.main.bounds.size.width, height: 64) + + VStack { + HStack { + Button(action: {}) { + HStack { + Image("jjho_logo") + .resizable() + .frame(width: 48, height: 48) + .shadow(radius: 4, x: 0, y: 2) + .padding(.leading) + VStack(alignment: .leading) { + Text("Acting in Bat Faith") + .foregroundColor(Color.theme.primary) + Text("1 February 2023") + .font(.caption) + .foregroundColor(Color.theme.secondary) + }.padding() + Spacer() + } + } + .buttonStyle(PlainButtonStyle()) + + Button(action: {}) { + Image(systemName: "play.fill") + .font(.title3) + } + .buttonStyle(PlainButtonStyle()) + .padding(.horizontal) + + Button(action: {}) { + Image(systemName: "gobackward.30") + .font(.title3) + } + .buttonStyle(PlainButtonStyle()) + .padding(.trailing, 30) + } + } + } + } +} + + +struct Blur: UIViewRepresentable { + var style: UIBlurEffect.Style = .systemChromeMaterial + + func makeUIView(context: Context) -> UIVisualEffectView { + return UIVisualEffectView(effect: UIBlurEffect(style: style)) + } + + func updateUIView(_ uiView: UIVisualEffectView, context: Context) { + uiView.effect = UIBlurEffect(style: style) + } +} + +struct NowPlayingBar_Previews: PreviewProvider { + static var previews: some View { + NowPlayingBar(content: Text("Hello Bar")) + } +} diff --git a/PodcastsClone/View/Episode/EpisodeViewCell.swift b/PodcastsClone/View/Episode/EpisodeViewCell.swift new file mode 100644 index 0000000..9255216 --- /dev/null +++ b/PodcastsClone/View/Episode/EpisodeViewCell.swift @@ -0,0 +1,83 @@ +// +// PodcastEpisodeViewCell.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-12. +// + +import SwiftUI + + +struct EpisodeViewCell: View { + + let episode: Episode + + var body: some View { + VStack { + Divider() + .foregroundColor(Color.theme.backgroundSecondary) + .edgesIgnoringSafeArea(.trailing) + .padding(.leading) + + VStack(alignment: .leading) { + + Text(formatter.localizedString(for: episode.publicationDate, relativeTo: Date())) + .font(.subheadline) + .foregroundColor(Color.theme.secondary) + + (Text(episode.title) + .font(.headline) + .foregroundColor(Color.theme.primary) + + Text("\n\(episode.description)") + .font(.body) + .foregroundColor(Color.theme.secondary)) + .lineLimit(4) + .truncationMode(.tail) + + HStack { + Image(systemName: "play.fill") + .foregroundColor(Color.theme.accent) + .padding() + .background(Color.theme.backgroundSecondary) + .clipShape(Circle()) + Text(timeString(time: episode.duration)) + .foregroundColor(Color.theme.accent) + Spacer() + Text(Strings.threeDots) + .foregroundColor(Color.theme.secondary) + .padding(.horizontal) + } + } + .padding() + } + } + + private func timeString(time: TimeInterval) -> String { + let hours = Int(time) / 3600 + let minutes = Int(time) / 60 % 60 + + var timeComponents = [String]() + if hours > 0 { + timeComponents.append("\(hours) hr") + } + if minutes > 0 { + timeComponents.append("\(minutes) min") + } + + return timeComponents.joined(separator: " ") + } + + private let formatter: RelativeDateTimeFormatter = { + let formatter = RelativeDateTimeFormatter() + formatter.unitsStyle = .full + return formatter + }() + +} + +struct EpisodeViewCell_Previews: PreviewProvider { + + static var previews: some View { + EpisodeViewCell(episode: Stub.episodes[1]) + } +} diff --git a/PodcastsClone/View/Extensions/Color.swift b/PodcastsClone/View/Extensions/Color.swift new file mode 100644 index 0000000..eb92b8a --- /dev/null +++ b/PodcastsClone/View/Extensions/Color.swift @@ -0,0 +1,28 @@ +// +// Color.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-21. +// + +import Foundation +import SwiftUI + +extension Color { + static let theme = ColorTheme() +} + +struct ColorTheme { + let primary = Color("primary") + let secondary = Color("secondary") + let background = Color("background") + let backgroundSecondary = Color("backgroundSecondary") + let accent = Color("accent") + let shadow = Color("shadow") + let unchangingPrimaryDark = Color("primaryDark") + let unchangingPrimaryLight = Color("primaryLight") + let unchangingSecondaryDark = Color("secondaryDark") + let unchangingSecondaryLight = Color("secondaryLight") + let unchangingBackgroundDark = Color("backgroundDark") + let unchangingBackgroundLight = Color("backgroundLight") +} diff --git a/PodcastsClone/View/Library/LibraryView.swift b/PodcastsClone/View/Library/LibraryView.swift new file mode 100644 index 0000000..98c350e --- /dev/null +++ b/PodcastsClone/View/Library/LibraryView.swift @@ -0,0 +1,49 @@ +// +// LibraryView.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-16. +// + +import SwiftUI + +struct LibraryView: View { + + var podcasts: [Podcast] + + var body: some View { + NavigationView { + ScrollView { + VStack(alignment: .leading) { + Text("Podcasts") + .font(.largeTitle) + .fontWeight(.bold) + .foregroundColor(Color.theme.primary) + .padding() + + let columns = [ + GridItem(.flexible(), spacing: 16), + GridItem(.flexible(), spacing: 16) + ] + + LazyVGrid(columns: columns, spacing: 16) { + ForEach(podcasts, id: \.id) { podcast in + NavigationLink( + destination: PodcastDetailView(podcast: podcast)) { + PodcastViewCell(podcast: podcast) + } + } + } + .padding(.horizontal) + } + } + } + .navigationViewStyle(StackNavigationViewStyle()) + } +} + +struct LibraryView_Previews: PreviewProvider { + static var previews: some View { + LibraryView(podcasts: Stub.podcasts) + } +} diff --git a/PodcastsClone/View/MainView.swift b/PodcastsClone/View/MainView.swift new file mode 100644 index 0000000..b6d02c5 --- /dev/null +++ b/PodcastsClone/View/MainView.swift @@ -0,0 +1,50 @@ +// +// MainView.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-12. +// + +import SwiftUI + +struct MainView: View { + var body: some View { + ZStack { + + TabView { + Text("Check out the library instead") + .tabItem { + Label("Listen Now", systemImage: "play") + } + Text("I swear, the library is where it's at") + .tabItem { + Label("Browse", systemImage: "square.grid.2x2") + } + LibraryView(podcasts: Stub.podcasts) + .tabItem { + Label("Library", systemImage: "book") + } + Text("Nothing to see here. The library, on the other hand...") + .tabItem { + Label("Search", systemImage: "magnifyingglass") + } + } + .accentColor(Color.theme.accent) + .tabViewStyle(.automatic) + + VStack { + Spacer() + VStack{ + NowPlayingBar(content: Text("Gahhh!")) + } + .offset(y: -42) + } + } + } +} + +struct MainView_Previews: PreviewProvider { + static var previews: some View { + MainView() + } +} diff --git a/PodcastsClone/View/Podcast/PodcastDetailView.swift b/PodcastsClone/View/Podcast/PodcastDetailView.swift new file mode 100644 index 0000000..6fbd033 --- /dev/null +++ b/PodcastsClone/View/Podcast/PodcastDetailView.swift @@ -0,0 +1,109 @@ +// +// PodcastDetailView.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-12. +// + +import SwiftUI + +struct PodcastDetailView: View { + + let podcast: Podcast + + var body: some View { + ScrollView { + ZStack() { + podcast.backgroundColor.ignoresSafeArea(.all, edges: .all) + VStack(alignment: .center) { + VStack(alignment: .center) { + + Image(uiImage: podcast.image) + .resizable() + .scaledToFit() + .cornerRadius(12) + .shadow(color: Color.theme.unchangingPrimaryLight, radius: 10, x: 0, y: 5) + .padding(.horizontal, 48) + .padding(.vertical, 16) + + Text(podcast.title) + .font(.title) + .foregroundColor(podcast.backgroundIsDark ? Color.theme.unchangingPrimaryDark : Color.theme.unchangingPrimaryLight) + .multilineTextAlignment(.center) + + Text(podcast.by) + .font(.headline) + .foregroundColor(podcast.backgroundIsDark ? Color.theme.unchangingSecondaryDark : Color.theme.unchangingSecondaryLight) + .multilineTextAlignment(.center) + + Button(action: {}) { + HStack { + Image(systemName: "play.fill") + .padding(.horizontal, 4) + Text(Strings.latestEpisode) + }} + .padding(.vertical) + .padding(.horizontal, 64) + .background(podcast.backgroundIsDark ? Color.theme.unchangingBackgroundLight : Color.theme.unchangingBackgroundDark) + .foregroundColor(podcast.backgroundIsDark ? Color.theme.unchangingPrimaryLight : Color.theme.unchangingPrimaryDark) + .clipShape(RoundedRectangle(cornerSize: CGSize(width: 12.0, height: 12.0))) + + // TODO replace '...' with Strings.readFurtherPrompt + Text(podcast.latestEpisodeDescription) + .lineLimit(3) + .truncationMode(.tail) + .padding() + .foregroundColor(podcast.backgroundIsDark ? Color.theme.unchangingPrimaryDark : Color.theme.unchangingPrimaryLight) + + HStack() { + Text("\(Image(systemName: "star.fill")) \(podcast.rating, specifier: "%.1f") (\(podcast.reviews)) \(Strings.classySeparator) \(podcast.genre) \(Strings.classySeparator) \(podcast.frequency)") + .padding(.horizontal) + + Spacer() + } + .foregroundColor(podcast.backgroundIsDark ? Color.theme.unchangingSecondaryDark : Color.theme.unchangingSecondaryLight) + } + + Divider() + .foregroundColor(Color.theme.backgroundSecondary) + + ZStack() { + Color.theme.background.ignoresSafeArea(.all, edges: .all) + + VStack(alignment: .leading) { + HStack { + Text(Strings.episodes) + .font(.title2) + .fontWeight(.bold) + .foregroundColor(Color.theme.primary) + .padding() + + Image(systemName: "chevron.down") + .foregroundColor(Color.theme.accent) + + Spacer() + + Text(Strings.seeAll) + .foregroundColor(Color.theme.accent) + .padding(.trailing) + } + + + ForEach(podcast.episodes, id: \.id) { episode in + EpisodeViewCell(episode: episode) + } + } + } + } + } + } + } +} + + +struct PodcastDetailView_Previews: PreviewProvider { + static var previews: some View { + PodcastDetailView(podcast: Stub.podcasts[2]) + PodcastDetailView(podcast: Stub.podcasts[2]) + } +} diff --git a/PodcastsClone/View/Podcast/PodcastViewCell.swift b/PodcastsClone/View/Podcast/PodcastViewCell.swift new file mode 100644 index 0000000..cf0d27e --- /dev/null +++ b/PodcastsClone/View/Podcast/PodcastViewCell.swift @@ -0,0 +1,54 @@ +// +// PodcastViewCell.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-21. +// + +import SwiftUI + +struct PodcastViewCell: View { + let podcast: Podcast + + var body: some View { + VStack(alignment: .leading) { + Image(uiImage: podcast.image) + .resizable() + .scaledToFit() + .cornerRadius(12) + + Text(podcast.title) + .foregroundColor(Color.theme.primary) + + Text("Updated \(smartDate(podcast.episodes.first?.publicationDate ?? Date()))") .foregroundColor(Color.theme.secondary) + .font(.footnote) + } + } + + func smartDate(_ date: Date) -> String { + let calendar = Calendar.current + let now = Date() + let components = calendar.dateComponents([.day, .year], from: date, to: now) + + if let day = components.day { + switch day { + case 0: + return "Today" + case 1..<7: + return "\(day)d ago" + default: + let formatter = DateFormatter() + formatter.dateFormat = "dd MMMM yyyy" + return formatter.string(from: date) + } + } else { + return "Unknown" + } + } +} + +struct PodcastViewCell_Previews: PreviewProvider { + static var previews: some View { + PodcastViewCell(podcast: Stub.podcasts[1]) + } +} diff --git a/PodcastsClone/View/Strings/Strings.swift b/PodcastsClone/View/Strings/Strings.swift new file mode 100644 index 0000000..27deab7 --- /dev/null +++ b/PodcastsClone/View/Strings/Strings.swift @@ -0,0 +1,17 @@ +// +// Strings.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-21. +// + +import Foundation + +struct Strings { + static let threeDots = "···" + static let classySeparator = "·" + static let latestEpisode = "Latest Episode" + static let readFurtherPrompt = "MORE" + static let episodes = "Episodes" + static let seeAll = "See All" +} diff --git a/PodcastsClone/View/Stubs/Stub.swift b/PodcastsClone/View/Stubs/Stub.swift new file mode 100644 index 0000000..97f6c4f --- /dev/null +++ b/PodcastsClone/View/Stubs/Stub.swift @@ -0,0 +1,149 @@ +// +// Stub.swift +// PodcastsClone +// +// Created by etudiant on 2023-05-21. +// + +import Foundation +import SwiftUI + +struct Stub { + static let episodes: [Episode] = [ + Episode( + id: UUID(), + publicationDate: Date.now.addingTimeInterval(-1000000000), + title: "A New Ipsum", + description: "Stand in doorway, unwilling to chose whether to stay in or go out more napping, more napping all the napping is exhausting sleep i'm bored inside, let me out i'm lonely outside, let me in i can't make up my mind whether to go in or out, guess i'll just stand partway in and partway out, contemplating the universe for half an hour how dare you nudge me with your foot?!?! leap into the air in greatest offense!", + duration: 3463 + ), + Episode( + id: UUID(), + publicationDate: Date.now.addingTimeInterval(-1000000), + title: "Return of the Hooman", + description: "Catch mouse and gave it as a present mewl for food at 4am drink water out of the faucet and have secret plans. Stretch chase dog then run away. Kitty. Mouse if it fits, i sits. Bite off human's toes. If human is on laptop sit on the keyboard.", + duration: 4480 + ), + Episode( + id: UUID(), + publicationDate: Date.now.addingTimeInterval(-100000000), + title: "Cat Ipsum Strikes Back", + description: "Chase after silly colored fish toys around the house i want to go outside let me go outside nevermind inside is better or get video posted to internet for chasing red dot eat owner's food wack the mini furry mouse so cat meoooow i iz master of hoomaan, not hoomaan master of i, oooh damn dat dog but stuff and things. Cats making all the muffins.", + duration: 4028 + ), + ] + + static let episodesOld = Stub.episodes.map { episode in + Episode( + id: episode.id, + publicationDate: Date.now.addingTimeInterval((episode.publicationDate.timeIntervalSinceNow - Date.now.timeIntervalSinceNow) * 10), + title: episode.title, + description: episode.description, + duration: episode.duration + ) + } + + static let episodesRecent = Stub.episodes.map { episode in + Episode( + id: episode.id, + publicationDate: Date.now.addingTimeInterval((episode.publicationDate.timeIntervalSinceNow - Date.now.timeIntervalSinceNow) / 10.0), + title: episode.title, + description: episode.description, + duration: episode.duration + ) + } + + static let podcasts: [Podcast] = [ + Podcast( + id: UUID(), + image: UIImage(named: "jjho_logo")!, + title: "Podcast Title 1", + by: "Author 1", + episodes: episodesOld, + rating: 4.2, + reviews: 2139, + genre: "Genre 1", + frequency: "Weekly", + backgroundColor: Color("jjhoColor"), + backgroundIsDark: true + ), + Podcast( + id: UUID(), + image: UIImage(named: "jjgo_logo")!, + title: "Podcast Title 2", + by: "Author 2", + episodes: episodes, + rating: 4.2, + reviews: 211, + genre: "Genre 2", + frequency: "Twice weekly", + backgroundColor: Color("jjgoColor"), + backgroundIsDark: true + ), + Podcast( + id: UUID(), + image: UIImage(named: "spy_logo")!, + title: "Podcast Title 3", + by: "Author 3", + episodes: episodesRecent, + rating: 4.812039, + reviews: 3981, + genre: "Genre 3", + frequency: "Complete", + backgroundColor: Color("spyColor"), + backgroundIsDark: true + ), + Podcast( + id: UUID(), + image: UIImage(named: "bdnp_logo")!, + title: "Podcast Title 4", + by: "Author 4", + episodes: episodesOld, + rating: 4.2, + reviews: 211, + genre: "Genre 4", + frequency: "Monthly", + backgroundColor: Color("bdnpColor"), + backgroundIsDark: false + ), + Podcast( + id: UUID(), + image: UIImage(named: "bewjt_logo")!, + title: "Podcast Title 5", + by: "Author 5", + episodes: episodes, + rating: 4.2, + reviews: 211, + genre: "Genre 5", + frequency: "Daily", + backgroundColor: Color("bewjtColor"), + backgroundIsDark: true + ), + Podcast( + id: UUID(), + image: UIImage(named: "onrac_logo")!, + title: "Podcast Title 6", + by: "Author 6", + episodes: episodesRecent, + rating: 4.2, + reviews: 211, + genre: "Genre 6", + frequency: "Weekly", + backgroundColor: Color("onracColor"), + backgroundIsDark: true + ), + Podcast( + id: UUID(), + image: UIImage(named: "dgs_logo")!, + title: "Podcast Title 7", + by: "Author 7", + episodes: episodes, + rating: 4.2, + reviews: 211, + genre: "Genre 7", + frequency: "Complete", + backgroundColor: Color("dgsColor"), + backgroundIsDark: false + ), + ] +} diff --git a/README.md b/README.md index 4dc040e..04e5e55 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,83 @@ -# AD_iOS +# Apple Podcasts Clone -iOS Apple Podcasts mini-clone \ No newline at end of file +## Project Overview + +Apple Podcasts allows users to listen to podcasts and interact with a +sophisticated UI to control their playback. + +This clone does not. + +Apple Podcasts Clone is an application built on the SwiftUI +framework. It focuses on the `Library` "master-detail". + +* ### :lipstick: The View + As of the 21st of May 2023, it is a façade: only the + View part of the project is serviceable. + + + +
+ + +
+ + +
+ + + +
more details... + + * #### Library ("master") 💐 + + Users can browse a `Library` of `Podcasts` and select any podcast to inspect. + + * #### Podcast ("detail") 🌻 + + Users can browse a `Podcast` of `Episodes`. + + * #### Dark/Light themes 🌙 ☀️ + + This clone replicates the original dark/light themes by Apple Podcasts. + + To test this, you can change your device's (or your emulator's) display +setting to dark/light theme. + + * #### Top bar ☝️ + + In Apple Podcasts, a stylish, slightly transparent top bar contains certain menu +options, and displays the name of the current +section once the user has scrolled past the corresponding header. + + This clone has no such thing. + + * #### Bottom bar 👇 + + In Apple Podcasts, a consistent, stylish, slightly transparent bottom bar allows +navigation between different views, and playing songs. + + This clone tried to emulate that, but only the Library is there to visit, and the + "Now Playing" bar is static. + + + +* ### :necktie: The Model + + A very basic Model is in place, to provide some sample data to views more easily. + +## Installation + +To run the Apple Podcasts Clone, you must use XCode -- your best bet is using a macOS machine. + +Once in XCode, you can look through the `Views` -- they come with their own `Previews`. Find the `MainView`, +and have a look around inside the preview. Sometimes, XCode is pretty neat. Sometimes... + +## Some known limitations and shortcomings + +Concerning the View part of this project: +* in the "detail" Podcast view, instead of saying "MORE", latest episode descriptions spanning more than 3 lines are truncated with a "..." +* throughout the app, the smart date formattings are close to the original app, but not quite as smart. +* the top bar was left unimplemented +* the "now playing bar" and the tabview don't have the same transparency / colors +* instead of i18, this clone has a very basic struct that provides static strings at need. Some string literals are still being used in the views. +* and many others will join this list, no doubt.