Browse Source

Development

master
Dwayne Harris 6 years ago
parent
commit
0025e069bb
  1. 26
      elpha-ios.xcodeproj/project.pbxproj
  2. 8
      elpha-ios.xcodeproj/xcuserdata/Dwayne.xcuserdatad/xcschemes/xcschememanagement.plist
  3. 47
      elpha-ios/Base.lproj/Main.storyboard
  4. 52
      elpha-ios/Elpha.xcdatamodeld/Elpha.xcdatamodel/contents
  5. 16
      elpha-ios/InstanceViewController.swift
  6. 235
      elpha-ios/InstancesDataManager.swift
  7. 13
      elpha-ios/InstancesNavigationController.swift
  8. 15
      elpha-ios/InstancesTableView.swift
  9. 4
      elpha-ios/InstancesTableViewCell.swift
  10. 191
      elpha-ios/InstancesTableViewController.swift
  11. 56
      elpha-ios/WebViewController.swift
  12. 22
      elpha-ios/WebViewHelper.swift

26
elpha-ios.xcodeproj/project.pbxproj

@ -8,8 +8,8 @@
/* Begin PBXBuildFile section */
157405A82150588A00EEAAEB /* InstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157405A72150588A00EEAAEB /* InstanceViewController.swift */; };
157405AA2150804200EEAAEB /* WebViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157405A92150804200EEAAEB /* WebViewHelper.swift */; };
157405AC215080D400EEAAEB /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157405AB215080D400EEAAEB /* WebViewController.swift */; };
157405B12151A5DA00EEAAEB /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 157405AF2151A5DA00EEAAEB /* README.md */; };
157405B42151A93E00EEAAEB /* InstancesDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157405B32151A93E00EEAAEB /* InstancesDataManager.swift */; };
15830D93214F6F6F0037C342 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15830D92214F6F6F0037C342 /* ImageCache.swift */; };
159048AF214F5015004F4014 /* InstancesTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159048AE214F5015004F4014 /* InstancesTableViewCell.swift */; };
15960E5B213145E100C38CE9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15960E5A213145E100C38CE9 /* AppDelegate.swift */; };
@ -27,14 +27,13 @@
15960E8021353DCF00C38CE9 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15960E7F21353DCF00C38CE9 /* TimelineViewController.swift */; };
15960E822136668500C38CE9 /* TimelinesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15960E812136668500C38CE9 /* TimelinesNavigationController.swift */; };
15960E84213774FC00C38CE9 /* InstancesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15960E83213774FC00C38CE9 /* InstancesTableViewController.swift */; };
15960E862137775D00C38CE9 /* InstancesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15960E852137775D00C38CE9 /* InstancesNavigationController.swift */; };
15960E88213902A400C38CE9 /* InstancesTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15960E87213902A400C38CE9 /* InstancesTableView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
157405A72150588A00EEAAEB /* InstanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceViewController.swift; sourceTree = "<group>"; };
157405A92150804200EEAAEB /* WebViewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewHelper.swift; sourceTree = "<group>"; };
157405AB215080D400EEAAEB /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = "<group>"; };
157405AF2151A5DA00EEAAEB /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
157405B02151A5DA00EEAAEB /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
157405B32151A93E00EEAAEB /* InstancesDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesDataManager.swift; sourceTree = "<group>"; };
15830D92214F6F6F0037C342 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
159048AE214F5015004F4014 /* InstancesTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesTableViewCell.swift; sourceTree = "<group>"; };
15960E57213145E100C38CE9 /* elpha-ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "elpha-ios.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -54,8 +53,6 @@
15960E7F21353DCF00C38CE9 /* TimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewController.swift; sourceTree = "<group>"; };
15960E812136668500C38CE9 /* TimelinesNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesNavigationController.swift; sourceTree = "<group>"; };
15960E83213774FC00C38CE9 /* InstancesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesTableViewController.swift; sourceTree = "<group>"; };
15960E852137775D00C38CE9 /* InstancesNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesNavigationController.swift; sourceTree = "<group>"; };
15960E87213902A400C38CE9 /* InstancesTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesTableView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -72,6 +69,8 @@
15960E4E213145E100C38CE9 = {
isa = PBXGroup;
children = (
157405AF2151A5DA00EEAAEB /* README.md */,
157405B02151A5DA00EEAAEB /* Package.swift */,
15960E59213145E100C38CE9 /* elpha-ios */,
15960E58213145E100C38CE9 /* Products */,
);
@ -93,7 +92,7 @@
15960E7B213272CD00C38CE9 /* AuthenticationManager.swift */,
15960E7621322C6F00C38CE9 /* Configuration.swift */,
15830D92214F6F6F0037C342 /* ImageCache.swift */,
157405A92150804200EEAAEB /* WebViewHelper.swift */,
157405B32151A93E00EEAAEB /* InstancesDataManager.swift */,
15960E63213145E200C38CE9 /* Assets.xcassets */,
15960E6E21321FA500C38CE9 /* Elpha.xcdatamodeld */,
15960E7121322B9F00C38CE9 /* Keychain Helper */,
@ -117,8 +116,6 @@
isa = PBXGroup;
children = (
15960E7D21329FED00C38CE9 /* AuthenticateViewController.swift */,
15960E852137775D00C38CE9 /* InstancesNavigationController.swift */,
15960E87213902A400C38CE9 /* InstancesTableView.swift */,
159048AE214F5015004F4014 /* InstancesTableViewCell.swift */,
15960E83213774FC00C38CE9 /* InstancesTableViewController.swift */,
157405A72150588A00EEAAEB /* InstanceViewController.swift */,
@ -126,7 +123,6 @@
15960E5E213145E100C38CE9 /* SecondViewController.swift */,
15960E812136668500C38CE9 /* TimelinesNavigationController.swift */,
15960E7F21353DCF00C38CE9 /* TimelineViewController.swift */,
157405AB215080D400EEAAEB /* WebViewController.swift */,
);
name = "View Controllers";
sourceTree = "<group>";
@ -192,6 +188,7 @@
files = (
15960E67213145E200C38CE9 /* LaunchScreen.storyboard in Resources */,
15960E64213145E200C38CE9 /* Assets.xcassets in Resources */,
157405B12151A5DA00EEAAEB /* README.md in Resources */,
15960E62213145E100C38CE9 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -203,15 +200,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
157405AC215080D400EEAAEB /* WebViewController.swift in Sources */,
15960E88213902A400C38CE9 /* InstancesTableView.swift in Sources */,
159048AF214F5015004F4014 /* InstancesTableViewCell.swift in Sources */,
15830D93214F6F6F0037C342 /* ImageCache.swift in Sources */,
15960E84213774FC00C38CE9 /* InstancesTableViewController.swift in Sources */,
15960E7021321FA500C38CE9 /* Elpha.xcdatamodeld in Sources */,
157405B42151A93E00EEAAEB /* InstancesDataManager.swift in Sources */,
15960E5F213145E100C38CE9 /* SecondViewController.swift in Sources */,
15960E8021353DCF00C38CE9 /* TimelineViewController.swift in Sources */,
157405AA2150804200EEAAEB /* WebViewHelper.swift in Sources */,
15960E7A2132387A00C38CE9 /* MainTabBarController.swift in Sources */,
15960E7C213272CD00C38CE9 /* AuthenticationManager.swift in Sources */,
15960E7E21329FED00C38CE9 /* AuthenticateViewController.swift in Sources */,
@ -220,7 +215,6 @@
15960E7521322BF800C38CE9 /* KeychainWrapper.swift in Sources */,
157405A82150588A00EEAAEB /* InstanceViewController.swift in Sources */,
15960E7321322BC700C38CE9 /* KeychainItemAccessibility.swift in Sources */,
15960E862137775D00C38CE9 /* InstancesNavigationController.swift in Sources */,
15960E822136668500C38CE9 /* TimelinesNavigationController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

8
elpha-ios.xcodeproj/xcuserdata/Dwayne.xcuserdatad/xcschemes/xcschememanagement.plist

@ -10,5 +10,13 @@
<integer>0</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>15960E56213145E100C38CE9</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>

47
elpha-ios/Base.lproj/Main.storyboard

@ -128,52 +128,11 @@
</objects>
<point key="canvasLocation" x="-615.20000000000005" y="770.46476761619192"/>
</scene>
<!--Web View Controller-->
<scene sceneID="MIo-vc-tec">
<objects>
<viewController storyboardIdentifier="WebViewController" id="ufm-NU-wEh" customClass="WebViewController" customModule="elpha_ios" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="oP6-QF-ODB">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<wkWebView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="69Y-fp-1th">
<rect key="frame" x="0.0" y="46" width="414" height="850"/>
<color key="backgroundColor" red="0.36078431370000003" green="0.38823529410000002" blue="0.4039215686" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="e2P-oa-L65">
<rect key="frame" x="0.0" y="44" width="414" height="2"/>
</progressView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Xhd-n0-wrI" firstAttribute="trailing" secondItem="69Y-fp-1th" secondAttribute="trailing" id="09z-LF-Bzf"/>
<constraint firstAttribute="bottom" secondItem="69Y-fp-1th" secondAttribute="bottom" id="DMr-RR-tQi"/>
<constraint firstItem="69Y-fp-1th" firstAttribute="top" secondItem="Xhd-n0-wrI" secondAttribute="top" constant="2" id="O50-pm-MnH"/>
<constraint firstItem="69Y-fp-1th" firstAttribute="leading" secondItem="Xhd-n0-wrI" secondAttribute="leading" id="RcD-DJ-C5a"/>
<constraint firstItem="e2P-oa-L65" firstAttribute="top" secondItem="Xhd-n0-wrI" secondAttribute="top" id="ZiT-m1-F3I"/>
<constraint firstItem="e2P-oa-L65" firstAttribute="leading" secondItem="Xhd-n0-wrI" secondAttribute="leading" id="h8f-GM-MWX"/>
<constraint firstItem="Xhd-n0-wrI" firstAttribute="trailing" secondItem="e2P-oa-L65" secondAttribute="trailing" id="tMb-ce-c9H"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Xhd-n0-wrI"/>
</view>
<connections>
<outlet property="mainProgressView" destination="e2P-oa-L65" id="lLE-Pv-wWV"/>
<outlet property="mainWebView" destination="69Y-fp-1th" id="Yi5-en-t7m"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="WjG-9G-tw9" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-616" y="1466"/>
</scene>
<!--Instances-->
<scene sceneID="24Q-ad-dey">
<objects>
<tableViewController id="bRx-56-pJs" customClass="InstancesTableViewController" customModule="elpha_ios" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="120" sectionHeaderHeight="28" sectionFooterHeight="28" id="MsU-Kh-W9V" customClass="InstancesTableView" customModule="elpha_ios" customModuleProvider="target">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="120" sectionHeaderHeight="28" sectionFooterHeight="28" id="MsU-Kh-W9V">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@ -472,10 +431,10 @@
</objects>
<point key="canvasLocation" x="749.60000000000002" y="-320.68965517241384"/>
</scene>
<!--Instances Navigation Controller-->
<!--Navigation Controller-->
<scene sceneID="UVY-Um-jQm">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="SqQ-GI-iCq" customClass="InstancesNavigationController" customModule="elpha_ios" customModuleProvider="target" sceneMemberID="viewController">
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="SqQ-GI-iCq" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="QFN-sS-62X">
<rect key="frame" x="0.0" y="44" width="414" height="96"/>

52
elpha-ios/Elpha.xcdatamodeld/Elpha.xcdatamodel/contents

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14135" systemVersion="17G65" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14315.18" systemVersion="17G65" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Account" representedClassName="AccountMO" syncable="YES" codeGenerationType="class">
<attribute name="acct" attributeType="String" syncable="YES"/>
<attribute name="avatarData" optional="YES" attributeType="Binary" syncable="YES"/>
@ -20,39 +20,63 @@
<attribute name="statusesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="url" attributeType="String" syncable="YES"/>
<attribute name="username" attributeType="String" syncable="YES"/>
<relationship name="sessions" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Session" inverseName="account" inverseEntity="Session" syncable="YES"/>
</entity>
<entity name="App" representedClassName="AppMO" syncable="YES" codeGenerationType="class">
<attribute name="clientId" attributeType="String" syncable="YES"/>
<attribute name="id" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<relationship name="sessions" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Session" inverseName="app" inverseEntity="Session" syncable="YES"/>
</entity>
<entity name="Instance" representedClassName="InstanceMO" syncable="YES" codeGenerationType="class">
<attribute name="connections" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="desc" optional="YES" attributeType="String" syncable="YES"/>
<entity name="ISCategory" representedClassName="ISCategoryMO" syncable="YES" codeGenerationType="class">
<attribute name="id" attributeType="String" syncable="YES"/>
<relationship name="instances" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ISInstance" inverseName="categories" inverseEntity="ISInstance" syncable="YES"/>
</entity>
<entity name="ISInstance" representedClassName="ISInstanceMO" syncable="YES" codeGenerationType="class">
<attribute name="activeUsers" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="checkedAt" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="connections" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="dead" optional="YES" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<attribute name="email" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="fullDescription" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="https" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="languages" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="id" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="ipv6" optional="YES" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="openRegistrations" optional="YES" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<attribute name="registrations" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="statuses" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="shortDesc" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="statuses" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="thumbnail" optional="YES" attributeType="URI" syncable="YES"/>
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="uptime" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="uri" attributeType="URI" syncable="YES"/>
<attribute name="topic" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="up" optional="YES" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="url" attributeType="URI" syncable="YES"/>
<attribute name="urls" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="users" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="users" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="version" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="categories" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ISCategory" inverseName="instances" inverseEntity="ISCategory" syncable="YES"/>
<relationship name="languages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ISLanguage" inverseName="instances" inverseEntity="ISLanguage" syncable="YES"/>
</entity>
<entity name="ISLanguage" representedClassName="ISLanguageMO" syncable="YES" codeGenerationType="class">
<attribute name="id" attributeType="String" syncable="YES"/>
<relationship name="instances" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ISInstance" inverseName="languages" inverseEntity="ISInstance" syncable="YES"/>
</entity>
<entity name="Session" representedClassName="SessionMO" syncable="YES" codeGenerationType="class">
<attribute name="color" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="selected" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" syncable="YES"/>
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="App" syncable="YES"/>
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="sessions" inverseEntity="Account" syncable="YES"/>
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="App" inverseName="sessions" inverseEntity="App" syncable="YES"/>
</entity>
<elements>
<element name="Account" positionX="-63" positionY="-18" width="128" height="330"/>
<element name="App" positionX="-54" positionY="135" width="128" height="75"/>
<element name="Instance" positionX="-18" positionY="153" width="128" height="255"/>
<element name="Account" positionX="-63" positionY="-18" width="128" height="345"/>
<element name="App" positionX="-54" positionY="135" width="128" height="90"/>
<element name="ISCategory" positionX="-27" positionY="144" width="128" height="75"/>
<element name="ISInstance" positionX="-18" positionY="153" width="128" height="435"/>
<element name="ISLanguage" positionX="-36" positionY="135" width="128" height="75"/>
<element name="Session" positionX="-36" positionY="144" width="128" height="135"/>
</elements>
</model>

16
elpha-ios/InstanceViewController.swift

@ -8,23 +8,25 @@
import Foundation
import UIKit
import SafariServices
class InstanceViewController: UIViewController {
class InstanceViewController: UIViewController, SFSafariViewControllerDelegate {
@IBOutlet var thumbnailImageView: UIImageView!
@IBOutlet var instanceNameLabel: UILabel!
@IBOutlet var instanceDescriptionLabel: UILabel!
@IBOutlet var webViewButton: UIButton!
var instance: Instance? = nil
var instance: ISInstanceMO? = nil
@IBAction func webViewButtonPressed(_ sender: Any) {
guard let instance = instance, let name = instance.name else {
return
}
if let webViewController = WebViewHelper.getWebViewController(url: URL(string: "https://\(name)")!) {
navigationController?.pushViewController(webViewController, animated: true)
}
let safariViewController = SFSafariViewController(url: URL(string: "https://\(name)")!)
safariViewController.delegate = self
present(safariViewController, animated: true)
}
@ -52,8 +54,8 @@ class InstanceViewController: UIViewController {
instanceDescriptionLabel.text = instance.fullDescription
}
if let thumbnailURL = instance.thumbnail {
ImageCache.shared.getImage(forURL: URL(string: thumbnailURL)!) { image, error in
if let thumbnail = instance.thumbnail {
ImageCache.shared.getImage(forURL: thumbnail) { image, error in
guard error == nil else {
return
}

235
elpha-ios/InstancesDataManager.swift

@ -0,0 +1,235 @@
//
// InstancesDataManager.swift
// elpha-ios
//
// Created by Dwayne Harris on 9/18/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import CoreData
import Foundation
import UIKit
class InstancesDataManager {
static let shared = InstancesDataManager()
let instanceRequestCount = 20
var instances: [ISInstanceMO] = []
var finished = false
var nextID: String? = nil
static func upsertLanguage(string: String) -> ISLanguageMO? {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest<ISLanguageMO>(entityName: "ISLanguage")
request.predicate = NSPredicate(format: "id == %@", string)
do {
let results: [ISLanguageMO] = try context.fetch(request)
switch results.count {
case 0:
let language = ISLanguageMO(context: context)
language.id = string
try context.save()
return language
case 1:
return results.first
default:
print("Duplicate ISLanguage entity")
return nil
}
} catch {
print("\(error)")
return nil
}
}
static func upsertCategory(string: String) -> ISCategoryMO? {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest<ISCategoryMO>(entityName: "ISCategory")
request.predicate = NSPredicate(format: "id == %@", string)
do {
let results: [ISCategoryMO] = try context.fetch(request)
switch results.count {
case 0:
let category = ISCategoryMO(context: context)
category.id = string
try context.save()
return category
case 1:
return results.first
default:
print("Duplicate ISCategory entity")
return nil
}
} catch {
print("\(error)")
return nil
}
}
static func setAttributes(on i: ISInstanceMO, attributes: [String: Any]) {
guard let id = attributes["id"] as? String, let name = attributes["name"] as? String else {
print("Error")
return
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
i.id = id
i.name = name
i.url = URL(string: "https://\(name)")!
i.uptime = attributes["uptime"] as! Int32
i.up = attributes["up"] as! Bool
i.dead = attributes["dead"] as! Bool
i.version = attributes["version"] as? String
i.ipv6 = attributes["ipv6"] as! Bool
i.users = Int64(attributes["users"] as! String)!
i.statuses = Int64(attributes["statuses"] as! String)!
i.connections = Int64(attributes["connections"] as! String)!
i.openRegistrations = attributes["open_registrations"] as! Bool
if let activeUsers = attributes["active_users"] as? Int64 {
i.activeUsers = activeUsers
}
if let updatedAt = attributes["updated_at"] as? String {
i.updatedAt = dateFormatter.date(from: updatedAt)
}
if let checkedAt = attributes["checked_at"] as? String {
i.checkedAt = dateFormatter.date(from: checkedAt)
}
if let thumbnail = attributes["thumbnail"] as? String {
i.thumbnail = URL(string: thumbnail)!
}
if let info = attributes["info"] as? [String: Any] {
i.shortDesc = info["short_description"] as? String
i.fullDescription = info["full_description"] as? String
i.topic = info["topic"] as? String
for language in info["languages"] as! [String] {
if let l = upsertLanguage(string: language) {
i.mutableSetValue(forKey: "languages").add(l)
}
}
for category in info["categories"] as! [String] {
if let c = upsertCategory(string: category) {
i.mutableSetValue(forKey: "categories").add(c)
}
}
}
}
func reloadInstances(completion: @escaping (Error?) -> Void) {
finished = false
nextID = nil
loadInstances(completion: completion)
}
func loadInstances(completion: @escaping (Error?) -> Void) {
var params = [
"count=\(instanceRequestCount)",
"sort_by=users",
"sort_order=desc",
"include_closed=true",
"include_down=false",
]
if let nextID = nextID {
params.append("min_id=\(nextID)")
}
let requestURL = "\(Config.instancesServiceUrl)\(Config.instancesServiceEndpoint)?\(params.joined(separator: "&"))"
print("loading instances: requestURL: \(requestURL)")
var request = URLRequest(url: URL(string: requestURL)!)
request.addValue("Bearer \(Config.instancesServiceSecret)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
completion(error)
return
}
guard let response = response as? HTTPURLResponse else {
print("No HTTP response")
completion(NSError())
return
}
switch response.statusCode {
case 200..<300:
guard let data = data else {
print("No HTTP response")
completion(NSError())
return
}
DispatchQueue.main.async {
do {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let instancesResult = try JSONSerialization.jsonObject(with: data) as! [String: Any]
if let pagination = instancesResult["pagination"] as? [String: Any] {
if let nextID = pagination["next_id"] as? String {
self.nextID = nextID
} else {
self.nextID = nil
self.finished = true
}
}
if let instances = instancesResult["instances"] as? [Any] {
for case let instance as [String: Any] in instances {
if let id = instance["id"] as? String {
let request = NSFetchRequest<ISInstanceMO>(entityName: "ISInstance")
request.predicate = NSPredicate(format: "id == %@", id)
let results: [ISInstanceMO] = try context.fetch(request)
switch results.count {
case 0:
let i = ISInstanceMO(context: context)
InstancesDataManager.setAttributes(on: i, attributes: instance)
case 1:
if let i = results.first {
InstancesDataManager.setAttributes(on: i, attributes: instance)
}
default:
print("Duplicate Instance entity")
}
}
}
do {
try context.save()
} catch {
completion(error)
return
}
}
completion(nil)
} catch {
completion(error)
return
}
}
case 400:
print("instances.social: 400")
completion(NSError())
default:
completion(nil)
return
}
}.resume()
}
}

13
elpha-ios/InstancesNavigationController.swift

@ -1,13 +0,0 @@
//
// InstancesNavigationController.swift
// elpha-ios
//
// Created by Dwayne Harris on 8/29/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import UIKit
class InstancesNavigationController: UINavigationController {
}

15
elpha-ios/InstancesTableView.swift

@ -1,15 +0,0 @@
//
// InstancesTableView.swift
// elpha-ios
//
// Created by Dwayne Harris on 8/30/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import UIKit
class InstancesTableView: UITableView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

4
elpha-ios/InstancesTableViewCell.swift

@ -15,10 +15,10 @@ class InstancesTableViewCell: UITableViewCell {
@IBOutlet var statusesLabel: UILabel!
@IBOutlet var usersLabel: UILabel!
var thumbnailURL: String? {
var thumbnailURL: URL? {
didSet {
if let thumbnailURL = thumbnailURL {
ImageCache.shared.getImage(forURL: URL(string: thumbnailURL)!) { image, error in
ImageCache.shared.getImage(forURL: thumbnailURL) { image, error in
guard error == nil else {
return
}

191
elpha-ios/InstancesTableViewController.swift

@ -39,11 +39,6 @@ struct Instance {
class InstancesTableViewController: UITableViewController, UIViewControllerPreviewingDelegate {
@IBOutlet var mainNavigationItem: UINavigationItem!
let instanceRequestCount = 20
var instances: [Instance] = []
var nextID: String? = nil
var finished: Bool = false
var loading: Bool = false {
didSet {
DispatchQueue.main.async {
@ -76,118 +71,54 @@ class InstancesTableViewController: UITableViewController, UIViewControllerPrevi
}
@objc func reloadInstances() {
self.nextID = nil
self.finished = false
loadInstances()
}
func loadInstances() {
loading = true
var params = [
"count=\(instanceRequestCount)",
"sort_by=users",
"sort_order=desc",
"include_closed=true",
"include_down=false",
]
if let nextID = nextID {
params.append("min_id=\(nextID)")
} else {
instances = []
InstancesDataManager.shared.reloadInstances() { error in
self.loading = false
guard error == nil else {
return
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
func loadInstances() {
loading = true
let requestURL = "\(Config.instancesServiceUrl)\(Config.instancesServiceEndpoint)?\(params.joined(separator: "&"))"
print("loading instances: requestURL: \(requestURL)")
var request = URLRequest(url: URL(string: requestURL)!)
request.addValue("Bearer \(Config.instancesServiceSecret)", forHTTPHeaderField: "Authorization")
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
InstancesDataManager.shared.loadInstances() { error in
self.loading = false
guard error == nil else {
return
}
defer {
self.loading = false
}
if let response = response as? HTTPURLResponse {
switch response.statusCode {
case 200..<300:
if let data = data {
do {
let instancesResult = try JSONSerialization.jsonObject(with: data) as! [String: Any]
if let pagination = instancesResult["pagination"] as? [String: Any] {
if let nextID = pagination["next_id"] as? String {
self.nextID = nextID
} else {
self.nextID = nil
self.finished = true
}
}
if let instances = instancesResult["instances"] as? [Any] {
for case let instance as [String: Any] in instances {
var i = Instance()
i.id = instance["id"] as? String
i.name = instance["name"] as? String
i.addedAt = instance["added_at"] as? String
i.updatedAt = instance["updated_at"] as? String
i.checkedAt = instance["checked_at"] as? String
i.uptime = instance["uptime"] as? Int
i.up = instance["up"] as? Bool
i.dead = instance["dead"] as? Bool
i.version = instance["version"] as? String
i.ipv6 = instance["ipv6"] as? Bool
i.httpsScore = instance["https_score"] as? Int
i.httpsRank = instance["https_rank"] as? String
i.users = instance["users"] as? String
i.statuses = instance["statuses"] as? String
i.connections = instance["connections"] as? Int
i.openRegistrations = instance["open_registrations"] as? Bool
i.thumbnail = instance["thumbnail"] as? String
i.thumbnailProxy = instance["thumbnail_proxy"] as? String
i.activeUsers = instance["active_users"] as? Int
let info = instance["info"] as? [String: Any]
if let info = info {
i.shortDescription = info["short_description"] as? String
i.fullDescription = info["full_description"] as? String
i.topic = info["topic"] as? String
i.languages = info["languages"] as? [String]
i.categories = info["categories"] as? [String]
}
self.instances.append(i)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
} catch let error {
print(error)
return
}
}
case 400:
print("instances.social: 400")
default:
return
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
func getInstancesRequest() -> NSFetchRequest<ISInstanceMO> {
let request = NSFetchRequest<ISInstanceMO>(entityName: "ISInstance")
let sort = NSSortDescriptor(key: "users", ascending: false)
request.sortDescriptors = [sort]
dataTask.resume()
return request
}
func getInstance(for indexPath: IndexPath) -> ISInstanceMO? {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
let instances = try context.fetch(self.getInstancesRequest())
return instances[indexPath.row]
} catch {
return nil
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
@ -195,40 +126,48 @@ class InstancesTableViewController: UITableViewController, UIViewControllerPrevi
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return instances.count
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
return try context.count(for: self.getInstancesRequest())
} catch {
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
guard let cell = tableView.dequeueReusableCell(withIdentifier: "InstancesTableViewCell", for: indexPath) as? InstancesTableViewCell else {
fatalError("Unable to find reusable cell")
}
let instance = instances[indexPath.row]
cell.instanceNameLabel.text = instance.name
if let statuses = instance.statuses {
cell.statusesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: Int64(statuses)!), number: .decimal)
}
if let users = instance.users {
cell.usersLabel.text = NumberFormatter.localizedString(from: NSNumber(value: Int64(users)!), number: .decimal)
}
if let thumbnail = instance.thumbnail {
cell.thumbnailURL = thumbnail
}
if indexPath.row == instances.count - 1 && !loading && !finished {
loadInstances()
do {
let instances = try context.fetch(self.getInstancesRequest())
let instance = instances[indexPath.row]
cell.instanceNameLabel.text = instance.name
cell.statusesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: instance.statuses), number: .decimal)
cell.usersLabel.text = NumberFormatter.localizedString(from: NSNumber(value: instance.users), number: .decimal)
if let thumbnail = instance.thumbnail {
cell.thumbnailURL = thumbnail
}
if indexPath.row == instances.count - 1 && !loading && !InstancesDataManager.shared.finished {
loadInstances()
}
return cell
} catch {
return cell
}
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "InstanceDetailSegue", let cell = segue.destination as? InstanceViewController {
if let row = tableView.indexPathForSelectedRow?.row {
cell.instance = instances[row]
if let indexPath = tableView.indexPathForSelectedRow {
cell.instance = getInstance(for: indexPath)
}
}
}
@ -240,7 +179,7 @@ class InstancesTableViewController: UITableViewController, UIViewControllerPrevi
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let detailViewController = storyboard.instantiateViewController(withIdentifier: "InstanceViewController") as? InstanceViewController {
detailViewController.instance = instances[indexPath.row]
detailViewController.instance = getInstance(for: indexPath)
return detailViewController
} else {
return nil

56
elpha-ios/WebViewController.swift

@ -1,56 +0,0 @@
//
// WebViewController.swift
// elpha-ios
//
// Created by Dwayne Harris on 9/17/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import Foundation
import UIKit
import WebKit
class WebViewController: UIViewController, WKNavigationDelegate {
@IBOutlet var mainWebView: WKWebView!
@IBOutlet var mainProgressView: UIProgressView!
var url: URL? = nil
override func viewDidLoad() {
super.viewDidLoad()
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
mainWebView.navigationDelegate = self
mainWebView.addObserver(self, forKeyPath: #keyPath(WKWebView.title), options: .new, context: nil)
mainWebView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
if let url = url {
mainWebView.load(URLRequest(url: url))
}
}
@objc func done() {
dismiss(animated: true, completion: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(WKWebView.title) {
if let title = mainWebView.title {
navigationItem.title = title
}
}
if keyPath == #keyPath(WKWebView.estimatedProgress) {
mainProgressView.progress = Float(mainWebView.estimatedProgress)
}
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
mainProgressView.alpha = 1.0
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
mainProgressView.alpha = 0.0
}
}

22
elpha-ios/WebViewHelper.swift

@ -1,22 +0,0 @@
//
// WebViewHelper.swift
// elpha-ios
//
// Created by Dwayne Harris on 9/17/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import Foundation
import UIKit
class WebViewHelper {
static func getWebViewController(url: URL) -> WebViewController? {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let webViewController = storyboard.instantiateViewController(withIdentifier: "WebViewController") as? WebViewController {
webViewController.url = url
return webViewController
}
fatalError("Could not instantiate WebViewController")
}
}
Loading…
Cancel
Save