├── CocoaHeadsNL
├── .gitattributes
├── .xctool-args
├── CocoaHeadsNL
│ ├── Images.xcassets
│ │ ├── Contents.json
│ │ ├── Banner.imageset
│ │ │ ├── Banner@1x.png
│ │ │ ├── Banner@2x.png
│ │ │ ├── Banner@3x.png
│ │ │ ├── BannerDark@1x.png
│ │ │ ├── BannerDark@2x.png
│ │ │ ├── BannerDark@3x.png
│ │ │ └── Contents.json
│ │ ├── AboutTabIcon.imageset
│ │ │ ├── about-1.png
│ │ │ ├── about-2.png
│ │ │ ├── about-3.png
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── AppIcon-120.png
│ │ │ ├── AppIcon-152.png
│ │ │ ├── AppIcon-167.png
│ │ │ ├── AppIcon-176.png
│ │ │ ├── AppIcon-180.png
│ │ │ ├── AppIcon-196.png
│ │ │ ├── AppIcon-29.png
│ │ │ ├── AppIcon-40.png
│ │ │ ├── AppIcon-48.png
│ │ │ ├── AppIcon-55.png
│ │ │ ├── AppIcon-58.png
│ │ │ ├── AppIcon-76.png
│ │ │ ├── AppIcon-80.png
│ │ │ ├── AppIcon-87.png
│ │ │ ├── AppIcon-120-1.png
│ │ │ ├── AppIcon-58-1.png
│ │ │ ├── AppIcon-58-2.png
│ │ │ ├── AppIcon-80-1.png
│ │ │ ├── AppIcon-80-2.png
│ │ │ ├── AppIcon-87-1.png
│ │ │ ├── iTunesArtwork@2x.png
│ │ │ └── Contents.json
│ │ ├── JobsTabIcon.imageset
│ │ │ ├── JobsTabIcon.png
│ │ │ ├── JobsTabIcon@2x.png
│ │ │ ├── JobsTabIcon@3x.png
│ │ │ └── Contents.json
│ │ ├── CocoaHeadsNLLogo.imageset
│ │ │ ├── CocoaHeadsNL.pdf
│ │ │ ├── CocoaHeadsNL-1.pdf
│ │ │ └── Contents.json
│ │ ├── EventsTabIcon.imageset
│ │ │ ├── EventsTabIcon.png
│ │ │ ├── EventsTabIcon@2x.png
│ │ │ ├── EventsTabIcon@3x.png
│ │ │ └── Contents.json
│ │ ├── CompaniesTabIcon.imageset
│ │ │ ├── CompaniesTabIcon.png
│ │ │ ├── CompaniesTabIconStroked@2x.png
│ │ │ ├── CompaniesTabIconStroked@3x.png
│ │ │ └── Contents.json
│ │ ├── MeetupPlaceholder.imageset
│ │ │ ├── MeetupPlaceholder.png
│ │ │ ├── MeetupPlaceholder@2x.png
│ │ │ ├── MeetupPlaceholder@3x.png
│ │ │ └── Contents.json
│ │ ├── CocoaHeadsNLLogoOnlyLight.imageset
│ │ │ ├── CocoaHeadsNL.pdf
│ │ │ └── Contents.json
│ │ ├── DayLabelColorHistory.colorset
│ │ │ └── Contents.json
│ │ ├── DayLabelColor.colorset
│ │ │ └── Contents.json
│ │ ├── DayBackgroundViewColor.colorset
│ │ │ └── Contents.json
│ │ ├── DayBackgroundViewColorHistory.colorset
│ │ │ └── Contents.json
│ │ └── DayBackgroundViewColorToday.colorset
│ │ │ └── Contents.json
│ ├── CocoaHeadsNL500.png
│ ├── CocoaHeadsNL-Bridging-Header.h
│ ├── LogoCell.swift
│ ├── MainViewContainer.swift
│ ├── UIColor+Extensions.swift
│ ├── Job+CoreDataClass.swift
│ ├── TabBarController.swift
│ ├── Meetup+CoreDataClass.swift
│ ├── Company+CoreDataClass.swift
│ ├── ParseConfig-template.plist
│ ├── Contributor+CoreDataClass.swift
│ ├── NSLocalizedString+Convenience.swift
│ ├── MapAnnotation.swift
│ ├── UINavigationItem+Banner.swift
│ ├── UIImage+Resize.swift
│ ├── OnlineCell.swift
│ ├── Identifiable.swift
│ ├── NSAttributedStringExtension.swift
│ ├── Contributor+CoreDataProperties.swift
│ ├── Job+CoreDataProperties.swift
│ ├── CoreDataStack.swift
│ ├── JobDataSource.swift
│ ├── CocoaHeadsNL.entitlements
│ ├── Company+CoreDataProperties.swift
│ ├── HTMLDataCell.swift
│ ├── UIAlertController+FetchErrors.swift
│ ├── RequestReview.swift
│ ├── TitleCell.swift
│ ├── Meetup+CoreDataProperties.swift
│ ├── ButtonCell.swift
│ ├── CoreLocationController.swift
│ ├── JobsCell.swift
│ ├── CompanyDataSource.swift
│ ├── NSManagedObjectContext+AsyncHelpers.swift
│ ├── MapViewCell.swift
│ ├── MeetupDataSource.swift
│ ├── UITableView+Extensions.swift
│ ├── CalendarTabImage.swift
│ ├── DetailDataSource.swift
│ ├── SplitViewController.swift
│ ├── Launch Screen.storyboard
│ ├── MeetupCell.swift
│ ├── Info.plist
│ ├── NSManagedObjectContext+SaveHelpers.swift
│ ├── Model.xcdatamodeld
│ │ └── Model.xcdatamodel
│ │ │ └── contents
│ ├── NSManagedObject+FetchHelpers.swift
│ ├── AppDelegate.swift
│ ├── DetailViewController.swift
│ └── JobsCell.xib
├── CocoaHeadsNL.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── CocoaHeadsNL.xccheckout
│ └── xcshareddata
│ │ └── xcschemes
│ │ ├── generalNotification.xcscheme
│ │ ├── ItemServiceExtension.xcscheme
│ │ ├── itemNotification.xcscheme
│ │ └── CocoaHeadsNL.xcscheme
├── itemNotification
│ ├── itemNotification.entitlements
│ ├── Info.plist
│ ├── NotificationViewController.swift
│ └── Base.lproj
│ │ └── MainInterface.storyboard
├── generalNotification
│ ├── generalNotification.entitlements
│ ├── NotificationViewController.swift
│ ├── Info.plist
│ └── Base.lproj
│ │ └── MainInterface.storyboard
├── ItemServiceExtension
│ ├── ItemServiceExtension.entitlements
│ ├── Info.plist
│ └── NotificationService.swift
├── .gitignore
├── CloudKitSync
│ ├── package.json
│ ├── config.js.example
│ ├── jobs
│ │ ├── loadEventInfo.js
│ │ ├── loadJobInfo.js
│ │ ├── loadContributorInfo.js
│ │ └── xmlreader.js
│ └── README.md
├── CocoaHeadsNLUITests
│ ├── Info.plist
│ └── CocoaHeadsNLUITests.swift
└── .swiftlint.yml
├── .swiftlint.yml
├── logo1024.png
├── Photoshop
├── AppIcon.psd
├── Banner.psd
├── JobsTabIcon.psd
├── EventsTabIcon.psd
├── CompaniesTabIcon.psd
└── MeetupPlaceholder.psd
├── provisioning
├── AppStoreCertificates.p12.gpg
├── CocoaHeadsNL-AppStore.mobileprovision.gpg
├── CocoaHeadsNL-AppStore-Item-Notification.mobileprovision.gpg
├── CocoaHeadsNL-AppStore-General-Notification.mobileprovision.gpg
├── CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision.gpg
├── README.md
├── App-Store.plist
└── import_provisioning.sh
├── .travis.yml
├── .gitignore
├── azure-pipelines.yml
├── CONTRIBUTING.md
├── .github
└── workflows
│ ├── ci.yml
│ └── deploy.yml
└── README.md
/CocoaHeadsNL/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj merge=union
2 | *.strings text diff
3 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - line_length
3 | excluded:
4 | - Pods
5 |
--------------------------------------------------------------------------------
/logo1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/logo1024.png
--------------------------------------------------------------------------------
/Photoshop/AppIcon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/AppIcon.psd
--------------------------------------------------------------------------------
/Photoshop/Banner.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/Banner.psd
--------------------------------------------------------------------------------
/Photoshop/JobsTabIcon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/JobsTabIcon.psd
--------------------------------------------------------------------------------
/CocoaHeadsNL/.xctool-args:
--------------------------------------------------------------------------------
1 | [
2 | "-workspace", "CocoaHeadsNL.xcworkspace",
3 | "-scheme", "CocoaHeadsNL"
4 | ]
5 |
6 |
--------------------------------------------------------------------------------
/Photoshop/EventsTabIcon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/EventsTabIcon.psd
--------------------------------------------------------------------------------
/Photoshop/CompaniesTabIcon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/CompaniesTabIcon.psd
--------------------------------------------------------------------------------
/Photoshop/MeetupPlaceholder.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/MeetupPlaceholder.psd
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/provisioning/AppStoreCertificates.p12.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/provisioning/AppStoreCertificates.p12.gpg
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/CocoaHeadsNL500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/CocoaHeadsNL500.png
--------------------------------------------------------------------------------
/provisioning/CocoaHeadsNL-AppStore.mobileprovision.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/provisioning/CocoaHeadsNL-AppStore.mobileprovision.gpg
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/CocoaHeadsNL-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@1x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@2x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@3x.png
--------------------------------------------------------------------------------
/provisioning/CocoaHeadsNL-AppStore-Item-Notification.mobileprovision.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/provisioning/CocoaHeadsNL-AppStore-Item-Notification.mobileprovision.gpg
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-1.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-2.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-3.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-120.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-152.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-167.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-176.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-176.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-180.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-196.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-29.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-40.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-48.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-55.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-55.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-76.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-87.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@1x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@2x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@3x.png
--------------------------------------------------------------------------------
/provisioning/CocoaHeadsNL-AppStore-General-Notification.mobileprovision.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/provisioning/CocoaHeadsNL-AppStore-General-Notification.mobileprovision.gpg
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-120-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-120-1.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58-1.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58-2.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80-1.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80-2.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-87-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-87-1.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon.png
--------------------------------------------------------------------------------
/provisioning/CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/provisioning/CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision.gpg
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon@2x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon@3x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/LogoCell.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import CloudKit
3 |
4 | class LogoCell: UITableViewCell {
5 | @IBOutlet weak var logoImageView: UIImageView!
6 | }
7 |
8 | extension LogoCell: Identifiable {}
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | xcode_project: CocoaHeadsNL/CocoaHeadsNL.xcodeproj
3 | xcode_scheme: CocoaHeadsNL
4 | xcode_destination: platform=iOS Simulator,OS=13.3,name=iPhone 11
5 | sudo: false
6 | osx_image: xcode11.3
7 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogo.imageset/CocoaHeadsNL.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogo.imageset/CocoaHeadsNL.pdf
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogo.imageset/CocoaHeadsNL-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogo.imageset/CocoaHeadsNL-1.pdf
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon@2x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon@3x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIcon.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogoOnlyLight.imageset/CocoaHeadsNL.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogoOnlyLight.imageset/CocoaHeadsNL.pdf
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder@2x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder@3x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIconStroked@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIconStroked@2x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIconStroked@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIconStroked@3x.png
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/MainViewContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabBarContainer.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bart Hoffman on 16/03/15.
6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/itemNotification/itemNotification.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/generalNotification/generalNotification.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/UIColor+Extensions.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public func UIColorWithRGB(_ red: Int, green: Int, blue: Int) -> UIColor {
4 | return UIColor(red: CGFloat(red) / 255.0,
5 | green: CGFloat(green) / 255.0,
6 | blue: CGFloat(blue) / 255.0,
7 | alpha: 1.0)
8 | }
9 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Job+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Job+CoreDataClass.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 20-02-18.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 | //
9 |
10 | import Foundation
11 | import CoreData
12 |
13 | @objc(Job)
14 | public class Job: NSManagedObject {
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogoOnlyLight.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "CocoaHeadsNL.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true
14 | }
15 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/TabBarController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabBarController.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bart Hoffman on 15/03/15.
6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class TabBarController: UITabBarController, UITabBarControllerDelegate {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Meetup+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Meetup+CoreDataClass.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 20-02-18.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 | //
9 |
10 | import Foundation
11 | import CoreData
12 |
13 | @objc(Meetup)
14 | public class Meetup: NSManagedObject {
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Company+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Company+CoreDataClass.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 20-02-18.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 | //
9 |
10 | import Foundation
11 | import CoreData
12 |
13 | @objc(Company)
14 | public class Company: NSManagedObject {
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/ParseConfig-template.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | applicationId
6 | PARSE_APPLICATION_ID
7 | clientKey
8 | PARSE_CLIENT_KEY
9 |
10 |
11 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Contributor+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Contributor+CoreDataClass.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 20-02-18.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 | //
9 |
10 | import Foundation
11 | import CoreData
12 |
13 | @objc(Contributor)
14 | public class Contributor: NSManagedObject {
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/ItemServiceExtension/ItemServiceExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 | com.apple.security.app-sandbox
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X Finder
2 | .DS_Store
3 |
4 | # Xcode per-user config
5 | *.mode1
6 | *.mode1v3
7 | *.mode2v3
8 | *.perspective
9 | *.perspectivev3
10 | *.pbxuser
11 | xcuserdata
12 | *.xccheckout
13 |
14 | # Build products
15 | build/
16 | *.o
17 | *.LinkFileList
18 | *.hmap
19 |
20 | # Automatic backup files
21 | *~.nib/
22 | *.swp
23 | *~
24 | *.dat
25 | *.dep
26 |
27 | # Cocoapods
28 | Pods
29 |
30 | # AppCode specific files
31 | .idea/
32 | *.iml
33 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/NSLocalizedString+Convenience.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSLocalizedString+Convenience.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bruno Scheele on 18/01/2017.
6 | // Copyright © 2017 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Returns `NSLocalizedString(key, comment: "")`
12 | public func NSLocalizedString(_ key: String) -> String {
13 | return NSLocalizedString(key, comment: "")
14 | }
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | CocoaHeadsNL/CloudCode/config/global.json
2 | CocoaHeadsNL/CocoaHeadsNL/Settings.bundle/Acknowledgements.plist
3 | CocoaHeadsNL/CocoaHeadsNL/ParseConfig.plist
4 | CocoaHeadsNL/Pods
5 | xcuserdata
6 | CocoaHeadsNL/parse
7 |
8 | screenshots
9 | report.xml
10 | Error*.png
11 | *.app.dSym.zip
12 | *.ipa
13 | *.itmsp
14 | *.mobileprovision
15 | config.js
16 | eckey.pem
17 | CocoaHeadsNL/CloudKitSync/node_modules
18 | cloudkit.js
19 | config.js.dev
20 | config.js.prod
21 |
22 | .DS_Store
23 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "CocoaHeadsNL.pdf",
5 | "idiom" : "universal"
6 | },
7 | {
8 | "appearances" : [
9 | {
10 | "appearance" : "luminosity",
11 | "value" : "dark"
12 | }
13 | ],
14 | "filename" : "CocoaHeadsNL-1.pdf",
15 | "idiom" : "universal"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "about-1.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "about-2.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "about-3.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "JobsTabIcon.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "JobsTabIcon@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "JobsTabIcon@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | # Xcode
2 | # Build, test, and archive an Xcode workspace on macOS.
3 | # Add steps that install certificates, test, sign, and distribute an app, save build artifacts, and more:
4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/xcode
5 |
6 | pool:
7 | vmImage: 'macos-latest'
8 |
9 | trigger:
10 | - master
11 | - feature/*
12 |
13 | steps:
14 | - task: Xcode@5
15 | inputs:
16 | actions: 'build'
17 | scheme: 'CocoaHeadsNL'
18 | sdk: 'iphoneos'
19 | configuration: 'Debug'
20 | xcodeVersion: '11'
21 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "EventsTabIcon.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "EventsTabIcon@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "EventsTabIcon@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "MeetupPlaceholder.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "MeetupPlaceholder@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "MeetupPlaceholder@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CloudKitSync/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "CocoaHeadsNLAppSync",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "generate-config": "cp config.js.example config.js",
9 | "install-cloudkit-js": "curl https://cdn.apple-cloudkit.com/ck/1/cloudkit.js > cloudkit.js"
10 | },
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "node-fetch": "^1.3.3",
15 | "request": "^2.67.0",
16 | "promise": "^7.0.2"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "CompaniesTabIcon.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "CompaniesTabIconStroked@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "CompaniesTabIconStroked@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/MapAnnotation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapAnnotation.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bart Hoffman on 14/03/15.
6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import MapKit
10 |
11 | class MapAnnotation: NSObject, MKAnnotation {
12 | var coordinate: CLLocationCoordinate2D
13 | var title: String?
14 | var subtitle: String?
15 |
16 | init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String) {
17 | self.coordinate = coordinate
18 | self.title = title
19 | self.subtitle = subtitle
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We welcome any additions to this repository. Please note, all contributions are considered donations to Stichting CocoaHeadsNL. Contributing constitutes transfer of copyright.
4 |
5 | ## Make sure to check with us before starting your work.
6 |
7 | Join us on Slack: [https://join.slack.com/t/cocoaheadsnl/shared_invite/enQtNjQwMjQzNzgxODQ2LThkYWY3ZDhkOGMyMmQ1ZTZkMzc3ZjYxY2VjMzY4MDkwOTEzYTJhZWNjYTEzMzBiMGM5ZjY4MDAzMDNiNDE2NzA](https://join.slack.com/t/cocoaheadsnl/shared_invite/enQtNjQwMjQzNzgxODQ2LThkYWY3ZDhkOGMyMmQ1ZTZkMzc3ZjYxY2VjMzY4MDkwOTEzYTJhZWNjYTEzMzBiMGM5ZjY4MDAzMDNiNDE2NzA)
8 |
9 |
10 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/UINavigationItem+Banner.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public extension UINavigationItem {
4 |
5 | func setupForRootViewController(withTitle title: String) {
6 |
7 | let imageView = UIImageView(image: UIImage(named: "Banner"))
8 | imageView.accessibilityTraits = UIAccessibilityTraits(rawValue: imageView.accessibilityTraits.rawValue & ~UIAccessibilityTraits.image.rawValue)
9 | imageView.accessibilityTraits = UIAccessibilityTraits(rawValue: imageView.accessibilityTraits.rawValue | UIAccessibilityTraits.header.rawValue)
10 | self.titleView = imageView
11 |
12 | self.title = title
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/UIImage+Resize.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | extension UIImage {
4 | func resizedImageWithBounds(_ bounds: CGSize) -> UIImage {
5 | let horizontalRatio = bounds.width / size.width
6 | let verticalRatio = bounds.height / size.height
7 | let ratio = min(horizontalRatio, verticalRatio)
8 | let newSize = CGSize(width: ceil(size.width * ratio), height: ceil(size.height * ratio))
9 |
10 | UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
11 | draw(in: CGRect(origin: CGPoint.zero, size: newSize))
12 | let newImage = UIGraphicsGetImageFromCurrentImageContext()
13 | UIGraphicsEndImageContext()
14 |
15 | return newImage!
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/provisioning/README.md:
--------------------------------------------------------------------------------
1 | We create the gpg files by running commands like...
2 | ```
3 | gpg --symmetric --cipher-algo AES256 CocoaHeadsNL-AppStore.mobileprovision
4 | gpg --symmetric --cipher-algo AES256 CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision
5 | gpg --symmetric --cipher-algo AES256 CocoaHeadsNL-AppStore-Item-Notification.mobileprovision
6 | gpg --symmetric --cipher-algo AES256 CocoaHeadsNL-AppStore-General-Notification.mobileprovision
7 | gpg --symmetric --cipher-algo AES256 AppStoreCertificates.p12
8 | ```
9 |
10 | The password on the P12 file is the same as the password used to perform the encryption. The password is also stored on Github as a secret in the env var `PROVISIONING_PASSWORD`.
11 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/OnlineCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnlineCell.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 19/04/2020.
6 | // Copyright © 2020 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class OnlineCell: UITableViewCell {
12 |
13 | @IBOutlet weak var onlineButton: UIButton!
14 | @IBAction func buttonPressed(_ sender: AnyObject) {
15 | if let url = URL(string: "https://youtube.com/stichtingcocoaheadsnl"), UIApplication.shared.canOpenURL(url) {
16 | UIApplication.shared.open(url, options: [:], completionHandler: { _ in
17 | })
18 | }
19 | }
20 |
21 | }
22 |
23 | extension OnlineCell: Identifiable {}
24 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Identifiable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Identifiable.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 10/01/2020.
6 | // Copyright © 2020 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol Identifiable: class {
12 | static var identifier: String { get }
13 | }
14 |
15 | public extension Identifiable {
16 |
17 | static var identifier: String {
18 | return String(describing: Self.self)
19 | }
20 | }
21 |
22 | extension UIStoryboard {
23 |
24 | public func instantiateViewController(type: T.Type) -> T where T: UIViewController {
25 | return instantiateViewController(withIdentifier: type.identifier) as! T // swiftlint:disable:this force_cast
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/DayLabelColorHistory.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "gray-gamma-22",
11 | "components" : {
12 | "white" : "0.000",
13 | "alpha" : "0.650"
14 | }
15 | }
16 | },
17 | {
18 | "idiom" : "universal",
19 | "appearances" : [
20 | {
21 | "appearance" : "luminosity",
22 | "value" : "dark"
23 | }
24 | ],
25 | "color" : {
26 | "color-space" : "gray-gamma-22",
27 | "components" : {
28 | "white" : "1.000",
29 | "alpha" : "0.650"
30 | }
31 | }
32 | }
33 | ]
34 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNLUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/NSAttributedStringExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSAttributedStringExtension.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Sidney de Koning on 29/05/2017.
6 | // Copyright © 2017 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension NSAttributedString {
12 |
13 | internal convenience init?(html: String) {
14 | guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else {
15 | return nil
16 | }
17 |
18 | guard let attributedString = try? NSMutableAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) else {
19 | return nil
20 | }
21 |
22 | self.init(attributedString: attributedString)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Contributor+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Contributor+CoreDataProperties.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 20-02-18.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 | //
9 |
10 | import Foundation
11 | import CoreData
12 |
13 | extension Contributor {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Contributor")
17 | }
18 |
19 | @NSManaged public var recordID: NSObject?
20 | @NSManaged public var recordName: String?
21 | @NSManaged public var avatarUrl: String?
22 | @NSManaged public var contributorId: Int64
23 | @NSManaged public var commitCount: Int32
24 | @NSManaged public var name: String?
25 | @NSManaged public var url: String?
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Job+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Job+CoreDataProperties.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 20-02-18.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 | //
9 |
10 | import Foundation
11 | import CoreData
12 |
13 | extension Job {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Job")
17 | }
18 |
19 | @NSManaged public var recordID: NSObject?
20 | @NSManaged public var recordName: String?
21 | @NSManaged public var content: String?
22 | @NSManaged public var date: Date?
23 | @NSManaged public var link: String?
24 | @NSManaged public var title: String?
25 | @NSManaged public var logoUrlString: String?
26 | @NSManaged public var logo: NSData?
27 | @NSManaged public var companyName: String?
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/DayLabelColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "0.000",
13 | "alpha" : "1.000",
14 | "blue" : "0.000",
15 | "green" : "0.000"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "1.000",
31 | "alpha" : "1.000",
32 | "blue" : "1.000",
33 | "green" : "1.000"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/DayBackgroundViewColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "232",
13 | "alpha" : "1.000",
14 | "blue" : "80",
15 | "green" : "88"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0.117",
31 | "alpha" : "1.000",
32 | "blue" : "0.653",
33 | "green" : "0.639"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/DayBackgroundViewColorHistory.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "169",
13 | "alpha" : "1.000",
14 | "blue" : "166",
15 | "green" : "166"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0.500",
31 | "alpha" : "1.000",
32 | "blue" : "0.808",
33 | "green" : "0.200"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/DayBackgroundViewColorToday.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "127",
13 | "alpha" : "1.000",
14 | "blue" : "33",
15 | "green" : "214"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0.890",
31 | "alpha" : "1.000",
32 | "blue" : "0.808",
33 | "green" : "0.200"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/CoreDataStack.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CoreData
3 |
4 | final class CoreDataStack {
5 | static let shared = CoreDataStack()
6 |
7 | lazy var persistentContainer: NSPersistentContainer = {
8 | let container = NSPersistentContainer(name: "Model")
9 | container.loadPersistentStores { _, error in
10 | if let error = error {
11 | print("Unable to Load Persistent Store")
12 | print("\(error), \(error.localizedDescription)")
13 | }
14 | }
15 | container.viewContext.automaticallyMergesChangesFromParent = true
16 |
17 | return container
18 | }()
19 |
20 | var viewContext: NSManagedObjectContext {
21 | return persistentContainer.viewContext
22 | }
23 |
24 | var newBackgroundContext: NSManagedObjectContext {
25 | return persistentContainer.newBackgroundContext()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | # Trigger the workflow on push or pull request,
5 | # but only for the master branch
6 | push:
7 | branches:
8 | - master
9 | pull_request:
10 | branches:
11 | - master
12 | jobs:
13 | SwiftLint:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v1
17 | - name: GitHub Action for SwiftLint
18 | uses: norio-nomura/action-swiftlint@3.0.1
19 |
20 | Test:
21 | runs-on: macOS-latest
22 | steps:
23 | - uses: actions/checkout@v1
24 | - name: List available Xcode versions
25 | run: ls /Applications | grep Xcode
26 | - name: Select Xcode
27 | run: sudo xcode-select -switch /Applications/Xcode_12.app && /usr/bin/xcodebuild -version
28 | - name: Run unit tests
29 | run: xcodebuild test -scheme CocoaHeadsNL -project CocoaHeadsNL/CocoaHeadsNL.xcodeproj -destination 'platform=iOS Simulator,name=iPhone 11,OS=14.0' | xcpretty && exit ${PIPESTATUS[0]}
30 |
31 |
32 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/JobDataSource.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class JobDataSource: DetailDataSource {
4 | var job: Job {
5 | return object as! Job // swiftlint:disable:this force_cast
6 | }
7 |
8 | override var title: String? {
9 | return job.title
10 | }
11 |
12 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
13 | return 3
14 | }
15 |
16 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
17 | switch (indexPath as NSIndexPath).row {
18 | case 0:
19 | return logoCellWithFile(job.logoImage, forTableView: tableView, forRowAt: indexPath)
20 | case 1:
21 | return titleCellWithText(job.title, forTableView: tableView, forRowAt: indexPath)
22 | case 2:
23 | return dataCellWithHTML(job.content, forTableView: tableView, forRowAt: indexPath)
24 | default:
25 | fatalError("This should not happen.")
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/CocoaHeadsNL.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 | com.apple.developer.associated-domains
8 |
9 | applinks:cocoaheads.nl
10 | webcredentials:*.cocoaheads.nl
11 |
12 | com.apple.developer.icloud-container-identifiers
13 |
14 | iCloud.nl.cocoaheads.app.CocoaHeadsNL
15 |
16 | com.apple.developer.icloud-services
17 |
18 | CloudKit
19 |
20 | com.apple.developer.ubiquity-kvstore-identifier
21 | $(TeamIdentifierPrefix)$(CFBundleIdentifier)
22 | com.apple.security.app-sandbox
23 |
24 | com.apple.security.network.client
25 |
26 | com.apple.security.personal-information.location
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CloudKitSync/config.js.example:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This is the container configuration that gets passed to CloudKit.configure.
7 | Customize this for your own environment.
8 | */
9 |
10 | module.exports.githubAuthOptions = {'user': '', 'pass': ''}
11 |
12 | module.exports.meetupApiKey = ""
13 |
14 | module.exports.slackHookUrl = undefined; //
15 |
16 | module.exports.containerConfig = {
17 | // Replace this with a container that you own.
18 | containerIdentifier:'iCloud.nl.cocoaheads.app.CocoaHeadsNL',
19 |
20 | environment: 'development',
21 |
22 | serverToServerKeyAuth: {
23 | // Generate a key ID through CloudKit Dashboard and insert it here.
24 | keyID: '',
25 |
26 | // This should reference the private key file that you used to generate the above key ID.
27 | privateKeyFile: __dirname + '/eckey.pem'
28 | }
29 | };
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Company+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Company+CoreDataProperties.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 20-02-18.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 | //
9 |
10 | import Foundation
11 | import CoreData
12 |
13 | extension Company {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Company")
17 | }
18 |
19 | @NSManaged public var recordID: NSObject?
20 | @NSManaged public var recordName: String?
21 | @NSManaged public var name: String?
22 | @NSManaged public var place: String?
23 | @NSManaged public var website: String?
24 | @NSManaged public var streetAddress: String?
25 | @NSManaged public var zipCode: String?
26 | @NSManaged public var companyDescription: String?
27 | @NSManaged public var emailAddress: String?
28 | @NSManaged public var latitude: Double
29 | @NSManaged public var longitude: Double
30 | @NSManaged public var logo: NSData?
31 | @NSManaged public var smallLogo: NSData?
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/HTMLDataCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTMLDataCell.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Sidney de Koning on 29/05/2017.
6 | // Copyright © 2017 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class HTMLDataCell: UITableViewCell {
12 |
13 | @IBOutlet weak var textView: UITextView!
14 |
15 | var html: String? {
16 | didSet {
17 | guard let html = html, html != oldValue else { return }
18 | let inputText = "\(html)"
19 | let attributedString = NSAttributedString(html: inputText)
20 |
21 | self.textView.attributedText = attributedString
22 | self.textView.isScrollEnabled = false
23 | self.textView.delegate = self
24 | }
25 | }
26 | }
27 |
28 | extension HTMLDataCell: UITextViewDelegate {
29 |
30 | func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
31 | UIApplication.shared.open(URL, options: [:])
32 | return false
33 | }
34 | }
35 |
36 | extension HTMLDataCell: Identifiable {}
37 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/UIAlertController+FetchErrors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIAlertController+FetchErrors.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bruno Scheele on 18/01/2017.
6 | // Copyright © 2017 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIAlertController {
12 | /// Returns a UIAlertController with the .alert style for a failed fetch request.
13 | ///
14 | /// - Parameter fetching: What failed to fetch. Will be prefixed with 'the list of ' (e.g. 'the list of ')
15 | /// - Returns: A UIAlertController to display.
16 | static func fetchErrorDialog(whileFetching fetching: String, error: Error) -> UIAlertController {
17 | let title = NSLocalizedString("Fetch failed")
18 | let message = String(format: NSLocalizedString("There was a problem fetching the list of %@; please try again.\n%@"), fetching, error.localizedDescription)
19 | let okButtonTitle = NSLocalizedString("OK")
20 |
21 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
22 | alertController.addAction(UIAlertAction(title: okButtonTitle, style: .default, handler: nil))
23 | return alertController
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CloudKitSync/jobs/loadEventInfo.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var Promise = require('promise');
4 | var request = require('request');
5 | var config = require('../config');
6 |
7 | exports.load = function() {
8 | var loadEventInfoPromise = new Promise(function(resolve, reject) {
9 |
10 | var options = {
11 | url: 'https://api.meetup.com/2/events?&sign=true&photo-host=public&group_urlname=cocoaheadsnl&desc=true&status=upcoming,past&key='+config.meetupApiKey,
12 | headers: { 'User-Agent': 'CocoaHeadsNL-Cloud-Sync', 'Accept-Charset': 'UTF-8'},
13 | }
14 |
15 | request(options, function(error, response, body){
16 | if (error) {
17 | reject(error);
18 | console.log("Error on fetching meetup information: " + error)
19 | return;
20 | }
21 |
22 | if (response.statusCode != 200) {
23 | reject(response.statusCode);
24 | console.log("Invalid HTTP status code on meetup information: " + response.statusCode)
25 | return;
26 | }
27 | resolve(body);
28 | });
29 | }).then(function(body) {
30 | var meetupData = JSON.parse(body);
31 | return Promise.resolve(meetupData.results)
32 | })
33 |
34 | return loadEventInfoPromise
35 | }
36 |
--------------------------------------------------------------------------------
/provisioning/App-Store.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | compileBitcode
6 |
7 | iCloudContainerEnvironment
8 | Production
9 | method
10 | app-store
11 | teamID
12 | D55PL7PU7S
13 | uploadBitcode
14 |
15 | uploadSymbols
16 |
17 | signingStyle
18 | manual
19 | signingCertificate
20 | Apple Distribution: Stichting CocoaheadsNL
21 | provisioningProfiles
22 |
23 | nl.cocoaheads.app.CocoaHeadsNL.generalNotification
24 | f310cafd-4976-454e-9a54-bd5e5f6ce2c9
25 | nl.cocoaheads.app.CocoaHeadsNL.itemNotification
26 | c5853b6d-9a45-41a4-90bd-e12344b6b613
27 | nl.cocoaheads.app.CocoaHeadsNL.ItemServiceExtension
28 | b2a2ec45-f1f8-47b3-9200-09c808d989b2
29 | nl.cocoaheads.app.CocoaHeadsNL
30 | 979eaa25-8ce3-4e21-a59e-5ec136f57adb
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/RequestReview.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestReview.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 29/03/2017.
6 | // Copyright © 2017 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import StoreKit
10 |
11 | public class RequestReview: NSObject {
12 |
13 | private static let defaultKeyRatingLastRequestDate = "app_rating_last_request_date"
14 |
15 | public static func requestReview() {
16 | if let lastRequestDate = UserDefaults.standard.object(forKey: RequestReview.defaultKeyRatingLastRequestDate) as? Date {
17 | if lastRequestDate.compare(Date()) == ComparisonResult.orderedAscending {
18 | SKStoreReviewController.requestReview()
19 | let reviewRequestDate = NSCalendar.current.date(byAdding: .day, value: 100, to: Date())
20 | UserDefaults.standard.set(reviewRequestDate, forKey: defaultKeyRatingLastRequestDate)
21 | }
22 | } else {
23 | // Ask for first review 7 days from now
24 | let reviewRequestDate = NSCalendar.current.date(byAdding: .day, value: 7, to: Date())
25 | UserDefaults.standard.set(reviewRequestDate, forKey: defaultKeyRatingLastRequestDate)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Banner@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "BannerDark@1x.png",
11 | "appearances" : [
12 | {
13 | "appearance" : "luminosity",
14 | "value" : "dark"
15 | }
16 | ],
17 | "scale" : "1x"
18 | },
19 | {
20 | "idiom" : "universal",
21 | "filename" : "Banner@2x.png",
22 | "scale" : "2x"
23 | },
24 | {
25 | "idiom" : "universal",
26 | "filename" : "BannerDark@2x.png",
27 | "appearances" : [
28 | {
29 | "appearance" : "luminosity",
30 | "value" : "dark"
31 | }
32 | ],
33 | "scale" : "2x"
34 | },
35 | {
36 | "idiom" : "universal",
37 | "filename" : "Banner@3x.png",
38 | "scale" : "3x"
39 | },
40 | {
41 | "idiom" : "universal",
42 | "filename" : "BannerDark@3x.png",
43 | "appearances" : [
44 | {
45 | "appearance" : "luminosity",
46 | "value" : "dark"
47 | }
48 | ],
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "version" : 1,
54 | "author" : "xcode"
55 | }
56 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/TitleCell.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class TitleCell: UITableViewCell {
4 | @IBOutlet weak var titleLabel: UILabel!
5 |
6 | var content: String? {
7 | didSet {
8 | self.titleLabel.text = content
9 | }
10 | }
11 |
12 | lazy var dateFormatter: DateFormatter = {
13 | let dateFormatter = DateFormatter()
14 | let dateFormat = DateFormatter.dateFormat(fromTemplate: "MMMMd",
15 | options: 0,
16 | locale: Locale.current)
17 | dateFormatter.dateFormat = dateFormat
18 | return dateFormatter
19 | }()
20 |
21 | lazy var timeFormatter: DateFormatter = {
22 | let dateFormatter = DateFormatter()
23 | dateFormatter.timeStyle = .short
24 | return dateFormatter
25 | }()
26 |
27 | var date: Date? {
28 | didSet {
29 | if let date = date {
30 | let dateString = dateFormatter.string(from: date)
31 | let timeString = timeFormatter.string(from: date)
32 | self.titleLabel.text = dateString + ", " + timeString
33 | } else {
34 | self.titleLabel.text = "-"
35 | }
36 | }
37 | }
38 | }
39 |
40 | extension TitleCell: Identifiable {}
41 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/generalNotification/NotificationViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotificationViewController.swift
3 | // generalNotification
4 | //
5 | // Created by Bart Hoffman on 10/03/2018.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import UserNotifications
11 | import UserNotificationsUI
12 |
13 | class NotificationViewController: UIViewController, UNNotificationContentExtension {
14 |
15 | @IBOutlet var postImageView: UIImageView?
16 | @IBOutlet var titleLabel: UILabel?
17 | @IBOutlet var bodyLabel: UILabel?
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 | // Do any required interface initialization here.
22 | }
23 |
24 | func didReceive(_ notification: UNNotification) {
25 |
26 | self.titleLabel?.text = notification.request.content.title
27 | self.bodyLabel?.text = notification.request.content.body
28 |
29 | if let attachment = notification.request.content.attachments.first {
30 | if attachment.url.startAccessingSecurityScopedResource() {
31 | if let data = NSData(contentsOfFile: attachment.url.path) as Data? {
32 | self.postImageView?.image = UIImage(data: data)
33 | attachment.url.stopAccessingSecurityScopedResource()
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/itemNotification/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | itemNotification
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | XPC!
19 | CFBundleShortVersionString
20 | $(CURRENT_PROJECT_VERSION)
21 | CFBundleVersion
22 | auto-generated
23 | NSExtension
24 |
25 | NSExtensionAttributes
26 |
27 | UNNotificationExtensionCategory
28 | nl.cocoaheads.app.CocoaHeadsNL.itemNotification
29 | UNNotificationExtensionInitialContentSizeRatio
30 | 1
31 |
32 | NSExtensionMainStoryboard
33 | MainInterface
34 | NSExtensionPointIdentifier
35 | com.apple.usernotifications.content-extension
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/ItemServiceExtension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | ItemServiceExtension
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | XPC!
19 | CFBundleShortVersionString
20 | $(CURRENT_PROJECT_VERSION)
21 | CFBundleVersion
22 | auto-generated
23 | NSAppTransportSecurity
24 |
25 | NSExceptionDomains
26 |
27 | cocoaheads.nl
28 |
29 | NSExceptionRequiresForwardSecrecy
30 |
31 |
32 |
33 |
34 | NSExtension
35 |
36 | NSExtensionPointIdentifier
37 | com.apple.usernotifications.service
38 | NSExtensionPrincipalClass
39 | $(PRODUCT_MODULE_NAME).NotificationService
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | on:
4 | # Trigger the workflow on push or pull request,
5 | # but only for the master branch
6 | push:
7 | branches:
8 | - master
9 | jobs:
10 | Build:
11 | runs-on: macOS-latest
12 | steps:
13 | - uses: actions/checkout@v1
14 | - name: Install GPG
15 | run: brew install gnupg
16 | - name: List available Xcode versions
17 | run: ls /Applications | grep Xcode
18 | - name: Decrypt large secret
19 | run: ./provisioning/import_provisioning.sh
20 | env:
21 | PROVISIONING_PASSWORD: ${{ secrets.provisioning_password }}
22 | - name: Select Xcode
23 | run: sudo xcode-select -switch /Applications/Xcode_12.app && /usr/bin/xcodebuild -version
24 | - name: Build archive
25 | run: xcodebuild -sdk iphoneos -project CocoaHeadsNL/CocoaHeadsNL.xcodeproj -configuration Release -scheme CocoaHeadsNL -derivedDataPath DerivedData -archivePath DerivedData/Archive/CocoaHeadsNL archive
26 | - name: Export Archive
27 | run: xcodebuild -exportArchive -archivePath DerivedData/Archive/CocoaHeadsNL.xcarchive -exportOptionsPlist provisioning/App-Store.plist -exportPath DerivedData/ipa
28 | - name: Dump file hierarchy
29 | run: ls -R
30 | - name: Deploy App to Apple
31 | run: xcrun altool --upload-app --type ios --file DerivedData/ipa/CocoaHeadsNL.ipa --username "${{ secrets.appstore_connect_username }}" --password "${{ secrets.appstore_connect_password }}" --verbose
32 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/generalNotification/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | generalNotification
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | XPC!
19 | CFBundleShortVersionString
20 | $(CURRENT_PROJECT_VERSION)
21 | CFBundleVersion
22 | auto-generated
23 | NSExtension
24 |
25 | NSExtensionAttributes
26 |
27 | UNNotificationExtensionCategory
28 | nl.cocoaheads.app.CocoaHeadsNL.generalNotification
29 | UNNotificationExtensionInitialContentSizeRatio
30 | 1
31 | UNNotificationExtensionDefaultContentHidden
32 |
33 |
34 | NSExtensionMainStoryboard
35 | MainInterface
36 | NSExtensionPointIdentifier
37 | com.apple.usernotifications.content-extension
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Meetup+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Meetup+CoreDataProperties.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 20-02-18.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 | //
9 |
10 | import Foundation
11 | import CoreData
12 |
13 | extension Meetup {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Meetup")
17 | }
18 |
19 | @NSManaged public var recordID: NSObject?
20 | @NSManaged public var recordName: String?
21 | @NSManaged public var duration: Int32
22 | @NSManaged public var latitude: Double
23 | @NSManaged public var longitude: Double
24 | @NSManaged public var locationName: String?
25 | @NSManaged public var meetupDescription: String?
26 | @NSManaged public var meetupId: String?
27 | @NSManaged public var name: String?
28 | @NSManaged public var rsvpLimit: Int32
29 | @NSManaged public var time: Date?
30 | @NSManaged public var yesRsvpCount: Int32
31 | @NSManaged public var logo: NSData?
32 | @NSManaged public var nextEvent: Bool
33 | @NSManaged public var smallLogo: NSData?
34 | @NSManaged public var location: String?
35 | @NSManaged public var meetupUrl: String?
36 | @NSManaged public var year: Int32
37 |
38 | @objc
39 | var sectionName: String {
40 | if isToday {
41 | return NSLocalizedString("Today", comment: "Section title for todays meetup.")
42 | }
43 | if isUpcoming {
44 | return NSLocalizedString("Upcoming", comment: "Section title for upcoming meetups.")
45 | }
46 |
47 | return "\(year)"
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CloudKitSync/README.md:
--------------------------------------------------------------------------------
1 | Code and instructions derived from [Apple Sample code](https://developer.apple.com/library/prerelease/ios/samplecode/CloudAtlas/Introduction/Intro.html#//apple_ref/doc/uid/TP40014599).
2 |
3 | ## Install dependencies
4 |
5 | Our script uses the npm module *node-fetch* and the *CloudKit JS* library. Install them by running the following
6 | commands from the same directory as this README.
7 | ```
8 | npm install
9 | npm run-script generate-config
10 | npm run-script install-cloudkit-js
11 | ```
12 |
13 | ## Generate a private key
14 |
15 | If you are using a Mac, you already have OpenSSL installed and you can generate a private key with this command (make
16 | sure you are in the same directory as this README).
17 | ```
18 | openssl ecparam -name prime256v1 -genkey -noout -out eckey.pem
19 | ```
20 | This will create the file `eckey.pem`. *Do not publish this file!*
21 |
22 | ## Create a Server-to-Server key in CloudKit Dashboard
23 |
24 | In [CloudKit Dashboard](https://icloud.developer.apple.com/dashboard) select the CocoaHeadsNL container and navigate to
25 | `API Access -> Server-to-Server Keys`. Copy the public key in the output of this command:
26 | ```
27 | openssl ec -in eckey.pem -pubout
28 | ```
29 |
30 | and paste it into the *Public Key* text field of the new key. Hit *Save* and the *Key ID* attribute will get populated.
31 | Copy this ID and fill in the **keyID** property in `config.js`.
32 |
33 | ## Make sure all placeholders in config.js are replaced with valid values.
34 |
35 | - github username
36 | - github access token
37 | - Meetup API Key
38 | - Slack Hook URL
39 | - Server-2-server CloudKit key
40 |
41 | See config.js for more information.
42 |
43 | ## Run the script
44 |
45 | ```
46 | node index.js
47 | ```
48 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CloudKitSync/jobs/loadJobInfo.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var xmlreader = require('./xmlreader.js');
3 | var Promise = require('promise');
4 | var request = require('request');
5 | var config = require('../config');
6 |
7 | exports.load = function() {
8 | var loadJobInfoPromise = new Promise(function(resolve, reject) {
9 | var options = {
10 | url: 'http://jobs.cocoaheads.nl/feed.xml',
11 | }
12 |
13 | request(options, function(error, response, body){
14 | if (error) {
15 | reject(error);
16 | console.log("Error on fetching Job information: " + error)
17 | return;
18 | }
19 |
20 | if (response.statusCode != 200) {
21 | reject(response.statusCode);
22 | console.log("Invalid HTTP status code on Job information: " + response.statusCode)
23 | return;
24 | }
25 |
26 | var jobPostings = []
27 |
28 | xmlreader.read(body, function (err, res){
29 | if (err) {
30 | reject(err);
31 | return;
32 | }
33 | var rssChannel = res.rss.channel;
34 | rssChannel.item.each(function (i, newJobItem){
35 | var jobPosting = {recordType: 'Job',
36 | fields: {
37 | link: {value: newJobItem.link.text()},
38 | title: {value: newJobItem.title.text()},
39 | content: {value: newJobItem.description.text()},
40 | date: {value: new Date(newJobItem.pubDate.text()).getTime()},
41 | logoUrl: {value: newJobItem['atom:link'].attributes()['href']}
42 | }
43 | }
44 |
45 | jobPostings.push(jobPosting)
46 | });
47 | });
48 |
49 | resolve(jobPostings);
50 | });
51 | });
52 |
53 | return loadJobInfoPromise;
54 | }
55 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL.xcodeproj/project.xcworkspace/xcshareddata/CocoaHeadsNL.xccheckout:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDESourceControlProjectFavoriteDictionaryKey
6 |
7 | IDESourceControlProjectIdentifier
8 | C5835790-E370-482D-B683-43C2E8CA66B3
9 | IDESourceControlProjectName
10 | CocoaHeadsNL
11 | IDESourceControlProjectOriginsDictionary
12 |
13 | 51DBBF3565E407743FF3E195305ECEEB9BD05414
14 | ssh://bitbucket.org/cocoaheadsnl/cocoaheadsnl.git
15 |
16 | IDESourceControlProjectPath
17 | CocoaHeadsNL/CocoaHeadsNL.xcodeproj
18 | IDESourceControlProjectRelativeInstallPathDictionary
19 |
20 | 51DBBF3565E407743FF3E195305ECEEB9BD05414
21 | ../../..
22 |
23 | IDESourceControlProjectURL
24 | ssh://bitbucket.org/cocoaheadsnl/cocoaheadsnl.git
25 | IDESourceControlProjectVersion
26 | 111
27 | IDESourceControlProjectWCCIdentifier
28 | 51DBBF3565E407743FF3E195305ECEEB9BD05414
29 | IDESourceControlProjectWCConfigurations
30 |
31 |
32 | IDESourceControlRepositoryExtensionIdentifierKey
33 | public.vcs.git
34 | IDESourceControlWCCIdentifierKey
35 | 51DBBF3565E407743FF3E195305ECEEB9BD05414
36 | IDESourceControlWCCName
37 | cocoaheadsnl
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/itemNotification/NotificationViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotificationViewController.swift
3 | // itemNotification
4 | //
5 | // Created by Bart Hoffman on 10/03/2018.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import UserNotifications
11 | import UserNotificationsUI
12 | import CloudKit
13 |
14 | class NotificationViewController: UIViewController, UNNotificationContentExtension {
15 |
16 | @IBOutlet var titlelabel: UILabel?
17 | @IBOutlet var nameLabel: UILabel?
18 | @IBOutlet var postImageView: UIImageView?
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 | // Do any required interface initialization here.
23 | }
24 |
25 | func didReceive(_ notification: UNNotification) {
26 |
27 | if let userInfo = notification.request.content.userInfo as NSDictionary as? [String: Any] {
28 | let query = CKQueryNotification(fromRemoteNotificationDictionary: userInfo)!
29 |
30 | if let title = query.recordFields?["title"] as? String,
31 | let name = query.recordFields?["name"] as? String {
32 | self.titlelabel?.text = title
33 | self.nameLabel?.text = name
34 | }
35 | if let attachment = notification.request.content.attachments.first {
36 | if attachment.url.startAccessingSecurityScopedResource() {
37 | if let data = NSData(contentsOfFile: attachment.url.path) as Data? {
38 | self.postImageView?.image = UIImage(data: data)
39 | attachment.url.stopAccessingSecurityScopedResource()
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/ButtonCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ButtonCell.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 29-09-16.
6 | // Copyright © 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ButtonCell: UITableViewCell {
12 |
13 | @IBOutlet weak var titleButton: UIButton!
14 | @IBAction func buttonPressed(_ sender: AnyObject) {
15 | if let url = selectURL() {
16 | UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: { _ in
17 | })
18 | }
19 | }
20 |
21 | fileprivate func selectURL() -> URL? {
22 | guard let urlString = urlString else {
23 | return nil
24 | }
25 |
26 | guard let url = URL(string: urlString) else {
27 | return nil
28 | }
29 |
30 | let appUrlString = "meetup:/\(url.path)"
31 |
32 | let appUrl = URL(string: appUrlString)
33 |
34 | if appUrl != nil && UIApplication.shared.canOpenURL(appUrl!) {
35 | return appUrl
36 | } else if UIApplication.shared.canOpenURL(url) {
37 | return url
38 | } else {
39 | return nil
40 | }
41 | }
42 |
43 | var title: String? {
44 | didSet {
45 | self.titleButton.setTitle(title, for: .normal)
46 | }
47 | }
48 |
49 | var urlString: String?
50 | }
51 |
52 | extension ButtonCell: Identifiable {}
53 |
54 | // Helper function inserted by Swift 4.2 migrator.
55 | private func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
56 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value) })
57 | }
58 |
--------------------------------------------------------------------------------
/provisioning/import_provisioning.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Decrypt the files
4 | # --batch to prevent interactive command --yes to assume "yes" for questions
5 | gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" --output provisioning/AppStoreCertificates.p12 provisioning/AppStoreCertificates.p12.gpg
6 | gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" --output provisioning/CocoaHeadsNL-AppStore-General-Notification.mobileprovision provisioning/CocoaHeadsNL-AppStore-General-Notification.mobileprovision.gpg
7 | gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" --output provisioning/CocoaHeadsNL-AppStore-Item-Notification.mobileprovision provisioning/CocoaHeadsNL-AppStore-Item-Notification.mobileprovision.gpg
8 | gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" --output provisioning/CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision provisioning/CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision.gpg
9 | gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" --output provisioning/CocoaHeadsNL-AppStore.mobileprovision provisioning/CocoaHeadsNL-AppStore.mobileprovision.gpg
10 |
11 | mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
12 |
13 | echo "List profiles"
14 | ls ~/Library/MobileDevice/Provisioning\ Profiles/
15 | echo "Move profiles"
16 | cp provisioning/*.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
17 | echo "List profiles"
18 | ls ~/Library/MobileDevice/Provisioning\ Profiles/
19 |
20 | security create-keychain -p "" build.keychain
21 | security import provisioning/AppStoreCertificates.p12 -t agg -k ~/Library/Keychains/build.keychain -P "$PROVISIONING_PASSWORD" -A
22 |
23 | security list-keychains -s ~/Library/Keychains/build.keychain
24 | security default-keychain -s ~/Library/Keychains/build.keychain
25 | security unlock-keychain -p "" ~/Library/Keychains/build.keychain
26 | security set-key-partition-list -S apple-tool:,apple: -s -k "" ~/Library/Keychains/build.keychain
27 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/CoreLocationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoreLocationController.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bart Hoffman on 23/08/15.
6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreLocation
11 |
12 | class CoreLocationController: NSObject, CLLocationManagerDelegate {
13 |
14 | let locationManager = CLLocationManager()
15 | let locationNotification = "LOCATION_AVAILABLE"
16 |
17 | override init() {
18 | super.init()
19 |
20 | self.locationManager.delegate = self
21 | self.locationManager.distanceFilter = 5000
22 | self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
23 | self.locationManager.requestWhenInUseAuthorization()
24 | }
25 |
26 | // MARK: - CLLocationManagerDelegate methods
27 |
28 | func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
29 | print("didChangeAuthorizationStatus")
30 |
31 | switch status {
32 | case .notDetermined:
33 | print(".NotDetermined")
34 | case .authorizedAlways:
35 | print(".Authorized")
36 | case .authorizedWhenInUse:
37 | print(".AuthorizedWhenInUse")
38 | self.locationManager.startUpdatingLocation()
39 | case .denied:
40 | print(".Denied")
41 | default:
42 | print("Unhandled authorization status")
43 | }
44 | }
45 |
46 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
47 |
48 | let location = locations.last!
49 |
50 | print("didUpdateLocations: \(location.coordinate.latitude), \(location.coordinate.longitude)")
51 |
52 | let userInfo = [ "location": location]
53 |
54 | let notificationCenter = NotificationCenter.default
55 | notificationCenter.post(name: Notification.Name(rawValue: locationNotification), object: nil, userInfo: userInfo)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/JobsCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JobsCell.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bart Hoffman on 10/03/16.
6 | // Copyright © 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class JobsCell: UICollectionViewCell {
12 |
13 | @IBOutlet weak var textLabel: UILabel!
14 | @IBOutlet weak var imageView: UIImageView!
15 | @IBOutlet weak var separator: UIView!
16 | @IBOutlet weak var container: UIView!
17 |
18 | fileprivate var imageLoaded = false
19 |
20 | var job: Job? {
21 | didSet {
22 | self.imageView.image = job?.logoImage
23 | self.textLabel.text = job?.title
24 |
25 | if let title = job?.title {
26 |
27 | let accessibilityLabel: String!
28 | if let companyName = job?.companyName {
29 | let text = NSLocalizedString("%1$@ at %2$@")
30 | accessibilityLabel = String(format: text, title, companyName)
31 | } else {
32 | accessibilityLabel = title
33 | }
34 | container.accessibilityLabel = accessibilityLabel
35 | container.accessibilityHint = NSLocalizedString("Double-tap for more details.")
36 | }
37 | }
38 | }
39 |
40 | var rightHandSide = false {
41 | didSet {
42 | self.separator.isHidden = rightHandSide
43 | }
44 | }
45 |
46 | override func awakeFromNib() {
47 | super.awakeFromNib()
48 |
49 | // Show a darker background when the cell is tapped (just like the
50 | // table view cells in the Meetups tab).
51 | let selectedView = UIView()
52 | selectedView.backgroundColor = UIColor(red: 0.85, green: 0.85, blue: 0.85, alpha: 1)
53 | self.selectedBackgroundView = selectedView
54 | }
55 |
56 | override func prepareForReuse() {
57 | super.prepareForReuse()
58 |
59 | self.textLabel.text = ""
60 | //self.imageView.image = nil
61 | }
62 | }
63 |
64 | extension JobsCell: Identifiable {}
65 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/CompanyDataSource.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import StoreKit
3 | import CloudKit
4 |
5 | class CompanyDataSource: DetailDataSource {
6 | weak var presenter: DetailViewController?
7 |
8 | var company: Company {
9 | return object as! Company // swiftlint:disable:this force_cast
10 | }
11 |
12 | override var title: String? {
13 | return company.name
14 | }
15 |
16 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
17 | switch section {
18 | case 1:
19 | return NSLocalizedString("Apps", comment: "Section title for app section in company details.")
20 | default:
21 | return nil
22 | }
23 | }
24 |
25 | override func numberOfSections(in tableView: UITableView) -> Int {
26 | return 1
27 | }
28 |
29 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
30 | return 4
31 | }
32 |
33 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
34 | switch (indexPath as NSIndexPath).row {
35 | case 0:
36 | return logoCellWithFile(company.logoImage, forTableView: tableView, forRowAt: indexPath)
37 | case 1:
38 | return titleCellWithText(company.emailAddress, forTableView: tableView, forRowAt: indexPath)
39 | case 2:
40 | return titleCellWithText(company.streetAddress, forTableView: tableView, forRowAt: indexPath)
41 | case 3:
42 | return titleCellWithText(company.zipCode, forTableView: tableView, forRowAt: indexPath)
43 | default:
44 | fatalError("This should not happen.")
45 | }
46 | }
47 |
48 | func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
49 | if section == 1 {
50 | tableView.headerView(forSection: 1)?.backgroundColor = UIColor.gray
51 | return tableView.headerView(forSection: 1)
52 | } else {
53 | return UIView(frame: CGRect.zero)
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://appcenter.ms)
2 |
3 | # About this app
4 |
5 | **Why an app?** We're a community of mostly mobile developers... so could you imagine we wouldn't?
6 |
7 | **Why open source it?** We are a non-profit organisation and organise our monthly meetup to share ideas, learn from each other and meet other developers. Keeping it closed seemed out of place.
8 |
9 | **How do we see this working?** We used an [open source license](LICENSE.md) to promote sharing for non-commercial use and educational purposes.
10 |
11 | In case you have any ideas, suggestions or additions, just get in contact so you can see what is happening already. We still got some things on our wishlist.
12 |
13 | Our email: [foundation@cocoaheads.nl](mailto:foundation@cocoaheads.nl)
14 |
15 |
16 | ## Project structure
17 |
18 | CocoaHeadsNL consists of a single project.
19 |
20 | Minimal deployment target of the app is iOS 12.0
21 |
22 | ## Contributions
23 |
24 | Please note that we consider all ownership of contributions made to this project to automatically transfer to Stichting CocoaHeadsNL. If you do not agree to this, do not contribute. When we conclude that you have an interest in contributing to this project, we will ask you to confirm transfer of ownership of you contributions in writing.
25 |
26 | Also note, any contributions we receive should be allowed to be transferred to Stichting CocoaHeadsNL. If you make contributions and at a later stage ownership of said contributions are not lawfully transfered to our possesion, you as a contributor are considered liable for this.
27 |
28 | The above statement may sound harsh, but basically if you write original code on your own or legally alloted time and use open source components with compatible licenses: You are perfectly fine.
29 |
30 | As maintainers of this project we do actively try and guide contributors through these hurdles, we want to work with our community to make this project a great success.
31 |
32 | All contributors to this project are listed here: [contributors](https://github.com/CocoaHeadsNL/CocoaHeadsNL-iOS/graphs/contributors)
33 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/NSManagedObjectContext+AsyncHelpers.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 | import Swift
3 |
4 | extension NSManagedObjectContext {
5 | /**
6 | Synchronously executes a given function on the receiver’s queue.
7 | You use this method to safely address managed objects on a concurrent
8 | queue.
9 | - attention: This method may safely be called reentrantly.
10 | - parameter body: The method body to perform on the reciever.
11 | - returns: The value returned from the inner function.
12 | - throws: Any error thrown by the inner function. This method should be
13 | technically `rethrows`, but cannot be due to Swift limitations.
14 | **/
15 | public func performAndWaitOrThrow(_ body: () throws -> Return) rethrows -> Return {
16 | #if swift(>=3.1)
17 | return try withoutActuallyEscaping(body) { work in
18 | var result: Return!
19 | var error: Error?
20 |
21 | performAndWait {
22 | do {
23 | result = try work()
24 | } catch let workError {
25 | error = workError
26 | }
27 | }
28 |
29 | if let error = error {
30 | throw error
31 | } else {
32 | return result
33 | }
34 | }
35 | #else
36 | func impl(execute work: () throws -> Return, recover: (Error) throws -> Void) rethrows -> Return {
37 | var result: Return!
38 | var error: Error?
39 |
40 | // performAndWait is marked @escaping as of iOS 10.0.
41 | typealias Fun = (() -> Void) -> Void // swiftlint:disable:this nesting
42 | let performAndWaitNoescape = unsafeBitCast(self.performAndWait, to: Fun.self)
43 | performAndWaitNoescape {
44 | do {
45 | result = try work()
46 | } catch let workError {
47 | error = workError
48 | }
49 | }
50 |
51 | if let error = error {
52 | try recover(error)
53 | }
54 |
55 | return result
56 | }
57 |
58 | return try impl(execute: body, recover: { throw $0 })
59 | #endif
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CloudKitSync/jobs/loadContributorInfo.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var Promise = require('promise');
4 | var request = require('request');
5 | var config = require('../config');
6 |
7 |
8 |
9 | exports.load = function() {
10 | var loadContributorsPromise = new Promise(function(resolve, reject) {
11 |
12 |
13 | var options = {
14 | url: 'https://api.github.com/repos/CocoaHeadsNL/CocoaHeadsNL-iOS/stats/contributors',
15 | headers: { 'User-Agent': 'CocoaHeadsNL-Cloud-Sync'},
16 | auth: config.githubAuthOptions
17 | }
18 |
19 | request(options, function(error, response, body){
20 | if (error) {
21 | reject(error);
22 | console.log("Error on fetching contributors: " + error)
23 | return;
24 | }
25 |
26 | if (response.statusCode != 200) {
27 | reject(response.statusCode);
28 | console.log("Invalid HTTP status code on fetching contributors: " + response.statusCode)
29 | return;
30 | }
31 | resolve(body);
32 | });
33 | }).then(function(body) {
34 | var contributorData = JSON.parse(body);
35 |
36 | var promises = []
37 | var authorDetails = []
38 |
39 | contributorData.forEach(function(contributorInfo) {
40 | var detailPromise = new Promise(function(resolve, reject) {
41 | var author = contributorInfo["author"];
42 | request({url: author.url,headers: { 'User-Agent': 'CocoaHeadsNL-Cloud-Sync'}, auth: config.githubAuthOptions}, function(error, response, body){
43 | if (error) {
44 | console.log("Error on fetching contributor details: " + error)
45 | reject(error);
46 | return;
47 | }
48 |
49 | if (response.statusCode != 200) {
50 | console.log("Invalid HTTP status code on fetching contributor details: " + response.statusCode)
51 | reject(response.statusCode);
52 | return;
53 | }
54 |
55 | var authorDetailData = JSON.parse(body)
56 | authorDetailData.commit_count = contributorInfo.total
57 | authorDetails.push(authorDetailData)
58 | resolve(authorDetailData)
59 | })
60 | })
61 |
62 | promises.push(detailPromise)
63 | })
64 |
65 | return Promise.all(promises)
66 | })
67 |
68 | return loadContributorsPromise
69 | }
70 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/MapViewCell.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import MapKit
3 |
4 | class MapViewCell: UITableViewCell, MKMapViewDelegate {
5 | @IBOutlet weak var littleMap: MKMapView!
6 |
7 | var geoLocation: CLLocation? {
8 | didSet {
9 | if geoLocation != oldValue {
10 | let mapRegion = MKCoordinateRegion(center: self.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
11 | littleMap.region = mapRegion
12 | }
13 | }
14 | }
15 |
16 | var locationName: String? {
17 | didSet {
18 | if let locationName = locationName, locationName != oldValue {
19 | let annotation = MapAnnotation(coordinate: self.coordinate, title: NSLocalizedString("Here it is!"), subtitle: locationName)
20 | littleMap.addAnnotation(annotation)
21 | }
22 | }
23 | }
24 |
25 | fileprivate var coordinate: CLLocationCoordinate2D {
26 | if let geoLocation = geoLocation {
27 | return CLLocationCoordinate2D(latitude: geoLocation.coordinate.latitude, longitude: geoLocation.coordinate.longitude)
28 | } else {
29 | return CLLocationCoordinate2D(latitude: 0, longitude: 0)
30 | }
31 | }
32 |
33 | func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
34 | let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pinAnnotationView")
35 | annotationView.animatesDrop = true
36 | return annotationView
37 | }
38 |
39 | func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
40 | self.openMapWithCoordinate(coordinate)
41 | }
42 |
43 | func openMapWithCoordinate(_ coordinate: CLLocationCoordinate2D? = nil) {
44 | let placemark = MKPlacemark(coordinate: coordinate ?? self.coordinate, addressDictionary: nil)
45 | let mapItem = MKMapItem(placemark: placemark)
46 |
47 | if let locationName = locationName {
48 | mapItem.name = locationName
49 | }
50 |
51 | let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
52 | let currentLocationMapItem = MKMapItem.forCurrentLocation()
53 |
54 | MKMapItem.openMaps(with: [currentLocationMapItem, mapItem], launchOptions: launchOptions)
55 | }
56 | }
57 |
58 | extension MapViewCell: Identifiable {}
59 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/MeetupDataSource.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import MapKit
3 |
4 | class MeetupDataSource: DetailDataSource {
5 | var meetup: Meetup {
6 | return object as! Meetup // swiftlint:disable:this force_cast
7 | }
8 |
9 | override var title: String? {
10 | return meetup.name
11 | }
12 |
13 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
14 | return 7
15 | }
16 |
17 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
18 | switch indexPath.row {
19 | case 1:
20 | if let mapCell = tableView.cellForRow(at: indexPath) as? MapViewCell {
21 | mapCell.openMapWithCoordinate()
22 | }
23 | tableView.deselectRow(at: indexPath, animated: true)
24 | default:
25 | tableView.deselectRow(at: indexPath, animated: true)
26 | }
27 |
28 | }
29 |
30 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
31 | switch indexPath.row {
32 | case 0:
33 | return logoCellWithFile(meetup.logoImage, forTableView: tableView, forRowAt: indexPath)
34 | case 1:
35 | if meetup.latitude == 0.0 && meetup.longitude == 0.0 {
36 | return onlineCell(forRowAt: indexPath)
37 | } else {
38 | let geoLocation = CLLocation(latitude: meetup.latitude, longitude: meetup.longitude)
39 | return mapViewCellWithLocation(geoLocation, name: meetup.locationName, forTableView: tableView, forRowAt: indexPath)
40 | }
41 | case 2:
42 | return titleCellWithText(meetup.name, forTableView: tableView, forRowAt: indexPath)
43 | case 3:
44 | let text = String("Number of Cocoaheads: \(meetup.yesRsvpCount)")
45 | return titleCellWithText(text, forTableView: tableView, forRowAt: indexPath)
46 | case 4:
47 | return titleCellWithDate(meetup.time, forTableView: tableView, forRowAt: indexPath)
48 | case 5:
49 | return buttonCell(meetup.meetupUrl, title: "Open Meetup", forTableView: tableView, forRowAt: indexPath)
50 | case 6:
51 | return dataCellWithHTML(meetup.meetupDescription, forTableView: tableView, forRowAt: indexPath)
52 | default:
53 | fatalError("This should not happen.")
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/UITableView+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+Extensions.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Jeroen Leenarts on 10/01/2020.
6 | // Copyright © 2020 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UICollectionView {
12 |
13 | func register(type: T.Type, ofKind kind: String, prefix: String = "") where T: UICollectionReusableView {
14 | register(type, forSupplementaryViewOfKind: kind, withReuseIdentifier: prefix + type.identifier)
15 | }
16 |
17 | func registerNib(type: T.Type, prefix: String = "") where T: UICollectionViewCell {
18 | register(UINib(nibName: type.identifier, bundle: nil), forCellWithReuseIdentifier: prefix + type.identifier)
19 | }
20 |
21 | func dequeueReusableSupplementaryView(ofKind kind: String, type: T.Type, for indexPath: IndexPath, prefix: String = "") -> T where T: UICollectionReusableView {
22 |
23 | return dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: prefix + type.identifier, for: indexPath) as! T // swiftlint:disable:this force_cast
24 | }
25 |
26 | func dequeueReusableCell(type: T.Type, for indexPath: IndexPath, prefix: String = "") -> T where T: UICollectionViewCell {
27 | return dequeueReusableCell(withReuseIdentifier: prefix + type.identifier, for: indexPath) as! T // swiftlint:disable:this force_cast
28 | }
29 | }
30 |
31 | extension UITableView {
32 |
33 | func register(type: T.Type, prefix: String = "") where T: UITableViewCell {
34 | register(type, forCellReuseIdentifier: prefix + type.identifier)
35 | }
36 |
37 | func dequeueReusableCell(type: T.Type, for indexPath: IndexPath, prefix: String = "") -> T where T: UITableViewCell {
38 | return dequeueReusableCell(withIdentifier: prefix + type.identifier, for: indexPath) as! T // swiftlint:disable:this force_cast
39 | }
40 |
41 | func registerNib(type: T.Type, prefix: String = "") where T: UITableViewCell {
42 | let nib = UINib(nibName: prefix + type.identifier, bundle: nil)
43 | register(nib, forCellReuseIdentifier: prefix + type.identifier)
44 | }
45 |
46 | func registerNib(type: T.Type, prefix: String = "") where T: UITableViewHeaderFooterView {
47 | let nib = UINib(nibName: prefix + type.identifier, bundle: nil)
48 | register(nib, forHeaderFooterViewReuseIdentifier: prefix + type.identifier)
49 | }
50 |
51 | func dequeueReusableHeaderFooterView(type: T.Type, prefix: String = "") -> T where T: UITableViewHeaderFooterView {
52 | return dequeueReusableHeaderFooterView(withIdentifier: prefix + type.identifier) as! T // swiftlint:disable:this force_cast
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/CalendarTabImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CalendarTabImage.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bruno Scheele on 29/05/15.
6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIImage {
13 | class func calendarTabImageWithCurrentDate() -> UIImage {
14 | return calendarTabImageWithDate(Date())
15 | }
16 |
17 | class func calendarTabImageWithDate(_ date: Date) -> UIImage {
18 | let dateFormatter = DateFormatter()
19 | let view = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
20 | let maskView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
21 |
22 | let calendarView = UIImageView()
23 | calendarView.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
24 | calendarView.image = UIImage(named: "EventsTabIcon")
25 | view.addSubview(calendarView)
26 |
27 | // Create the month
28 |
29 | dateFormatter.dateFormat = "MMM"
30 | let month = dateFormatter.string(from: date).uppercased()
31 |
32 | let monthLabel = UILabel(frame: CGRect(x: 0, y: 4, width: 30, height: 6))
33 | monthLabel.text = month
34 | monthLabel.font = UIFont.boldSystemFont(ofSize: 6)
35 | monthLabel.textAlignment = .center
36 | monthLabel.textColor = UIColor.white
37 | maskView.addSubview(monthLabel)
38 |
39 | // Create the day number
40 |
41 | dateFormatter.dateFormat = "dd"
42 | let day = dateFormatter.string(from: date)
43 |
44 | let numberLabel = UILabel(frame: CGRect(x: 0, y: 10, width: 30, height: 17))
45 | numberLabel.text = day
46 | numberLabel.font = UIFont.boldSystemFont(ofSize: 14)
47 | numberLabel.textAlignment = .center
48 | view.addSubview(numberLabel)
49 |
50 | UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, UIScreen.main.scale)
51 | view.layer.render(in: UIGraphicsGetCurrentContext()!)
52 | let image = UIGraphicsGetImageFromCurrentImageContext()
53 | UIGraphicsEndImageContext()
54 |
55 | UIGraphicsBeginImageContextWithOptions(maskView.bounds.size, false, UIScreen.main.scale)
56 | maskView.layer.render(in: UIGraphicsGetCurrentContext()!)
57 | let maskingImage = UIGraphicsGetImageFromCurrentImageContext()
58 | UIGraphicsEndImageContext()
59 |
60 | // Mask month out of the image to show in tabBar.
61 | let resultImage = image?.maskImage(maskingImage!)
62 | return resultImage!
63 | }
64 |
65 | func maskImage(_ maskImage: UIImage) -> UIImage {
66 | let maskRef = maskImage.cgImage
67 |
68 | let mask = CGImage(maskWidth: (maskRef?.width)!, height: (maskRef?.height)!, bitsPerComponent: (maskRef?.bitsPerComponent)!, bitsPerPixel: (maskRef?.bitsPerPixel)!, bytesPerRow: (maskRef?.bytesPerRow)!, provider: (maskRef?.dataProvider!)!, decode: nil, shouldInterpolate: false)
69 |
70 | let masked = self.cgImage?.masking(mask!)!
71 | let maskedImage = UIImage(cgImage: masked!, scale: UIScreen.main.scale, orientation: .up)
72 |
73 | return maskedImage
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/DetailDataSource.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import CloudKit
3 |
4 | /**
5 | * Base class for all data sources used by the Detail screen.
6 | * It has convenience methods for making specific types of cells.
7 | */
8 | class DetailDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
9 | let object: AnyObject
10 | var tableView: UITableView!
11 |
12 | init(object: AnyObject) {
13 | self.object = object
14 | }
15 |
16 | var title: String? {
17 | return ""
18 | }
19 |
20 | func numberOfSections(in tableView: UITableView) -> Int {
21 | return 1
22 | }
23 |
24 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
25 | return 1
26 | }
27 |
28 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
29 | fatalError("Subclass must implement this")
30 | }
31 |
32 | func logoCellWithFile(_ logo: UIImage, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> LogoCell {
33 | let cell = tableView.dequeueReusableCell(type: LogoCell.self, for: indexPath)
34 | cell.logoImageView.image = logo
35 | cell.logoImageView.contentMode = .scaleAspectFit
36 | return cell
37 | }
38 |
39 | func onlineCell(forRowAt indexPath: IndexPath) -> OnlineCell {
40 | let cell = tableView.dequeueReusableCell(type: OnlineCell.self, for: indexPath)
41 | return cell
42 | }
43 |
44 | func mapViewCellWithLocation(_ location: CLLocation?, name: String?, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> MapViewCell {
45 | let cell = tableView.dequeueReusableCell(type: MapViewCell.self, for: indexPath)
46 | cell.geoLocation = location
47 | cell.locationName = name
48 | return cell
49 | }
50 |
51 | func titleCellWithText(_ text: String?, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> TitleCell {
52 | let cell = tableView.dequeueReusableCell(type: TitleCell.self, for: indexPath)
53 | cell.content = text
54 | return cell
55 | }
56 |
57 | func titleCellWithDate(_ date: Date?, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> TitleCell {
58 | let cell = tableView.dequeueReusableCell(type: TitleCell.self, for: indexPath)
59 | cell.date = date
60 | return cell
61 | }
62 |
63 | func dataCellWithHTML(_ html: String?, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> HTMLDataCell {
64 | let cell = tableView.dequeueReusableCell(type: HTMLDataCell.self, for: indexPath)
65 | cell.html = html
66 | return cell
67 | }
68 |
69 | func buttonCell(_ urlString: String?, title: String, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> ButtonCell {
70 | let cell = tableView.dequeueReusableCell(type: ButtonCell.self, for: indexPath)
71 | cell.title = title
72 | cell.urlString = urlString
73 | return cell
74 | }
75 |
76 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
77 | return UITableView.automaticDimension
78 | }
79 |
80 | func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
81 | return CGFloat(88)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/ItemServiceExtension/NotificationService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotificationService.swift
3 | // ItemServiceExtension
4 | //
5 | // Created by Bart Hoffman on 11/03/2018.
6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UserNotifications
10 | import CloudKit
11 |
12 | class NotificationService: UNNotificationServiceExtension {
13 |
14 | var contentHandler: ((UNNotificationContent) -> Void)?
15 | var bestAttemptContent: UNMutableNotificationContent?
16 |
17 | override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
18 |
19 | self.contentHandler = contentHandler
20 | bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
21 |
22 | if let bestAttemptContent = bestAttemptContent {
23 |
24 | if let userInfo = request.content.userInfo as NSDictionary as? [String: Any] {
25 | let query = CKQueryNotification(fromRemoteNotificationDictionary: userInfo)!
26 |
27 | if let title = query.recordFields?["title"] as? String,
28 | let name = query.recordFields?["name"] as? String,
29 | let imageUrl = query.recordFields?["imageUrl"] as? String {
30 |
31 | bestAttemptContent.title = title
32 | bestAttemptContent.body = name
33 |
34 | if let fileUrl = URL(string: imageUrl) {
35 |
36 | //let request = NSURLRequest(url: fileUrl)
37 | URLSession.shared.downloadTask(with: fileUrl, completionHandler: { fileLocation, _, err in
38 |
39 | //create attachment from file at url in folder
40 | if let location = fileLocation, err == nil {
41 | let tmpDirectory = NSTemporaryDirectory()
42 | let tmpFile = "file:".appending(tmpDirectory).appending(fileUrl.lastPathComponent)
43 | let tmpUrl = URL(string: tmpFile)!
44 |
45 | do {
46 | try? FileManager.default.copyItem(at: location, to: tmpUrl)
47 |
48 | if let attachment = try? UNNotificationAttachment(identifier: "", url: tmpUrl, options: nil) {
49 | self.bestAttemptContent?.attachments = [attachment]
50 | contentHandler(bestAttemptContent)
51 | }
52 | }
53 | }
54 | }) .resume()
55 | }
56 | }
57 | }
58 | }
59 | }
60 |
61 | override func serviceExtensionTimeWillExpire() {
62 | // Called just before the extension will be terminated by the system.
63 | // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
64 | if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
65 | contentHandler(bestAttemptContent)
66 | }
67 | }
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/itemNotification/Base.lproj/MainInterface.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNLUITests/CocoaHeadsNLUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CocoaHeadsNLUITests.swift
3 | // CocoaHeadsNLUITests
4 | //
5 | // Created by Jeroen Leenarts on 25-05-16.
6 | // Copyright © 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class CocoaHeadsNLUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 |
16 | let app = XCUIApplication()
17 | app.launch()
18 | }
19 |
20 | override func tearDown() {
21 | // Put teardown code here. This method is called after the invocation of each test method in the class.
22 | super.tearDown()
23 | }
24 |
25 | func testApp() {
26 | let app = XCUIApplication()
27 |
28 | waitForHittable(app.tables.staticTexts["CocoaHeads rules"], waitSeconds: 10)
29 |
30 | app.tables.cells.element(boundBy: 0).tap()
31 |
32 | app.tabBars.buttons["Jobs"].tap()
33 | waitForNotHittable(app.activityIndicators.element(boundBy: 0), waitSeconds: 10)
34 |
35 | app.tabBars.buttons["About"].tap()
36 | waitForNotHittable(app.activityIndicators.element(boundBy: 0), waitSeconds: 10)
37 | }
38 |
39 | }
40 |
41 | extension XCTestCase {
42 | func waitForHittable(_ element: XCUIElement, waitSeconds: Double, file: String = #file, line: UInt = #line) {
43 | let existsPredicate = NSPredicate(format: "hittable == true")
44 | expectation(for: existsPredicate, evaluatedWith: element, handler: nil)
45 |
46 | waitForExpectations(timeout: waitSeconds) { error -> Void in
47 | if error != nil {
48 | //let message = "Failed to find \(element) after \(waitSeconds) seconds."
49 | //self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true)
50 | let issue = XCTIssue(type: .assertionFailure, compactDescription: "Failed to find \(element) after \(waitSeconds) seconds.")
51 | self.record(issue)
52 | }
53 | }
54 | }
55 |
56 | func waitForNotHittable(_ element: XCUIElement, waitSeconds: Double, file: String = #file, line: UInt = #line) {
57 | let existsPredicate = NSPredicate(format: "hittable == false")
58 | expectation(for: existsPredicate, evaluatedWith: element, handler: nil)
59 |
60 | waitForExpectations(timeout: waitSeconds) { error -> Void in
61 | if error != nil {
62 | //let message = "Failed to find \(element) after \(waitSeconds) seconds."
63 | //self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true)
64 | let issue = XCTIssue(type: .assertionFailure, compactDescription: "Failed to find \(element) after \(waitSeconds) seconds.")
65 | self.record(issue)
66 | }
67 | }
68 | }
69 |
70 | func waitForExists(_ element: XCUIElement, waitSeconds: Double, file: String = #file, line: UInt = #line) {
71 | let existsPredicate = NSPredicate(format: "exists == true")
72 | expectation(for: existsPredicate, evaluatedWith: element, handler: nil)
73 |
74 | waitForExpectations(timeout: waitSeconds) { error -> Void in
75 | if error != nil {
76 | //let message = "Failed to find \(element) after \(waitSeconds) seconds."
77 | //self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true)
78 | let issue = XCTIssue(type: .assertionFailure, compactDescription: "Failed to find \(element) after \(waitSeconds) seconds.")
79 | self.record(issue)
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/SplitViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitViewController.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bart Hoffman on 14/03/15.
6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class SplitViewController: UISplitViewController, UISplitViewControllerDelegate {
13 | override func viewDidLoad() {
14 | self.delegate = self
15 | self.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
16 |
17 | NotificationCenter.default.addObserver(self, selector: #selector(SplitViewController.searchNotification(_:)), name: NSNotification.Name(rawValue: searchNotificationName), object: nil)
18 |
19 | //Inspect paste board for userInfo
20 | if let pasteBoard = UIPasteboard(name: UIPasteboard.Name(rawValue: searchPasteboardName), create: false) {
21 | let uniqueIdentifier = pasteBoard.string
22 | if let components = uniqueIdentifier?.components(separatedBy: ":") {
23 | if !components.isEmpty {
24 | let type = components[0]
25 | displayTabForType(type)
26 | }
27 | }
28 | }
29 | }
30 | @objc func searchNotification(_ notification: Notification) {
31 | guard let userInfo = (notification as NSNotification).userInfo as? [String: String] else {
32 | return
33 | }
34 |
35 | if let type = userInfo["type"] {
36 | displayTabForType(type)
37 | }
38 | }
39 |
40 | func displayTabForType(_ type: String) {
41 | guard let tabBarController = self.viewControllers[0] as? UITabBarController else {
42 | return
43 | }
44 |
45 | if type == "meetup" {
46 | tabBarController.selectedIndex = 0
47 | } else if type == "job" {
48 | tabBarController.selectedIndex = 1
49 | }
50 | }
51 |
52 | // MARK: - UISplitViewControllerDelegate
53 |
54 | func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
55 | if let primaryTab = primaryViewController as? UITabBarController, primaryTab.selectedViewController is UINavigationController {
56 | return true
57 | }
58 | return false
59 | }
60 |
61 | func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? {
62 | if let tabBarController = primaryViewController as? UITabBarController, let navigationController = tabBarController.selectedViewController as? UINavigationController, navigationController.children.count > 1 {
63 | guard let poppedControllers = navigationController.popToRootViewController(animated: false) else {
64 | return nil
65 | }
66 | let childNavigationController = UINavigationController()
67 | childNavigationController.viewControllers = poppedControllers
68 | return childNavigationController
69 | }
70 | return nil
71 | }
72 |
73 | func splitViewController(_ splitViewController: UISplitViewController,
74 | showDetail detailVc: UIViewController,
75 | sender: Any?) -> Bool {
76 | if splitViewController.isCollapsed, let master = splitViewController.viewControllers[0] as? UITabBarController, let masterNavigation = master.selectedViewController as? UINavigationController {
77 | masterNavigation.show(detailVc, sender: self)
78 | return true
79 | } else if let masterNavigation = splitViewController.viewControllers[1] as? UINavigationController {
80 | masterNavigation.setViewControllers([detailVc], animated: true)
81 | return true
82 | }
83 | return false
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Launch Screen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/MeetupCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MeetupCell.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Matthijs Hollemans on 25-05-15.
6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | private let dateFormatter = DateFormatter()
13 |
14 | class MeetupCell: UITableViewCell {
15 |
16 | @IBOutlet weak var titleLabel: UILabel!
17 | @IBOutlet weak var timeLabel: UILabel!
18 | @IBOutlet weak var logoImageView: UIImageView!
19 | @IBOutlet weak var dateContainer: UIView!
20 | @IBOutlet weak var monthBackgroundView: UIView!
21 | @IBOutlet weak var dayBackgroundView: UIView!
22 | @IBOutlet weak var dayLabel: UILabel!
23 | @IBOutlet weak var monthLabel: UILabel!
24 | @IBOutlet weak var rsvpLabel: UILabel!
25 |
26 | required init?(coder aDecoder: NSCoder) {
27 | super.init(coder: aDecoder)
28 | }
29 |
30 | override func awakeFromNib() {
31 | super.awakeFromNib()
32 | }
33 |
34 | override func prepareForReuse() {
35 | super.prepareForReuse()
36 |
37 | titleLabel.text = ""
38 | timeLabel.text = ""
39 | dayLabel.text = ""
40 | monthLabel.text = ""
41 | }
42 |
43 | func configureCellForMeetup(_ meetup: Meetup) {
44 | titleLabel.text = meetup.name
45 |
46 | if let date = meetup.time {
47 | dateFormatter.dateStyle = .none
48 | dateFormatter.timeStyle = .short
49 | let timeText = dateFormatter.string(from: date as Date)
50 | if meetup.latitude == 0.0 && meetup.longitude == 0.0 {
51 | timeLabel.text = timeText
52 | } else {
53 | timeLabel.text = String(format: "%@ %@", meetup.location ?? NSLocalizedString("Location unknown"), timeText)
54 | }
55 |
56 | dateFormatter.dateFormat = "dd"
57 | dayLabel.text = dateFormatter.string(from: date as Date)
58 |
59 | dateFormatter.dateFormat = "MMM"
60 | monthLabel.text = dateFormatter.string(from: date as Date).uppercased()
61 | dateFormatter.dateFormat = "MMMM"
62 | monthLabel.accessibilityLabel = dateFormatter.string(from: date as Date)
63 |
64 | if meetup.isToday || meetup.isUpcoming {
65 | dayLabel.textColor = UIColor(named: "DayLabelColor")
66 | monthBackgroundView.backgroundColor = meetup.isToday ? UIColor(named: "DayBackgroundViewColorToday") : UIColor(named: "DayBackgroundViewColor")
67 |
68 | if meetup.yesRsvpCount > 0 {
69 |
70 | rsvpLabel.text = "\(meetup.yesRsvpCount) " + NSLocalizedString("CocoaHeads going")
71 |
72 | if meetup.rsvpLimit > 0 {
73 | let text = rsvpLabel.text! + "\n\(meetup.rsvpLimit - meetup.yesRsvpCount) "
74 | rsvpLabel.text = text + NSLocalizedString("seats available")
75 | }
76 | } else {
77 | rsvpLabel.text = ""
78 | }
79 | } else {
80 | dayLabel.textColor = UIColor(named: "DayLabelColorHistory")
81 | monthBackgroundView.backgroundColor = UIColor(named: "DayBackgroundViewColorHistory")
82 |
83 | rsvpLabel.text = "\(meetup.yesRsvpCount) \(NSLocalizedString("CocoaHeads had a blast"))"
84 | }
85 | }
86 |
87 | self.logoImageView.image = meetup.smallLogoImage
88 | }
89 |
90 | override func setHighlighted(_ highlighted: Bool, animated: Bool) {
91 | super.setHighlighted(highlighted, animated: animated)
92 |
93 | if highlighted {
94 | monthBackgroundView.backgroundColor = .gray
95 | dayBackgroundView.backgroundColor = UIColor(white: 1, alpha: 0.4)
96 | }
97 | }
98 |
99 | override func setSelected(_ selected: Bool, animated: Bool) {
100 | super.setSelected(selected, animated: animated)
101 |
102 | if selected {
103 | monthBackgroundView.backgroundColor = .gray
104 | dayBackgroundView.backgroundColor = UIColor(white: 1, alpha: 0.4)
105 | }
106 | }
107 | }
108 |
109 | extension MeetupCell: Identifiable {}
110 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL.xcodeproj/xcshareddata/xcschemes/generalNotification.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
16 |
22 |
23 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
69 |
71 |
77 |
78 |
79 |
80 |
87 |
89 |
95 |
96 |
97 |
98 |
100 |
101 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | CocoaHeads
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(CURRENT_PROJECT_VERSION)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | auto-generated
25 | ITSAppUsesNonExemptEncryption
26 |
27 | LSApplicationQueriesSchemes
28 |
29 | meetup
30 |
31 | LSRequiresIPhoneOS
32 |
33 | NSAppTransportSecurity
34 |
35 | NSExceptionDomains
36 |
37 | cocoaheads.nl
38 |
39 | NSExceptionRequiresForwardSecrecy
40 |
41 |
42 | files.parsetfss.com
43 |
44 | NSIncludesSubdomains
45 |
46 |
47 | jobs.cocoaheads.nl
48 |
49 | NSTemporaryExceptionAllowsInsecureHTTPLoads
50 |
51 |
52 | mzstatic.com
53 |
54 | NSIncludesSubdomains
55 |
56 | NSTemporaryExceptionAllowsInsecureHTTPLoads
57 |
58 |
59 |
60 |
61 | NSLocationAlwaysUsageDescription
62 | Your location is used to show you which companies are nearby.
63 | NSLocationWhenInUseUsageDescription
64 | Your location is used to show you which companies are nearby.
65 | UIApplicationShortcutItems
66 |
67 |
68 | UIApplicationShortcutItemIconType
69 | UIApplicationShortcutIconTypeSearch
70 | UIApplicationShortcutItemTitle
71 | Show companies
72 | UIApplicationShortcutItemType
73 | nl.cocoaheads.CocoaHeadsNL.companies
74 |
75 |
76 | UIApplicationShortcutItemIconType
77 | UIApplicationShortcutIconTypeSearch
78 | UIApplicationShortcutItemTitle
79 | Show jobs
80 | UIApplicationShortcutItemType
81 | nl.cocoaheads.CocoaHeadsNL.job
82 |
83 |
84 | UIApplicationShortcutItemIconType
85 | UIApplicationShortcutIconTypeSearch
86 | UIApplicationShortcutItemTitle
87 | Show meetups
88 | UIApplicationShortcutItemType
89 | nl.cocoaheads.CocoaHeadsNL.meetup
90 |
91 |
92 | UIBackgroundModes
93 |
94 | fetch
95 | remote-notification
96 |
97 | UILaunchStoryboardName
98 | Launch Screen
99 | UIMainStoryboardFile
100 | Main
101 | UIRequiredDeviceCapabilities
102 |
103 | armv7
104 |
105 | UIStatusBarTintParameters
106 |
107 | UINavigationBar
108 |
109 | Style
110 | UIBarStyleDefault
111 | Translucent
112 |
113 |
114 |
115 | UISupportedInterfaceOrientations
116 |
117 | UIInterfaceOrientationPortrait
118 | UIInterfaceOrientationLandscapeLeft
119 | UIInterfaceOrientationLandscapeRight
120 | UIInterfaceOrientationPortraitUpsideDown
121 |
122 | UISupportedInterfaceOrientations~ipad
123 |
124 | UIInterfaceOrientationPortrait
125 | UIInterfaceOrientationLandscapeLeft
126 | UIInterfaceOrientationLandscapeRight
127 | UIInterfaceOrientationPortraitUpsideDown
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL.xcodeproj/xcshareddata/xcschemes/ItemServiceExtension.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
16 |
22 |
23 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
69 |
70 |
76 |
77 |
78 |
79 |
86 |
89 |
90 |
91 |
97 |
98 |
99 |
100 |
102 |
103 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL.xcodeproj/xcshareddata/xcschemes/itemNotification.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
16 |
22 |
23 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
69 |
73 |
74 |
75 |
81 |
82 |
83 |
84 |
91 |
93 |
99 |
100 |
101 |
102 |
104 |
105 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "size" : "29x29",
15 | "idiom" : "iphone",
16 | "filename" : "AppIcon-58.png",
17 | "scale" : "2x"
18 | },
19 | {
20 | "size" : "29x29",
21 | "idiom" : "iphone",
22 | "filename" : "AppIcon-87.png",
23 | "scale" : "3x"
24 | },
25 | {
26 | "size" : "40x40",
27 | "idiom" : "iphone",
28 | "filename" : "AppIcon-80.png",
29 | "scale" : "2x"
30 | },
31 | {
32 | "size" : "40x40",
33 | "idiom" : "iphone",
34 | "filename" : "AppIcon-120.png",
35 | "scale" : "3x"
36 | },
37 | {
38 | "size" : "60x60",
39 | "idiom" : "iphone",
40 | "filename" : "AppIcon-120-1.png",
41 | "scale" : "2x"
42 | },
43 | {
44 | "size" : "60x60",
45 | "idiom" : "iphone",
46 | "filename" : "AppIcon-180.png",
47 | "scale" : "3x"
48 | },
49 | {
50 | "idiom" : "ipad",
51 | "size" : "20x20",
52 | "scale" : "1x"
53 | },
54 | {
55 | "idiom" : "ipad",
56 | "size" : "20x20",
57 | "scale" : "2x"
58 | },
59 | {
60 | "size" : "29x29",
61 | "idiom" : "ipad",
62 | "filename" : "AppIcon-29.png",
63 | "scale" : "1x"
64 | },
65 | {
66 | "size" : "29x29",
67 | "idiom" : "ipad",
68 | "filename" : "AppIcon-58-1.png",
69 | "scale" : "2x"
70 | },
71 | {
72 | "size" : "40x40",
73 | "idiom" : "ipad",
74 | "filename" : "AppIcon-40.png",
75 | "scale" : "1x"
76 | },
77 | {
78 | "size" : "40x40",
79 | "idiom" : "ipad",
80 | "filename" : "AppIcon-80-1.png",
81 | "scale" : "2x"
82 | },
83 | {
84 | "size" : "76x76",
85 | "idiom" : "ipad",
86 | "filename" : "AppIcon-76.png",
87 | "scale" : "1x"
88 | },
89 | {
90 | "size" : "76x76",
91 | "idiom" : "ipad",
92 | "filename" : "AppIcon-152.png",
93 | "scale" : "2x"
94 | },
95 | {
96 | "size" : "83.5x83.5",
97 | "idiom" : "ipad",
98 | "filename" : "AppIcon-167.png",
99 | "scale" : "2x"
100 | },
101 | {
102 | "size" : "1024x1024",
103 | "idiom" : "ios-marketing",
104 | "filename" : "iTunesArtwork@2x.png",
105 | "scale" : "1x"
106 | },
107 | {
108 | "size" : "24x24",
109 | "idiom" : "watch",
110 | "filename" : "AppIcon-48.png",
111 | "scale" : "2x",
112 | "role" : "notificationCenter",
113 | "subtype" : "38mm"
114 | },
115 | {
116 | "size" : "27.5x27.5",
117 | "idiom" : "watch",
118 | "filename" : "AppIcon-55.png",
119 | "scale" : "2x",
120 | "role" : "notificationCenter",
121 | "subtype" : "42mm"
122 | },
123 | {
124 | "size" : "29x29",
125 | "idiom" : "watch",
126 | "filename" : "AppIcon-58-2.png",
127 | "role" : "companionSettings",
128 | "scale" : "2x"
129 | },
130 | {
131 | "size" : "29x29",
132 | "idiom" : "watch",
133 | "filename" : "AppIcon-87-1.png",
134 | "role" : "companionSettings",
135 | "scale" : "3x"
136 | },
137 | {
138 | "size" : "40x40",
139 | "idiom" : "watch",
140 | "filename" : "AppIcon-80-2.png",
141 | "scale" : "2x",
142 | "role" : "appLauncher",
143 | "subtype" : "38mm"
144 | },
145 | {
146 | "size" : "44x44",
147 | "idiom" : "watch",
148 | "scale" : "2x",
149 | "role" : "appLauncher",
150 | "subtype" : "40mm"
151 | },
152 | {
153 | "size" : "50x50",
154 | "idiom" : "watch",
155 | "scale" : "2x",
156 | "role" : "appLauncher",
157 | "subtype" : "44mm"
158 | },
159 | {
160 | "size" : "86x86",
161 | "idiom" : "watch",
162 | "filename" : "AppIcon-176.png",
163 | "scale" : "2x",
164 | "role" : "quickLook",
165 | "subtype" : "38mm"
166 | },
167 | {
168 | "size" : "98x98",
169 | "idiom" : "watch",
170 | "filename" : "AppIcon-196.png",
171 | "scale" : "2x",
172 | "role" : "quickLook",
173 | "subtype" : "42mm"
174 | },
175 | {
176 | "size" : "108x108",
177 | "idiom" : "watch",
178 | "scale" : "2x",
179 | "role" : "quickLook",
180 | "subtype" : "44mm"
181 | },
182 | {
183 | "idiom" : "watch-marketing",
184 | "size" : "1024x1024",
185 | "scale" : "1x"
186 | }
187 | ],
188 | "info" : {
189 | "version" : 1,
190 | "author" : "xcode"
191 | }
192 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CloudKitSync/jobs/xmlreader.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2013 Sam Decrock
3 |
4 | MIT License
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | var sax = require('./sax');
26 |
27 | exports.read = function(xmlstring, callback){
28 | var saxparser = sax.parser(true);
29 | var rootobject = {};
30 | var object = rootobject;
31 |
32 | saxparser.onerror = function (err) {
33 | // an error happened.
34 |
35 | return callback(err);
36 | };
37 |
38 | saxparser.onopentag = function (node) {
39 | // opened a tag. node has "name" and "attributes"
40 |
41 | // create a new object and fill it with a function that returns the attributes:
42 | var newobject = {
43 | attributes: function(name){
44 | return node.attributes;
45 | }
46 | };
47 |
48 | // add the parent() function so that we can use it later:
49 | addParentFunction(newobject, object);
50 |
51 | // add the functions count(), at() and each() to access the nodes as if they were multiple nodes of the same name:
52 | addCountFunction(newobject);
53 | addAtFunction(newobject);
54 | addEachFunction(newobject);
55 |
56 | // check if a node with that name already exists
57 | if(object[node.name]){
58 | // we're dealing with objects of the same name, let's wrap them in an array
59 |
60 | // check if there's already an array:
61 | if(!object[node.name].array){
62 | // no array, create one and at itself + newobject:
63 | var firstobject = object[node.name];
64 | object[node.name] = {};
65 | object[node.name].array = new Array(firstobject, newobject);
66 | }else{
67 | // alreay an array, just add the newobject to it:
68 | object[node.name].array.push(newobject);
69 | }
70 |
71 | // add 3 functions to work with that array:
72 | addCountFunction(object[node.name]);
73 | addAtFunction(object[node.name]);
74 | addEachFunction(object[node.name]);
75 | }else{
76 | // add the functions count() and at() to access the nodes from the array:
77 | object[node.name] = newobject;
78 | }
79 |
80 | // set the current object to the newobject:
81 | object = newobject;
82 | };
83 |
84 | saxparser.oncdata = function(cdata){
85 | // add the function text() to the object to return the cdata value:
86 | object.text = function(){
87 | return cdata;
88 | }
89 | };
90 |
91 | saxparser.ontext = function (text) {
92 | // add the function text() to the object to return the text value:
93 | !object.text ? object.text = function(){
94 | return text;
95 | } : null;
96 | };
97 |
98 | saxparser.onclosetag = function (node) {
99 | // set the object back to its parent:
100 | object = object.parent();
101 | }
102 |
103 | saxparser.onend = function () {
104 | return callback(null, rootobject);
105 | };
106 |
107 | // Functions that add functions like count(), at() and parent()
108 | // We need closures for this:
109 | function addCountFunction(object){
110 | if(object.array){
111 | object.count = function(){
112 | return object.array.length;
113 | }
114 | }else{
115 | object.count = function(){
116 | return 1;
117 | }
118 | }
119 | }
120 |
121 | function addAtFunction(object){
122 | if(object.array){
123 | object.at = function(index){
124 | return object.array[index];
125 | }
126 | }else{
127 | object.at = function(index){
128 | return object;
129 | }
130 | }
131 | }
132 |
133 | function addEachFunction(object){
134 | if(object.array){
135 | object.each = function(callback){
136 | for(var i in object.array){
137 | callback(i, object.array[i]);
138 | }
139 | return;
140 | }
141 | }else{
142 | object.each = function(callback){
143 | return callback(0, object);
144 | }
145 | }
146 | }
147 |
148 |
149 |
150 | function addParentFunction(object, parent){
151 | object.parent = function(){
152 | return parent;
153 | }
154 | }
155 |
156 | // pass the xml string to the awesome sax parser:
157 | saxparser.write(xmlstring).close();
158 | }
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/NSManagedObjectContext+SaveHelpers.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 |
3 | public enum SuccessResult {
4 | /// A success case
5 | case success
6 | /// A failure case with associated ErrorType instance
7 | case failure(Swift.Error)
8 | }
9 |
10 | public typealias SaveResult = SuccessResult
11 | public typealias CoreDataStackSaveCompletion = (SaveResult) -> Void
12 |
13 | /**
14 | Convenience extension to `NSManagedObjectContext` that ensures that saves to contexts of type
15 | `MainQueueConcurrencyType` and `PrivateQueueConcurrencyType` are dispatched on the correct GCD queue.
16 | */
17 | public extension NSManagedObjectContext {
18 |
19 | /**
20 | Convenience method to synchronously save the `NSManagedObjectContext` if changes are present.
21 | Method also ensures that the save is executed on the correct queue when using Main/Private queue concurrency types.
22 | - throws: Errors produced by the `save()` function on the `NSManagedObjectContext`
23 | */
24 | func saveContextAndWait() throws {
25 | switch concurrencyType {
26 | case .confinementConcurrencyType:
27 | try sharedSaveFlow()
28 | case .mainQueueConcurrencyType,
29 | .privateQueueConcurrencyType:
30 | try performAndWaitOrThrow(sharedSaveFlow)
31 | @unknown default:
32 | fatalError()
33 | }
34 | }
35 |
36 | /**
37 | Convenience method to asynchronously save the `NSManagedObjectContext` if changes are present.
38 | Method also ensures that the save is executed on the correct queue when using Main/Private queue concurrency types.
39 | - parameter completion: Completion closure with a `SaveResult` to be executed upon the completion of the save operation.
40 | */
41 | func saveContext(_ completion: CoreDataStackSaveCompletion? = nil) {
42 | func saveFlow() {
43 | do {
44 | try sharedSaveFlow()
45 | completion?(.success)
46 | } catch let saveError {
47 | completion?(.failure(saveError))
48 | }
49 | }
50 |
51 | switch concurrencyType {
52 | case .confinementConcurrencyType:
53 | saveFlow()
54 | case .privateQueueConcurrencyType,
55 | .mainQueueConcurrencyType:
56 | perform(saveFlow)
57 | @unknown default:
58 | fatalError()
59 | }
60 | }
61 |
62 | /**
63 | Convenience method to synchronously save the `NSManagedObjectContext` if changes are present.
64 | If any parent contexts are found, they too will be saved synchronously.
65 | Method also ensures that the save is executed on the correct queue when using Main/Private queue concurrency types.
66 | - throws: Errors produced by the `save()` function on the `NSManagedObjectContext`
67 | */
68 | func saveContextToStoreAndWait() throws {
69 | func saveFlow() throws {
70 | try sharedSaveFlow()
71 | if let parentContext = parent {
72 | try parentContext.saveContextToStoreAndWait()
73 | }
74 | }
75 |
76 | switch concurrencyType {
77 | case .confinementConcurrencyType:
78 | try saveFlow()
79 | case .mainQueueConcurrencyType,
80 | .privateQueueConcurrencyType:
81 | try performAndWaitOrThrow(saveFlow)
82 | @unknown default:
83 | fatalError()
84 | }
85 | }
86 |
87 | /**
88 | Convenience method to asynchronously save the `NSManagedObjectContext` if changes are present.
89 | If any parent contexts are found, they too will be saved asynchronously.
90 | Method also ensures that the save is executed on the correct queue when using Main/Private queue concurrency types.
91 | - parameter completion: Completion closure with a `SaveResult` to be executed
92 | either upon the completion of the top most context's save operation or the first encountered save error.
93 | */
94 | func saveContextToStore(_ completion: CoreDataStackSaveCompletion? = nil) {
95 | func saveFlow() {
96 | do {
97 | try sharedSaveFlow()
98 | if let parentContext = parent {
99 | parentContext.saveContextToStore(completion)
100 | } else {
101 | completion?(.success)
102 | }
103 | } catch let saveError {
104 | completion?(.failure(saveError))
105 | }
106 | }
107 |
108 | switch concurrencyType {
109 | case .confinementConcurrencyType:
110 | saveFlow()
111 | case .privateQueueConcurrencyType,
112 | .mainQueueConcurrencyType:
113 | perform(saveFlow)
114 | @unknown default:
115 | fatalError()
116 | }
117 | }
118 |
119 | private func sharedSaveFlow() throws {
120 | guard hasChanges else {
121 | return
122 | }
123 |
124 | try save()
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL.xcodeproj/xcshareddata/xcschemes/CocoaHeadsNL.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
51 |
52 |
53 |
54 |
56 |
62 |
63 |
64 |
66 |
72 |
73 |
74 |
75 |
76 |
86 |
88 |
94 |
95 |
96 |
97 |
103 |
105 |
111 |
112 |
113 |
114 |
116 |
117 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/Model.xcdatamodeld/Model.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/NSManagedObject+FetchHelpers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | import CoreData
4 |
5 | /**
6 | Extends `NSFetchRequestResult` with
7 | methods that make fetching, inserting, deleting, and change management easier.
8 | */
9 | @available(iOS, introduced: 10.0)
10 | @available(tvOS, introduced: 10.0)
11 | @available(OSX, introduced: 10.12)
12 | extension NSFetchRequestResult where Self: NSManagedObject {
13 | /**
14 | Creates a new fetch request for the `NSManagedObject` entity.
15 | - parameter context: `NSManagedObjectContext` to create the object within.
16 | - returns: `NSFetchRequest`: The new fetch request.
17 | */
18 | public static func fetchRequestForEntity(inContext context: NSManagedObjectContext) -> NSFetchRequest {
19 | let fetchRequest = NSFetchRequest()
20 | fetchRequest.entity = entity()
21 | return fetchRequest
22 | }
23 |
24 | /**
25 | Fetches the first Entity that matches the optional predicate within the specified `NSManagedObjectContext`.
26 | - parameter context: `NSManagedObjectContext` to find the entities within.
27 | - parameter predicate: An optional `NSPredicate` for filtering
28 | - throws: Any error produced from `executeFetchRequest`
29 | - returns: `Self?`: The first entity that matches the optional predicate or `nil`.
30 | */
31 | public static func findFirstInContext(_ context: NSManagedObjectContext, predicate: NSPredicate? = nil) throws -> Self? {
32 | let fetchRequest = fetchRequestForEntity(inContext: context)
33 | fetchRequest.predicate = predicate
34 | fetchRequest.fetchLimit = 1
35 | fetchRequest.returnsObjectsAsFaults = false
36 | fetchRequest.fetchBatchSize = 1
37 | return try context.fetch(fetchRequest).first
38 | }
39 |
40 | /**
41 | Fetches all Entities within the specified `NSManagedObjectContext`.
42 | - parameter context: `NSManagedObjectContext` to find the entities within.
43 | - parameter sortDescriptors: Optional array of `NSSortDescriptors` to apply to the fetch
44 | - parameter predicate: An optional `NSPredicate` for filtering
45 | - throws: Any error produced from `executeFetchRequest`
46 | - returns: `[Self]`: The array of matching entities.
47 | */
48 | public static func allInContext(_ context: NSManagedObjectContext, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil) throws -> [Self] {
49 | let fetchRequest = fetchRequestForEntity(inContext: context)
50 | fetchRequest.sortDescriptors = sortDescriptors
51 | fetchRequest.predicate = predicate
52 | return try context.fetch(fetchRequest)
53 | }
54 |
55 | // MARK: - Counting Objects
56 | /**
57 | Returns count of Entities that matches the optional predicate within the specified `NSManagedObjectContext`.
58 | - parameter context: `NSManagedObjectContext` to count the entities within.
59 | - parameter predicate: An optional `NSPredicate` for filtering
60 | - throws: Any error produced from `countForFetchRequest`
61 | - returns: `Int`: Count of entities that matches the optional predicate.
62 | */
63 | public static func countInContext(_ context: NSManagedObjectContext, predicate: NSPredicate? = nil) throws -> Int {
64 | let fetchReqeust = fetchRequestForEntity(inContext: context)
65 | fetchReqeust.includesSubentities = false
66 | fetchReqeust.predicate = predicate
67 | return try context.count(for: fetchReqeust)
68 | }
69 |
70 | // MARK: - Removing Objects
71 | /**
72 | Removes all entities from within the specified `NSManagedObjectContext`.
73 | - parameter context: `NSManagedObjectContext` to remove the entities from.
74 | - throws: Any error produced from `executeFetchRequest`
75 | */
76 | public static func removeAllInContext(_ context: NSManagedObjectContext) throws {
77 | let fetchRequest = fetchRequestForEntity(inContext: context)
78 | try removeAllObjectsReturnedByRequest(fetchRequest, inContext: context)
79 | }
80 |
81 | /**
82 | Removes all entities from within the specified `NSManagedObjectContext` excluding a supplied array of entities.
83 | - parameter context: The `NSManagedObjectContext` to remove the Entities from.
84 | - parameter except: An Array of `NSManagedObjects` belonging to the `NSManagedObjectContext` to exclude from deletion.
85 | - throws: Any error produced from `executeFetchRequest`
86 | */
87 | public static func removeAllInContext(_ context: NSManagedObjectContext, except toKeep: [Self]) throws {
88 | let fetchRequest = fetchRequestForEntity(inContext: context)
89 | fetchRequest.predicate = NSPredicate(format: "NOT (self IN %@)", toKeep)
90 | try removeAllObjectsReturnedByRequest(fetchRequest, inContext: context)
91 | }
92 |
93 | // MARK: Private Funcs
94 | private static func removeAllObjectsReturnedByRequest(_ fetchRequest: NSFetchRequest, inContext context: NSManagedObjectContext) throws {
95 | // A batch delete would be more efficient here on iOS 9 and up
96 | // however it complicates things since the request requires a context with
97 | // an NSPersistentStoreCoordinator directly connected. (MOC cannot be a child of another MOC)
98 | fetchRequest.includesPropertyValues = false
99 | fetchRequest.includesSubentities = false
100 | try context.fetch(fetchRequest).lazy.forEach(context.delete(_:))
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/generalNotification/Base.lproj/MainInterface.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bart Hoffman on 07/03/15.
6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreSpotlight
11 | import CloudKit
12 |
13 | import CoreData
14 |
15 | import UserNotifications
16 |
17 | let searchNotificationName = "CocoaHeadsNLSpotLightSearchOccured"
18 | let searchPasteboardName = "CocoaHeadsNL-searchInfo-pasteboard"
19 |
20 | @UIApplicationMain
21 | class AppDelegate: UIResponder, UIApplicationDelegate {
22 | var window: UIWindow?
23 |
24 | func applicationDidBecomeActive(_ application: UIApplication) {
25 | if let pasteboard = UIPasteboard(name: UIPasteboard.Name(rawValue: "searchPasteboardName"), create: false) {
26 | pasteboard.string = ""
27 | }
28 |
29 | UIApplication.shared.applicationIconBadgeNumber = 0
30 | }
31 |
32 | func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
33 | // TODO: Do we need this?
34 | }
35 |
36 | func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
37 | if error._code == 3010 {
38 | print("Push Notifications are not supported in the simulator")
39 | } else {
40 | print("application didFailToRegisterForRemoteNotificationsWithError: %@", error)
41 | }
42 | }
43 |
44 | func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
45 |
46 | guard let userInfo = userInfo as? [String: NSObject], let cloudKitNotification = CKNotification(fromRemoteNotificationDictionary: userInfo) else { return }
47 | if cloudKitNotification.notificationType == .query,
48 | let queryNotification = cloudKitNotification as? CKQueryNotification {
49 | //TODO: handle the different notifications to show the correct items
50 | let recordID = queryNotification.recordID
51 | print(recordID as Any)
52 | //...
53 | self.presentMeetupsViewController()
54 | }
55 |
56 | }
57 |
58 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
59 |
60 | _ = CoreDataStack.shared.persistentContainer
61 |
62 | UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
63 | if !granted {
64 | print("Notifications not granted")
65 | }
66 |
67 | if let error = error {
68 | print("Error occured when requesting notification authorization. \(error)")
69 | }
70 | }
71 | UNUserNotificationCenter.current().delegate = self
72 |
73 | application.registerForRemoteNotifications()
74 |
75 | return true
76 | }
77 |
78 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
79 | if userActivity.activityType == CSSearchableItemActionType {
80 | guard let uniqueIdentifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String else {
81 | return false
82 | }
83 | let components = uniqueIdentifier.components(separatedBy: ":")
84 | let type = components[0]
85 | let objectId = components[1]
86 | if type == "job" || type == "meetup" {
87 | //post uniqueIdentifier string to paste board
88 | let pasteboard = UIPasteboard(name: UIPasteboard.Name(rawValue: "searchPasteboardName"), create: true)
89 | pasteboard?.string = uniqueIdentifier
90 |
91 | //open tab, select based on uniqueId
92 | NotificationCenter.default.post(name: Notification.Name(rawValue: searchNotificationName), object: self, userInfo: ["type": type, "objectId": objectId])
93 | return true
94 | }
95 | }
96 |
97 | return false
98 | }
99 |
100 | func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
101 | handleShortCutItem(shortcutItem)
102 | completionHandler(true)
103 | }
104 |
105 | func handleShortCutItem(_ shortCutItem: UIApplicationShortcutItem) {
106 |
107 | switch shortCutItem.type {
108 | case "nl.cocoaheads.CocoaHeadsNL.meetup" :
109 | presentMeetupsViewController()
110 | case "nl.cocoaheads.CocoaHeadsNL.job" :
111 | presentJobsViewController()
112 | case "nl.cocoaheads.CocoaHeadsNL.companies" :
113 | presentCompaniesViewController()
114 | default: break
115 | }
116 | }
117 |
118 | func presentMeetupsViewController() {
119 | if let splitViewController = self.window?.rootViewController as? SplitViewController {
120 | if let tabBar = splitViewController.viewControllers[0] as? UITabBarController {
121 | tabBar.selectedIndex = 0
122 | }
123 | }
124 | }
125 |
126 | func presentJobsViewController() {
127 | if let splitViewController = self.window?.rootViewController as? SplitViewController {
128 | if let tabBar = splitViewController.viewControllers[0] as? UITabBarController {
129 | tabBar.selectedIndex = 1
130 | }
131 | }
132 | }
133 |
134 | func presentCompaniesViewController() {
135 | if let splitViewController = self.window?.rootViewController as? SplitViewController {
136 | if let tabBar = splitViewController.viewControllers[0] as? UITabBarController {
137 | tabBar.selectedIndex = 2
138 | }
139 | }
140 | }
141 | }
142 |
143 | extension AppDelegate: UNUserNotificationCenterDelegate {
144 |
145 | func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
146 | completionHandler()
147 | }
148 | func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
149 | completionHandler([.alert, .sound])
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/DetailViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DetailViewController.swift
3 | // CocoaHeadsNL
4 | //
5 | // Created by Bart Hoffman on 14/03/15.
6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import StoreKit
11 |
12 | extension UIResponder {
13 | @objc func reloadCell(_ cell: UITableViewCell) {
14 | self.next?.reloadCell(cell)
15 | }
16 | }
17 |
18 | class DetailViewController: UITableViewController, SKStoreProductViewControllerDelegate {
19 | var dataSource: DetailDataSource!
20 | weak var presentingVC: UIViewController?
21 | fileprivate var activityViewController: UIActivityViewController?
22 |
23 | required init?(coder aDecoder: NSCoder) {
24 | super.init(coder: aDecoder)
25 | }
26 |
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 |
30 | tableView.tableFooterView = UIView()
31 |
32 | //Can be used to hide masterViewController and increase size of detailView if wanted
33 | self.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
34 | self.navigationItem.leftItemsSupplementBackButton = true
35 |
36 | //For some reason this triggers correct resizing behavior when rotating views.
37 | self.tableView.estimatedRowHeight = 100
38 |
39 | if let dataSource = dataSource {
40 | dataSource.tableView = self.tableView
41 | self.tableView.dataSource = dataSource
42 | self.tableView.delegate = dataSource
43 | self.navigationItem.title = dataSource.title
44 | }
45 |
46 | if let data = dataSource as? CompanyDataSource {
47 | data.presenter = self
48 | }
49 |
50 | self.tableView.reloadData()
51 | }
52 |
53 | override func viewDidAppear(_ animated: Bool) {
54 | super.viewDidAppear(animated)
55 |
56 | var url: URL?
57 | var title: String?
58 | var activityType: String?
59 |
60 | if let companyDataSource = dataSource as? CompanyDataSource {
61 | if let urlString = companyDataSource.company.website, let titleString = companyDataSource.company.name {
62 | title = titleString
63 | url = URL(string: urlString)
64 | activityType = "nl.cocoaheads.app.company"
65 | }
66 | } else if let jobsDataSource = dataSource as? JobDataSource {
67 | title = jobsDataSource.job.title
68 | url = URL(string: jobsDataSource.job.link!)
69 | activityType = "nl.cocoaheads.app.job"
70 | } else if let meetupDataSource = dataSource as? MeetupDataSource, let urlString = meetupDataSource.meetup.meetupUrl {
71 | title = meetupDataSource.meetup.name
72 | url = URL(string: urlString)
73 | activityType = "nl.cocoaheads.app.meetup"
74 | }
75 |
76 | if let title = title, let url = url, let activityType = activityType {
77 | let activity = NSUserActivity(activityType: activityType)
78 | activity.title = title
79 |
80 | guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
81 | return
82 | }
83 |
84 | if urlComponents.scheme == nil {
85 | urlComponents.scheme = "http"
86 | }
87 |
88 | guard let checkedUrl = urlComponents.url, urlComponents.scheme == "http" || urlComponents.scheme == "https" else {
89 | return
90 | }
91 |
92 | activity.webpageURL = checkedUrl
93 | activity.becomeCurrent()
94 | self.userActivity = activity
95 | }
96 | }
97 |
98 | override func viewDidDisappear(_ animated: Bool) {
99 | super.viewDidDisappear(animated)
100 |
101 | self.userActivity?.resignCurrent()
102 | }
103 |
104 | override func reloadCell(_ cell: UITableViewCell) {
105 | tableView.beginUpdates()
106 | tableView.endUpdates()
107 | }
108 |
109 | func showStoreView(_ parameters: [String: AnyObject], indexPath: IndexPath) {
110 |
111 | let storeViewController = SKStoreProductViewController()
112 | storeViewController.delegate = self
113 |
114 | storeViewController.loadProduct(withParameters: parameters,
115 | completionBlock: {result, _ in
116 | if result {
117 | self.present(storeViewController,
118 | animated: true, completion: nil)
119 | self.tableView.deselectRow(at: indexPath, animated: true)
120 | }
121 | })
122 | }
123 |
124 | func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
125 | self.dismiss(animated: true, completion: nil)
126 | }
127 |
128 | override var previewActionItems: [UIPreviewActionItem] {
129 | let shareAction = UIPreviewAction(title: NSLocalizedString("Share"), style: .default) { _, _ in
130 |
131 | if let meetup = self.dataSource.object as? Meetup, let meetupId = meetup.meetupId {
132 | let string: String = "http://www.meetup.com/CocoaHeadsNL/events/\(meetupId)/"
133 | let URL: Foundation.URL = Foundation.URL(string: string)!
134 |
135 | let acViewController = UIActivityViewController(activityItems: [string, URL], applicationActivities: nil)
136 |
137 | if let meetupVC = self.presentingVC {
138 | self.activityViewController = acViewController
139 | meetupVC.present(self.activityViewController!, animated: true, completion: nil)
140 | }
141 | }
142 | }
143 |
144 | let rsvpAction = UIPreviewAction(title: NSLocalizedString("RSVP"), style: .default) { _, _ in
145 |
146 | if let meetup = self.dataSource.object as? Meetup, let meetupId = meetup.meetupId {
147 | if let URL = URL(string: "http://www.meetup.com/CocoaHeadsNL/events/\(meetupId)/") {
148 | UIApplication.shared.open(URL, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: { _ in
149 | })
150 | }
151 | }
152 | }
153 |
154 | return [rsvpAction, shareAction]
155 | }
156 | }
157 |
158 | // Helper function inserted by Swift 4.2 migrator.
159 | private func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
160 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value) })
161 | }
162 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | included:
2 | - CocoaHeadsNL
3 |
4 | excluded:
5 | - Pods
6 | - Frameworks
7 | - bin
8 | - Build-Phases
9 | - Products
10 |
11 | opt_in_rules:
12 | # 0️⃣ - Geen meerwaarde
13 | # 1️⃣ - Minimale meerwaarde
14 | # 2️⃣ - Gemiddelde meerwaarde
15 | # 3️⃣ - Hoge meerwaarde
16 | # 4️⃣ - No-brainers
17 | # 🚗 - supports autocorrect
18 | # ⚠️ - Number of warnings
19 |
20 | # Stylistic Rules 💅🏻
21 | # - let_var_whitespace # 1️⃣ ⚠️ 434, Zorg ervoor dat in variable en een functie niet tegen elkaar aanstaan (protocols) style
22 | - operator_usage_whitespace # 1️⃣ 🚗 dubbele spaties, missende spaties
23 | - closure_end_indentation # 1️⃣ 🚗, { } closures
24 | - closure_parameter_position # 1️⃣, { text within closures }
25 | # - collection_alignment # 1️⃣ ⚠️ 3, 3 regels code die niet netjes uitgelijnd zijn.
26 | # - conditional_returns_on_newline # 1️⃣ ⚠️ 2, # only for -if statements
27 | # - function_default_parameter_at_end # 1️⃣ ⚠️ 10, #in functies worden default parameters aan het einde gezet: func bla(foo: String, roo: Int = 200)
28 | # - multiline_arguments # 1️⃣ ⚠️ 49, Of alle argumenten van een functie in 1 regel of alle argumenten per regel en niet 3 argumenten op regel 1 en 4 argumenten op regel 2
29 | - multiline_parameters # 1️⃣, parameters of allemaal op 1 regel of per regel een parameter
30 | # - multiline_function_chains # 1️⃣ ⚠️ 129, chaining op meerdere regels, zorgt voor structuur
31 | # - multiline_parameters_brackets # 1️⃣ ⚠️ 10,
32 | - closure_spacing # 2️⃣ 🚗, spacing tussen clusures zorgt voor veel spatie irritatie
33 |
34 | # Hygienic Rules 🧻
35 | # - file_header # 0️⃣, ⚠️ 688 warnings
36 | # - closure_body_length # 1️⃣ ⚠️ 8, Teveel logica in een closure () -- Emiel
37 | - explicit_init # 1️⃣ 🚗, Compleet overbodig om init aan te roepen voor bijvoorbeeld: UIColor.init(hexcode: "5288f5") ipv UIColor(hexcode: "5288f5"
38 | # - toggle_bool # 1️⃣ ⚠️ 2, someBool.toggle() over someBool = !someBool.
39 | - unused_import # 1️⃣ 🚗
40 | - unneeded_parentheses_in_closure_argument # 3️⃣ 🚗,
41 | - redundant_type_annotation # 1️⃣ 🚗, let storyBoard: UIStoryboard = UIStoryboard("")
42 | # - empty_string # 4️⃣ ⚠️ 9 performance verbetering wanneer we aString.isEmpty gebruiken ipv aString == ""
43 | # - first_where # 4️⃣ ⚠️ 5 performance verbetering Prefer using .first(where:) over .filter { }.first in collections.
44 | - last_where # 4️⃣ performance, zelfde als first_where
45 | # - sorted_first_last # 4️⃣ ⚠️ 5 performance, gebruik maken van min/max functies van array's ipv sorteren en vervolgens .first/.last
46 |
47 | # Convention Rules 🤝
48 | - modifier_order # 1️⃣ 🚗 ⚠️ 17, style, 'class private func' moet worden 'private class func'
49 | # - no_grouping_extension # 1️⃣ ⚠️ 477, style, persoonelijk vind ik het niet logisch dat een class geextend wordt om daarin code te groeperen (alleen intereresant voor class extensies zonder dat ze ook een protocol inmplementeren)
50 | # - legacy_random # 2️⃣ ⚠️ 1, random is vernieuwd
51 | # - fatal_error_message # 2️⃣ ⚠️ 72, Helpt de andere developers, kleine moeite https://medium.com/@abhimuralidharan/what-are-fatal-errors-and-when-to-use-them-53ad1a66c6d6
52 | # - convenience_type # 3️⃣ ⚠️ 60, structs, classes en enums die verkeerd gebruikt worden (classes die geen struct zijn) En dit heeft invloed op de performance waarom: https://stackoverflow.com/a/24232845/2826164
53 | - prohibited_super_call
54 | - override_in_extension
55 | - unused_optional_binding
56 | - yoda_condition
57 |
58 | # Code Smells Rules 💩
59 | # - force_unwrapping # 3️⃣ ⚠️ 147, force unwrapping can cause crashes (is nu ook al een ongeschreven regel)
60 | # - implicitly_unwrapped_optional # 3️⃣ ⚠️ 96, avoid force unwrap
61 | - redundant_nil_coalescing # 2️⃣ 🚗 ⚠️ 3, var myVar: Int? = nil; myVar ?? nil
62 | # - contains_over_first_not_nil # 2️⃣ ⚠️ 4, performance verbetering
63 | # - pattern_matching_keywords # 3️⃣ ⚠️ 8, case let .foo(x, y): break ipv case .foo(↓let x, ↓let y): break
64 |
65 | # Bug Avoiding Rules 🐞
66 | - fallthrough # 2️⃣ switch falltrough zorgt voor onduidelijkheid, wordt gelukkig nergens gebruikt! Kan Aan!
67 | - no_fallthrough_only
68 | # - lower_acl_than_parent # 2️⃣ ⚠️ 292, onduidelijkheid: accesss van functies en variabelen die meer toegangkelijkheid bieden hebben dan de klasse
69 | # - no_extension_access_modifier # 2️⃣ ⚠️ 25, extensions horen niet het toegangsniveau aan te passen bijv: (private extension String)
70 | # - private_action # 3️⃣ ⚠️ 47, voorkomt om deze actions via een andere manier aan te roepen
71 | # - private_outlet # 3️⃣ ⚠️ 153, voorkomt om deze outlets via een andere klasse aangepast kunnen worden
72 | # - strong_iboutlet # 3️⃣ ⚠️ 341, kan crashes voorkomen als de view die de outlet bevat verdwijnt https://cocoacasts.com/should-outlets-be-weak-or-strong
73 | # - overridden_super_call # 3️⃣ ⚠️ 24, inpact onbekend, lijkt me goed om uit te zoeken
74 | # - file_name # 4️⃣ 26 ⚠️, met honderden bestanden is het belangrijk dat de "Bestandsnaam.swift" overeen komt met de klassenaam "Bestandennaam"
75 | - empty_count # 4️⃣, performance verbetering wanneer we count > 0 vervangen met isEmpty
76 |
77 | disabled_rules:
78 | - trailing_newline
79 | - trailing_whitespace
80 |
81 | # regels om aan te zetten:
82 | - large_tuple # Aanzetten, gebruik een struct of een class (max 3)
83 | - function_parameter_count # aanzetten, functies met meer dan 5 counts kunnen in de praktijk gewoon een struct of een class als parameter mee krijgen en dan daarnaar handelen
84 |
85 |
86 | type_name:
87 | min_length: 3
88 | max_length: 70
89 |
90 | # min_length op 3 met uitzondering voor Id
91 | identifier_name:
92 | min_length: 1
93 | max_length: 60
94 | allowed_symbols: "_"
95 |
96 | file_length:
97 | warning: 901
98 | error: 900 # files boven de 900 regels zijn error's
99 |
100 | conditional_returns_on_newline:
101 | if_only: true
102 |
103 | # Langste functie is 174 regels+ . het is belangrijk dat verantwoordelijkheid in functies wordt opgesplitst
104 | # 50 regels maximaal liever korter. Voor te lange functies is // swiftlint:disable:next function_body_length
105 | function_body_length:
106 | warning: 50
107 | error: 50
108 |
109 | large_tuple:
110 | warning: 3
111 |
112 | # Geen idee of hier enige waarde aan gehecht wordt, regellengte (emiel says: 'no')
113 | # Xcode, Settings, Text Editing, indention tab, 'Check linewrapping' om lengte van regel irritatie te voorkomen.
114 | line_length:
115 | warning: 301
116 | error: 300
117 | ignores_comments: true
118 |
119 | file_header:
120 | required_pattern: |
121 | \/\/
122 | \/\/ .*?\.swift
123 | \/\/ EvenAppen
124 | \/\/
125 | \/\/ Created by .*? on \d{1,2}\/\d{1,2}\/\d{2}\.
126 | \/\/ Copyright © \d{4} Achmea\. All rights reserved\.
127 | \/\/
128 |
129 | reporter: "xcode"
130 |
--------------------------------------------------------------------------------
/CocoaHeadsNL/CocoaHeadsNL/JobsCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------