├── banner.png
├── Social Contributor
├── Resources
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── quote
│ │ │ ├── Contents.json
│ │ │ ├── dark-blue.colorset
│ │ │ │ └── Contents.json
│ │ │ └── light-blue.colorset
│ │ │ │ └── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── newsletters.json
│ ├── quotes.json
│ └── no-results-found.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Screens
│ ├── Home
│ │ ├── Models
│ │ │ └── Quote.swift
│ │ ├── Views
│ │ │ ├── Components
│ │ │ │ └── ViewModifiers.swift
│ │ │ ├── QuoteView.swift
│ │ │ └── HomeView.swift
│ │ └── ViewModels
│ │ │ └── HomeViewModel.swift
│ ├── Newsletter
│ │ ├── Models
│ │ │ └── NewsLetter.swift
│ │ ├── NewsLetterListViewModel.swift
│ │ └── Views
│ │ │ └── NewsLetterListView.swift
│ ├── Settings
│ │ ├── Models
│ │ │ └── Contributor.swift
│ │ ├── SettingsLabelStyle.swift
│ │ └── Views
│ │ │ └── SettingsView.swift
│ └── Muse
│ │ ├── MuseViewModel.swift
│ │ └── Views
│ │ ├── LottieView.swift
│ │ └── MuseView.swift
├── Utils
│ ├── CustomColors.swift
│ ├── Constants.swift
│ ├── AppReviewRequest.swift
│ └── DecoderReport.swift
├── Extension
│ ├── View+Extensions.swift
│ ├── String+BuildInfo.swift
│ ├── URL+Extensions.swift
│ ├── View+Theme.swift
│ ├── Bundle+Decodable.swift
│ ├── Color+HEX.swift
│ └── URLSession+APICall.swift
├── CoreData
│ ├── Model.xcdatamodeld
│ │ └── Model.xcdatamodel
│ │ │ └── contents
│ └── PersistenceController.swift
├── Services
│ ├── ContributorsProvider.swift
│ └── NewsLetterService.swift
├── Social_ContributorApp.swift
├── ColorScheme
│ └── ColorScheme.swift
└── ContentView.swift
├── Social Contributor.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ └── adamrush.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── adamrush.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
├── Social ContributorTests
├── Social_ContributorTests.swift
└── Home
│ └── HomeViewModelTests.swift
├── README.md
├── .swiftlint.yml
└── .gitignore
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamrushy/social-swiftui-app/HEAD/banner.png
--------------------------------------------------------------------------------
/Social Contributor/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Social Contributor/Resources/Assets.xcassets/quote/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Social Contributor/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Social Contributor.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Social Contributor/Resources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Social Contributor.xcodeproj/project.xcworkspace/xcuserdata/adamrush.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamrushy/social-swiftui-app/HEAD/Social Contributor.xcodeproj/project.xcworkspace/xcuserdata/adamrush.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/Social Contributor/Screens/Home/Models/Quote.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Quote.swift
3 | // Social Contributor
4 | //
5 | // Created by Danijela Vrzan on 2022-05-22.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Quote: Decodable {
11 | let text: String
12 | let author: String
13 | }
14 |
--------------------------------------------------------------------------------
/Social Contributor/Screens/Newsletter/Models/NewsLetter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsLetter.swift
3 | // Social Contributor
4 | //
5 | // Created by Mohammad Azam on 5/7/22.
6 | //
7 |
8 | import Foundation
9 |
10 | struct NewsLetter: Decodable {
11 | let name: String
12 | let url: URL
13 | }
14 |
--------------------------------------------------------------------------------
/Social Contributor/Utils/CustomColors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomColors.swift
3 | // Social Contributor
4 | //
5 | // Created by Danijela Vrzan on 2022-05-23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension Color {
11 | static let lightBlue = Color("light-blue")
12 | static let darkBlue = Color("dark-blue")
13 | }
14 |
--------------------------------------------------------------------------------
/Social Contributor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Social Contributor/Screens/Settings/Models/Contributor.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | // Github Contributors for this app
4 | // https://api.github.com/repos/adamrushy/social-swiftui-app/contributors
5 | struct Contributor: Codable {
6 | let id: Int
7 | let login: String
8 | let avatarUrl: URL
9 | let htmlUrl: URL
10 | }
11 |
--------------------------------------------------------------------------------
/Social Contributor/Extension/View+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+Extensions.swift
3 | // Social Contributor
4 | //
5 | // Created by Mohammad Azam on 5/7/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | extension View {
12 |
13 | func embedInNavigationView() -> some View {
14 | NavigationView { self }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Social Contributor/CoreData/Model.xcdatamodeld/Model.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Social Contributor/Extension/String+BuildInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+BuildInfo.swift
3 | // Social Contributor
4 | //
5 | // Created by Alex Logan on 04/05/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | public static func basicBuildInfo() -> String {
12 | let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? ""
13 | return version
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Social Contributor/Extension/URL+Extensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | // See more details @ https://www.swiftbysundell.com/articles/constructing-urls-in-swift/#static-urls
4 | extension URL {
5 | init(staticString string: StaticString) {
6 | guard let url = URL(string: "\(string)") else {
7 | preconditionFailure("Invalid static URL string: \(string)")
8 | }
9 |
10 | self = url
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Social Contributor/Extension/View+Theme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+Theme.swift
3 | // Social Contributor
4 | //
5 | // Created by Yash Shah on 04/05/2022.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | extension View {
12 | // Force Dark Mode
13 | func darkMode() -> some View {
14 | self.preferredColorScheme(.dark)
15 | }
16 |
17 | // Force Light mode Mode
18 | func lightMode() -> some View {
19 | self.preferredColorScheme(.light)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Social Contributor/Resources/Assets.xcassets/quote/dark-blue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.200",
9 | "green" : "0.110",
10 | "red" : "0.016"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Social Contributor/Resources/Assets.xcassets/quote/light-blue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.702",
9 | "green" : "0.431",
10 | "red" : "0.102"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Social Contributor.xcodeproj/xcuserdata/adamrush.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Social Contributor.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Social Contributor/Screens/Home/Views/Components/ViewModifiers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModifiers.swift
3 | // Social Contributor
4 | //
5 | // Created by Danijela Vrzan on 2022-05-23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct QuoteViewModifier: ViewModifier {
11 | func body(content: Content) -> some View {
12 | content
13 | .shadow(color: .black, radius: 1, x: 2, y: 2)
14 | .padding(.horizontal, 15)
15 | .foregroundColor(.white)
16 | .font(.headline)
17 | }
18 | }
19 |
20 | extension View {
21 | func quoteViewModifier() -> some View {
22 | modifier(QuoteViewModifier())
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Social Contributor/Screens/Home/ViewModels/HomeViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeViewModel.swift
3 | // Social Contributor
4 | //
5 | // Created by Danijela Vrzan on 2022-05-22.
6 | //
7 |
8 | import Foundation
9 |
10 | class HomeViewModel: ObservableObject {
11 | @Published var quote: Quote!
12 | var quotes: [Quote] = Bundle.main.decode(Constants.FileName.quotes)
13 |
14 | init() {
15 | getRandomQuote()
16 | }
17 |
18 | func getRandomQuote() {
19 | guard let randomQuote = quotes.randomElement() else {
20 | quote = Constants.placeholderQuote
21 | return
22 | }
23 | quote = randomQuote
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Social ContributorTests/Social_ContributorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Social_ContributorTests.swift
3 | // Social ContributorTests
4 | //
5 | // Created by Mikaela Caron on 5/6/22.
6 | //
7 |
8 | import XCTest
9 |
10 | class Social_ContributorTests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 | }
15 |
16 | override func tearDownWithError() throws {
17 | // Put teardown code here. This method is called after the invocation of each test method in the class.
18 | }
19 |
20 | func testExample() throws {
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Social Contributor/Screens/Home/Views/QuoteView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuoteView.swift
3 | // Social Contributor
4 | //
5 | // Created by Danijela Vrzan on 2022-05-22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct QuoteView: View {
11 | @ObservedObject var viewModel: HomeViewModel
12 |
13 | var body: some View {
14 | VStack {
15 | Text(viewModel.quote.text)
16 | .multilineTextAlignment(.center)
17 | .padding(.bottom, 10)
18 | Text("- \(viewModel.quote.author)")
19 | }
20 | .quoteViewModifier()
21 | }
22 | }
23 |
24 | struct QuoteView_Previews: PreviewProvider {
25 | static var previews: some View {
26 | QuoteView(viewModel: HomeViewModel())
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Social ContributorTests/Home/HomeViewModelTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeViewModelTests.swift
3 | // Social ContributorTests
4 | //
5 | // Created by Danijela Vrzan on 2022-05-23.
6 | //
7 |
8 | import XCTest
9 | @testable import Social_Contributor
10 |
11 | class HomeViewModelTests: XCTestCase {
12 | var viewModel: HomeViewModel!
13 |
14 | override func setUpWithError() throws {
15 | try super.setUpWithError()
16 | viewModel = HomeViewModel()
17 | }
18 |
19 | override func tearDownWithError() throws {
20 | try super.tearDownWithError()
21 | viewModel = nil
22 | }
23 |
24 | //MARK: Tests
25 | func test_quote_notNil_whenVMInitialized() {
26 | XCTAssertNotNil(viewModel.quote)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Social Contributor/Services/ContributorsProvider.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @MainActor final class ContributorsProvider: ObservableObject {
4 | @Published var contributors: String = ""
5 |
6 | func fetchContributors() async {
7 |
8 | do {
9 | let url = Constants.URLPath.gitHubContributors
10 | let response = try await URLSession.shared.decode(
11 | [Contributor].self,
12 | from: url,
13 | keyDecodingStrategy: .convertFromSnakeCase
14 | )
15 | let names: [String] = response.map { $0.login }
16 | contributors = names.joined(separator: ", ")
17 | } catch {
18 | // Add error handling 🤔
19 | print("ERROR: \(error)")
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Social Contributor/Extension/Bundle+Decodable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle+Decodable.swift
3 | // Social Contributor
4 | //
5 | // Created by Danijela Vrzan on 2022-05-22.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Bundle {
11 | func decode(_ file: String) -> T {
12 | guard let url = self.url(forResource: file, withExtension: nil) else {
13 | fatalError("Failed to locate \(file) in bundle.")
14 | }
15 |
16 | guard let data = try? Data(contentsOf: url) else {
17 | fatalError("Failed to load \(file) from bundle.")
18 | }
19 |
20 | let decoder = JSONDecoder()
21 |
22 | guard let loadedObject = try? decoder.decode(T.self, from: data) else {
23 | fatalError("Failed to decode \(file) from bundle.")
24 | }
25 |
26 | return loadedObject
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Social Contributor/Services/NewsLetterService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsLetterService.swift
3 | // Social Contributor
4 | //
5 | // Created by Mohammad Azam on 5/7/22.
6 | //
7 |
8 | import Foundation
9 |
10 | class NewsLetterService {
11 |
12 | func load(_ fileName: String) -> [NewsLetter] {
13 |
14 | guard let path = Bundle.main.path(forResource: fileName, ofType: "json") else {
15 | fatalError("newsletters file is not found!")
16 | }
17 |
18 | do {
19 | let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
20 | let newsletters = try JSONDecoder().decode([NewsLetter].self, from: data)
21 | return newsletters
22 | } catch {
23 | return []
24 | }
25 |
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Social Contributor/Screens/Newsletter/NewsLetterListViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsLetterListViewModel.swift
3 | // Social Contributor
4 | //
5 | // Created by Mohammad Azam on 5/7/22.
6 | //
7 |
8 | import Foundation
9 |
10 | class NewsLetterListViewModel: ObservableObject {
11 |
12 | @Published var newsLetters: [NewsLetterViewModel] = []
13 |
14 | func populateAllNewsLetters() {
15 |
16 | let newsLetters = NewsLetterService().load(Constants.FileName.newsletters)
17 | self.newsLetters = newsLetters.map(NewsLetterViewModel.init)
18 | }
19 |
20 | }
21 |
22 | struct NewsLetterViewModel: Identifiable {
23 |
24 | private var newsLetter: NewsLetter
25 |
26 | init(newsLetter: NewsLetter) {
27 | self.newsLetter = newsLetter
28 | }
29 |
30 | var id: UUID {
31 | return UUID()
32 | }
33 |
34 | var name: String {
35 | newsLetter.name
36 | }
37 |
38 | var url: URL {
39 | newsLetter.url
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/Social Contributor/Screens/Newsletter/Views/NewsLetterListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsLetterListView.swift
3 | // Social Contributor
4 | //
5 | // Created by Mohammad Azam on 5/7/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct NewsLetterListView: View {
11 |
12 | @StateObject private var viewModel = NewsLetterListViewModel()
13 |
14 | var body: some View {
15 |
16 | List(viewModel.newsLetters) { newsLetter in
17 | HStack {
18 | Link(newsLetter.name, destination: newsLetter.url)
19 | Spacer()
20 | Image(systemName: "chevron.right")
21 | }
22 | }
23 | .navigationTitle(Constants.NavigationTitle.newsletters)
24 | .embedInNavigationView()
25 | .listStyle(.plain)
26 | .onAppear {
27 | viewModel.populateAllNewsLetters()
28 | }
29 | }
30 | }
31 |
32 | struct NewsLetterListView_Previews: PreviewProvider {
33 | static var previews: some View {
34 | NewsLetterListView()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Social Contributor/Social_ContributorApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Social_ContributorApp.swift
3 | // Social Contributor
4 | //
5 | // Created by Adam Rush on 04/05/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct Social_ContributorApp: App {
12 |
13 | @StateObject private var colorSchemeManager = AppColorSchemeManager()
14 |
15 | let persistenceController = PersistenceController.shared
16 |
17 | var body: some Scene {
18 | WindowGroup {
19 |
20 | // This will silence the constraints errors displayed on the output window
21 | // swiftlint:disable: redundant_discardable_let
22 | let _ = UserDefaults.standard.set(false, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable")
23 |
24 | ContentView()
25 | .environment(\.managedObjectContext, persistenceController.container.viewContext)
26 | .environmentObject(colorSchemeManager)
27 | .preferredColorScheme(colorSchemeManager.currentScheme)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Social Contributor/Utils/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // Social Contributor
4 | //
5 | // Created by Mohammad Azam on 5/7/22.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Constants {
11 | static let placeholderQuote = Quote(
12 | text: "If you're waiting until you feel talented enough to make it, you'll never make it.",
13 | author: "Criss Jami"
14 | )
15 |
16 | struct FileName {
17 | static let newsletters = "newsletters"
18 | static let quotes = "quotes.json"
19 | }
20 |
21 | struct NavigationTitle {
22 | static let home = "Welcome"
23 | static let settings = "Settings"
24 | static let newsletters = "Newsletters"
25 | }
26 |
27 | struct URLPath {
28 | static let gitHubContributors = URL(
29 | staticString: "https://api.github.com/repos/adamrushy/social-swiftui-app/contributors"
30 | )
31 | static let gitHubProject = URL(
32 | staticString: "https://github.com/adamrushy/social-swiftui-app"
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Social Contributor/Screens/Muse/MuseViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // Social Contributor
4 | //
5 | // Created by Yash Shah on 04/05/2022.
6 | //
7 |
8 | import Foundation
9 | import MusicKit
10 |
11 | class MuseViewModel: ObservableObject {
12 | @Published private(set) var tracks: MusicItemCollection