Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ios][i91] browse data categories: show list of posts/medias/reactions #12

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def sharedPods
pod 'UPCarouselFlowLayout'
pod 'SwiftRichString'
pod 'MaterialProgressBar'
pod 'RxAppState'
end

target 'Spring' do
Expand Down
288 changes: 280 additions & 8 deletions Spring.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Spring/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

func applicationWillEnterForeground(_ application: UIApplication) {
guard Global.current.account != nil else { return }
Navigator.evaluatePolicyWhenUserSetEnable()
Navigator.refreshOnboardingStateIfNeeded()
Global.pollingSyncAppArchiveStatus()
}

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
Expand Down
45 changes: 39 additions & 6 deletions Spring/Application/Navigator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,18 @@ class Navigator {
case signInWall(viewModel: SignInWallViewModel)
case signIn(viewModel: SignInViewModel)
case trustIsCritical(buttonItemType: ButtonItemType)
case howItWorks(viewModel: HowItWorksViewModel)
case howItWorks
case checkDataRequested
case safari(URL)
case safariController(URL)
case hometabs
case hometabs(missions: [Mission])
case uploadData(viewModel: UploadDataViewModel)
case updateYourData(viewModel: UpdateYourDataViewModel)
case postList(viewModel: PostListViewModel)
case reactionList(viewModel: ReactionListViewModel)
case postListSection(viewModel: PostListSectionViewModel)
case mediaListSection(viewModel: MediaListSectionViewModel)
case reactionListSection(viewModel: ReactionListSectionViewModel)
case incomeQuestion
case account(viewModel: AccountViewModel)
case signOutWarning
Expand Down Expand Up @@ -80,7 +85,8 @@ class Navigator {
trustIsCriticalViewController.buttonItemType = buttonItemType
return trustIsCriticalViewController

case .howItWorks(let viewModel): return HowItWorksViewController(viewModel: viewModel)
case .howItWorks: return HowItWorksViewController()
case .checkDataRequested: return CheckDataRequestedViewController()
case .safari(let url):
UIApplication.shared.open(url, options: [:], completionHandler: nil)
return nil
Expand All @@ -90,11 +96,30 @@ class Navigator {
vc.hidesBottomBarWhenPushed = true
return vc

case .hometabs:
return HomeTabbarController.tabbarController()
case .hometabs(let missions):
let hometabVC = HomeTabbarController.tabbarController()
hometabVC.missions = missions
return hometabVC

case .postList(let viewModel): return PostListViewController(viewModel: viewModel)
case .reactionList(let viewModel): return ReactionListViewController(viewModel: viewModel)

case .postListSection, .mediaListSection, .reactionListSection:
let viewController: UIViewController!
switch segue {
case .postListSection(let viewModel):
viewController = PostListSectionViewController(viewModel: viewModel)
case .mediaListSection(let viewModel):
viewController = MediaListSectionViewController(viewModel: viewModel)
case .reactionListSection(let viewModel):
viewController = ReactionListSectionViewController(viewModel: viewModel)
default:
viewController = UIViewController()
}

viewController.hidesBottomBarWhenPushed = true
return viewController

case .incomeQuestion, .uploadData:
let viewController: UIViewController!
switch segue {
Expand All @@ -115,7 +140,7 @@ class Navigator {
case .signOutWarning, .signOut,
.biometricAuth,
.viewRecoveryKeyWarning, .viewRecoverykey,
.deleteAccount,
.updateYourData, .deleteAccount,
.increasePrivacyList, .increasePrivacy,
.releaseNote:

Expand All @@ -126,6 +151,7 @@ class Navigator {
case .biometricAuth: viewController = BiometricAuthViewController()
case .viewRecoveryKeyWarning: viewController = ViewRecoveryKeyWarningViewController()
case .viewRecoverykey(let viewModel): viewController = ViewRecoveryKeyViewController(viewModel: viewModel)
case .updateYourData(let viewModel): viewController = UpdateYourDataViewController(viewModel: viewModel)
case .deleteAccount(let viewModel): viewController = DeleteAccountViewController(viewModel: viewModel)
case .increasePrivacyList: viewController = IncreasePrivacyListViewController()
case .increasePrivacy(let viewModel): viewController = IncreasePrivacyViewController(viewModel: viewModel)
Expand Down Expand Up @@ -228,6 +254,13 @@ class Navigator {
}
}

// MARK: - refreshOnboardingStateIfNeeded
static func refreshOnboardingStateIfNeeded() {
guard GetYourData.standard.requestedAtRelay.value != nil else { return }

Navigator.default.show(segue: .checkDataRequested, sender: nil, transition: .replace(type: .none))
}

static let requireAuthorizationTime = 30 // minutes
static var retryAuthenticationAlert: UIAlertController?
static func evaluatePolicyWhenUserSetEnable(force: Bool = false) {
Expand Down
3 changes: 3 additions & 0 deletions Spring/Application/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}

func sceneWillEnterForeground(_ scene: UIScene) {
guard Global.current.account != nil else { return }
Navigator.evaluatePolicyWhenUserSetEnable()
Navigator.refreshOnboardingStateIfNeeded()
Global.pollingSyncAppArchiveStatus()
}

func sceneDidEnterBackground(_ scene: UIScene) {
Expand Down
2 changes: 2 additions & 0 deletions Spring/Common/Constant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ public struct Constant {
}

static let appStoreURLScheme = "com.spring"
static let facebookCreationDate = Date("01-01-2004")!

static let separator = ","
static let fbImageServerURL = Credential.valueForKey(keyName: "API_FBM_SERVER_URL") + "/api/media"
static let surveyURL = URL(string: "https://docs.google.com/forms/d/e/1FAIpQLScL41kNU6SBzo7ndcraUf7O-YJ_JrPqg_rlI588UjLK-_sGtQ/viewform?usp=sf_link")!
static let fbVideoExtensions = [".3g2", ".3gp", ".3gpp", ".asf", ".avi", ".dat", ".divx", ".dv", ".f4v", ".flv", ".gif", ".m2ts", ".m4v", ".mkv", ".mod", ".mov", ".mp4", ".mpe", ".mpeg", ".mpeg4", ".mpg", ".mts", ".nsv", ".ogm", ".ogv", ".qt", ".tod", ".ts", ".vob", ".wmv"]
}
6 changes: 6 additions & 0 deletions Spring/Common/Controllers/DocumentPickerDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ protocol DocumentPickerDelegate: AnyObject {
func browseFile(fileTypes: [String])
func didPickDocument(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL)
func handle(selectedFileURL: URL)

func fileSizeIfValid(_ fileSize: Int64) -> Bool
}

extension DocumentPickerDelegate where Self: UIViewController {
Expand Down Expand Up @@ -67,4 +69,8 @@ extension DocumentPickerDelegate where Self: UIViewController {
}
}
}

func fileSizeIfValid(_ fileSize: Int64) -> Bool {
return fileSize <= 5 * 1024 * 1024 * 1024 // limit 5GB
}
}
55 changes: 55 additions & 0 deletions Spring/Common/Controllers/VideoPlayerDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// VideoPlayerDelegate.swift
// Spring
//
// Created by Thuyen Truong on 3/19/20.
// Copyright © 2020 Bitmark Inc. All rights reserved.
//

import UIKit
import RxSwift
import AVKit
import AVFoundation
import MediaPlayer
import AudioToolbox

protocol VideoPlayerDelegate: class {
var disposeBag: DisposeBag { get }
func playVideo(key: String)
func playVideo(sourcePath: String)
}

extension VideoPlayerDelegate where Self: UIViewController {
func playVideo(key: String) {
MediaService.makeVideoURL(key: key)
.subscribe(onSuccess: { [weak self] (asset) in
guard let self = self else { return }
self.play(with: asset)

}, onError: { [weak self] (error) in
guard !AppError.errorByNetworkConnection(error) else { return }
guard let self = self, !self.showIfRequireUpdateVersion(with: error) else { return }

Global.log.error(error)
})
.disposed(by: disposeBag)
}

func playVideo(sourcePath: String) {
guard let videoURL = URL(string: sourcePath) else { return }
let asset = AVURLAsset(url: videoURL)
play(with: asset)
}

fileprivate func play(with asset: AVURLAsset) {
let playerItem = AVPlayerItem(asset: asset)

let player = AVPlayer(playerItem: playerItem)
let playerVC = AVPlayerViewController()

playerVC.player = player
self.present(playerVC, animated: true) {
player.play()
}
}
}
61 changes: 50 additions & 11 deletions Spring/Common/Global.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ class Global {

var account: Account?
var currency: Currency?
lazy var userDefault: UserDefaults? = {
var userDefault: UserDefaults? {
guard let accountNumber = account?.getAccountNumber()
else { return nil }
return UserDefaults.userStandard(for: accountNumber)
}()
}

lazy var decoder: JSONDecoder = {
let decoder = JSONDecoder()
Expand Down Expand Up @@ -79,7 +79,9 @@ class Global {
try FileManager.default.removeItem(at: FileManager.filesDocumentDirectoryURL)
try RealmConfig.removeRealm(of: account.getAccountNumber())
UserDefaults.standard.clickedIncreasePrivacyURLs = nil
Global.current.userDefault?.latestAppArchiveStatus = nil
UserDefaults.standard.FBArchiveCreatedAt = nil
Global.current.userDefault?.latestAppArchiveStatus = []
BackgroundTaskManager.shared.urlSession(identifier: SessionIdentifier.upload.rawValue).invalidateAndCancel()

// clear user cookie in webview
HTTPCookieStorage.shared.cookies?.forEach(HTTPCookieStorage.shared.deleteCookie)
Expand All @@ -94,7 +96,6 @@ class Global {
SettingsBundle.setAccountNumber(accountNumber: nil)

Global.current = Global() // reset local variable
AppArchiveStatus.currentState.accept(nil)
AuthService.shared = AuthService()
BackgroundTaskManager.shared = BackgroundTaskManager()

Expand All @@ -104,23 +105,40 @@ class Global {
ErrorReporting.setUser(bitmarkAccountNumber: nil)
}

static func clearCacheStorage() {
Global.log.info("[start] clearCacheStorage")
do {
let realm = try RealmConfig.currentRealm()
try realm.write {
realm.delete(realm.objects(QueryTrack.self))
}
} catch {
Global.log.error(error)
}
}

static func pollingSyncAppArchiveStatus() {
// avoid override the tracking status in local
let currentState = AppArchiveStatus.currentState.value
guard currentState.isEmpty || !AppArchiveStatus.isRequestingPoint else {
return
}

func pollingFunction() -> Observable<Void> {
return ArchiveDataEngine.fetchAppArchiveStatus()
.do(onSuccess: {
Global.current.userDefault?.latestAppArchiveStatus = $0
AppArchiveStatus.currentState.accept($0)
})
.asObservable()
.flatMap({ (appArchiveStatus) -> Observable<Void> in
return appArchiveStatus == .processing ?
.flatMap({ (appArchiveStatuses) -> Observable<Void> in
return appArchiveStatuses.contains(where: { $0 == .processing }) ?
Observable.error(AppError.archiveIsNotProcessed) :
Observable.empty()
})
}

pollingFunction()
.retry(.delayed(maxCount: 1000, time: 5 * 60))
.retry(.delayed(maxCount: 1000, time: 2 * 60))
.subscribe()
.disposed(by: disposeBag)
}
Expand Down Expand Up @@ -171,6 +189,7 @@ enum AppError: Error {
case didRemoteQuery
case archiveIsNotProcessed
case invalidPresignedURL
case fbRequiredPageIsNotReady

static func errorByNetworkConnection(_ error: Error) -> Bool {
guard let error = error as? Self else { return false }
Expand Down Expand Up @@ -207,6 +226,14 @@ extension UserDefaults {
set { set(newValue, forKey: #function) }
}

var FBArchiveCreatedAt: Date? {
get { return date(forKey: #function) }
set {
GetYourData.standard.requestedAtRelay.accept(newValue)
set(newValue, forKey: #function)
}
}

// MARK: - Settings
var appVersion: String? {
get { return string(forKey: "version_preference") }
Expand All @@ -219,13 +246,25 @@ extension UserDefaults {
}

// Per Account
var latestAppArchiveStatus: AppArchiveStatus? {
get { return AppArchiveStatus(rawValue: string(forKey: #function) ?? "") }
set { set(newValue?.rawValue, forKey: #function) }
var latestAppArchiveStatus: [AppArchiveStatus] {
get {
let archiveStatusStrings = stringArray(forKey: #function) ?? []
return archiveStatusStrings.compactMap { AppArchiveStatus(rawValue: $0) }
}
set {
AppArchiveStatus.currentState.accept(newValue)
let archiveStatusStrings = newValue.compactMap { $0.rawValue }
set(archiveStatusStrings, forKey: #function)
}
}

var isAccountSecured: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}

var numberOfProcessedArchives: Int {
get { return integer(forKey: #function) }
set { set(newValue, forKey: #function) }
}
}
10 changes: 3 additions & 7 deletions Spring/Common/ThemeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ let globalStatusBarStyle = BehaviorRelay<UIStatusBarStyle>(value: .default)
var themeService = ThemeType.currentThemeService(for: .unspecified)

struct OurTheme {
static let rowPadding: CGFloat = 18.0
static let paddingInset = UIEdgeInsets(top: 4, left: 18, bottom: 0, right: 18)
static let scrollingPaddingInset = UIEdgeInsets(top: 0, left: 18, bottom: 150, right: 18)
static var paddingBottom: CGFloat = {
Expand Down Expand Up @@ -69,13 +70,8 @@ struct OurTheme {
}
}()

static let trustIsCriticalTop: CGFloat = {
switch UIScreen.main.bounds.size.height {
case let x where x <= 667:
return 43
default:
return 100
}
static let titleListSectionPaddingInset: UIEdgeInsets = {
return UIEdgeInsets(top: 21, left: rowPadding, bottom: 10, right: rowPadding)
}()

static let halfImagePercent: CGFloat = {
Expand Down
11 changes: 11 additions & 0 deletions Spring/Common/Views/Button.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ extension Button {
.bind({ $0.themeColor }, to: rx.titleColor(for: .normal))
.disposed(by: disposeBag)

case .internationalKleinBlue:
themeService.rx
.bind({ $0.themeBlueColor }, to: rx.titleColor(for: .normal))
.disposed(by: disposeBag)

default:
themeService.rx
.bind({ $0.blackButtonTextColor }, to: rx.titleColor(for: .normal))
Expand All @@ -75,6 +80,12 @@ extension Button {
.bind({ $0.themeMercuryColor }, to: rx.backgroundColor)
.disposed(by: disposeBag)

case .cognac:
themeService.rx
.bind({ $0.lightButtonTextColor }, to: rx.titleColor(for: .normal))
.bind({ $0.themeColor }, to: rx.backgroundColor)
.disposed(by: disposeBag)

default:
themeService.rx
.bind({ $0.blackButtonTextColor }, to: rx.titleColor(for: .normal))
Expand Down