From 600707745f47caf58e3cb31f902c773cf043b262 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Sun, 3 Mar 2019 15:33:03 -0300 Subject: [PATCH 01/25] Add script to generate secrets file --- CouchTracker.xcodeproj/project.pbxproj | 10 +++++----- CouchTracker/Info.plist | 2 -- Readme.md | 2 +- {scripts => build_phases}/swiftformat | 0 scripts/generate_secrets | 24 ++++++++++++++++++++++++ 5 files changed, 30 insertions(+), 8 deletions(-) rename {scripts => build_phases}/swiftformat (100%) create mode 100755 scripts/generate_secrets diff --git a/CouchTracker.xcodeproj/project.pbxproj b/CouchTracker.xcodeproj/project.pbxproj index be9c0f59..870d07be 100644 --- a/CouchTracker.xcodeproj/project.pbxproj +++ b/CouchTracker.xcodeproj/project.pbxproj @@ -2881,7 +2881,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "sh $SRCROOT/scripts/swiftformat\n"; + shellScript = "sh $SRCROOT/build_phases/swiftformat\n"; }; 4FAC4A92210F281D0096BCE0 /* SwiftFormat */ = { isa = PBXShellScriptBuildPhase; @@ -2909,7 +2909,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "sh $SRCROOT/scripts/swiftformat\n"; + shellScript = "sh $SRCROOT/build_phases/swiftformat\n"; }; 4FAC4A94210FDD4B0096BCE0 /* SwiftFormat */ = { isa = PBXShellScriptBuildPhase; @@ -2923,7 +2923,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "sh $SRCROOT/scripts/swiftformat\n"; + shellScript = "sh $SRCROOT/build_phases/swiftformat\n"; }; 6455BA751F45B862000660C6 /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; @@ -3015,7 +3015,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "sh $SRCROOT/scripts/swiftformat\n"; + shellScript = "sh $SRCROOT/build_phases/swiftformat\n"; }; 824A784C220F68830001F3BF /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; @@ -3051,7 +3051,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "sh $SRCROOT/scripts/swiftformat\n"; + shellScript = "sh $SRCROOT/build_phases/swiftformat\n"; }; 9C6F0D2340CF87135C62A2BE /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; diff --git a/CouchTracker/Info.plist b/CouchTracker/Info.plist index 0c9f8a16..33f33a98 100644 --- a/CouchTracker/Info.plist +++ b/CouchTracker/Info.plist @@ -31,8 +31,6 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance diff --git a/Readme.md b/Readme.md index 5140edcb..230e2b43 100644 --- a/Readme.md +++ b/Readme.md @@ -19,7 +19,7 @@ Run the following commands * This project uses the [Trakt API](https://trakt.docs.apiary.io/), [TMDB API](https://developers.themoviedb.org/3/getting-started) and [TVDB API](https://api.thetvdb.com/swagger) -* To run the app, please create a file at `CouchTracker/Utils/Secrets.swift` with yours API keys like this +* To run the app, please create a file at `CouchTrackerApp/Utils/Secrets.swift` with yours API keys like this ```swift enum Secrets { diff --git a/scripts/swiftformat b/build_phases/swiftformat similarity index 100% rename from scripts/swiftformat rename to build_phases/swiftformat diff --git a/scripts/generate_secrets b/scripts/generate_secrets new file mode 100755 index 00000000..a170a205 --- /dev/null +++ b/scripts/generate_secrets @@ -0,0 +1,24 @@ +#!/bin/bash + +SCRIPTS_DIR=$(cd "$(dirname "$0")" || exit 1; pwd) +SECRETS_FILE="${SCRIPTS_DIR}/../CouchTrackerApp/Utils/Secrets.swift" + +cat > "$SECRETS_FILE" << EOF +enum Secrets { + enum Trakt { + static let clientId = "${COUCH_TRACKER_TRAKT_CLIENT_ID}" + static let clientSecret = "${COUCH_TRACKER_TRAKT_CLIENT_SECRET}" + static let redirectURL = "${COUCH_TRACKER_TRAKT_REDIRECT_URL}" + } + + enum TMDB { + static let apiKey = "${COUCH_TRACKER_TMDB_API_KEY}" + } + + enum TVDB { + static let apiKey = "${COUCH_TRACKER_TVDB_API_KEY}" + } +} +EOF + +echo "Generated file: ${SECRETS_FILE}" From 7f43eb565bdbc2ec2ec111a2e99dcfc8b1ba55d2 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Sun, 3 Mar 2019 15:33:37 -0300 Subject: [PATCH 02/25] ShellScript lint --- scripts/update_translations | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/update_translations b/scripts/update_translations index 7b256ab2..08cad4fe 100755 --- a/scripts/update_translations +++ b/scripts/update_translations @@ -1,7 +1,7 @@ #!/bin/bash -SCRIPTS_DIR=$(cd `dirname $0`; pwd) -COUCH_TRACKER_CORE_DIR=$(cd $SCRIPTS_DIR/../CouchTrackerCore; pwd) +SCRIPTS_DIR=$(cd "$(dirname "$0")" || exit 1; pwd) +COUCH_TRACKER_CORE_DIR=$(cd "$SCRIPTS_DIR"/../CouchTrackerCore || exit 1; pwd) TRANSLATIONS_ZIP_PATH="${SCRIPTS_DIR}/translations.zip" function copy_to_languange() { @@ -10,12 +10,12 @@ function copy_to_languange() { cp -rfv "$SCRIPTS_DIR/couchtracker-strings-archive/$LANGUAGE_DIR/Localizable.strings" "$COUCH_TRACKER_CORE_DIR/$LANGUAGE_DIR" } -curl https://localise.biz/api/export/archive/strings.zip?key=${LOCALISE_BIZ_KEY} > $TRANSLATIONS_ZIP_PATH +curl https://localise.biz/api/export/archive/strings.zip?key=${LOCALISE_BIZ_KEY} > "$TRANSLATIONS_ZIP_PATH" -unzip $TRANSLATIONS_ZIP_PATH -d $SCRIPTS_DIR +unzip "$TRANSLATIONS_ZIP_PATH" -d "$SCRIPTS_DIR" copy_to_languange "en.lproj" copy_to_languange "pt-BR.lproj" -rm -rf $SCRIPTS_DIR/couchtracker-strings-archive -rm -rf $SCRIPTS_DIR/translations.zip +rm -rf "$SCRIPTS_DIR"/couchtracker-strings-archive +rm -rf "$SCRIPTS_DIR"/translations.zip From 1740d839f111e1a68653e9d199cd8cca8a9a2342 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Sun, 3 Mar 2019 15:34:25 -0300 Subject: [PATCH 03/25] Bump build 2 [ci skip] --- CouchTracker.xcodeproj/project.pbxproj | 32 +++++++++++++------------- CouchTracker/Info.plist | 2 +- CouchTrackerApp/Info.plist | 2 +- CouchTrackerCore-iOS/Info.plist | 2 +- CouchTrackerCore/Info.plist | 2 +- CouchTrackerCoreTests/Info.plist | 2 +- CouchTrackerPersistence/Info.plist | 2 +- TMDBTestsResources/Info.plist | 2 +- TVDBTestsResources/Info.plist | 2 +- TraktTestsResources/Info.plist | 2 +- changelog.md | 16 +++++++++++++ 11 files changed, 41 insertions(+), 25 deletions(-) diff --git a/CouchTracker.xcodeproj/project.pbxproj b/CouchTracker.xcodeproj/project.pbxproj index be9c0f59..4c6fb783 100644 --- a/CouchTracker.xcodeproj/project.pbxproj +++ b/CouchTracker.xcodeproj/project.pbxproj @@ -3742,11 +3742,11 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 17; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 17; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3781,11 +3781,11 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 17; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 17; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3886,11 +3886,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 17; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 17; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "CouchTrackerCore-iOS/Info.plist"; @@ -3925,11 +3925,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 17; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 17; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "CouchTrackerCore-iOS/Info.plist"; @@ -4273,11 +4273,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 17; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = B5RPM7SE3L; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 17; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = CouchTrackerApp/Info.plist; @@ -4315,11 +4315,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 17; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = B5RPM7SE3L; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 17; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = CouchTrackerApp/Info.plist; @@ -4354,11 +4354,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = B5RPM7SE3L; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = CouchTrackerPersistence/Info.plist; @@ -4394,11 +4394,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = B5RPM7SE3L; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = CouchTrackerPersistence/Info.plist; diff --git a/CouchTracker/Info.plist b/CouchTracker/Info.plist index 0c9f8a16..78619cae 100644 --- a/CouchTracker/Info.plist +++ b/CouchTracker/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 17 + 2 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/CouchTrackerApp/Info.plist b/CouchTrackerApp/Info.plist index b590af90..e88f5f7a 100644 --- a/CouchTrackerApp/Info.plist +++ b/CouchTrackerApp/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 17 + 2 diff --git a/CouchTrackerCore-iOS/Info.plist b/CouchTrackerCore-iOS/Info.plist index 99215218..91bd4f6e 100644 --- a/CouchTrackerCore-iOS/Info.plist +++ b/CouchTrackerCore-iOS/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 17 + 2 NSPrincipalClass diff --git a/CouchTrackerCore/Info.plist b/CouchTrackerCore/Info.plist index b9630abe..547aff12 100644 --- a/CouchTrackerCore/Info.plist +++ b/CouchTrackerCore/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 17 + 2 NSHumanReadableCopyright Copyright © 2018 Pietro Caselani. All rights reserved. NSPrincipalClass diff --git a/CouchTrackerCoreTests/Info.plist b/CouchTrackerCoreTests/Info.plist index 8ba115ba..446c9997 100644 --- a/CouchTrackerCoreTests/Info.plist +++ b/CouchTrackerCoreTests/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 17 + 2 diff --git a/CouchTrackerPersistence/Info.plist b/CouchTrackerPersistence/Info.plist index e1fe4cfb..0c21197c 100644 --- a/CouchTrackerPersistence/Info.plist +++ b/CouchTrackerPersistence/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 1.0 CFBundleVersion - $(CURRENT_PROJECT_VERSION) + 2 diff --git a/TMDBTestsResources/Info.plist b/TMDBTestsResources/Info.plist index e51bae64..29a12726 100644 --- a/TMDBTestsResources/Info.plist +++ b/TMDBTestsResources/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 17 + 2 NSHumanReadableCopyright Copyright © 2018 Pietro Caselani. All rights reserved. NSPrincipalClass diff --git a/TVDBTestsResources/Info.plist b/TVDBTestsResources/Info.plist index e51bae64..29a12726 100644 --- a/TVDBTestsResources/Info.plist +++ b/TVDBTestsResources/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 17 + 2 NSHumanReadableCopyright Copyright © 2018 Pietro Caselani. All rights reserved. NSPrincipalClass diff --git a/TraktTestsResources/Info.plist b/TraktTestsResources/Info.plist index e51bae64..29a12726 100644 --- a/TraktTestsResources/Info.plist +++ b/TraktTestsResources/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 17 + 2 NSHumanReadableCopyright Copyright © 2018 Pietro Caselani. All rights reserved. NSPrincipalClass diff --git a/changelog.md b/changelog.md index d77e577b..c2bb5659 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,19 @@ +# 0.0.1 (2) +* Merge pull request #184 from pietrocaselani/sync_state [view commit](http://github.com/pietrocaselani/CouchTracker/commit/2281baa3e04a08d187a6f312a638709196a0c66e) +* Wrap Realm around a initialized/not initialized state [view commit](http://github.com/pietrocaselani/CouchTracker/commit/9c97375002e1d2d18c254e59cc0a08287f664520) +* Clean up and fix some tests [view commit](http://github.com/pietrocaselani/CouchTracker/commit/a729c1c7dd6720d7701c9d6fd7ddab1bd5bebb00) +* Tests compile again [view commit](http://github.com/pietrocaselani/CouchTracker/commit/9d40f69ab36cf2f0a60e74c23150b804b99bc9b9) +* Move everything related to persistence into their own framework [view commit](http://github.com/pietrocaselani/CouchTracker/commit/f960fef03e429a82700a6b78cc5cdf6658a76cf9) +* Replace all instances of appConfigsObservable to appStateObservable [view commit](http://github.com/pietrocaselani/CouchTracker/commit/a62b3c510e0d1fb41db450704f56e6761df99cb5) +* Big refactor on how login and app state are handle [view commit](http://github.com/pietrocaselani/CouchTracker/commit/a3d133f2fcef2a418f9a80e7b782a2337b9cfdb4) +* Merge branch 'master' into sync_state [view commit](http://github.com/pietrocaselani/CouchTracker/commit/63f53566f02b79bec1e4b64a4a148b31bd336601) +* Merge branch 'master' of github.com:pietrocaselani/CouchTracker into sync_state [view commit](http://github.com/pietrocaselani/CouchTracker/commit/616c209baf788ae53987874c563f462ffb1e0be1) +* Fixing ShowsProgress ViewState using SyncState [view commit](http://github.com/pietrocaselani/CouchTracker/commit/641eb5cc7750d92cf07e2fca875b4aaaa675a3e0) +* WIP - Implementing tests for DefaultWatchedShowSynchronizerSyncStateTests [view commit](http://github.com/pietrocaselani/CouchTracker/commit/c34942ff5e8f3f9e85627a115e8f6b2229bf2ef8) +* Add test when sync error occurs [view commit](http://github.com/pietrocaselani/CouchTracker/commit/3074ab9790c4138e697fd1d2a9746a346756db4d) +* Add tests to verify DefaultWatchedShowsSynchronizer is notifing the current sync state [view commit](http://github.com/pietrocaselani/CouchTracker/commit/05e2119449db3b14c042f8ac77f2eab06713b54a) +* Create SyncState and SyncStateStore. Now we have a centralized place to know about the synchronization state [view commit](http://github.com/pietrocaselani/CouchTracker/commit/d85547da1a6d154c354e9cb9926b9b439afa0a3b) + # 0.0.1 (17) * Merge pull request #185 from pietrocaselani/episode_image_fix [view commit](http://github.com/pietrocaselani/CouchTracker/commit/fd23d6c37300ea5106bb6a14b2a96b87b27f45a6) * Add tests for each image repository [view commit](http://github.com/pietrocaselani/CouchTracker/commit/8d8d27ff11e5895a3cb3873cf8714a41779e770a) From f0317042aa57b58f85e3386518151af747ec824f Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Sun, 3 Mar 2019 15:38:04 -0300 Subject: [PATCH 04/25] Bump everything to 0.0.1 (17) --- CouchTracker.xcodeproj/project.pbxproj | 32 +++++++++++++------------- CouchTracker/Info.plist | 2 +- CouchTrackerApp/Info.plist | 2 +- CouchTrackerCore-iOS/Info.plist | 2 +- CouchTrackerCore/Info.plist | 2 +- CouchTrackerCoreTests/Info.plist | 4 ++-- CouchTrackerPersistence/Info.plist | 4 ++-- TMDBTestsResources/Info.plist | 2 +- TVDBTestsResources/Info.plist | 2 +- TraktTestsResources/Info.plist | 2 +- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/CouchTracker.xcodeproj/project.pbxproj b/CouchTracker.xcodeproj/project.pbxproj index 4c6fb783..afbe11a4 100644 --- a/CouchTracker.xcodeproj/project.pbxproj +++ b/CouchTracker.xcodeproj/project.pbxproj @@ -3742,11 +3742,11 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 17; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 17; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3781,11 +3781,11 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 17; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 17; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3886,11 +3886,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 17; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 17; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "CouchTrackerCore-iOS/Info.plist"; @@ -3925,11 +3925,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 17; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 17; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "CouchTrackerCore-iOS/Info.plist"; @@ -4273,11 +4273,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 17; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = B5RPM7SE3L; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 17; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = CouchTrackerApp/Info.plist; @@ -4315,11 +4315,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 17; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = B5RPM7SE3L; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 17; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = CouchTrackerApp/Info.plist; @@ -4354,11 +4354,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 17; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = B5RPM7SE3L; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 17; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = CouchTrackerPersistence/Info.plist; @@ -4394,11 +4394,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 17; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = B5RPM7SE3L; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 17; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = CouchTrackerPersistence/Info.plist; diff --git a/CouchTracker/Info.plist b/CouchTracker/Info.plist index 78619cae..0c9f8a16 100644 --- a/CouchTracker/Info.plist +++ b/CouchTracker/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 2 + 17 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/CouchTrackerApp/Info.plist b/CouchTrackerApp/Info.plist index e88f5f7a..b590af90 100644 --- a/CouchTrackerApp/Info.plist +++ b/CouchTrackerApp/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 2 + 17 diff --git a/CouchTrackerCore-iOS/Info.plist b/CouchTrackerCore-iOS/Info.plist index 91bd4f6e..99215218 100644 --- a/CouchTrackerCore-iOS/Info.plist +++ b/CouchTrackerCore-iOS/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 2 + 17 NSPrincipalClass diff --git a/CouchTrackerCore/Info.plist b/CouchTrackerCore/Info.plist index 547aff12..b9630abe 100644 --- a/CouchTrackerCore/Info.plist +++ b/CouchTrackerCore/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 2 + 17 NSHumanReadableCopyright Copyright © 2018 Pietro Caselani. All rights reserved. NSPrincipalClass diff --git a/CouchTrackerCoreTests/Info.plist b/CouchTrackerCoreTests/Info.plist index 446c9997..27ccd9d8 100644 --- a/CouchTrackerCoreTests/Info.plist +++ b/CouchTrackerCoreTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.0 + 0.0.1 CFBundleVersion - 2 + 17 diff --git a/CouchTrackerPersistence/Info.plist b/CouchTrackerPersistence/Info.plist index 0c21197c..b590af90 100644 --- a/CouchTrackerPersistence/Info.plist +++ b/CouchTrackerPersistence/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 0.0.1 CFBundleVersion - 2 + 17 diff --git a/TMDBTestsResources/Info.plist b/TMDBTestsResources/Info.plist index 29a12726..e51bae64 100644 --- a/TMDBTestsResources/Info.plist +++ b/TMDBTestsResources/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 2 + 17 NSHumanReadableCopyright Copyright © 2018 Pietro Caselani. All rights reserved. NSPrincipalClass diff --git a/TVDBTestsResources/Info.plist b/TVDBTestsResources/Info.plist index 29a12726..e51bae64 100644 --- a/TVDBTestsResources/Info.plist +++ b/TVDBTestsResources/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 2 + 17 NSHumanReadableCopyright Copyright © 2018 Pietro Caselani. All rights reserved. NSPrincipalClass diff --git a/TraktTestsResources/Info.plist b/TraktTestsResources/Info.plist index 29a12726..e51bae64 100644 --- a/TraktTestsResources/Info.plist +++ b/TraktTestsResources/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 0.0.1 CFBundleVersion - 2 + 17 NSHumanReadableCopyright Copyright © 2018 Pietro Caselani. All rights reserved. NSPrincipalClass From 34dfb2bef2715472238aef442b48d2adb295a313 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Sun, 3 Mar 2019 15:40:15 -0300 Subject: [PATCH 05/25] Stop building master branch on Travis --- ci/travis.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/ci/travis.sh b/ci/travis.sh index d5cba667..261fe61c 100755 --- a/ci/travis.sh +++ b/ci/travis.sh @@ -3,7 +3,4 @@ if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then echo "Running pull_request.sh" ./ci/pull_request.sh -elif [ "$TRAVIS_BRANCH" = "master" ]; then - echo "Running master.sh" - ./ci/master.sh fi From 2bad896fd98253622fece7765f39bc6af532e795 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Sun, 3 Mar 2019 15:40:35 -0300 Subject: [PATCH 06/25] Make Rubocop happy --- fastlane/Appfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastlane/Appfile b/fastlane/Appfile index 88269b08..050ae784 100644 --- a/fastlane/Appfile +++ b/fastlane/Appfile @@ -1,7 +1,7 @@ -app_identifier ENV["APP_IDENTIFIER"] # The bundle identifier of your app -apple_id ENV["APPLE_ID"] # Your Apple email address +app_identifier ENV['APP_IDENTIFIER'] # The bundle identifier of your app +apple_id ENV['APPLE_ID'] # Your Apple email address -team_id "[[DEV_PORTAL_TEAM_ID]]" # Developer Portal Team ID +team_id ENV['DEV_PORTAL_TEAM_ID'] # Developer Portal Team ID # you can even provide different app identifiers, Apple IDs and team names per lane: # More information: https://docs.fastlane.tools/advanced/#appfile From 000e2cc38d5c5196d3169082c3bd5ccf12f94f0e Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Sun, 3 Mar 2019 15:40:53 -0300 Subject: [PATCH 07/25] Generate secrets, build the app and upload to test flight --- fastlane/Fastfile | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index fd328be8..e3d16d0d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -48,7 +48,18 @@ lane :beta do changelog: changelog ) - # build_app(scheme: 'CouchTracker') + sh '../scripts/generate_secrets' + + build_ios_app( + workspace: 'CouchTracker.xcworkspace', + scheme: 'CouchTracker', + configuration: 'Release' + ) + + upload_to_testflight( + changelog: changelog, + skip_waiting_for_build_processing: false + ) upload_sonar end From 2adfe57c6da36d23dd021da3b6366d7ce2c8e7e4 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Wed, 17 Apr 2019 19:14:35 -0300 Subject: [PATCH 08/25] Create Brewfile and uses on setup.sh --- Brewfile | 6 ++++++ setup.sh | 11 ++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 Brewfile diff --git a/Brewfile b/Brewfile new file mode 100644 index 00000000..e478fe3d --- /dev/null +++ b/Brewfile @@ -0,0 +1,6 @@ +tap 'blender/homebrew-tap' +brew 'rome' +brew 'swiftlint' +brew 'sonar-scanner' +brew 'tailor' +brew 'swiftformat' diff --git a/setup.sh b/setup.sh index ddb68255..5a89e468 100755 --- a/setup.sh +++ b/setup.sh @@ -1,16 +1,17 @@ #!/bin/bash function clone_dependency() { - echo "Cloning $1" - git clone https://github.com/pietrocaselani/$1.git vendor/$1 + if [ ! -d "vendor/$1" ]; then + echo "Cloning $1" + git clone "https://github.com/pietrocaselani/${1}.git" "vendor/$1" + fi } -brew install sonar-scanner -brew install tailor -brew install swiftformat +brew bundle sudo pip install lizard bundle install --path vendor/bundle bundle exec pod repo update + clone_dependency "Trakt-Swift" clone_dependency "TMDB-Swift" clone_dependency "TVDB-Swift" From 204b0d10f1e8748f754f63e7c938b326229837a4 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Wed, 17 Apr 2019 19:15:40 -0300 Subject: [PATCH 09/25] Fix icon; Remove FlowKit and uses RxDataSources --- CouchTracker.xcodeproj/project.pbxproj | 38 ++- .../AppIcon.appiconset/1024.png | Bin 0 -> 23680 bytes .../AppIcon.appiconset/114.png | Bin 0 -> 850 bytes .../AppIcon.appiconset/120.png | Bin 0 -> 1009 bytes .../AppIcon.appiconset/180.png | Bin 0 -> 1962 bytes .../Assets.xcassets/AppIcon.appiconset/29.png | Bin 0 -> 173 bytes .../Assets.xcassets/AppIcon.appiconset/40.png | Bin 0 -> 267 bytes .../Assets.xcassets/AppIcon.appiconset/57.png | Bin 0 -> 339 bytes .../Assets.xcassets/AppIcon.appiconset/58.png | Bin 0 -> 353 bytes .../Assets.xcassets/AppIcon.appiconset/60.png | Bin 0 -> 382 bytes .../Assets.xcassets/AppIcon.appiconset/80.png | Bin 0 -> 604 bytes .../Assets.xcassets/AppIcon.appiconset/87.png | Bin 0 -> 588 bytes .../AppIcon.appiconset/Contents.json | 41 +++- CouchTracker/Assets.xcassets/Contents.json | 6 + .../Persistence/Realm/RealmAdapters.swift | 6 +- .../Show/Episode/ShowEpisodeView.swift | 19 +- .../Episode/ShowEpisodeViewController.swift | 13 +- .../ShowsProgressViewController.swift | 94 +++----- .../Trending/TrendingViewController.swift | 17 ++ CouchTrackerApp/Views/LoadingButton.swift | 43 ++++ CouchTrackerCore/Entities/EpisodeEntity.swift | 4 +- .../EntityMappers/EpisodeEntityMapper.swift | 7 +- .../Extensions/CompletableExtensions.swift | 2 +- .../Extensions/EpisodeEntity+Formatter.swift | 17 ++ .../Extensions/ObservableExtensions.swift | 6 + .../MovieDetails/MovieDetailsService.swift | 2 +- .../Show/Overview/ShowOverviewService.swift | 2 +- .../Progress/ShowProgressListState.swift | 8 - .../Progress/ShowsProgressContract.swift | 9 +- .../ShowsProgressDefaultPresenter.swift | 60 ++--- .../Shows/Progress/ShowsProgressService.swift | 29 ++- .../Trending/TrendingContract.swift | 1 + .../Trending/TrendingDefaultPresenter.swift | 4 +- .../Images/ImageRepositoryMock.swift | 2 +- .../MovieDetails/MovieDetailsMocks.swift | 2 +- CouchTrackerCoreTests/RxAsserts.swift | 2 +- .../Shows/Progress/ShowProgressSortTest.swift | 60 +++-- .../Shows/Progress/ShowsProgressMocks.swift | 57 ++++- .../Progress/ShowsProgressPresenterTest.swift | 41 ++-- .../Progress/ShowsProgressServiceTest.swift | 2 +- .../Trending/TrendingInteractorTest.swift | 2 +- .../Trending/TrendingMocks.swift | 15 +- .../Trending/TrendingPresenterTest.swift | 16 +- .../Realm/Entities/EpisodeEntityRealm.swift | 4 +- .../Contents.swift | 223 +++++++++++++++++- Podfile | 4 +- Podfile.lock | 34 +-- 47 files changed, 651 insertions(+), 241 deletions(-) create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/1024.png create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/114.png create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/120.png create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/180.png create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/29.png create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/40.png create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/57.png create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/58.png create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/60.png create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/80.png create mode 100644 CouchTracker/Assets.xcassets/AppIcon.appiconset/87.png rename {CouchTrackerApp => CouchTracker}/Assets.xcassets/AppIcon.appiconset/Contents.json (58%) create mode 100644 CouchTracker/Assets.xcassets/Contents.json create mode 100644 CouchTrackerApp/Views/LoadingButton.swift create mode 100644 CouchTrackerCore/Extensions/EpisodeEntity+Formatter.swift diff --git a/CouchTracker.xcodeproj/project.pbxproj b/CouchTracker.xcodeproj/project.pbxproj index 3da7a2a4..deefb34f 100644 --- a/CouchTracker.xcodeproj/project.pbxproj +++ b/CouchTracker.xcodeproj/project.pbxproj @@ -403,8 +403,6 @@ 8204073321D004AF00127F05 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64343E1D1F7EB3B7004557E4 /* Secrets.swift */; }; 8204073421D004AF00127F05 /* NoCacheMoyaPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FAA2542205693BE0006735A /* NoCacheMoyaPlugin.swift */; }; 8204073E21D0180C00127F05 /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8204073D21D0180C00127F05 /* R.generated.swift */; }; - 8204073F21D0187800127F05 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 647632431F44824900F277F1 /* Assets.xcassets */; }; - 8204074021D01A9D00127F05 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 64BE63231F4F692B002E55EA /* Localizable.strings */; }; 8209AB0B21CED6800027BBF3 /* Error+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8209AB0A21CED6800027BBF3 /* Error+Mock.swift */; }; 8209AB1021CEF39E0027BBF3 /* WatchedShowEntity+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8209AB0F21CEF39E0027BBF3 /* WatchedShowEntity+Mock.swift */; }; 8209AB1221CEF3F40027BBF3 /* Bundle+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8209AB1121CEF3F40027BBF3 /* Bundle+Testing.swift */; }; @@ -438,7 +436,6 @@ 821F9CC9220CE3F300541D61 /* CompletableExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821F9CC8220CE3F300541D61 /* CompletableExtensions.swift */; }; 821F9CCA220CE3F300541D61 /* CompletableExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821F9CC8220CE3F300541D61 /* CompletableExtensions.swift */; }; 8226D9E621D704EC007BB979 /* ShowOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8226D9E521D704EC007BB979 /* ShowOverviewView.swift */; }; - 8226D9E721D70D76007BB979 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 64BE63231F4F692B002E55EA /* Localizable.strings */; }; 8226D9E921D71719007BB979 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8226D9E821D71719007BB979 /* Colors.swift */; }; 8226D9EB21D72731007BB979 /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8226D9EA21D72731007BB979 /* CollectionViewCell.swift */; }; 822A02FD21D2352400440AF8 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 822A02FC21D2352400440AF8 /* View.swift */; }; @@ -512,17 +509,25 @@ 82930A21216C1C8500A824F6 /* SynchronizerDataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69723AC6CAF4ED73AC5EA0EF /* SynchronizerDataSources.swift */; }; 8296594621F3905200DB1B4D /* SyncStateMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8296594521F3905200DB1B4D /* SyncStateMocks.swift */; }; 8296594E21F3F98500DB1B4D /* ShowIdsMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8296594D21F3F98500DB1B4D /* ShowIdsMock.swift */; }; + 82983F8A22336BB1003DDCF0 /* EpisodeEntity+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82983F8922336BB1003DDCF0 /* EpisodeEntity+Formatter.swift */; }; + 82983F8B22336BB1003DDCF0 /* EpisodeEntity+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82983F8922336BB1003DDCF0 /* EpisodeEntity+Formatter.swift */; }; 829A590121B2404700B67753 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 829A590021B2404700B67753 /* Container.swift */; }; 829A590221B2404700B67753 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 829A590021B2404700B67753 /* Container.swift */; }; 82A1A78222067A2300EB50BB /* ObservableExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82A1A78122067A2300EB50BB /* ObservableExtensions.swift */; }; 82A1A78322067A2300EB50BB /* ObservableExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82A1A78122067A2300EB50BB /* ObservableExtensions.swift */; }; + 82A67D502267DA7C007B3622 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 82A67D4F2267DA7C007B3622 /* Assets.xcassets */; }; 82AC56E321F12E2300DCF165 /* SyncState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AC56E221F12E2300DCF165 /* SyncState.swift */; }; 82AC56E421F12E2300DCF165 /* SyncState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AC56E221F12E2300DCF165 /* SyncState.swift */; }; 82AC56E621F12E9800DCF165 /* SyncStateObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AC56E521F12E9800DCF165 /* SyncStateObservable.swift */; }; 82AC56E721F12E9800DCF165 /* SyncStateObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AC56E521F12E9800DCF165 /* SyncStateObservable.swift */; }; + 82B431382231C0A0006AB320 /* LoadingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B431372231C0A0006AB320 /* LoadingButton.swift */; }; 82C6477C21D72F3500175B24 /* TrendingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82C6477B21D72F3500175B24 /* TrendingView.swift */; }; 82C6477E21D72F5200175B24 /* DefaultEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82C6477D21D72F5200175B24 /* DefaultEmptyView.swift */; }; 82C6478021D7345200175B24 /* PosterAndTitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82C6477F21D7345200175B24 /* PosterAndTitleCell.swift */; }; + 82CE7981222C61730093AE40 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 64BE63231F4F692B002E55EA /* Localizable.strings */; }; + 82CE7982222C61920093AE40 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 647632431F44824900F277F1 /* Assets.xcassets */; }; + 82CE7983222C67820093AE40 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 647632431F44824900F277F1 /* Assets.xcassets */; }; + 82CE7984222C67910093AE40 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 64BE63231F4F692B002E55EA /* Localizable.strings */; }; 82D2434621C6DBA40003FC3D /* SynchronizerMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82D2434521C6DBA40003FC3D /* SynchronizerMocks.swift */; }; 82E1DE6321D95B9A006C8EA8 /* AppFlowViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82E1DE6221D95B9A006C8EA8 /* AppFlowViewState.swift */; }; 82E1DE6421D95B9C006C8EA8 /* AppFlowViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82E1DE6221D95B9A006C8EA8 /* AppFlowViewState.swift */; }; @@ -996,10 +1001,13 @@ 82930A18216C1C7B00A824F6 /* DefaultWatchedShowEntityDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultWatchedShowEntityDownloader.swift; sourceTree = ""; }; 8296594521F3905200DB1B4D /* SyncStateMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStateMocks.swift; sourceTree = ""; }; 8296594D21F3F98500DB1B4D /* ShowIdsMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowIdsMock.swift; sourceTree = ""; }; + 82983F8922336BB1003DDCF0 /* EpisodeEntity+Formatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EpisodeEntity+Formatter.swift"; sourceTree = ""; }; 829A590021B2404700B67753 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; 82A1A78122067A2300EB50BB /* ObservableExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableExtensions.swift; sourceTree = ""; }; + 82A67D4F2267DA7C007B3622 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 82AC56E221F12E2300DCF165 /* SyncState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncState.swift; sourceTree = ""; }; 82AC56E521F12E9800DCF165 /* SyncStateObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStateObservable.swift; sourceTree = ""; }; + 82B431372231C0A0006AB320 /* LoadingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingButton.swift; sourceTree = ""; }; 82C6477B21D72F3500175B24 /* TrendingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingView.swift; sourceTree = ""; }; 82C6477D21D72F5200175B24 /* DefaultEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultEmptyView.swift; sourceTree = ""; }; 82C6477F21D7345200175B24 /* PosterAndTitleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterAndTitleCell.swift; sourceTree = ""; }; @@ -1060,9 +1068,9 @@ files = ( 824A7859220FCC350001F3BF /* CouchTrackerPersistence.framework in Frameworks */, 820406F421D0032100127F05 /* CouchTrackerApp.framework in Frameworks */, - 6454D1811F704AC0008C4E5F /* WebKit.framework in Frameworks */, 4F2F0FD22040093700A76E95 /* CouchTrackerCore.framework in Frameworks */, A3145F5442CF1A9D833FEF0B /* Pods_CouchTracker.framework in Frameworks */, + 6454D1811F704AC0008C4E5F /* WebKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1304,6 +1312,7 @@ 82F85FD721D5ACCF006A66B9 /* StringExtensions.swift */, 8209AB1821CF343E0027BBF3 /* URLExtensions.swift */, 821F9CC8220CE3F300541D61 /* CompletableExtensions.swift */, + 82983F8922336BB1003DDCF0 /* EpisodeEntity+Formatter.swift */, ); path = Extensions; sourceTree = ""; @@ -1591,6 +1600,7 @@ children = ( 82C6477D21D72F5200175B24 /* DefaultEmptyView.swift */, 82C6477F21D7345200175B24 /* PosterAndTitleCell.swift */, + 82B431372231C0A0006AB320 /* LoadingButton.swift */, ); path = Views; sourceTree = ""; @@ -1897,6 +1907,7 @@ 6476323B1F44824900F277F1 /* CouchTracker */ = { isa = PBXGroup; children = ( + 82A67D4F2267DA7C007B3622 /* Assets.xcassets */, 6476323C1F44824900F277F1 /* AppDelegate.swift */, 647632481F44824900F277F1 /* Info.plist */, 647632451F44824900F277F1 /* LaunchScreen.storyboard */, @@ -2644,8 +2655,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 82CE7984222C67910093AE40 /* Localizable.strings in Resources */, + 82CE7983222C67820093AE40 /* Assets.xcassets in Resources */, + 82A67D502267DA7C007B3622 /* Assets.xcassets in Resources */, 647632471F44824900F277F1 /* LaunchScreen.storyboard in Resources */, - 8226D9E721D70D76007BB979 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2690,8 +2703,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 8204074021D01A9D00127F05 /* Localizable.strings in Resources */, - 8204073F21D0187800127F05 /* Assets.xcassets in Resources */, + 82CE7982222C61920093AE40 /* Assets.xcassets in Resources */, + 82CE7981222C61730093AE40 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2800,11 +2813,12 @@ "${BUILT_PRODUCTS_DIR}/ActionSheetPicker-3.0/ActionSheetPicker_3_0.framework", "${BUILT_PRODUCTS_DIR}/AutoInsetter/AutoInsetter.framework", "${BUILT_PRODUCTS_DIR}/Cartography/Cartography.framework", - "${BUILT_PRODUCTS_DIR}/FlowKitManager/FlowKitManager.framework", + "${BUILT_PRODUCTS_DIR}/Differentiator/Differentiator.framework", "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework", "${BUILT_PRODUCTS_DIR}/Pageboy/Pageboy.framework", "${BUILT_PRODUCTS_DIR}/R.swift.Library/Rswift.framework", "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", + "${BUILT_PRODUCTS_DIR}/RxDataSources/RxDataSources.framework", "${BUILT_PRODUCTS_DIR}/Tabman/Tabman.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", @@ -2826,11 +2840,12 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ActionSheetPicker_3_0.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AutoInsetter.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Cartography.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FlowKitManager.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differentiator.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Pageboy.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Rswift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxDataSources.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Tabman.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", @@ -2895,7 +2910,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "sh $SRCROOT/scripts/swiftformat CouchTrackerCoreTests/\n"; + shellScript = "sh $SRCROOT/build_phases/swiftformat\n"; }; 4FAC4A93210F28310096BCE0 /* SwiftFormat */ = { isa = PBXShellScriptBuildPhase; @@ -3215,6 +3230,7 @@ 82E1DE6921D96E2C006C8EA8 /* ShowsManagerViewState.swift in Sources */, 4F70910720D5F3650017B259 /* WatchedMovieResult.swift in Sources */, 4F0F5779203D5FA000B86CB8 /* ShowEpisodeMoyaNetwork.swift in Sources */, + 82983F8A22336BB1003DDCF0 /* EpisodeEntity+Formatter.swift in Sources */, 4F0F577A203D5FA000B86CB8 /* ShowManagerContract.swift in Sources */, 4F0F577C203D5FA000B86CB8 /* ShowManagerDefaultPresenter.swift in Sources */, 829A590121B2404700B67753 /* Container.swift in Sources */, @@ -3471,6 +3487,7 @@ 829A590221B2404700B67753 /* Container.swift in Sources */, 4F70910820D5F3650017B259 /* WatchedMovieResult.swift in Sources */, 4F0F58C0203D7B0A00B86CB8 /* SearchService.swift in Sources */, + 82983F8B22336BB1003DDCF0 /* EpisodeEntity+Formatter.swift in Sources */, 4F0F58C2203D7B0A00B86CB8 /* ShowEpisodeAPIRepository.swift in Sources */, 82836E9F218EABE20037A798 /* SyncOptions.swift in Sources */, 4F0F58C3203D7B0A00B86CB8 /* ShowEpisodeContract.swift in Sources */, @@ -3578,6 +3595,7 @@ 82C6478021D7345200175B24 /* PosterAndTitleCell.swift in Sources */, 8204072121D0048900127F05 /* ShowsManagerModule.swift in Sources */, 8204070821D0041300127F05 /* MovieDetailsViewController.swift in Sources */, + 82B431382231C0A0006AB320 /* LoadingButton.swift in Sources */, 8204071021D0046400127F05 /* SearchModule.swift in Sources */, 8204071121D0046400127F05 /* SearchViewController.swift in Sources */, 820406FF21D003A500127F05 /* AppFlowModule.swift in Sources */, diff --git a/CouchTracker/Assets.xcassets/AppIcon.appiconset/1024.png b/CouchTracker/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000000000000000000000000000000000000..abbff360b74fc9f4f36caad28945e28d09ed12a4 GIT binary patch literal 23680 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 D6dr(N literal 0 HcmV?d00001 diff --git a/CouchTracker/Assets.xcassets/AppIcon.appiconset/120.png b/CouchTracker/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000000000000000000000000000000000000..5d7853b17bd56deb57e58d603f787e082134ed98 GIT binary patch literal 1009 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 literal 0 HcmV?d00001 diff --git a/CouchTracker/Assets.xcassets/AppIcon.appiconset/29.png b/CouchTracker/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000000000000000000000000000000000000..b9607b58c096df197d4fb7454c4fb6f70b3ee948 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^vLMXC1|-8Kr}G1;5>FS$5RcBcS2pr8C~~k~*gvIO zQ2SaTfBEn0m*y;=aQTRXlMau~1_2uZ50i_!8Sm#FxRb+hBdUAxwujPoySNvM1%ysY zSzF3kyjZOKdfzO!%e}kIJWVdTym#;abP0l+XkKUO7P& literal 0 HcmV?d00001 diff --git a/CouchTracker/Assets.xcassets/AppIcon.appiconset/40.png b/CouchTracker/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000000000000000000000000000000000000..76c6893978d60a3b4674b324c3d448971985548f GIT binary patch literal 267 zcmV+m0rdWfP))o0X7F%&vThxXw#RC}jR)B*6&j99%8%4HaZj zK^7HcIVA`{fbu<+tS?GC8^mm^xE#IiTx>l zrKVB-L%_WX)d{D{!P9ik zrJ0l7+kJgu;2+WULihIPop;qrFzknD({=>}n7d_{!$8moYHk z;fl0CYOC`CfiLqz`z*Id9}|)~WO@DEHN9-j*7oUtvpZNP zzb#Xq%$=*?clL8kOo;?rb)a@vR}^!gUzk+G3PypaDN7kUq!_Sse>^^W(wlj`tyklV z-qr7aKiT(sS9*ugTzS9vi9V<1-q|g9cbl%D^zn^7FZR7ZdjDc+>{r*$xT2u{>p{Lk bcu+p4+wW7p)#<~)pkwfK^>bP0l+XkKz+jir literal 0 HcmV?d00001 diff --git a/CouchTracker/Assets.xcassets/AppIcon.appiconset/60.png b/CouchTracker/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000000000000000000000000000000000000..88cd2f096767d28824631a854daac00145b650e3 GIT binary patch literal 382 zcmV-^0fGLBP)a@eU?k`J5lGN<^ksD34Oj!= z=s-9+5RMLnqXS{_1fAVItC3Yj_;<9TW=54RbI#mQ-}g19BqE|=7#q*r!$@47BQnABEo0jGgZ2gKf-Ekz%TkHiyM#s{BGFGCTXOcohDJ2%0risRJ zCYtlF^u@c6r;mS;HU7Z<)XZaqqmsUl%-Sy+^192utfdLN!2GTJI660m9OHk5CN|mey-SEu|mv7mw$F@Fxqu zX1&XH6MpymJkP~9XWO=C3%6R~ec$;yr9}6A#}$6mJNyLc;=YS3KNJW@2g1>TaC9IX c9SBF~2Q8vxVJo@O&Hw-a07*qoM6N<$f~{|=*#H0l literal 0 HcmV?d00001 diff --git a/CouchTracker/Assets.xcassets/AppIcon.appiconset/80.png b/CouchTracker/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000000000000000000000000000000000000..88a620c75ff5fc96716f47c4b609884cc2bc13e7 GIT binary patch literal 604 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdoh8U{dyUaSW-L^Y*r37n7kx+e07K z4u!Y_FKt@pPOV_oK4|TudZI#1`+VZ!c7Z)jH~co8J+~>cm+SugpOc?fE1rFoGRa*w zMY1HufHbEJC$u9QSyz+t-d-W^l9$13L7`;>pyG0o!c6>e(~JZYd4<{4-~ogY|YlB z;^^$FdeU>_7c4PfRl7HI_o2no*V}$aw%?jLxA^+&tIiQRV)^|u&p*FsIQ{g}=BaJo zk4{HLPLn(Jx30e8+}pCx^Ni*CMZ`&E_zn;Ey>#X|6$ zDY?~GUorY8-P^S;_Oi*O=xNM6d$WUNlYUQ|vE=^y)y)}-U1t{Ye$;2&{pb0?-ENBm z;~IYdjZ2EU=(jxBu3;szkFf*-7NOH@2ChdUvU{r150zMz&OYl_dM0i2gI_M+^o%?c z8dvg9+}rIliGhug^GD5_vfU4oPd_!{I;LKKp6SEY$M5@G8E#8o>Rwc_(+{Lh=*E=- zi<)2BO36L5ebn^(pIRFyEI@v_}hZ-MYnXWV^Qz>ylcD1E^W*Fc-; z%XiJ<-v8=4_sQNnUv=ijynR`6s%y@?S{u3DmfI%>KT165>bE@j%buG#)4qGZ{j#MZ zqv+uC&o@i{uUZvj)3?cvg_D7Q#reoh^ZuP+Qn~xMY`3KU&7@^_n2&_5zAAk#!QjWM z;?&4}wRsyO4ot3_FMTJXiv3;L?pxn;w?~KfH|+Z3`$eO5ciei#76HBK-PKoS+&?L| zbV98|@6Y)LSD3#me%74*&gA{+JBLb)*&XIJv}(sZd~`VKdiEVV6%_Ab@i8u;Ytb?9 z4}974E8g%Sm$`LM!Cd`d{>x`&9@I4bdiH+rk>1|pkB?o~O?>~o`fW~VsOhhH*n&)tIoGco2D))Uee5#1X|r{wpMGOe zA36Etkx9)zSyS_t+$n!^nek?5t3XRPYP?`^ EpisodeIdsRealm { + func toRealm() -> EpisodeIdsRealm { let entity = EpisodeIdsRealm() entity.trakt = trakt @@ -217,7 +217,8 @@ extension EpisodeEntityRealm { overview: overview, number: number, season: season, - firstAired: firstAired) + firstAired: firstAired, + absoluteNumber: absoluteNumber.value) } } @@ -232,6 +233,7 @@ extension EpisodeEntity { entity.number = number entity.season = season entity.firstAired = firstAired + entity.absoluteNumber.value = absoluteNumber return entity } diff --git a/CouchTrackerApp/Show/Episode/ShowEpisodeView.swift b/CouchTrackerApp/Show/Episode/ShowEpisodeView.swift index 959b3d23..fd7a6174 100644 --- a/CouchTrackerApp/Show/Episode/ShowEpisodeView.swift +++ b/CouchTrackerApp/Show/Episode/ShowEpisodeView.swift @@ -1,6 +1,6 @@ import Cartography -public class ShowEpisodeView: View { +public final class ShowEpisodeView: View { public var didTouchOnPreview: (() -> Void)? public var didTouchOnWatch: (() -> Void)? @@ -49,10 +49,16 @@ public class ShowEpisodeView: View { return label }() - public let watchButton: UIButton = { - let button = UIButton() - button.addTarget(self, action: #selector(didTapOnWatch), for: .touchUpInside) - return button + public let seasonAndNumberLabel: UILabel = { + let label = UILabel() + label.textColor = Colors.Text.secondaryTextColor + return label + }() + + public lazy var watchButton: LoadingButton = { + let view = LoadingButton() + view.button.addTarget(self, action: #selector(didTapOnWatch), for: .touchUpInside) + return view }() // Private Views @@ -70,7 +76,7 @@ public class ShowEpisodeView: View { private lazy var contentStackView: UIStackView = { let subviews = [previewImageView, titleLabel, overviewLabel, - releaseDateLabel, watchedAtLabel, watchButton] + releaseDateLabel, seasonAndNumberLabel, watchedAtLabel, watchButton] let stackView = UIStackView(arrangedSubviews: subviews) let spacing: CGFloat = 20 @@ -81,6 +87,7 @@ public class ShowEpisodeView: View { stackView.distribution = .equalSpacing stackView.layoutMargins = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing) stackView.isLayoutMarginsRelativeArrangement = true + stackView.isUserInteractionEnabled = true return stackView }() diff --git a/CouchTrackerApp/Show/Episode/ShowEpisodeViewController.swift b/CouchTrackerApp/Show/Episode/ShowEpisodeViewController.swift index 496e8de5..68d39c31 100644 --- a/CouchTrackerApp/Show/Episode/ShowEpisodeViewController.swift +++ b/CouchTrackerApp/Show/Episode/ShowEpisodeViewController.swift @@ -42,13 +42,21 @@ final class ShowEpisodeViewController: UIViewController { self?.handleWatch() } + episodeView.didTouchOnPreview = { + print("Preview!!!") + } + presenter.viewDidLoad() } private func handleWatch() { presenter.handleWatch() .observeOn(schedulers.mainScheduler) - .subscribe { [weak self] error in + .do(onSubscribe: { [weak self] in + self?.episodeView.watchButton.isLoading = true + }, onDispose: { [weak self] in + self?.episodeView.watchButton.isLoading = false + }).subscribe { [weak self] error in self?.handleSync(error: error) }.disposed(by: disposeBag) } @@ -78,12 +86,13 @@ final class ShowEpisodeViewController: UIViewController { episodeView.overviewLabel.text = episode.episode.overview ?? "No overview" episodeView.releaseDateLabel.text = episode.episode.firstAired?.shortString() ?? "Unknown".localized episodeView.watchedAtLabel.text = episode.lastWatched?.shortString() ?? "Unwatched" + episodeView.seasonAndNumberLabel.text = episode.episode.seasonAndNumberFormatted() let buttonTitle = episode.lastWatched == nil ? R.string.localizable.addToHistory() : R.string.localizable.removeFromHistory() - episodeView.watchButton.setTitle(buttonTitle, for: .normal) + episodeView.watchButton.button.setTitle(buttonTitle, for: .normal) } private func handleViewState(_ viewState: ShowEpisodeViewState) { diff --git a/CouchTrackerApp/Shows/Progress/ShowsProgressViewController.swift b/CouchTrackerApp/Shows/Progress/ShowsProgressViewController.swift index f817275b..2311c8aa 100644 --- a/CouchTrackerApp/Shows/Progress/ShowsProgressViewController.swift +++ b/CouchTrackerApp/Shows/Progress/ShowsProgressViewController.swift @@ -1,8 +1,8 @@ import ActionSheetPicker_3_0 import CouchTrackerCore -import FlowKitManager import NonEmpty import RxCocoa +import RxDataSources import RxSwift final class ShowsProgressViewController: UIViewController { @@ -38,13 +38,37 @@ final class ShowsProgressViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + let tableView = showsView.tableView + let cellIdentifier = ShowProgressCell.identifier + let cellType = ShowProgressCell.self + + tableView.register(cellType, forCellReuseIdentifier: cellIdentifier) + presenter.observeViewState() .observeOn(schedulers.mainScheduler) - .subscribe(onNext: { [weak self] viewState in + .do(onNext: { [weak self] viewState in self?.handleViewState(viewState) + }).map { viewState -> [WatchedShowEntity]? in + if case let .shows(entities, _) = viewState { + return Array(entities) + } + return nil + }.unwrap() + .bind(to: tableView.rx.items(cellIdentifier: cellIdentifier, + cellType: cellType)) { [localCellInteractor = cellInteractor] _, model, cell in + let viewModel = WatchedShowEntityMapper.viewModel(for: model) + + let presenter = ShowProgressCellDefaultPresenter(interactor: localCellInteractor, viewModel: viewModel) + cell.presenter = presenter + }.disposed(by: disposeBag) + + tableView.rx.modelSelected(WatchedShowEntity.self) + .subscribe(onNext: { [localPresenter = presenter, localTableView = tableView] entity in + localPresenter.select(show: entity) + if let indexPath = localTableView.indexPathForSelectedRow { + localTableView.deselectRow(at: indexPath, animated: true) + } }).disposed(by: disposeBag) - - presenter.viewDidLoad() } private func handleViewState(_ state: ShowProgressViewState) { @@ -61,8 +85,8 @@ final class ShowsProgressViewController: UIViewController { showLoadingData() case let .error(error): showError(error: error) - case let .shows(entities, menu): - showData(entities: entities, menu: menu) + case let .shows(_, menu): + showData(menu: menu) } } @@ -72,28 +96,9 @@ final class ShowsProgressViewController: UIViewController { showsView.emptyView.label.text = message } - private func showData(entities: NonEmpty<[WatchedShowEntity]>, menu: ShowsProgressMenuOptions) { + private func showData(menu: ShowsProgressMenuOptions) { configureBarButtonItems(menu: menu) - let tableView = showsView.tableView - let director = tableView.director - - if !director.sections.isEmpty { - director.removeAll() - } - - let viewModels = Array(entities.map(WatchedShowEntityMapper.viewModel(for:))) - - let adapter = ShowProgressTableAdapter(presenter: presenter, interactor: cellInteractor, entities: Array(entities)) - - // CT-TODO Remove this line when this got fixed: https://github.com/malcommac/FlowKit/issues/21 - tableView.register(ShowProgressCell.self, forCellReuseIdentifier: ShowProgressCell.identifier) - - director.register(adapter: adapter) - director.add(models: viewModels) - director.rowHeight = .fixed(height: 100) - director.reloadData() - showsView.emptyView.isHidden = true showsView.tableView.isHidden = false } @@ -130,9 +135,11 @@ final class ShowsProgressViewController: UIViewController { }).disposed(by: disposeBag) let directionItem = UIBarButtonItem(image: R.image.direction(), style: .plain, target: nil, action: nil) - directionItem.rx.tap.asDriver().drive(onNext: { [weak self] in - self?.presenter.toggleDirection() - }).disposed(by: disposeBag) + + directionItem.rx.tap.flatMapLatest { [localPresenter = presenter] in + localPresenter.toggleDirection() + }.subscribe() + .disposed(by: disposeBag) parentPageboy?.navigationItem.rightBarButtonItems = [filterItem, directionItem] navigationItem.rightBarButtonItems = [filterItem, directionItem] @@ -157,37 +164,14 @@ final class ShowsProgressViewController: UIViewController { let sort = ShowProgressSort.allValues()[sortIndex] let filter = ShowProgressFilter.allValues()[filterIndex] - self?.presenter.change(sort: sort, filter: filter) + self?.change(sort: sort, filter: filter) }, cancel: { _ in }, origin: view) picker?.show() } -} - -extension WatchedShowViewModel: ModelProtocol { - public var modelID: Int { - return hashValue - } -} -private class ShowProgressTableAdapter: TableAdapter { - init(presenter: ShowsProgressPresenter, interactor: ShowProgressCellInteractor, entities: [WatchedShowEntity]) { - super.init() - - on.dequeue = { context in - guard let cell = context.cell else { return } - - let viewModel = context.model - let presenter = ShowProgressCellDefaultPresenter(interactor: interactor, viewModel: viewModel) - - cell.presenter = presenter - } - - on.tap = { context in - let entity = entities[context.indexPath.row] - presenter.select(show: entity) - return .deselectAnimated - } + private func change(sort: ShowProgressSort, filter: ShowProgressFilter) { + presenter.change(sort: sort, filter: filter).subscribe().disposed(by: disposeBag) } } diff --git a/CouchTrackerApp/Trending/TrendingViewController.swift b/CouchTrackerApp/Trending/TrendingViewController.swift index 2683764c..23a591c7 100644 --- a/CouchTrackerApp/Trending/TrendingViewController.swift +++ b/CouchTrackerApp/Trending/TrendingViewController.swift @@ -81,7 +81,24 @@ extension TrendingViewController: TrendingViewProtocol { public func showEmptyView() { trendingView.collectionView.isHidden = true + + let emptyText: String + + if trendingType == .movies { + emptyText = R.string.localizable.noMoviesToShowRightNow() + } else { + emptyText = R.string.localizable.noTvShowsToShowRightNow() + } + + trendingView.emptyView.label.text = emptyText + + trendingView.emptyView.isHidden = false + } + + public func showLoadingView() { + trendingView.collectionView.isHidden = true trendingView.emptyView.isHidden = false + trendingView.emptyView.label.text = "Loading" } private func makeListVisible() { diff --git a/CouchTrackerApp/Views/LoadingButton.swift b/CouchTrackerApp/Views/LoadingButton.swift new file mode 100644 index 00000000..4e15619b --- /dev/null +++ b/CouchTrackerApp/Views/LoadingButton.swift @@ -0,0 +1,43 @@ +import Cartography + +public final class LoadingButton: View { + public let button: UIButton = { + UIButton() + }() + + private let spinner: UIActivityIndicatorView = { + UIActivityIndicatorView(style: .white) + }() + + public var isLoading: Bool = false { + didSet { + update(loading: isLoading) + } + } + + private func update(loading: Bool) { + if loading { + spinner.startAnimating() + } else { + spinner.stopAnimating() + } + + spinner.isHidden = !loading + button.isHidden = loading + } + + public override func initialize() { + update(loading: false) + + addSubview(spinner) + addSubview(button) + } + + public override func installConstraints() { + constrain(button, spinner) { button, spinner in + button.center == button.superview!.center + spinner.center == spinner.superview!.center + button.height == button.superview!.height + } + } +} diff --git a/CouchTrackerCore/Entities/EpisodeEntity.swift b/CouchTrackerCore/Entities/EpisodeEntity.swift index dd34ad67..1d49b579 100644 --- a/CouchTrackerCore/Entities/EpisodeEntity.swift +++ b/CouchTrackerCore/Entities/EpisodeEntity.swift @@ -9,9 +9,10 @@ public struct EpisodeEntity: Hashable, Codable, EpisodeImageInput { public let number: Int public let season: Int public let firstAired: Date? + public let absoluteNumber: Int? public init(ids: EpisodeIds, showIds: ShowIds, title: String, - overview: String?, number: Int, season: Int, firstAired: Date?) { + overview: String?, number: Int, season: Int, firstAired: Date?, absoluteNumber: Int?) { self.ids = ids self.showIds = showIds self.title = title @@ -19,6 +20,7 @@ public struct EpisodeEntity: Hashable, Codable, EpisodeImageInput { self.number = number self.season = season self.firstAired = firstAired + self.absoluteNumber = absoluteNumber } public var tvdb: Int? { diff --git a/CouchTrackerCore/EntityMappers/EpisodeEntityMapper.swift b/CouchTrackerCore/EntityMappers/EpisodeEntityMapper.swift index b4d7b8de..70cf0b51 100644 --- a/CouchTrackerCore/EntityMappers/EpisodeEntityMapper.swift +++ b/CouchTrackerCore/EntityMappers/EpisodeEntityMapper.swift @@ -1,8 +1,6 @@ import TraktSwift -public final class EpisodeEntityMapper { - private init() {} - +public enum EpisodeEntityMapper { public static func entity(for episode: Episode, showIds: ShowIds) -> EpisodeEntity { // CT-TODO Don't handle title here return EpisodeEntity(ids: episode.ids, @@ -11,6 +9,7 @@ public final class EpisodeEntityMapper { overview: episode.overview, number: episode.number, season: episode.season, - firstAired: episode.firstAired) + firstAired: episode.firstAired, + absoluteNumber: episode.absoluteNumber) } } diff --git a/CouchTrackerCore/Extensions/CompletableExtensions.swift b/CouchTrackerCore/Extensions/CompletableExtensions.swift index 139af3d4..4d6d5a2d 100644 --- a/CouchTrackerCore/Extensions/CompletableExtensions.swift +++ b/CouchTrackerCore/Extensions/CompletableExtensions.swift @@ -1,7 +1,7 @@ import RxSwift public extension PrimitiveSequence where Trait == CompletableTrait { - public static func from(_ function: @escaping () throws -> Void) -> Completable { + static func from(_ function: @escaping () throws -> Void) -> Completable { return Completable.create { completable -> Disposable in do { try function() diff --git a/CouchTrackerCore/Extensions/EpisodeEntity+Formatter.swift b/CouchTrackerCore/Extensions/EpisodeEntity+Formatter.swift new file mode 100644 index 00000000..2e554ec3 --- /dev/null +++ b/CouchTrackerCore/Extensions/EpisodeEntity+Formatter.swift @@ -0,0 +1,17 @@ +extension EpisodeEntity { + public func seasonAndNumberFormatted() -> String { + var numberText = String(number) + + if numberText.count == 1 { + numberText.insert("0", at: numberText.startIndex) + } + + var text = "\(season)x\(numberText)" + + if let absoluteNumber = absoluteNumber { + text.append(" (\(absoluteNumber))") + } + + return text + } +} diff --git a/CouchTrackerCore/Extensions/ObservableExtensions.swift b/CouchTrackerCore/Extensions/ObservableExtensions.swift index 7c9600b7..864ada33 100644 --- a/CouchTrackerCore/Extensions/ObservableExtensions.swift +++ b/CouchTrackerCore/Extensions/ObservableExtensions.swift @@ -6,6 +6,12 @@ extension ObservableType where E: Sequence { } } +extension ObservableType { + public func unwrap() -> Observable where E == T? { + return filter { $0 != nil }.map { $0! } + } +} + extension PrimitiveSequenceType where TraitType == MaybeTrait, ElementType: Sequence { public func mapElements(_ mapper: @escaping (ElementType.Element) -> R) -> Maybe<[R]> { return map { $0.map(mapper) } diff --git a/CouchTrackerCore/MovieDetails/MovieDetailsService.swift b/CouchTrackerCore/MovieDetails/MovieDetailsService.swift index 5770a440..1432a03a 100644 --- a/CouchTrackerCore/MovieDetails/MovieDetailsService.swift +++ b/CouchTrackerCore/MovieDetails/MovieDetailsService.swift @@ -24,7 +24,7 @@ public final class MovieDetailsService: MovieDetailsInteractor { genresObservable, watchedObservable) { (movie, genres, watchedMovieResult) -> MovieEntity in let movieGenres = genres.filter { genre -> Bool in - return movie.genres?.contains(genre.slug) ?? false + movie.genres?.contains(genre.slug) ?? false } let watchedAt: Date? diff --git a/CouchTrackerCore/Show/Overview/ShowOverviewService.swift b/CouchTrackerCore/Show/Overview/ShowOverviewService.swift index 37def0fc..0f86f89a 100644 --- a/CouchTrackerCore/Show/Overview/ShowOverviewService.swift +++ b/CouchTrackerCore/Show/Overview/ShowOverviewService.swift @@ -21,7 +21,7 @@ public final class ShowOverviewService: ShowOverviewInteractor { return Observable.combineLatest(showObservable, genreObservable) { (show, genres) -> ShowEntity in let showGenres = genres.filter { genre -> Bool in - return show.genres?.contains(where: { $0 == genre.slug }) ?? false + show.genres?.contains(where: { $0 == genre.slug }) ?? false } return ShowEntityMapper.entity(for: show, with: showGenres) diff --git a/CouchTrackerCore/Shows/Progress/ShowProgressListState.swift b/CouchTrackerCore/Shows/Progress/ShowProgressListState.swift index 00e52d98..47c9ff59 100644 --- a/CouchTrackerCore/Shows/Progress/ShowProgressListState.swift +++ b/CouchTrackerCore/Shows/Progress/ShowProgressListState.swift @@ -17,14 +17,6 @@ public struct ShowProgressListState: Hashable { self.direction = direction } - public var hashValue: Int { - return sort.hashValue ^ filter.hashValue ^ direction.hashValue - } - - public static func == (lhs: ShowProgressListState, rhs: ShowProgressListState) -> Bool { - return lhs.hashValue == rhs.hashValue - } - public func builder() -> Builder { return Builder(state: self) } diff --git a/CouchTrackerCore/Shows/Progress/ShowsProgressContract.swift b/CouchTrackerCore/Shows/Progress/ShowsProgressContract.swift index 75f06ad4..2c845477 100644 --- a/CouchTrackerCore/Shows/Progress/ShowsProgressContract.swift +++ b/CouchTrackerCore/Shows/Progress/ShowsProgressContract.swift @@ -11,9 +11,11 @@ public protocol ShowsProgressListStateDataSource: class { } public protocol ShowsProgressInteractor: class { - var listState: ShowProgressListState { get set } + var listState: Observable { get } func fetchWatchedShowsProgress() -> Observable + func toggleDirection() -> Completable + func change(sort: ShowProgressSort, filter: ShowProgressFilter) -> Completable } public protocol ShowsProgressRouter: class { @@ -22,9 +24,8 @@ public protocol ShowsProgressRouter: class { public protocol ShowsProgressPresenter: class { func observeViewState() -> Observable - func viewDidLoad() - func change(sort: ShowProgressSort, filter: ShowProgressFilter) - func toggleDirection() + func change(sort: ShowProgressSort, filter: ShowProgressFilter) -> Completable + func toggleDirection() -> Completable func select(show: WatchedShowEntity) } diff --git a/CouchTrackerCore/Shows/Progress/ShowsProgressDefaultPresenter.swift b/CouchTrackerCore/Shows/Progress/ShowsProgressDefaultPresenter.swift index f50d25d3..5d29b83b 100644 --- a/CouchTrackerCore/Shows/Progress/ShowsProgressDefaultPresenter.swift +++ b/CouchTrackerCore/Shows/Progress/ShowsProgressDefaultPresenter.swift @@ -2,8 +2,6 @@ import NonEmpty import RxSwift public final class ShowsProgressDefaultPresenter: ShowsProgressPresenter { - private let viewStateSubject = BehaviorSubject(value: .notLogged) - private let disposeBag = DisposeBag() private let interactor: ShowsProgressInteractor private let router: ShowsProgressRouter private let syncStateObservable: SyncStateObservable @@ -20,39 +18,14 @@ public final class ShowsProgressDefaultPresenter: ShowsProgressPresenter { } public func observeViewState() -> Observable { - return viewStateSubject.distinctUntilChanged() - } - - public func viewDidLoad() { - fetchShows() - } - - public func toggleDirection() { - let currentListState = interactor.listState - let newListState = currentListState.builder().direction(currentListState.direction.toggle()).build() - interactor.listState = newListState - - fetchShows() - } - - public func change(sort: ShowProgressSort, filter: ShowProgressFilter) { - let newListState = interactor.listState.builder().sort(sort).filter(filter).build() - interactor.listState = newListState - - fetchShows() - } - - public func select(show: WatchedShowEntity) { - router.show(tvShow: show) - } - - private func fetchShows() { let appStateStream = appStateObservable.observe() let syncStateStream = syncStateObservable.observe() + let listStateStream = interactor.listState - Observable.combineLatest(appStateStream, - syncStateStream) { (appState, syncState) -> AllStates in - return AllStates(appState: appState, syncState: syncState) + return Observable.combineLatest(appStateStream, + syncStateStream, + listStateStream) { (appState, syncState, listState) -> AllStates in + AllStates(appState: appState, syncState: syncState, listState: listState) }.flatMap { [weak self] states -> Observable in guard states.appState.isLogged else { return .just(.notLogged) } @@ -60,28 +33,37 @@ public final class ShowsProgressDefaultPresenter: ShowsProgressPresenter { let defaultViewState: ShowProgressViewState = states.syncState.isSyncing ? .loading : .empty - let listState = strongSelf.interactor.listState - return strongSelf.interactor.fetchWatchedShowsProgress() .map { showsState -> ShowProgressViewState in switch showsState { case .unavailable: return defaultViewState case let .available(shows): - return createViewState(entities: shows, listState: listState) + return createViewState(entities: shows, listState: states.listState) } } } .catchError { error -> Observable in - return .just(ShowProgressViewState.error(error: error)) - }.subscribe(onNext: { [weak self] viewState in - self?.viewStateSubject.onNext(viewState) - }).disposed(by: disposeBag) + .just(ShowProgressViewState.error(error: error)) + } + } + + public func toggleDirection() -> Completable { + return interactor.toggleDirection() + } + + public func change(sort: ShowProgressSort, filter: ShowProgressFilter) -> Completable { + return interactor.change(sort: sort, filter: filter) + } + + public func select(show: WatchedShowEntity) { + router.show(tvShow: show) } } private struct AllStates { let appState: AppState let syncState: SyncState + let listState: ShowProgressListState } private func createViewState(entities: [WatchedShowEntity], diff --git a/CouchTrackerCore/Shows/Progress/ShowsProgressService.swift b/CouchTrackerCore/Shows/Progress/ShowsProgressService.swift index 4c8da77a..d8123a5d 100644 --- a/CouchTrackerCore/Shows/Progress/ShowsProgressService.swift +++ b/CouchTrackerCore/Shows/Progress/ShowsProgressService.swift @@ -2,18 +2,12 @@ import RxSwift import TraktSwift public final class ShowsProgressService: ShowsProgressInteractor { + private let listStateSubject: BehaviorSubject private let listStateDataSource: ShowsProgressListStateDataSource private let showsObserable: WatchedShowEntitiesObservable private let schedulers: Schedulers - public var listState: ShowProgressListState { - get { - return listStateDataSource.currentState - } - set { - listStateDataSource.currentState = newValue - } - } + public var listState: Observable public init(listStateDataSource: ShowsProgressListStateDataSource, showsObserable: WatchedShowEntitiesObservable, @@ -21,9 +15,28 @@ public final class ShowsProgressService: ShowsProgressInteractor { self.listStateDataSource = listStateDataSource self.showsObserable = showsObserable self.schedulers = schedulers + listStateSubject = BehaviorSubject(value: listStateDataSource.currentState) + listState = listStateSubject.distinctUntilChanged() } public func fetchWatchedShowsProgress() -> Observable { return showsObserable.observeWatchedShows().distinctUntilChanged() } + + public func toggleDirection() -> Completable { + let listState = listStateDataSource.currentState.builder().toggleDirection().build() + newListState(listState: listState) + return Completable.empty() + } + + public func change(sort: ShowProgressSort, filter: ShowProgressFilter) -> Completable { + let listState = listStateDataSource.currentState.builder().filter(filter).sort(sort).build() + newListState(listState: listState) + return Completable.empty() + } + + private func newListState(listState: ShowProgressListState) { + listStateDataSource.currentState = listState + listStateSubject.onNext(listState) + } } diff --git a/CouchTrackerCore/Trending/TrendingContract.swift b/CouchTrackerCore/Trending/TrendingContract.swift index fac2d127..3b917c45 100644 --- a/CouchTrackerCore/Trending/TrendingContract.swift +++ b/CouchTrackerCore/Trending/TrendingContract.swift @@ -21,6 +21,7 @@ public protocol TrendingViewProtocol: BaseView { func showEmptyView() func showTrendingsView() + func showLoadingView() } public protocol TrendingRouter: class { diff --git a/CouchTrackerCore/Trending/TrendingDefaultPresenter.swift b/CouchTrackerCore/Trending/TrendingDefaultPresenter.swift index 65e504b9..36b2a3c9 100644 --- a/CouchTrackerCore/Trending/TrendingDefaultPresenter.swift +++ b/CouchTrackerCore/Trending/TrendingDefaultPresenter.swift @@ -61,7 +61,9 @@ public final class TrendingDefaultPresenter: TrendingPresenter { private func subscribe(on single: Single<[PosterViewModel]>) { single.observeOn(schedulers.mainScheduler) - .subscribe(onSuccess: { [unowned self] in + .do(onSubscribe: { [weak self] in + self?.view?.showLoadingView() + }).subscribe(onSuccess: { [unowned self] in self.present(viewModels: $0) }, onError: { error in guard let moviesListError = error as? TrendingError else { diff --git a/CouchTrackerCoreTests/Images/ImageRepositoryMock.swift b/CouchTrackerCoreTests/Images/ImageRepositoryMock.swift index 64577c06..1e253f8c 100644 --- a/CouchTrackerCoreTests/Images/ImageRepositoryMock.swift +++ b/CouchTrackerCoreTests/Images/ImageRepositoryMock.swift @@ -78,7 +78,7 @@ final class ImageRepositoryMock: ImageRepository { func fetchMovieImages(for movieId: Int, posterSize: PosterImageSize?, backdropSize: BackdropImageSize?) -> Maybe { fetchMovieImagesInvoked = true let observable = configuration.fetchConfiguration().asObservable().flatMap { [unowned self] config -> Observable in - return self.tmdb.movies.rx.request(.images(movieId: movieId)).asObservable().map(Images.self).map { + self.tmdb.movies.rx.request(.images(movieId: movieId)).asObservable().map(Images.self).map { let posterSize = posterSize ?? .w342 let backdropSize = backdropSize ?? .w300 diff --git a/CouchTrackerCoreTests/MovieDetails/MovieDetailsMocks.swift b/CouchTrackerCoreTests/MovieDetails/MovieDetailsMocks.swift index ee3b57cd..94f9e971 100644 --- a/CouchTrackerCoreTests/MovieDetails/MovieDetailsMocks.swift +++ b/CouchTrackerCoreTests/MovieDetails/MovieDetailsMocks.swift @@ -163,7 +163,7 @@ final class MovieDetailsServiceMock: MovieDetailsInteractor { return Observable.combineLatest(detailsObservable, genresObservable, watchedObservable) { movie, genres, watched in let movieGenres = genres.filter { genre -> Bool in - return movie.genres?.contains(genre.slug) ?? false + movie.genres?.contains(genre.slug) ?? false } let watchedAt: Date? diff --git a/CouchTrackerCoreTests/RxAsserts.swift b/CouchTrackerCoreTests/RxAsserts.swift index c66a60e1..77821d1f 100644 --- a/CouchTrackerCoreTests/RxAsserts.swift +++ b/CouchTrackerCoreTests/RxAsserts.swift @@ -29,7 +29,7 @@ func RXAssertEvents(_ observerEvents: [Recorded>], _ ev } } -fileprivate func assertArrayEvent(_ lhs: Event<[T]>, _ rhs: Event<[T]>, file: StaticString = #file, line: UInt = #line) { +private func assertArrayEvent(_ lhs: Event<[T]>, _ rhs: Event<[T]>, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(lhs.error?.localizedDescription, rhs.error?.localizedDescription, file: file, line: line) XCTAssertEqual(lhs.isCompleted, rhs.isCompleted, file: file, line: line) diff --git a/CouchTrackerCoreTests/Shows/Progress/ShowProgressSortTest.swift b/CouchTrackerCoreTests/Shows/Progress/ShowProgressSortTest.swift index 8d9657f9..f9a135d7 100644 --- a/CouchTrackerCoreTests/Shows/Progress/ShowProgressSortTest.swift +++ b/CouchTrackerCoreTests/Shows/Progress/ShowProgressSortTest.swift @@ -107,7 +107,7 @@ final class ShowProgressSortTest: XCTestCase { XCTAssertEqual(sortedShows, expectedShows) } - func testShowProgressSort_nextEpisodeDataComparator() { + func testShowProgressSort_nextEpisodeDateComparator() { // Given let mock = ShowsProgressMocks.mockWatchedShowEntity() let episodeMock = ShowsProgressMocks.mockEpisodeEntity() @@ -120,26 +120,44 @@ final class ShowProgressSortTest: XCTestCase { let date2 = Date(timeIntervalSince1970: 10) let date3: Date? = nil - let episode1 = EpisodeEntity(ids: episodeIds, showIds: showIds, title: "", overview: nil, number: 0, season: 0, firstAired: date1) - - let episode2 = EpisodeEntity(ids: episodeIds, showIds: showIds, title: "", overview: nil, number: 0, season: 0, firstAired: date2) - - let episode3 = EpisodeEntity(ids: episodeIds, showIds: showIds, title: "", overview: nil, number: 0, season: 0, firstAired: date3) - - let episode4 = EpisodeEntity(ids: episodeIds, showIds: showIds, title: "", overview: nil, number: 0, season: 0, firstAired: date3) - - let nextEpisode1 = WatchedEpisodeEntity(episode: episode1, lastWatched: nil) - let nextEpisode2 = WatchedEpisodeEntity(episode: episode2, lastWatched: nil) - let nextEpisode3 = WatchedEpisodeEntity(episode: episode3, lastWatched: nil) - let nextEpisode4 = WatchedEpisodeEntity(episode: episode4, lastWatched: nil) - - let watchedShow1 = WatchedShowEntity(show: show, aired: 10, completed: 10, nextEpisode: nextEpisode1, lastWatched: date1, seasons: seasons) - - let watchedShow2 = WatchedShowEntity(show: show, aired: 10, completed: 5, nextEpisode: nextEpisode2, lastWatched: date2, seasons: seasons) - - let watchedShow3 = WatchedShowEntity(show: show, aired: 3, completed: 1, nextEpisode: nextEpisode3, lastWatched: date3, seasons: seasons) - - let watchedShow4 = WatchedShowEntity(show: show, aired: 3, completed: 3, nextEpisode: nextEpisode4, lastWatched: date3, seasons: seasons) + let episodeEntityAired = { date in + EpisodeEntity(ids: episodeIds, + showIds: showIds, + title: "", + overview: nil, + number: 0, + season: 0, + firstAired: date, + absoluteNumber: 1) + } + + let episode1 = episodeEntityAired(date1) + let episode2 = episodeEntityAired(date2) + let episode3 = episodeEntityAired(date3) + let episode4 = episodeEntityAired(date3) + + let watchedEpisode = { episode in + WatchedEpisodeEntity(episode: episode, lastWatched: nil) + } + + let nextEpisode1 = watchedEpisode(episode1) + let nextEpisode2 = watchedEpisode(episode2) + let nextEpisode3 = watchedEpisode(episode3) + let nextEpisode4 = watchedEpisode(episode4) + + let watchedShow = { nextEpisode, lastWatched in + WatchedShowEntity(show: show, + aired: 10, + completed: 10, + nextEpisode: nextEpisode, + lastWatched: lastWatched, + seasons: seasons) + } + + let watchedShow1 = watchedShow(nextEpisode1, date1) + let watchedShow2 = watchedShow(nextEpisode2, date2) + let watchedShow3 = watchedShow(nextEpisode3, date3) + let watchedShow4 = watchedShow(nextEpisode4, date3) let shows = [watchedShow2, watchedShow4, watchedShow1, watchedShow3] diff --git a/CouchTrackerCoreTests/Shows/Progress/ShowsProgressMocks.swift b/CouchTrackerCoreTests/Shows/Progress/ShowsProgressMocks.swift index f2164364..ec61ff12 100644 --- a/CouchTrackerCoreTests/Shows/Progress/ShowsProgressMocks.swift +++ b/CouchTrackerCoreTests/Shows/Progress/ShowsProgressMocks.swift @@ -102,7 +102,8 @@ enum ShowsProgressMocks { overview: "Ned Stark, Lord of Winterfell learns that his mentor, Jon Arryn, has died and that King Robert is on his way north to offer Ned Arryn’s position as the King’s Hand. Across the Narrow Sea in Pentos, Viserys Targaryen plans to wed his sister Daenerys to the nomadic Dothraki warrior leader, Khal Drogo to forge an alliance to take the throne.", number: 1, season: 1, - firstAired: aired) + firstAired: aired, + absoluteNumber: 1) } static func mockWatchedShowEntityWithoutNextEpisodeDate() -> WatchedShowEntity { @@ -118,7 +119,19 @@ enum ShowsProgressMocks { } final class EmptyShowsProgressInteractorMock: ShowsProgressInteractor { - var listState: ShowProgressListState = ShowProgressListState.initialState + func toggleDirection() -> Completable { + return Completable.empty() + } + + func change(sort _: ShowProgressSort, filter _: ShowProgressFilter) -> Completable { + return Completable.empty() + } + + var listState: Observable { + return Observable.just(ShowProgressListState.initialState) + } + + func newListState(listState _: ShowProgressListState) {} func fetchWatchedShowsProgress() -> Observable { return Observable.just(WatchedShowEntitiesState.available(shows: [WatchedShowEntity]())) @@ -126,7 +139,17 @@ enum ShowsProgressMocks { } final class DelayEmptyShowsProgressInteractorMock: ShowsProgressInteractor { - var listState: ShowProgressListState = ShowProgressListState.initialState + func toggleDirection() -> Completable { + return Completable.empty() + } + + func change(sort _: ShowProgressSort, filter _: ShowProgressFilter) -> Completable { + return Completable.empty() + } + + var listState: Observable { + return Observable.just(ShowProgressListState.initialState) + } private let schedulers: TestSchedulers @@ -135,18 +158,30 @@ enum ShowsProgressMocks { } func fetchWatchedShowsProgress() -> Observable { - return Observable.empty().delay(2, scheduler: schedulers.mainScheduler as! SchedulerType) + return Observable.empty().delay(2, scheduler: schedulers.mainScheduler) } + + func newListState(listState _: ShowProgressListState) {} } final class ShowsProgressInteractorMock: ShowsProgressInteractor { - var listState: ShowProgressListState = ShowProgressListState.initialState let error: Error? let entities: [WatchedShowEntity] + let initialListState: ShowProgressListState - init(entities: [WatchedShowEntity] = [WatchedShowEntity](), error: Error? = nil) { + var toggleDirectionInvokedCount = 0 + var changeInvokedCount = 0 + + var listState: Observable { + return Observable.just(initialListState) + } + + init(entities: [WatchedShowEntity] = [WatchedShowEntity](), + error: Error? = nil, + listState: ShowProgressListState = ShowProgressListState.initialState) { self.entities = entities self.error = error + initialListState = listState } func fetchWatchedShowsProgress() -> Observable { @@ -157,7 +192,15 @@ enum ShowsProgressMocks { return Observable.just(WatchedShowEntitiesState.available(shows: entities)) } - func updateListState(newState _: ShowProgressListState) {} + func toggleDirection() -> Completable { + toggleDirectionInvokedCount += 1 + return Completable.empty() + } + + func change(sort _: ShowProgressSort, filter _: ShowProgressFilter) -> Completable { + changeInvokedCount += 1 + return Completable.empty() + } } final class ShowsProgressRouterMock: ShowsProgressRouter { diff --git a/CouchTrackerCoreTests/Shows/Progress/ShowsProgressPresenterTest.swift b/CouchTrackerCoreTests/Shows/Progress/ShowsProgressPresenterTest.swift index 2fc050de..ed9ce47d 100644 --- a/CouchTrackerCoreTests/Shows/Progress/ShowsProgressPresenterTest.swift +++ b/CouchTrackerCoreTests/Shows/Progress/ShowsProgressPresenterTest.swift @@ -55,11 +55,8 @@ final class ShowsProgressDefaultPresenterTest: XCTestCase { // When _ = presenter.observeViewState().subscribe(viewStateObserver) - presenter.viewDidLoad() - // Then - let expectedViewState = [Recorded.next(0, ShowProgressViewState.notLogged), - Recorded.next(0, ShowProgressViewState.empty)] + let expectedViewState = [Recorded.next(0, ShowProgressViewState.empty)] XCTAssertEqual(viewStateObserver.events, expectedViewState) } @@ -77,15 +74,12 @@ final class ShowsProgressDefaultPresenterTest: XCTestCase { // When _ = presenter.observeViewState().subscribe(viewStateObserver) - presenter.viewDidLoad() - // Then let nonEmptyEntities = NonEmptyArray(entities.first!, Array(entities.dropFirst())) let showViewState = ShowProgressViewState.shows(entities: nonEmptyEntities, menu: ShowsProgressMenuOptions.mock) - let expectedViewState = [Recorded.next(0, ShowProgressViewState.notLogged), - Recorded.next(0, showViewState)] + let expectedViewState = [Recorded.next(0, showViewState)] XCTAssertEqual(viewStateObserver.events, expectedViewState) } @@ -110,14 +104,12 @@ final class ShowsProgressDefaultPresenterTest: XCTestCase { setupPresenter(logged: true) _ = presenter.observeViewState().subscribe(viewStateObserver) - presenter.viewDidLoad() // When appStateObservable.change(state: AppState.initialState()) // Then - let expectedViewState = [Recorded.next(0, ShowProgressViewState.notLogged), - Recorded.next(0, ShowProgressViewState.empty), + let expectedViewState = [Recorded.next(0, ShowProgressViewState.empty), Recorded.next(0, ShowProgressViewState.notLogged)] XCTAssertEqual(viewStateObserver.events, expectedViewState) @@ -134,7 +126,6 @@ final class ShowsProgressDefaultPresenterTest: XCTestCase { setupPresenter(logged: true) _ = presenter.observeViewState().subscribe(viewStateObserver) - presenter.viewDidLoad() // When presenter.select(show: entities[1]) @@ -155,11 +146,10 @@ final class ShowsProgressDefaultPresenterTest: XCTestCase { // When _ = presenter.observeViewState().subscribe(viewStateObserver) - presenter.viewDidLoad() // Then - let expectedViewState = [Recorded.next(0, ShowProgressViewState.notLogged), - Recorded.next(0, ShowProgressViewState.error(error: error))] + let expectedViewState = [Recorded.next(0, ShowProgressViewState.error(error: error)), + Recorded.completed(0)] XCTAssertEqual(viewStateObserver.events, expectedViewState) } @@ -177,11 +167,10 @@ final class ShowsProgressDefaultPresenterTest: XCTestCase { _ = presenter.observeViewState().subscribe(viewStateObserver) // When - presenter.change(sort: .releaseDate, filter: .watched) + _ = presenter.change(sort: .releaseDate, filter: .watched).subscribe() // Then - let expectedListState = ShowProgressListState(sort: .releaseDate, filter: .watched, direction: .asc) - XCTAssertEqual(expectedListState, interactor.listState) + XCTAssertEqual((interactor as! ShowsProgressMocks.ShowsProgressInteractorMock).changeInvokedCount, 1) } func testShowsProgressPresenter_whenChangeDirection_shouldNotifyInteractor() { @@ -197,11 +186,10 @@ final class ShowsProgressDefaultPresenterTest: XCTestCase { _ = presenter.observeViewState().subscribe(viewStateObserver) // When - presenter.toggleDirection() + _ = presenter.toggleDirection().subscribe() // Then - let expectedListState = ShowProgressListState(sort: .title, filter: .none, direction: .desc) - XCTAssertEqual(expectedListState, interactor.listState) + XCTAssertEqual((interactor as! ShowsProgressMocks.ShowsProgressInteractorMock).toggleDirectionInvokedCount, 1) } func testShowsProgressPresenter_whenDirectionIsDesc_emitsShowsReversed() { @@ -211,13 +199,15 @@ final class ShowsProgressDefaultPresenterTest: XCTestCase { entities.append(ShowsProgressMocks.mockWatchedShowEntityWithoutNextEpisode()) entities.append(ShowsProgressMocks.mockWatchedShowEntityWithoutNextEpisodeDate()) - interactor = ShowsProgressMocks.ShowsProgressInteractorMock(entities: entities) - interactor.listState = ShowProgressListState(sort: .title, filter: .none, direction: .desc) + let initialListState = ShowProgressListState(sort: .title, filter: .none, direction: .desc) + + interactor = ShowsProgressMocks.ShowsProgressInteractorMock(entities: entities, + error: nil, + listState: initialListState) setupPresenter(logged: true) // When _ = presenter.observeViewState().subscribe(viewStateObserver) - presenter.viewDidLoad() // Then let reversedList = entities.reversed() @@ -225,8 +215,7 @@ final class ShowsProgressDefaultPresenterTest: XCTestCase { let showViewState = ShowProgressViewState.shows(entities: nonEmptyEntities, menu: ShowsProgressMenuOptions.mock) - let expectedViewState = [Recorded.next(0, ShowProgressViewState.notLogged), - Recorded.next(0, showViewState)] + let expectedViewState = [Recorded.next(0, showViewState)] XCTAssertEqual(viewStateObserver.events, expectedViewState) } diff --git a/CouchTrackerCoreTests/Shows/Progress/ShowsProgressServiceTest.swift b/CouchTrackerCoreTests/Shows/Progress/ShowsProgressServiceTest.swift index 68730ada..fa225e00 100644 --- a/CouchTrackerCoreTests/Shows/Progress/ShowsProgressServiceTest.swift +++ b/CouchTrackerCoreTests/Shows/Progress/ShowsProgressServiceTest.swift @@ -110,7 +110,7 @@ final class ShowsProgressServiceTest: XCTestCase { // When let listState = ShowProgressListState(sort: .lastWatched, filter: .watched, direction: .asc) - interactor.listState = listState + _ = interactor.change(sort: .lastWatched, filter: .watched).subscribe() // Then XCTAssertEqual(listStateDataSource.currentStateInvokedCount, 1) diff --git a/CouchTrackerCoreTests/Trending/TrendingInteractorTest.swift b/CouchTrackerCoreTests/Trending/TrendingInteractorTest.swift index c35f0f01..a62b3e3f 100644 --- a/CouchTrackerCoreTests/Trending/TrendingInteractorTest.swift +++ b/CouchTrackerCoreTests/Trending/TrendingInteractorTest.swift @@ -107,7 +107,7 @@ final class TrendingInteractorTest: XCTestCase { scheduler.start() let expectedMovies = movies.map { trendingMovie -> TrendingMovieEntity in - return MovieEntityMapper.entity(for: trendingMovie, with: [Genre]()) + MovieEntityMapper.entity(for: trendingMovie, with: [Genre]()) } let events: [Recorded>] = [Recorded.next(0, expectedMovies), Recorded.completed(0)] diff --git a/CouchTrackerCoreTests/Trending/TrendingMocks.swift b/CouchTrackerCoreTests/Trending/TrendingMocks.swift index fbfad67c..c7635d19 100644 --- a/CouchTrackerCoreTests/Trending/TrendingMocks.swift +++ b/CouchTrackerCoreTests/Trending/TrendingMocks.swift @@ -7,16 +7,21 @@ let trendingRepositoryMock = TrendingRepositoryMock(traktProvider: createTraktPr final class TrendingViewMock: TrendingViewProtocol { var presenter: TrendingPresenter! - var invokedShowEmptyView = false + + var showEmptyViewInvokedCount = 0 + var showTrendingsViewInvokedCount = 0 + var showLoadingViewInvokedCount = 0 func showEmptyView() { - invokedShowEmptyView = true + showEmptyViewInvokedCount += 1 } - var invokedShow = false - func showTrendingsView() { - invokedShow = true + showTrendingsViewInvokedCount += 1 + } + + func showLoadingView() { + showLoadingViewInvokedCount += 1 } } diff --git a/CouchTrackerCoreTests/Trending/TrendingPresenterTest.swift b/CouchTrackerCoreTests/Trending/TrendingPresenterTest.swift index 02c4abfd..09e59a43 100644 --- a/CouchTrackerCoreTests/Trending/TrendingPresenterTest.swift +++ b/CouchTrackerCoreTests/Trending/TrendingPresenterTest.swift @@ -38,8 +38,8 @@ final class TrendingPresenterTest: XCTestCase { presenter.viewDidLoad() schedulers.start() - XCTAssertTrue(view.invokedShowEmptyView) - XCTAssertFalse(view.invokedShow) + XCTAssertEqual(view.showEmptyViewInvokedCount, 1) + XCTAssertEqual(view.showTrendingsViewInvokedCount, 0) } func testTrendingPresenter_fetchShowsSuccessWithEmptyData_andPresentNothing() { @@ -50,8 +50,8 @@ final class TrendingPresenterTest: XCTestCase { presenter.viewDidLoad() schedulers.start() - XCTAssertTrue(view.invokedShowEmptyView) - XCTAssertFalse(view.invokedShow) + XCTAssertEqual(view.showEmptyViewInvokedCount, 1) + XCTAssertEqual(view.showTrendingsViewInvokedCount, 0) XCTAssertFalse(dataSource.invokedSetViewModels) XCTAssertTrue(dataSource.viewModels.isEmpty) } @@ -96,7 +96,7 @@ final class TrendingPresenterTest: XCTestCase { return PosterViewModel(title: trendingMovie.movie.title ?? "TBA", type: type) } - XCTAssertTrue(view.invokedShow) + XCTAssertEqual(view.showTrendingsViewInvokedCount, 1) XCTAssertTrue(dataSource.invokedSetViewModels) XCTAssertEqual(dataSource.viewModels, expectedViewModel) } @@ -108,7 +108,7 @@ final class TrendingPresenterTest: XCTestCase { presenter.viewDidLoad() schedulers.start() - XCTAssertTrue(view.invokedShow) + XCTAssertEqual(view.showTrendingsViewInvokedCount, 1) } func testTrendingPresenter_fetchMoviewSuccess_andPresentNoMovies() { @@ -121,8 +121,8 @@ final class TrendingPresenterTest: XCTestCase { presenter.viewDidLoad() schedulers.start() - XCTAssertTrue(view.invokedShowEmptyView) - XCTAssertFalse(view.invokedShow) + XCTAssertEqual(view.showEmptyViewInvokedCount, 1) + XCTAssertEqual(view.showTrendingsViewInvokedCount, 0) } func testTrendingPresenter_fetchMoviesFailure_andIsCustomError() { diff --git a/CouchTrackerPersistence/Realm/Entities/EpisodeEntityRealm.swift b/CouchTrackerPersistence/Realm/Entities/EpisodeEntityRealm.swift index 43c47efe..aa4af34e 100644 --- a/CouchTrackerPersistence/Realm/Entities/EpisodeEntityRealm.swift +++ b/CouchTrackerPersistence/Realm/Entities/EpisodeEntityRealm.swift @@ -9,6 +9,7 @@ public final class EpisodeEntityRealm: Object { @objc public dynamic var number = -1 @objc public dynamic var season = -1 @objc public dynamic var firstAired: Date? + public var absoluteNumber = RealmOptional() public var ids: EpisodeIdsRealm? { get { @@ -45,6 +46,7 @@ public final class EpisodeEntityRealm: Object { lhs.overview == rhs.overview && lhs.number == rhs.number && lhs.season == rhs.season && - lhs.firstAired == rhs.firstAired + lhs.firstAired == rhs.firstAired && + lhs.absoluteNumber.value == rhs.absoluteNumber.value } } diff --git a/CouchTrackerPlayground.playground/Contents.swift b/CouchTrackerPlayground.playground/Contents.swift index 6434e0c0..4224b8d7 100644 --- a/CouchTrackerPlayground.playground/Contents.swift +++ b/CouchTrackerPlayground.playground/Contents.swift @@ -12,6 +12,225 @@ import Kingfisher import Cartography import RxSwift -//let vc = ColorsViewController() -let vc = ShowProgressViewControllerDemo() +final class DemoVC: UIViewController { + private var showView: ShowEpisodeView2 { + guard let loadingButtonView = self.view as? ShowEpisodeView2 else { + preconditionFailure("self.view should be of type ShowEpisodeView2") + } + return loadingButtonView + } + + override func loadView() { + view = ShowEpisodeView2() + } + + override func viewDidLoad() { + super.viewDidLoad() + + vikings() + + showView.didTouchOnPreview = { + print("Preview") + } + + showView.didTouchOnWatch = { + print("Eita!") +// self.showView.watchButton.isLoading = true +// +// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3.0, execute: { +// self.showView.watchButton.isLoading = false +// }) + } + } + + private func vikings() { + let posterLink = "https://image.tmdb.org/t/p/w342/mBDlsOhNOV1MkNii81aT14EYQ4S.jpg" + let backdropLink = "https://image.tmdb.org/t/p/w780/A30ZqEoDbchvE7mCZcSp6TEwB1Q.jpg" + + showView.watchButton.button.setTitle("Add to history 123", for: .normal) + + showView.posterImageView.kf.setImage(with: URL(string: posterLink)) + showView.previewImageView.kf.setImage(with: URL(string: backdropLink)) + showView.titleLabel.text = "Vikings" + showView.releaseDateLabel.text = "10/01/2011" + showView.overviewLabel.text = "History sees the Vikings as a band of bloodthirsty pirates, raiding peaceful Christian monasteries... and it's true. The vikings took no prisoners, relished cruel retribution and prided themselves as fierce warriors. But their Prowess in battle is only the start of the story. Going on the trail of the real Vikings this series reveals an extraordinary story of a people who, from the brink of destruction, built an empire reaching around a qarter of the globe. Where did they come from? How did they really live?" + } +} + +public final class ShowEpisodeView2: CouchTrackerApp.View { + public var didTouchOnPreview: (() -> Void)? + public var didTouchOnWatch: (() -> Void)? + + // Public Views + + public let posterImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = UIView.ContentMode.scaleAspectFill + imageView.clipsToBounds = true + return imageView + }() + + public let previewImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = UIView.ContentMode.scaleAspectFill + imageView.isUserInteractionEnabled = true + imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTapOnPreview))) + imageView.clipsToBounds = true + return imageView + }() + + public let titleLabel: UILabel = { + let label = UILabel() + label.font = UIFont.boldSystemFont(ofSize: 22) + label.textColor = Colors.Text.primaryTextColor + label.numberOfLines = 0 + return label + }() + + public let overviewLabel: UILabel = { + let label = UILabel() + label.textColor = Colors.Text.secondaryTextColor + label.numberOfLines = 0 + return label + }() + + public let releaseDateLabel: UILabel = { + let label = UILabel() + label.textColor = Colors.Text.secondaryTextColor + return label + }() + + public let watchedAtLabel: UILabel = { + let label = UILabel() + label.textColor = Colors.Text.secondaryTextColor + return label + }() + + public let watchButton: LoadingButton = { + let loadingButton = LoadingButton() + loadingButton.button.addTarget(self, action: #selector(didTapOnWatch), for: .touchUpInside) + return loadingButton + }() + + // Private Views + + private let scrollView: UIScrollView = { + UIScrollView() + }() + + private let posterShadowView: UIView = { + let view = UIView() + view.backgroundColor = .black + view.alpha = 0.75 + return view + }() + + private lazy var contentStackView: UIStackView = { + let subviews = [previewImageView, titleLabel, overviewLabel, + releaseDateLabel, watchedAtLabel, watchButton] + let stackView = UIStackView(arrangedSubviews: subviews) + + let spacing: CGFloat = 20 + + stackView.axis = .vertical + stackView.alignment = .fill + stackView.spacing = spacing + stackView.distribution = .equalSpacing + stackView.layoutMargins = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing) + stackView.isLayoutMarginsRelativeArrangement = true + + return stackView + }() + + // Setup + + public override func initialize() { + super.initialize() + + backgroundColor = Colors.View.background + + addSubview(posterImageView) + addSubview(posterShadowView) + + scrollView.addSubview(contentStackView) + + addSubview(scrollView) + } + + public override func installConstraints() { + super.installConstraints() + + constrain(scrollView, + contentStackView, + posterImageView, + previewImageView, + posterShadowView) { scroll, content, poster, preview, shadow in + scroll.size == scroll.superview!.size + + poster.size == poster.superview!.size + shadow.size == shadow.superview!.size + + preview.height == scroll.superview!.height * 0.27 + + content.width == content.superview!.width + content.top == content.superview!.top + content.leading == content.superview!.leading + content.bottom == content.superview!.bottom + content.trailing == content.superview!.trailing + } + } + + @objc private func didTapOnPreview() { + print("didTouchOnPreview") + didTouchOnPreview?() + } + + @objc private func didTapOnWatch() { + print("didTouchOnWatch") + didTouchOnWatch?() + } +} + +public final class LoadingButton: CouchTrackerApp.View { + public let button: UIButton = { + UIButton() + }() + + private let spinner: UIActivityIndicatorView = { + UIActivityIndicatorView(style: .white) + }() + + public var isLoading: Bool = false { + didSet { + update(loading: isLoading) + } + } + + private func update(loading: Bool) { + if loading { + spinner.startAnimating() + } else { + spinner.stopAnimating() + } + + spinner.isHidden = !loading + button.isHidden = loading + } + + public override func initialize() { + update(loading: false) + + addSubview(spinner) + addSubview(button) + } + + public override func installConstraints() { + constrain(button, spinner) { button, spinner in + button.center == button.superview!.center + spinner.center == spinner.superview!.center + } + } +} + +let vc = DemoVC() PlaygroundPage.current.liveView = vc diff --git a/Podfile b/Podfile index a71f608c..bfb5fda2 100644 --- a/Podfile +++ b/Podfile @@ -19,9 +19,9 @@ def ios_pods pod 'Kingfisher', '5.0.1' pod 'RxCocoa', RX_SWIFT_VERSION pod 'ActionSheetPicker-3.0', '2.2.0' - pod 'Tabman', '2.1.0' + pod 'Tabman', '2.1.4' pod 'Cartography', '3.1.0' - pod 'FlowKitManager', '0.6.1' + pod 'RxDataSources', '3.1.0' end def persistence_pods diff --git a/Podfile.lock b/Podfile.lock index 8f98d09b..ba4288d2 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,9 +1,9 @@ PODS: - ActionSheetPicker-3.0 (2.2.0) - Alamofire (4.8.0) - - AutoInsetter (1.4.1) + - AutoInsetter (1.5.1) - Cartography (3.1.0) - - FlowKitManager (0.6.1) + - Differentiator (3.1.0) - Kingfisher (5.0.1) - Moya/Core (12.0.1): - Alamofire (~> 4.1) @@ -13,7 +13,7 @@ PODS: - RxSwift (~> 4.0) - Nimble (7.3.1) - NonEmpty (0.1.2) - - Pageboy (3.0.1) + - Pageboy (3.0.7) - R.swift (5.0.0): - R.swift.Library (~> 5.0.0) - R.swift.Library (5.0.0) @@ -26,6 +26,10 @@ PODS: - RxAtomic (4.4.0) - RxCocoa (4.4.0): - RxSwift (~> 4.0) + - RxDataSources (3.1.0): + - Differentiator (~> 3.0) + - RxCocoa (~> 4.0) + - RxSwift (~> 4.0) - RxRealm (0.7.6): - RealmSwift (~> 3.0) - RxSwift (~> 4.0) @@ -35,9 +39,9 @@ PODS: - RxAtomic (~> 4.4) - RxSwift (~> 4.0) - SwiftLint (0.29.2) - - Tabman (2.1.0): - - AutoInsetter (~> 1.4.1) - - Pageboy (~> 3.0.1) + - Tabman (2.1.4): + - AutoInsetter (~> 1.5) + - Pageboy (~> 3.0) - TMDB (0.1.0): - Moya/RxSwift (= 12.0.1) - SwiftLint (= 0.29.2) @@ -51,18 +55,18 @@ PODS: DEPENDENCIES: - ActionSheetPicker-3.0 (= 2.2.0) - Cartography (= 3.1.0) - - FlowKitManager (= 0.6.1) - Kingfisher (= 5.0.1) - Nimble (= 7.3.1) - NonEmpty (= 0.1.2) - R.swift (= 5.0.0) - RealmSwift (= 3.7.6) - RxCocoa (= 4.4.0) + - RxDataSources (= 3.1.0) - RxRealm (= 0.7.6) - RxSwift (= 4.4.0) - RxTest (= 4.4.0) - SwiftLint (= 0.29.2) - - Tabman (= 2.1.0) + - Tabman (= 2.1.4) - TMDB (from `./vendor/TMDB-Swift`) - Trakt (from `./vendor/Trakt-Swift`) - TVDB (from `./vendor/TVDB-Swift`) @@ -73,7 +77,7 @@ SPEC REPOS: - Alamofire - AutoInsetter - Cartography - - FlowKitManager + - Differentiator - Kingfisher - Moya - Nimble @@ -86,6 +90,7 @@ SPEC REPOS: - Result - RxAtomic - RxCocoa + - RxDataSources - RxRealm - RxSwift - RxTest @@ -103,14 +108,14 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: ActionSheetPicker-3.0: d11a4c12c6aaf704b8a3f56d179198c99f3e41e6 Alamofire: 3ec537f71edc9804815215393ae2b1a8ea33a844 - AutoInsetter: d0eafc611d686f83f925ded729084cf916a39863 + AutoInsetter: 249b103e316d5a9f2c7b88193e966f59bc1aa7b2 Cartography: 1988b7578871a56c036e7af17195cb2190edf18c - FlowKitManager: fd5ac628b3ed7047228eae653f5f2cde89a862f7 + Differentiator: be49ca3408f0ecfc761e4c7763d20c62be01b9ad Kingfisher: 4f771421b9208185217550528a06fd48756cb7c2 Moya: cf730b3cd9e005401ef37a85143aa141a12fd38f Nimble: 04f732da099ea4d153122aec8c2a88fd0c7219ae NonEmpty: b1054f956f717e9f771d379485c9dd7c2bc65a74 - Pageboy: ec0170fd880ea6fa3589219edfd64b554443ff6e + Pageboy: 8210fff0db9dd487f58dc9769d5d046f2618ebe0 R.swift: f88c7d7d48a2be4dffa132dde1b3d389620310eb R.swift.Library: 0bf390e729bc10bb2c9e4fb78bd043164a7be4ff Realm: 9eaecad54712d6246d08ba34c10f354e4715d7d3 @@ -118,15 +123,16 @@ SPEC CHECKSUMS: Result: 7645bb3f50c2ce726dd0ff2fa7b6f42bbe6c3713 RxAtomic: eacf60db868c96bfd63320e28619fe29c179656f RxCocoa: df63ebf7b9a70d6b4eeea407ed5dd4efc8979749 + RxDataSources: a843bad90c29817f5923ec8163f4af2de084ceb3 RxRealm: 5379eddd74f8d617ca7681d1f8d144af25b432b0 RxSwift: 5976ecd04fc2fefd648827c23de5e11157faa973 RxTest: 19d03286bdc0a3aaea5d61d4cde31fdf4bb8a5ba SwiftLint: 47df60bdea6e7e902b193b6596db8683aafd86a3 - Tabman: 1903b01c88a728895ca1bd1f69d1e842e2ca8b1a + Tabman: f8a6a1967312077334631d2f67010fe905a545f1 TMDB: c898c13c09362e35225a92badb51a406b692e844 Trakt: 98bf8f1ef173603deff3a4c0eeda818522529bb6 TVDB: 02147961f77ddc25989b72bff5746e3da78ffb3d -PODFILE CHECKSUM: 26e27882d587f0ea03bbdae8f6d1c9c0a371c532 +PODFILE CHECKSUM: 473cb1a0a9d806763393bbbcb4b9d58a9561a16c COCOAPODS: 1.5.3 From c4876677df2942164684eb75ba3e16104d4faa86 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Thu, 18 Apr 2019 10:36:22 -0300 Subject: [PATCH 10/25] Bump Fastlane --- Gemfile | 2 +- Gemfile.lock | 52 +++++++++++++++++++++++------------------------ fastlane/Fastfile | 2 +- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Gemfile b/Gemfile index eaabfaec..56f5978f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source 'https://rubygems.org' gem 'cocoapods', '1.5.3' -gem 'fastlane', '2.112.0' +gem 'fastlane', '2.120.0' gem 'slather', '2.4.6' diff --git a/Gemfile.lock b/Gemfile.lock index 6a52c76f..5e52fcce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,12 +2,12 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (2.3.6) - activesupport (4.2.11) + activesupport (4.2.11.1) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - addressable (2.5.2) + addressable (2.6.0) public_suffix (>= 2.0.2, < 4.0) atomos (0.1.3) babosa (1.0.2) @@ -36,12 +36,12 @@ GEM activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) + cocoapods-deintegrate (1.0.4) cocoapods-downloader (1.2.2) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) + cocoapods-stats (1.1.0) cocoapods-trunk (1.3.1) nap (>= 0.8, < 2.0) netrc (~> 0.11) @@ -50,33 +50,33 @@ GEM colored2 (3.1.2) commander-fastlane (4.4.6) highline (~> 1.7.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.5) declarative (0.0.10) declarative-option (0.1.0) digest-crc (0.4.1) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - dotenv (2.5.0) - emoji_regex (0.1.1) + dotenv (2.7.2) + emoji_regex (1.0.1) escape (0.0.4) - excon (0.62.0) + excon (0.64.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) faraday (>= 0.7.4) http-cookie (~> 1.0.0) - faraday_middleware (0.12.2) + faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) fastimage (2.1.5) - fastlane (2.112.0) + fastlane (2.120.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) colored commander-fastlane (>= 4.4.6, < 5.0.0) dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (~> 0.1) + emoji_regex (>= 0.1, < 2.0) excon (>= 0.45.0, < 1.0.0) faraday (~> 0.9) faraday-cookie_jar (~> 0.0.6) @@ -97,12 +97,12 @@ GEM security (= 0.1.3) simctl (~> 1.6.3) slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 1.6.2, < 2.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (>= 1.4.5, < 2.0.0) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) - xcodeproj (>= 1.6.0, < 2.0.0) + xcodeproj (>= 1.8.1, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) fourflusher (2.0.1) @@ -116,15 +116,15 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.9) - google-cloud-core (1.2.7) + google-cloud-core (1.3.0) google-cloud-env (~> 1.0) google-cloud-env (1.0.5) faraday (~> 0.11) - google-cloud-storage (1.15.0) + google-cloud-storage (1.16.0) digest-crc (~> 0.4) google-api-client (~> 0.23) google-cloud-core (~> 1.2) - googleauth (~> 0.6.2) + googleauth (>= 0.6.2, < 0.10.0) googleauth (0.6.7) faraday (~> 0.12) jwt (>= 1.4, < 3.0) @@ -138,12 +138,12 @@ GEM httpclient (2.8.3) i18n (0.9.5) concurrent-ruby (~> 1.0) - json (2.1.0) + json (2.2.0) jwt (2.1.0) memoist (0.16.0) mime-types (3.2.2) mime-types-data (~> 3.2015) - mime-types-data (3.2018.0812) + mime-types-data (3.2019.0331) mini_magick (4.5.1) mini_portile2 (2.3.0) minitest (5.11.3) @@ -166,7 +166,7 @@ GEM uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) - ruby-macho (1.3.1) + ruby-macho (1.4.0) rubyzip (1.2.2) security (0.1.3) signet (0.11.0) @@ -184,11 +184,11 @@ GEM clamp (~> 0.6) nokogiri (~> 1.8.2) xcodeproj (~> 1.4) - terminal-notifier (1.8.0) + terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - tty-cursor (0.6.0) + tty-cursor (0.6.1) tty-screen (0.6.5) tty-spinner (0.9.0) tty-cursor (~> 0.6.0) @@ -198,9 +198,9 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.4.1) + unicode-display_width (1.5.0) word_wrap (1.0.0) - xcodeproj (1.7.0) + xcodeproj (1.8.2) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -216,8 +216,8 @@ PLATFORMS DEPENDENCIES cocoapods (= 1.5.3) - fastlane (= 2.112.0) + fastlane (= 2.120.0) slather (= 2.4.6) BUNDLED WITH - 1.16.3 + 1.17.3 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index e3d16d0d..a35c449b 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,4 +1,4 @@ -fastlane_version '2.93.1' +fastlane_version '2.120.0' desc 'Run bundle exec pod install --repo-update' lane :pods do From 30b06d97e0dde3bdee4d9188677e48c00c6a314c Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Thu, 18 Apr 2019 10:36:41 -0300 Subject: [PATCH 11/25] Add bitrise.yml --- .bitrise.secrets.yml | 1 + bitrise.yml | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 .bitrise.secrets.yml create mode 100644 bitrise.yml diff --git a/.bitrise.secrets.yml b/.bitrise.secrets.yml new file mode 100644 index 00000000..7bd35869 --- /dev/null +++ b/.bitrise.secrets.yml @@ -0,0 +1 @@ +envs: [] diff --git a/bitrise.yml b/bitrise.yml new file mode 100644 index 00000000..94c7befc --- /dev/null +++ b/bitrise.yml @@ -0,0 +1,33 @@ +format_version: "7" +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +project_type: ios +app: + envs: + - FASTLANE_XCODE_LIST_TIMEOUT: "120" + - FASTLANE_WORK_DIR: . +trigger_map: +- push_branch: master + workflow: master +- pull_request_source_branch: '*' + workflow: master +workflows: + master: + steps: + - activate-ssh-key@4.0.3: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@4.0.14: {} + - certificate-and-profile-installer@1.10.1: {} + - fastlane@2.3.12: + inputs: + - lane: beta + - work_dir: $FASTLANE_WORK_DIR + pullRequests: + steps: + - activate-ssh-key@4.0.3: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@4.0.14: {} + - certificate-and-profile-installer@1.10.1: {} + - fastlane@2.3.12: + inputs: + - lane: lint + - work_dir: $FASTLANE_WORK_DIR From 278ee97f97b5a8f0681b9c11235f0cf01a5f529e Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Thu, 18 Apr 2019 10:48:16 -0300 Subject: [PATCH 12/25] Fix workflow to run on PRs --- bitrise.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitrise.yml b/bitrise.yml index 94c7befc..2c01fcbd 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -9,7 +9,7 @@ trigger_map: - push_branch: master workflow: master - pull_request_source_branch: '*' - workflow: master + workflow: pullRequests workflows: master: steps: From e7a7d0f2d4846ec02f3c46cb8929d61b3915f042 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Thu, 18 Apr 2019 10:51:59 -0300 Subject: [PATCH 13/25] Go CI. Go From b03d1a5a18e41879c7fbb8ef594817ca00da1dc7 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Thu, 18 Apr 2019 10:58:09 -0300 Subject: [PATCH 14/25] Remove fastlane working directory --- bitrise.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/bitrise.yml b/bitrise.yml index 2c01fcbd..f092aab7 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -4,7 +4,6 @@ project_type: ios app: envs: - FASTLANE_XCODE_LIST_TIMEOUT: "120" - - FASTLANE_WORK_DIR: . trigger_map: - push_branch: master workflow: master @@ -20,7 +19,6 @@ workflows: - fastlane@2.3.12: inputs: - lane: beta - - work_dir: $FASTLANE_WORK_DIR pullRequests: steps: - activate-ssh-key@4.0.3: @@ -30,4 +28,3 @@ workflows: - fastlane@2.3.12: inputs: - lane: lint - - work_dir: $FASTLANE_WORK_DIR From 7ee6d5ee69dc8ae53b0e4ed1ff766a7c4318f10f Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Thu, 18 Apr 2019 11:15:17 -0300 Subject: [PATCH 15/25] Run setup.sh before Fastlane --- bitrise.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/bitrise.yml b/bitrise.yml index f092aab7..4c8ad42f 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -16,6 +16,16 @@ workflows: run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - git-clone@4.0.14: {} - certificate-and-profile-installer@1.10.1: {} + - script@1.1.5: + inputs: + - content: |- + #!/usr/bin/env bash + # fail if any commands fails + set -e + # debug log + set -x + + bash setup.sh - fastlane@2.3.12: inputs: - lane: beta @@ -25,6 +35,16 @@ workflows: run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - git-clone@4.0.14: {} - certificate-and-profile-installer@1.10.1: {} + - script@1.1.5: + inputs: + - content: |- + #!/usr/bin/env bash + # fail if any commands fails + set -e + # debug log + set -x + + bash setup.sh - fastlane@2.3.12: inputs: - lane: lint From 4ab6149198933c70f5ebd075ca81d1001d1bd201 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Thu, 18 Apr 2019 11:30:07 -0300 Subject: [PATCH 16/25] Go CI. Go From 14153b55c6864160dca7556cfd7635690124eafd Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Fri, 19 Apr 2019 20:39:57 -0300 Subject: [PATCH 17/25] Update interpreter on all scripts to Bash --- build_phases/swiftformat | 2 +- ci/master.sh | 2 +- ci/pull_request.sh | 4 ++-- ci/travis.sh | 2 +- scripts/generate_secrets | 2 +- scripts/update_translations | 2 +- setup.sh | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build_phases/swiftformat b/build_phases/swiftformat index f7e78802..abbc3bda 100755 --- a/build_phases/swiftformat +++ b/build_phases/swiftformat @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash swiftformat --indent 2 CouchTracker/ swiftformat --indent 2 CouchTrackerApp/ diff --git a/ci/master.sh b/ci/master.sh index fea7d477..3fc4be33 100755 --- a/ci/master.sh +++ b/ci/master.sh @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash bundle exec fastlane beta diff --git a/ci/pull_request.sh b/ci/pull_request.sh index 4261682f..65c76b65 100755 --- a/ci/pull_request.sh +++ b/ci/pull_request.sh @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash -bundle exec fastlane lint \ No newline at end of file +bundle exec fastlane lint diff --git a/ci/travis.sh b/ci/travis.sh index 261fe61c..8ddb46b9 100755 --- a/ci/travis.sh +++ b/ci/travis.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then echo "Running pull_request.sh" diff --git a/scripts/generate_secrets b/scripts/generate_secrets index a170a205..eb1171b7 100755 --- a/scripts/generate_secrets +++ b/scripts/generate_secrets @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash SCRIPTS_DIR=$(cd "$(dirname "$0")" || exit 1; pwd) SECRETS_FILE="${SCRIPTS_DIR}/../CouchTrackerApp/Utils/Secrets.swift" diff --git a/scripts/update_translations b/scripts/update_translations index 08cad4fe..5469e432 100755 --- a/scripts/update_translations +++ b/scripts/update_translations @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash SCRIPTS_DIR=$(cd "$(dirname "$0")" || exit 1; pwd) COUCH_TRACKER_CORE_DIR=$(cd "$SCRIPTS_DIR"/../CouchTrackerCore || exit 1; pwd) diff --git a/setup.sh b/setup.sh index 5a89e468..0944e9f7 100755 --- a/setup.sh +++ b/setup.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash function clone_dependency() { if [ ! -d "vendor/$1" ]; then From f3125133e72ecdd53da0de55717a24456a469ef2 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Fri, 19 Apr 2019 21:01:48 -0300 Subject: [PATCH 18/25] Add CircleCI config file --- .circleci/config.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..6d7ffc67 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,33 @@ +version: 2 + +jobs: + build-and-test: + macos: # Specify the Xcode version to use + xcode: "10.1.0" + steps: + - checkout + - run: + name: Setup build dependencies + command: bash setup.sh + - run: + name: Build and run tests + command: bundle exec fastlane lint + # Collect XML test results data to show in the UI, + # and save the same XML files under test-results folder + # in the Artifacts tab + - store_test_results: + path: reports/ + - store_test_results: + path: test_output/report.xml + - store_artifacts: + path: /tmp/test-results + destination: scan-test-results + - store_artifacts: + path: ~/Library/Logs/scan + destination: scan-logs + +workflows: + version: 2 + build-and-test: + jobs: + - build-and-test From cca8e4101d372f0b871e89bc092a555953124423 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Sat, 20 Apr 2019 12:31:00 -0300 Subject: [PATCH 19/25] Add cache to CircleCI --- .circleci/config.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6d7ffc67..bac54abf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,14 +1,31 @@ version: 2 +defaults: &defaults + macos: + xcode: '10.1.0' + parallelism: 1 + environment: + LANG: en_US.UTF-8 + jobs: build-and-test: - macos: # Specify the Xcode version to use - xcode: "10.1.0" steps: - checkout + - restore_cache: + key: 1-gems-{{ checksum "Gemfile.lock" }} + - restore_cache: + key: 1-pods-{{ checksum "Podfile.lock" }} - run: name: Setup build dependencies command: bash setup.sh + - save_cache: + key: 1-gems-{{ checksum "Gemfile.lock" }} + paths: + - vendor/bundle + - save_cache: + key: 1-pods-{{ checksum "Podfile.lock" }} + paths: + - Pods - run: name: Build and run tests command: bundle exec fastlane lint From 5ab0385488b668d915a48b586c1a6a9d98794a3b Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Sat, 20 Apr 2019 12:33:41 -0300 Subject: [PATCH 20/25] Setup Environment inside jobs --- .circleci/config.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bac54abf..1548ebc7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,14 +1,11 @@ version: 2 -defaults: &defaults - macos: - xcode: '10.1.0' - parallelism: 1 - environment: - LANG: en_US.UTF-8 - jobs: build-and-test: + macos: + xcode: '10.1.0' + environment: + LANG: en_US.UTF-8 steps: - checkout - restore_cache: From a5a1e6a661d837fd258808988e0a8f1943b2ca08 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Mon, 29 Apr 2019 21:02:10 -0300 Subject: [PATCH 21/25] Configure fastlane match --- CouchTracker.xcodeproj/project.pbxproj | 12 ++++++------ fastlane/Fastfile | 5 ++++- fastlane/Matchfile | 13 +++++++++++++ 3 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 fastlane/Matchfile diff --git a/CouchTracker.xcodeproj/project.pbxproj b/CouchTracker.xcodeproj/project.pbxproj index bd0359fb..1ca48523 100644 --- a/CouchTracker.xcodeproj/project.pbxproj +++ b/CouchTracker.xcodeproj/project.pbxproj @@ -2560,7 +2560,7 @@ 647632381F44824900F277F1 = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = B5RPM7SE3L; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; SystemCapabilities = { com.apple.BackgroundModes = { enabled = 0; @@ -4127,7 +4127,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = B5RPM7SE3L; INFOPLIST_FILE = CouchTracker/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -4136,7 +4136,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.github.pietrocaselani.couchtracker; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development io.github.pietrocaselani.couchtracker"; SWIFT_VERSION = 4.2; }; name = Debug; @@ -4147,16 +4147,16 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = B5RPM7SE3L; INFOPLIST_FILE = CouchTracker/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.github.pietrocaselani.couchtracker; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore io.github.pietrocaselani.couchtracker"; SWIFT_VERSION = 4.2; }; name = Release; diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a35c449b..ee8d0285 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -48,12 +48,15 @@ lane :beta do changelog: changelog ) + match(type: 'appstore') + sh '../scripts/generate_secrets' build_ios_app( workspace: 'CouchTracker.xcworkspace', scheme: 'CouchTracker', - configuration: 'Release' + configuration: 'Release', + export_method: 'app-store' ) upload_to_testflight( diff --git a/fastlane/Matchfile b/fastlane/Matchfile new file mode 100644 index 00000000..d4f2966d --- /dev/null +++ b/fastlane/Matchfile @@ -0,0 +1,13 @@ +git_url(ENV['MATCH_GIT_URL']) + +storage_mode('git') + +type('appstore') # The default type, can be: appstore, adhoc, enterprise or development + +app_identifier([ENV['APP_IDENTIFIER']]) +username(ENV['APPLE_ID']) # Your Apple Developer Portal username + +# For all available options run `fastlane match --help` +# Remove the # in the beginning of the line to enable the other options + +# The docs are available on https://docs.fastlane.tools/actions/match From b85f41381ad1dc5aed539aa8297a3e63785ddc21 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Mon, 29 Apr 2019 21:08:17 -0300 Subject: [PATCH 22/25] Add flow to master branch --- .circleci/config.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1548ebc7..35375426 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,9 +39,40 @@ jobs: - store_artifacts: path: ~/Library/Logs/scan destination: scan-logs + build-beta-release: + macos: + xcode: '10.1.0' + environment: + LANG: en_US.UTF-8 + steps: + - checkout + - restore_cache: + key: 1-gems-{{ checksum "Gemfile.lock" }} + - restore_cache: + key: 1-pods-{{ checksum "Podfile.lock" }} + - run: + name: Setup build dependencies + command: bash setup.sh + - save_cache: + key: 1-gems-{{ checksum "Gemfile.lock" }} + paths: + - vendor/bundle + - save_cache: + key: 1-pods-{{ checksum "Podfile.lock" }} + paths: + - Pods + - run: + name: Release to Test Flight + command: bundle exec fastlane beta workflows: version: 2 build-and-test: jobs: - build-and-test + beta-release: + jobs: + - build-beta-release: + filters: + branches: + only: master From 8e4ab23d93d3e6431da3ed549a60b4ebc3a95b4f Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Mon, 29 Apr 2019 21:09:32 -0300 Subject: [PATCH 23/25] Delete Bitrise files --- .bitrise.secrets.yml | 1 - bitrise.yml | 50 -------------------------------------------- 2 files changed, 51 deletions(-) delete mode 100644 .bitrise.secrets.yml delete mode 100644 bitrise.yml diff --git a/.bitrise.secrets.yml b/.bitrise.secrets.yml deleted file mode 100644 index 7bd35869..00000000 --- a/.bitrise.secrets.yml +++ /dev/null @@ -1 +0,0 @@ -envs: [] diff --git a/bitrise.yml b/bitrise.yml deleted file mode 100644 index 4c8ad42f..00000000 --- a/bitrise.yml +++ /dev/null @@ -1,50 +0,0 @@ -format_version: "7" -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -project_type: ios -app: - envs: - - FASTLANE_XCODE_LIST_TIMEOUT: "120" -trigger_map: -- push_branch: master - workflow: master -- pull_request_source_branch: '*' - workflow: pullRequests -workflows: - master: - steps: - - activate-ssh-key@4.0.3: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@4.0.14: {} - - certificate-and-profile-installer@1.10.1: {} - - script@1.1.5: - inputs: - - content: |- - #!/usr/bin/env bash - # fail if any commands fails - set -e - # debug log - set -x - - bash setup.sh - - fastlane@2.3.12: - inputs: - - lane: beta - pullRequests: - steps: - - activate-ssh-key@4.0.3: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@4.0.14: {} - - certificate-and-profile-installer@1.10.1: {} - - script@1.1.5: - inputs: - - content: |- - #!/usr/bin/env bash - # fail if any commands fails - set -e - # debug log - set -x - - bash setup.sh - - fastlane@2.3.12: - inputs: - - lane: lint From e6854ef73acd7216d79b7fb005c0c854209bcd79 Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Mon, 29 Apr 2019 21:09:42 -0300 Subject: [PATCH 24/25] Delete Travis files --- .travis.yml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ac3373d6..00000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -# references: -# * http://www.objc.io/issue-6/travis-ci.html -# * https://github.com/supermarin/xcpretty#usage - -osx_image: xcode10.1 -language: objective-c -branches: - only: - - master -cache: - - bundler - - cocoapods -before_install: - - bash setup.sh -script: - - bash ci/travis.sh -after_success: - - bash <(curl -s https://codecov.io/bash) -notifications: - email: false From b869415528e56e33789cab8c235e7a3e7db80b3f Mon Sep 17 00:00:00 2001 From: Pietro Caselani Date: Mon, 29 Apr 2019 21:15:17 -0300 Subject: [PATCH 25/25] Fix CircleCI yaml syntax --- .circleci/config.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 35375426..1a2c11ac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,7 +39,7 @@ jobs: - store_artifacts: path: ~/Library/Logs/scan destination: scan-logs - build-beta-release: + beta-release: macos: xcode: '10.1.0' environment: @@ -67,12 +67,10 @@ jobs: workflows: version: 2 - build-and-test: + build-test-deploy: jobs: - build-and-test - beta-release: - jobs: - - build-beta-release: - filters: - branches: - only: master + - beta-release: + filters: + branches: + only: master