├── .gitignore
├── FunCorpSteamApp.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── swiftpm
│ └── Package.resolved
├── Modules
├── App
│ ├── App.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── App.xcscheme
│ ├── Resources
│ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── steamAuth.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── steamAuth.png
│ │ │ └── tabbar
│ │ │ │ ├── Contents.json
│ │ │ │ ├── tabbarNews.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── news.png
│ │ │ │ ├── tabbarProfile.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── profile.png
│ │ │ │ └── tabbarSessions.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── sessions.png
│ │ ├── Base.lproj
│ │ │ └── LaunchScreen.storyboard
│ │ ├── Info.plist
│ │ ├── en.lproj
│ │ │ └── Localizable.strings
│ │ └── ru.lproj
│ │ │ └── Localizable.strings
│ └── Sources
│ │ ├── initialization
│ │ ├── AppDelegate.swift
│ │ ├── AppDependency.swift
│ │ ├── AppStartPoint.swift
│ │ ├── Application.swift
│ │ └── StartPoints.swift
│ │ ├── logger
│ │ ├── ConsoleLogDestination.swift
│ │ ├── LogFileDestination.swift
│ │ ├── LogInitialization.swift
│ │ └── XcodeDebugLogDestination.swift
│ │ └── routing
│ │ └── AppRouter.swift
├── Common
│ ├── Package.swift
│ ├── README.md
│ └── Sources
│ │ ├── Localization.swift
│ │ ├── logs
│ │ ├── LogDestination.swift
│ │ ├── LogLevel.swift
│ │ ├── Logger+Levels.swift
│ │ └── Logger.swift
│ │ ├── multithreads
│ │ ├── DispatchQueue+mainSync.swift
│ │ └── FastLock.swift
│ │ ├── ui
│ │ ├── ChangeableImage.swift
│ │ └── ConstImage.swift
│ │ └── utils
│ │ ├── Getter.swift
│ │ ├── ModelsDiff.swift
│ │ ├── Notifier.swift
│ │ ├── Weak.swift
│ │ └── WeakArray.swift
├── Core
│ ├── .swiftpm
│ │ └── xcode
│ │ │ └── package.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ ├── Package.swift
│ ├── README.md
│ └── Sources
│ │ ├── CoreStartPoint.swift
│ │ ├── deeplinks
│ │ └── DeepLink.swift
│ │ ├── module_initialization
│ │ ├── CommonStartPoint.swift
│ │ ├── UIModuleName.swift
│ │ └── UIStartPoint.swift
│ │ └── routing
│ │ ├── IRouter.swift
│ │ ├── Navigator.swift
│ │ ├── RoutingParamaters.swift
│ │ └── Screen.swift
├── Domain
│ ├── Entities
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ │ ├── ServiceCompletion.swift
│ │ │ ├── ServiceError.swift
│ │ │ ├── SteamIDs.swift
│ │ │ ├── SteamLocalization.swift
│ │ │ └── StorageResult.swift
│ ├── UseCases
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ │ ├── ImageService.swift
│ │ │ ├── auth
│ │ │ ├── SteamAuthService.swift
│ │ │ └── models
│ │ │ │ └── SteamAuthErrors.swift
│ │ │ ├── games
│ │ │ ├── dota
│ │ │ │ ├── SteamDotaCompletion.swift
│ │ │ │ ├── SteamDotaService.swift
│ │ │ │ ├── SteamDotaServiceCalculator.swift
│ │ │ │ └── models
│ │ │ │ │ ├── DotaHero.swift
│ │ │ │ │ ├── DotaItem.swift
│ │ │ │ │ ├── DotaLeaverStatus.swift
│ │ │ │ │ ├── DotaLobby.swift
│ │ │ │ │ ├── DotaMatch.swift
│ │ │ │ │ ├── DotaMatchDetails.swift
│ │ │ │ │ ├── DotaSide.swift
│ │ │ │ │ ├── SteamDotaError.swift
│ │ │ │ │ └── calculated
│ │ │ │ │ ├── DotaAvgScores.swift
│ │ │ │ │ ├── DotaPopularHero.swift
│ │ │ │ │ └── DotaWinLose.swift
│ │ │ ├── game
│ │ │ │ ├── SteamAchievementService.swift
│ │ │ │ ├── SteamGameService.swift
│ │ │ │ └── models
│ │ │ │ │ ├── SteamAchievementsSummary.swift
│ │ │ │ │ ├── SteamGameInfo.swift
│ │ │ │ │ ├── SteamGameProgress.swift
│ │ │ │ │ └── SteamGameScheme.swift
│ │ │ └── sessions
│ │ │ │ ├── SteamSessionsService.swift
│ │ │ │ └── models
│ │ │ │ └── SteamSession.swift
│ │ │ └── profile
│ │ │ ├── SteamFriendsService.swift
│ │ │ ├── SteamProfileGamesService.swift
│ │ │ ├── SteamProfileService.swift
│ │ │ └── models
│ │ │ ├── SteamFriend.swift
│ │ │ ├── SteamProfile.swift
│ │ │ └── SteamProfileGameInfo.swift
│ └── UseCasesContracts
│ │ ├── .swiftpm
│ │ └── xcode
│ │ │ └── package.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ ├── auth
│ │ ├── SteamAuthNetwork.swift
│ │ └── SteamAuthStorage.swift
│ │ ├── games
│ │ ├── dota
│ │ │ ├── SteamDotaNetwork.swift
│ │ │ └── SteamDotaStorage.swift
│ │ ├── game
│ │ │ ├── SteamGameNetwork.swift
│ │ │ └── SteamGameStorage.swift
│ │ └── sessions
│ │ │ ├── SteamSessionsNetwork.swift
│ │ │ └── SteamSessionsStorage.swift
│ │ └── profile
│ │ ├── SteamProfileNetwork.swift
│ │ └── SteamProfileStorage.swift
├── Infrastructure
│ ├── Network
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ │ ├── NetworkDependency.swift
│ │ │ ├── NetworkStartPoint.swift
│ │ │ ├── auth
│ │ │ └── SteamAuthNetworkImpl.swift
│ │ │ ├── games
│ │ │ ├── dota
│ │ │ │ └── SteamDotaNetworkImpl.swift
│ │ │ ├── game
│ │ │ │ └── SteamGameNetworkImpl.swift
│ │ │ └── sessions
│ │ │ │ └── SteamSessionsNetworkImpl.swift
│ │ │ ├── profile
│ │ │ └── SteamProfileNetworkImpl.swift
│ │ │ └── root
│ │ │ ├── Int64+ToDate.swift
│ │ │ ├── NetworkSession.swift
│ │ │ ├── Request.swift
│ │ │ ├── Response.swift
│ │ │ ├── SteamLocalization+ToString.swift
│ │ │ └── SteamRequest.swift
│ ├── Storage
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ │ ├── StorageDependency.swift
│ │ │ ├── StorageStartPoint.swift
│ │ │ ├── auth
│ │ │ ├── SteamAuthData.swift
│ │ │ └── SteamAuthStorageImpl.swift
│ │ │ ├── games
│ │ │ ├── dota
│ │ │ │ ├── DotaLobby+Convert.swift
│ │ │ │ ├── DotaSide+Convert.swift
│ │ │ │ ├── SteamDotaHeroData.swift
│ │ │ │ ├── SteamDotaMatchData.swift
│ │ │ │ ├── SteamDotaMatchDetailsData.swift
│ │ │ │ └── SteamDotaStorageImpl.swift
│ │ │ ├── game
│ │ │ │ ├── SteamGameProgressData.swift
│ │ │ │ ├── SteamGameSchemeData.swift
│ │ │ │ └── SteamGameStorageImpl.swift
│ │ │ └── sessions
│ │ │ │ ├── SteamSessionData.swift
│ │ │ │ └── SteamSessionsStorageImpl.swift
│ │ │ ├── profile
│ │ │ ├── SteamFriendData.swift
│ │ │ ├── SteamProfileData.swift
│ │ │ ├── SteamProfileGameData.swift
│ │ │ └── SteamProfileStorageImpl.swift
│ │ │ └── support
│ │ │ ├── DataConverters.swift
│ │ │ ├── Realm+threadSafe.swift
│ │ │ └── TimeInterval+minutes.swift
│ └── UseCasesImpl
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ ├── UseCasesImplDependency.swift
│ │ ├── UseCasesImplStartPoint.swift
│ │ └── usecases
│ │ ├── ImageServiceImpl.swift
│ │ ├── auth
│ │ └── SteamAuthServiceImpl.swift
│ │ ├── base
│ │ └── UniversalServiceImpl.swift
│ │ ├── games
│ │ ├── dota
│ │ │ ├── SteamDotaDetailsSynchronizer.swift
│ │ │ ├── SteamDotaHistorySynchronizer.swift
│ │ │ ├── SteamDotaServiceCalculatorImpl.swift
│ │ │ ├── SteamDotaServiceImpl.swift
│ │ │ └── SteamDotaSynchonizers.swift
│ │ ├── game
│ │ │ ├── SteamAchievementServiceImpl.swift
│ │ │ └── SteamGameServiceImpl.swift
│ │ └── sessions
│ │ │ └── SteamSessionsServiceImpl.swift
│ │ └── profile
│ │ ├── SteamFriendsServiceImpl.swift
│ │ ├── SteamProfileGamesServiceImpl.swift
│ │ └── SteamProfileServiceImpl.swift
└── UIs
│ ├── AppUIComponents
│ ├── Package.swift
│ ├── README.md
│ └── Sources
│ │ ├── AppUIComponentsStartPoint.swift
│ │ ├── SteamAvatarView.swift
│ │ └── SteamGameImageView.swift
│ ├── Design
│ ├── Package.swift
│ ├── README.md
│ └── Sources
│ │ ├── Common
│ │ ├── Gradient.swift
│ │ ├── TimeInterval+String.swift
│ │ └── UIColor+Hex.swift
│ │ ├── DesignStartPoint.swift
│ │ └── Style
│ │ ├── Style.swift
│ │ ├── StyleMaker.swift
│ │ ├── StylizingView.swift
│ │ ├── StylizingViewsContainer.swift
│ │ └── consts
│ │ ├── ConstColors.swift
│ │ ├── ConstsAnimation.swift
│ │ └── ConstsFonts.swift
│ ├── Modules
│ ├── Auth
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ │ ├── AuthDependency.swift
│ │ │ ├── AuthRouter.swift
│ │ │ ├── AuthStartPoint.swift
│ │ │ └── auth
│ │ │ ├── AuthScreenPresenter.swift
│ │ │ └── AuthScreenView.swift
│ ├── GameInformation
│ │ ├── Dota
│ │ │ ├── Package.swift
│ │ │ ├── README.md
│ │ │ └── Sources
│ │ │ │ ├── DotaDependency.swift
│ │ │ │ ├── DotaRouter.swift
│ │ │ │ ├── DotaStartPoint.swift
│ │ │ │ ├── gameInfo
│ │ │ │ ├── DotaGameInfoPresenter.swift
│ │ │ │ ├── last2WeeksSummary
│ │ │ │ │ ├── Dota2WeeksDetailsViewModel.swift
│ │ │ │ │ ├── Dota2WeeksGamesCountViewModel.swift
│ │ │ │ │ ├── Dota2WeeksSummaryCell.swift
│ │ │ │ │ └── Dota2WeeksSummaryConfigurator.swift
│ │ │ │ └── lastGame
│ │ │ │ │ ├── DotaLastMatchCell.swift
│ │ │ │ │ ├── DotaLastMatchConfigurator.swift
│ │ │ │ │ └── DotaLastMatchViewModel.swift
│ │ │ │ └── statistics
│ │ │ │ ├── DotaStatisticViewModel.swift
│ │ │ │ ├── DotaStatisticsScreenPresenter.swift
│ │ │ │ └── view
│ │ │ │ ├── DotaGraphic.swift
│ │ │ │ ├── DotaGraphicHint.swift
│ │ │ │ ├── DotaStatisticCell.swift
│ │ │ │ ├── DotaStatisticsScreenView.swift
│ │ │ │ └── DotaStatisticsTableView.swift
│ │ └── GameInformation
│ │ │ ├── Package.swift
│ │ │ ├── README.md
│ │ │ └── Sources
│ │ │ ├── GameInfoDependency.swift
│ │ │ ├── GameInfoRouter.swift
│ │ │ ├── GameInfoStartPoint.swift
│ │ │ ├── custom
│ │ │ ├── CustomGameInfoPresenter.swift
│ │ │ ├── CustomGameInfoRouter.swift
│ │ │ ├── CustomGameInfoViewContract.swift
│ │ │ ├── CustomTableCellConfigurator.swift
│ │ │ └── universal
│ │ │ │ └── CustomGameInfoPresenterConfigurator.swift
│ │ │ └── gameInfo
│ │ │ ├── AchievementsSummaryViewModel.swift
│ │ │ ├── GameInfoScreen.swift
│ │ │ ├── GameInfoScreenPresenter.swift
│ │ │ ├── GameInfoViewModel.swift
│ │ │ └── view
│ │ │ ├── AchievementsSummaryCell.swift
│ │ │ ├── GameInfoCell.swift
│ │ │ ├── GameInfoScreenView.swift
│ │ │ └── GameInfoTableView.swift
│ ├── Menu
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ │ ├── MenuDependency.swift
│ │ │ ├── MenuRouter.swift
│ │ │ ├── MenuStartPoint.swift
│ │ │ └── menu
│ │ │ ├── MenuScreenPresenter.swift
│ │ │ ├── MenuScreenView.swift
│ │ │ └── MenuViewModel.swift
│ ├── News
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ │ ├── NewsDependency.swift
│ │ │ ├── NewsRouter.swift
│ │ │ ├── NewsStartPoint.swift
│ │ │ └── ribbon
│ │ │ ├── RibbonScreenPresenter.swift
│ │ │ └── view
│ │ │ └── RibbonScreenView.swift
│ ├── Profile
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ │ ├── ProfileDependency.swift
│ │ │ ├── ProfileRouter.swift
│ │ │ ├── ProfileStartPoint.swift
│ │ │ ├── friends
│ │ │ ├── FriendViewModel.swift
│ │ │ ├── FriendsScreenPresenter.swift
│ │ │ └── view
│ │ │ │ ├── FriendCell.swift
│ │ │ │ ├── FriendsScreenView.swift
│ │ │ │ └── FriendsTableView.swift
│ │ │ └── profile
│ │ │ ├── ProfileGameInfoViewModel.swift
│ │ │ ├── ProfileScreenPresenter.swift
│ │ │ ├── ProfileViewModel.swift
│ │ │ └── view
│ │ │ ├── ProfileCell.swift
│ │ │ ├── ProfileGameCell.swift
│ │ │ ├── ProfileScreenView.swift
│ │ │ └── ProfileTableView.swift
│ └── Sessions
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources
│ │ ├── SessionsDependency.swift
│ │ ├── SessionsRouter.swift
│ │ ├── SessionsStartPoint.swift
│ │ └── sessions
│ │ ├── SessionViewModel.swift
│ │ ├── SessionsScreenPresenter.swift
│ │ └── view
│ │ ├── SessionCell.swift
│ │ ├── SessionsScreenView.swift
│ │ └── SessionsTableView.swift
│ └── UIComponents
│ ├── Package.swift
│ ├── README.md
│ └── Sources
│ ├── UIComponentsStartPoint.swift
│ ├── alerts
│ └── ErrorAlert.swift
│ ├── base
│ ├── ApHapticFeedback.swift
│ ├── ApNavigationController.swift
│ ├── ApTabBarController.swift
│ ├── ApViewController.swift
│ └── table
│ │ ├── ApCollectionView.swift
│ │ ├── ApSectionTitleView.swift
│ │ ├── ApTableView.swift
│ │ └── ApTableViewCell.swift
│ ├── skeleton
│ ├── SkeletonView.swift
│ ├── SkeletonViewModel.swift
│ └── UIView+Skeleton.swift
│ └── views
│ ├── AvatarView.swift
│ ├── GradientView.swift
│ └── IdImageView.swift
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots/**/*.png
68 | fastlane/test_output
69 |
--------------------------------------------------------------------------------
/FunCorpSteamApp.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "DITranquillity",
6 | "repositoryURL": "https://github.com/ivlevAstef/DITranquillity.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "2a366d93b213d42d6b32d724f488feba996bdd53",
10 | "version": "3.9.2"
11 | }
12 | },
13 | {
14 | "package": "Realm",
15 | "repositoryURL": "https://github.com/realm/realm-cocoa.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "11d7853b750f367525b174331e72f5431e15dfcc",
19 | "version": "4.1.1"
20 | }
21 | },
22 | {
23 | "package": "RealmCore",
24 | "repositoryURL": "https://github.com/realm/realm-core",
25 | "state": {
26 | "branch": null,
27 | "revision": "d2f6573960c84ebea3e0236047b3d976f95a5d7a",
28 | "version": "5.23.6"
29 | }
30 | },
31 | {
32 | "package": "SnapKit",
33 | "repositoryURL": "https://github.com/SnapKit/SnapKit.git",
34 | "state": {
35 | "branch": null,
36 | "revision": "d458564516e5676af9c70b4f4b2a9178294f1bc6",
37 | "version": "5.0.1"
38 | }
39 | },
40 | {
41 | "package": "SwiftLazy",
42 | "repositoryURL": "https://github.com/ivlevAstef/SwiftLazy.git",
43 | "state": {
44 | "branch": null,
45 | "revision": "988bd1bc5850115a8f3ae20e4db937963b076960",
46 | "version": "1.1.5"
47 | }
48 | }
49 | ]
50 | },
51 | "version": 1
52 | }
53 |
--------------------------------------------------------------------------------
/Modules/App/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/steamAuth.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "steamAuth.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/steamAuth.imageset/steamAuth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivlevAstef/FunCorpSteamApp/b7137ea140c578322aaf2af908481e8415695293/Modules/App/Resources/Assets.xcassets/steamAuth.imageset/steamAuth.png
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/tabbar/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/tabbar/tabbarNews.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "news.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/tabbar/tabbarNews.imageset/news.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivlevAstef/FunCorpSteamApp/b7137ea140c578322aaf2af908481e8415695293/Modules/App/Resources/Assets.xcassets/tabbar/tabbarNews.imageset/news.png
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/tabbar/tabbarProfile.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "profile.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/tabbar/tabbarProfile.imageset/profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivlevAstef/FunCorpSteamApp/b7137ea140c578322aaf2af908481e8415695293/Modules/App/Resources/Assets.xcassets/tabbar/tabbarProfile.imageset/profile.png
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/tabbar/tabbarSessions.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "sessions.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Modules/App/Resources/Assets.xcassets/tabbar/tabbarSessions.imageset/sessions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivlevAstef/FunCorpSteamApp/b7137ea140c578322aaf2af908481e8415695293/Modules/App/Resources/Assets.xcassets/tabbar/tabbarSessions.imageset/sessions.png
--------------------------------------------------------------------------------
/Modules/App/Resources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Modules/App/Sources/initialization/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ApLife
4 | //
5 | // Created by Alexander Ivlev on 22/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Common
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate
14 | {
15 | var window: UIWindow?
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | AppStartPoint.configure()
19 | log.info("configuration finished")
20 | AppStartPoint.reg()
21 | log.info("registration and validate dependency finished")
22 | AppStartPoint.initialize()
23 | log.info("initialize modules finished")
24 |
25 | let window = UIWindow(frame: UIScreen.main.bounds)
26 | self.window = window
27 | AppStartPoint.app.configureWindow(window)
28 | AppStartPoint.app.start(launchOptions: launchOptions)
29 | window.makeKeyAndVisible()
30 |
31 | log.info("setup windows and started application")
32 |
33 | return true
34 | }
35 |
36 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
37 | return AppStartPoint.app.openUrl(url)
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/Modules/App/Sources/initialization/AppDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDependency.swift
3 | // ApLife
4 | //
5 | // Created by Alexander Ivlev on 22/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 | import Core
11 | import Common
12 |
13 | final class AppDependency
14 | {
15 | static func configure() {
16 | DISetting.Defaults.injectToSubviews = false
17 | DISetting.Defaults.lifeTime = .prototype
18 | DISetting.Log.level = .verbose
19 | DISetting.Log.tab = " "
20 | DISetting.Log.fun = Self.logMethod
21 | }
22 |
23 | static func reg(container: DIContainer) {
24 | container.append(framework: AppFramework.self)
25 | }
26 |
27 | static func validate(container: DIContainer) {
28 | #if DEBUG
29 | if !container.validate(checkGraphCycles: false) {
30 | log.fatal("DI graph validation failed")
31 | }
32 | #endif
33 | }
34 |
35 | private static func logMethod(level: DILogLevel, msg: String) {
36 | switch level {
37 | case .error:
38 | log.error(msg)
39 | case .warning:
40 | log.warning(msg)
41 | case .info:
42 | log.debug(msg)
43 | case .verbose:
44 | log.trace(msg)
45 | case .none:
46 | break
47 | }
48 |
49 | }
50 | }
51 |
52 | private class AppFramework: DIFramework
53 | {
54 | static func load(container: DIContainer) {
55 | container.register(Application.init)
56 | .lifetime(.perRun(.strong))
57 |
58 | container.register(AppRouter.init)
59 | .as(IRouter.self)
60 | .lifetime(.objectGraph)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Modules/App/Sources/initialization/AppStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppStartPoint.swift
3 | // ApLife
4 | //
5 | // Created by Alexander Ivlev on 23/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import DITranquillity
11 |
12 | final class AppStartPoint
13 | {
14 | static var app: Application {
15 | return container.resolve()
16 | }
17 |
18 | private static let container = DIContainer()
19 |
20 | static func configure() {
21 | LogInitialization.configure()
22 | AppDependency.configure()
23 |
24 | for startPoint in StartPoints.common + StartPoints.ui.values.map({ $0 as CommonStartPoint }) {
25 | startPoint.configure()
26 | }
27 | }
28 |
29 | static func reg() {
30 | AppDependency.reg(container: container)
31 |
32 | for startPoint in StartPoints.common + StartPoints.ui.values.map({ $0 as CommonStartPoint }) {
33 | startPoint.reg(container: container)
34 | }
35 |
36 | AppDependency.validate(container: container)
37 | }
38 |
39 | static func initialize() {
40 | for startPoint in StartPoints.common + StartPoints.ui.values.map({ $0 as CommonStartPoint }) {
41 | startPoint.initialize()
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Modules/App/Sources/initialization/Application.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Application.swift
3 | // ApLife
4 | //
5 | // Created by Alexander Ivlev on 22/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Core
11 | import UseCases
12 | import SwiftLazy
13 |
14 | // Start point
15 | final class Application
16 | {
17 | private let router: AppRouter
18 |
19 | init(router: AppRouter) {
20 | self.router = router
21 | }
22 |
23 | func configureWindow(_ window: UIWindow) {
24 | window.rootViewController = router.rootViewController
25 | }
26 |
27 | func start(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
28 | // TODO: support parse launch options
29 | router.start(parameters: RoutingParamaters())
30 | }
31 |
32 | func openUrl(_ url: URL) -> Bool {
33 | // TODO: support parse url
34 | return false
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Modules/App/Sources/initialization/StartPoints.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StartPoints.swift
3 | // ApLife
4 | //
5 | // Created by Alexander Ivlev on 24/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import Design
11 | import UIComponents
12 | import AppUIComponents
13 |
14 | import Storage
15 | import Network
16 | import UseCasesImpl
17 |
18 | import Auth
19 | import Menu
20 |
21 | import News
22 | import Profile
23 | import GameInformation
24 | import Sessions
25 |
26 | import Dota
27 |
28 | enum StartPoints
29 | {
30 | static let auth = AuthStartPoint()
31 | static let menu = MenuStartPoint()
32 |
33 | static let news = NewsStartPoint()
34 | static let profile = ProfileStartPoint()
35 | static let gameInfo = GameInfoStartPoint()
36 | static let sessions = SessionsStartPoint()
37 |
38 | // Порядок инициализации этих точек важен - всегда идем от низа к вверху
39 | static let common: [CommonStartPoint] = [
40 | CoreStartPoint(),
41 | DesignStartPoint(),
42 | UIComponentsStartPoint(),
43 | AppUIComponentsStartPoint(),
44 | StorageStartPoint(),
45 | NetworkStartPoint(),
46 | UseCasesImplStartPoint(),
47 |
48 | DotaStartPoint(),
49 | ]
50 |
51 | static let ui: [UIModuleName: UIStartPoint] = [
52 | AuthStartPoint.name: auth,
53 | MenuStartPoint.name: menu,
54 | NewsStartPoint.name: news,
55 | ProfileStartPoint.name: profile,
56 | GameInfoStartPoint.name: gameInfo,
57 | SessionsStartPoint.name: sessions,
58 | ]
59 | }
60 |
--------------------------------------------------------------------------------
/Modules/App/Sources/logger/ConsoleLogDestination.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConsoleLogDestination.swift
3 | // Logger
4 | //
5 | // Created by Alexander Ivlev on 25/02/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | final class ConsoleLogDestination: LogDestination
13 | {
14 | let format: String
15 | let limitOutputLevel: LogLevel
16 |
17 | init(format: String = " [%L] %F:%l: %m", limitOutputLevel: LogLevel = .trace) {
18 | self.format = format
19 | self.limitOutputLevel = limitOutputLevel
20 | }
21 |
22 | func process(_ msg: String, level: LogLevel) {
23 | // Не print ибо print не показывается в консоле девайса
24 | // То есть print имеет смысл только при запуске прям из xCode, а просто посмотреть логи на устройстве с помощью print нельзя
25 | NSLog(msg)
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Modules/App/Sources/logger/LogFileDestination.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogFileDestination.swift
3 | // Logger
4 | //
5 | // Created by Alexander Ivlev on 25/02/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | final class LogFileDestination: LogDestination
13 | {
14 | let format: String
15 | let limitOutputLevel: LogLevel
16 |
17 | private let fileHandler: FileHandle?
18 |
19 | init(format: String = "%Df [%s]: %m", limitOutputLevel: LogLevel = .info) {
20 | self.format = format
21 | self.limitOutputLevel = limitOutputLevel
22 | guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
23 | self.fileHandler = nil
24 | return
25 | }
26 | let dateFormatter = DateFormatter()
27 | dateFormatter.dateFormat = "yyyy_MM_dd_HH_mm_ss"
28 | let fileURL = documentsURL.appendingPathComponent("log_\(dateFormatter.string(from: Date())).log")
29 |
30 | if !FileManager.default.createFile(atPath: fileURL.path, contents: nil, attributes: nil) {
31 | self.fileHandler = nil
32 | return
33 | }
34 |
35 | self.fileHandler = try? FileHandle(forWritingTo: fileURL)
36 | }
37 |
38 | deinit {
39 | self.fileHandler?.synchronizeFile()
40 | self.fileHandler?.closeFile()
41 | }
42 |
43 | func process(_ msg: String, level: LogLevel) {
44 | guard let fileHandler = self.fileHandler, let data = "\(msg)\n".data(using: .utf8) else {
45 | return
46 | }
47 |
48 | fileHandler.write(data)
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/Modules/App/Sources/logger/LogInitialization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogInitialization.swift
3 | // ApLife
4 | //
5 | // Created by Alexander Ivlev on 02/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 |
11 | final class LogInitialization
12 | {
13 | static func configure() {
14 | #if DEBUG
15 | log.addDestination(XcodeDebugLogDestination(format: " [%e] %F:%l: %m", limitOutputLevel: .trace))
16 | #else
17 | log.addDestination(ConsoleLogDestination(format: " [%s] %F:%l: %m", limitOutputLevel: .info))
18 | log.addDestination(LogFileDestination(format: "%Df [%s]: %m", limitOutputLevel: .info))
19 | #endif
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Modules/App/Sources/logger/XcodeDebugLogDestination.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcodeDebugLogDestination.swift
3 | // Logger
4 | //
5 | // Created by Alexander Ivlev on 25/02/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | final class XcodeDebugLogDestination: LogDestination
13 | {
14 | let format: String
15 | let limitOutputLevel: LogLevel
16 |
17 | init(format: String = " [%e] %F:%l: %m", limitOutputLevel: LogLevel = .trace) {
18 | self.format = format
19 | self.limitOutputLevel = limitOutputLevel
20 | }
21 |
22 | func process(_ msg: String, level: LogLevel) {
23 | print(msg)
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/Common/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Common",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Common", targets: ["Common"]),
11 | ],
12 | dependencies: [
13 | ],
14 | targets: [
15 | .target(name: "Common", dependencies: [], path: "./Sources"),
16 | ]
17 | )
18 |
--------------------------------------------------------------------------------
/Modules/Common/README.md:
--------------------------------------------------------------------------------
1 | # Common
2 |
3 | Пакет с кучей вспомогательных программных сущностей. Может переноситься без зазрения совести - ни от чего не зависит, и все сущности универсальные.
4 |
5 | Правда сюда еще логгер забрался, несмотря на то что это архитектурное решение - мне так удобней, ктомуже если не тут, то отдельной либой бы был.
6 |
--------------------------------------------------------------------------------
/Modules/Common/Sources/Localization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Localization.swift
3 | // Common
4 | //
5 | // Created by Alexander Ivlev on 28/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public let loc = Localization()
12 |
13 | /// Простецкая локализация
14 | public final class Localization
15 | {
16 | private let notFound = "!@#can_not_found_key!@#"
17 |
18 | public subscript(_ key: String) -> String {
19 | let result = NSLocalizedString(key, value: notFound, comment: key)
20 | if result == notFound {
21 | log.assert("can't found localization for key: \(key)")
22 | return key
23 | }
24 |
25 | return result
26 | }
27 |
28 | public var languageCode: String {
29 | return Locale.current.languageCode ?? "en"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Modules/Common/Sources/logs/LogDestination.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogDestination.swift
3 | // Logger
4 | //
5 | // Created by Alexander Ivlev on 22/02/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | /// Протокол от которого должны наследоваться все потоки вывода.
10 | public protocol LogDestination: class
11 | {
12 | // формат:
13 | // %Dx - вывод даты варианты:
14 | // %Dd (debug) - mm:ss.SSS
15 | // %Dn (nano) - HH:mm:ss.SSS
16 | // %Da (any) - yyyy-MM-dd HH:mm:ss.SSS
17 | // %Df (file) - MM-dd HH:mm:ss
18 | // %L - вывод уровня логирования
19 | // %s - вывод уровня логирования в коротком варианте
20 | // %e - вывод уровня логирования используя emoji
21 | // %F - вывод имени файла
22 | // %l - вывод строчки
23 | // %M - вывод метода
24 | // %m - вывод сообщения
25 | var format: String { get }
26 |
27 | /// Максимально допустимый уровень логирования - выше этого уровня логи не будут приходить в функцию process
28 | var limitOutputLevel: LogLevel { get }
29 |
30 | /// Обработчик нового сообщения от логгера. Сообщение приходит в конечном варианте - отформатированным
31 | ///
32 | /// - Parameters:
33 | /// - msg: Отформатированное сообщение
34 | /// - level: Уровень логирования для этого сообщения
35 | func process(_ msg: String, level: LogLevel)
36 | }
37 |
--------------------------------------------------------------------------------
/Modules/Common/Sources/logs/LogLevel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogLevel.swift
3 | // Logger
4 | //
5 | // Created by Alexander Ivlev on 22/02/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 |
10 | /// Уровни логирования
11 | public enum LogLevel
12 | {
13 | /// Полностью отключает логирование
14 | case none
15 |
16 | /// Критическая ошибка. После возникновения критической ошибки программа точно не может работать дальше.
17 | case fatal
18 |
19 | /// assert. По своей сути похожа на обычную ошибку, но предназначенную для дебага.
20 | /// То есть это вид ошибок которые помогают отлавливать несогласование контрактов вызова функций
21 | case assert
22 |
23 | /// Обычная Ошибка. После возникновения обычной ошибки программа может продолжить исполнение, но корректность ее работы не гарантируется.
24 | case error
25 |
26 | /// Предупреждение. После возникновения предупреждения программа продолжает корректное исполение, но ситуация в которой прозошла проблема скорей всего не будет закончена корректна.
27 | case warning
28 |
29 | /// Информационное. Данный уровень используется для написания какой либо полезной информации, но которая не является переодической.
30 | /// Более того если нужно написать много данных то сам факт наличия данных пишется на этом уровне, а данные переносятся на уровень trace.
31 | case info
32 |
33 | /// Дебаг. Эквивалент информационному сообщению, но используется исключительно для дебага.
34 | /// На этом уровне можно писать некоторые приватные данные, так как данный уровень не будет использоваться у клиетов и при выпуске.
35 | case debug
36 |
37 | /// Свалка. Данный уровень используется для чего угодно. Позволяет подробно проанализировать ситуацию, но обычно является избыточным и сильно большим.
38 | case trace
39 |
40 | internal var priority: Int {
41 | switch self {
42 | case .none: return 0
43 | case .fatal: return 100
44 | case .assert: return 150
45 | case .error: return 200
46 | case .warning: return 300
47 | case .info: return 400
48 | case .debug: return 450
49 | case .trace: return 500
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Modules/Common/Sources/multithreads/DispatchQueue+mainSync.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DispatchQueue+mainSync.swift
3 | // Common
4 | //
5 | // Created by Alexander Ivlev on 29/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension DispatchQueue
12 | {
13 | static func mainSync(execute: () -> Void) {
14 | if Thread.isMainThread {
15 | execute()
16 | } else {
17 | DispatchQueue.main.sync(execute: execute)
18 | }
19 | }
20 |
21 | static func mainSync(execute: () throws -> T) rethrows -> T {
22 | if Thread.isMainThread {
23 | return try execute()
24 | } else {
25 | return try DispatchQueue.main.sync(execute: execute)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Modules/Common/Sources/multithreads/FastLock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FastLock.swift
3 | // Common
4 | //
5 | // Created by Alexander Ivlev on 26/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public final class FastLock
12 | {
13 | private var monitor: os_unfair_lock = os_unfair_lock()
14 |
15 | public init() {
16 |
17 | }
18 |
19 | public func lock() {
20 | os_unfair_lock_lock(&monitor)
21 | }
22 |
23 | public func unlock() {
24 | os_unfair_lock_unlock(&monitor)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/Common/Sources/ui/ConstImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConstImage.swift
3 | // Common
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public final class ConstImage
12 | {
13 | public let image: UIImage?
14 |
15 | public init(image: UIImage? = nil) {
16 | self.image = image
17 | }
18 |
19 | public init(named name: String) {
20 | self.image = UIImage(named: name)
21 | log.assert(self.image != nil, "Can't initialize image by name: \(name)")
22 | }
23 |
24 | func apply(to imageView: UIImageView) {
25 | imageView.image = image
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Modules/Common/Sources/utils/ModelsDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModelsDiff.swift
3 | // Common
4 | //
5 | // Created by Alexander Ivlev on 30/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | @available(iOS 13, *)
10 | public struct ModelsDiff
11 | {
12 | public enum Change {
13 | case insert(T, at: Int)
14 | case remove(T, at: Int)
15 | }
16 | public let old: [T]
17 | public let new: [T]
18 |
19 | public let diff: [Change]
20 |
21 | public func make(old: [T], new: [T]) -> ModelsDiff {
22 | let systemDiff = new.difference(from: old)
23 | var diff: [ModelsDiff.Change] = []
24 | for systemAction in systemDiff {
25 | switch systemAction {
26 | case .insert(let offset, let element, _):
27 | diff.append(.insert(element, at: offset))
28 | case .remove(let offset, let element, _):
29 | diff.append(.remove(element, at: offset))
30 | }
31 | }
32 |
33 | return ModelsDiff(old: old, new: new, diff: diff)
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Modules/Common/Sources/utils/Weak.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Weak.swift
3 | // Common
4 | //
5 | // Created by Alexander Ivlev on 01/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public class Weak
10 | {
11 | public var value: T? {
12 | return _value as? T
13 | }
14 |
15 | private weak var _value: AnyObject?
16 |
17 | public init(_ value: T) {
18 | _value = value as AnyObject
19 | }
20 | }
21 |
22 | public class WeakRef
23 | {
24 | public private(set) weak var value: T?
25 |
26 | public init(_ value: T) {
27 | self.value = value
28 | }
29 | }
30 |
31 | extension Weak: Equatable where T: Equatable
32 | {
33 | public static func ==(_ lhs: Weak, _ rhs: Weak) -> Bool {
34 | return lhs.value == rhs.value
35 | }
36 | }
37 |
38 | extension Weak: Hashable where T: Hashable
39 | {
40 | public func hash(into hasher: inout Hasher) {
41 | hasher.combine(value)
42 | }
43 | }
44 |
45 | extension WeakRef: Equatable where T: Equatable
46 | {
47 | public static func ==(_ lhs: WeakRef, _ rhs: WeakRef) -> Bool {
48 | return lhs.value == rhs.value
49 | }
50 | }
51 |
52 | extension WeakRef: Hashable where T: Hashable
53 | {
54 | public func hash(into hasher: inout Hasher) {
55 | hasher.combine(value)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Modules/Common/Sources/utils/WeakArray.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeakArray.swift
3 | // Common
4 | //
5 | // Created by Alexander Ivlev on 14/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public struct WeakArray: Sequence
10 | {
11 | public typealias Iterator = StrongIterator
12 | public struct StrongIterator: IteratorProtocol {
13 | public typealias Element = T
14 |
15 | private let data: [T]
16 | private var index: Int = 0
17 |
18 | fileprivate init(data: [T]) {
19 | self.data = data
20 | }
21 |
22 | public mutating func next() -> T? {
23 | if index < data.count {
24 | defer { index += 1 }
25 | return data[index]
26 | }
27 | return nil
28 | }
29 | }
30 |
31 | private var data: [Weak] = []
32 |
33 | public init() {
34 |
35 | }
36 |
37 | public mutating func append(_ object: T) {
38 | data.removeAll(where: { $0.value == nil })
39 | data.append(Weak(object))
40 | }
41 |
42 | public mutating func remove(_ object: T) {
43 | data.removeAll(where: { $0.value as AnyObject === object as AnyObject || $0.value == nil })
44 | }
45 |
46 | public func makeIterator() -> Self.Iterator {
47 | return StrongIterator(data: data.compactMap { $0.value })
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Modules/Core/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Modules/Core/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Core",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Core", targets: ["Core"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../Common"),
14 | .package(url: "https://github.com/ivlevAstef/DITranquillity.git", from: "3.9.2")
15 | ],
16 | targets: [
17 | .target(name: "Core", dependencies: [
18 | .product(name: "Common"),
19 | .product(name: "DITranquillity")
20 | ], path: "./Sources"),
21 | ]
22 | )
23 |
--------------------------------------------------------------------------------
/Modules/Core/README.md:
--------------------------------------------------------------------------------
1 | # Core
2 |
3 | Сердце архитектуры. Тут находятся все базовые классы/протоколы, без которых архитектура развалиться.
4 | Соответственно переносим только на те проекты где планируется использоваться эквивалентная архитектура.
5 |
--------------------------------------------------------------------------------
/Modules/Core/Sources/CoreStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoreStartPoint.swift
3 | // Core
4 | //
5 | // Created by Alexander Ivlev on 01/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 |
10 | import UIKit
11 | import DITranquillity
12 |
13 | public final class CoreStartPoint: CommonStartPoint
14 | {
15 | public init() {
16 |
17 | }
18 |
19 | public func configure() {
20 |
21 | }
22 |
23 | public func reg(container: DIContainer) {
24 | container.register(Navigator.init)
25 | .lifetime(.prototype)
26 | }
27 |
28 | public func initialize() {
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/Modules/Core/Sources/deeplinks/DeepLink.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeepLink.swift
3 | // Core
4 | //
5 | // Created by Alexander Ivlev on 22/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public struct DeepLink
10 | {
11 | public typealias Name = String
12 | }
13 |
--------------------------------------------------------------------------------
/Modules/Core/Sources/module_initialization/CommonStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommonStartPoint.swift
3 | // Core
4 | //
5 | // Created by Alexander Ivlev on 23/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 |
11 | public protocol CommonStartPoint
12 | {
13 | /// Pre configure module. Called before reg
14 | func configure()
15 |
16 | /// Registrate dependencies
17 | /// - Parameter container: common container for registration dependencies
18 | func reg(container: DIContainer)
19 |
20 | /// Post configure module. Called after reg
21 | func initialize()
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/Modules/Core/Sources/module_initialization/UIModuleName.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIModuleName.swift
3 | // Core
4 | //
5 | // Created by Alexander Ivlev on 30/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public enum UIModuleName {
10 | case auth
11 | case menu
12 |
13 | case news
14 | case profile
15 | case gameInfo
16 | case sessions
17 |
18 | public var deeplinkName: DeepLink.Name {
19 | switch self {
20 | case .auth: return "auth"
21 | case .menu: return "menu"
22 | case .news: return "news"
23 | case .profile: return "profile"
24 | case .gameInfo: return "gameInfo"
25 | case .sessions: return "sessions"
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Modules/Core/Sources/module_initialization/UIStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIStartPoint.swift
3 | // Core
4 | //
5 | // Created by Alexander Ivlev on 23/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public protocol UIStartPoint: CommonStartPoint
10 | {
11 | static var name: UIModuleName { get }
12 |
13 | /// check for can make and start this module for paramater
14 | /// - Parameter parameters: start parameters
15 | func isSupportOpen(with parameters: RoutingParamaters) -> Bool
16 |
17 | /// make router for startup project
18 | func makeRouter(use navigator: Navigator) -> IRouter
19 | }
20 |
--------------------------------------------------------------------------------
/Modules/Core/Sources/routing/IRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IRouter.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 22/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol IRouter
12 | {
13 | func start(parameters: RoutingParamaters)
14 | }
15 |
16 | public extension IRouter
17 | {
18 | func start() {
19 | start(parameters: RoutingParamaters())
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Modules/Core/Sources/routing/RoutingParamaters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoutingParamaters.swift
3 | // Core
4 | //
5 | // Created by Alexander Ivlev on 22/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public struct RoutingParamaters
10 | {
11 | public var isEmpty: Bool {
12 | return moduleName != nil
13 | }
14 | public let moduleName: UIModuleName?
15 | public let options: [String: String]
16 |
17 | public init() {
18 | self.moduleName = nil
19 | self.options = [:]
20 | }
21 |
22 | public init(moduleName: UIModuleName, options: [String: String] = [:]) {
23 | self.moduleName = moduleName
24 | self.options = options
25 | }
26 | }
27 |
28 | extension RoutingParamaters: Equatable
29 | {
30 | public static func ==(lhs: RoutingParamaters, rhs: RoutingParamaters) -> Bool {
31 | return lhs.moduleName == rhs.moduleName && lhs.options == rhs.options
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Modules/Core/Sources/routing/Screen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Screen.swift
3 | // Core
4 | //
5 | // Created by Alexander Ivlev on 28/09/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | private /*static*/var associatedForRetainKey: UInt8 = 0
13 | private /*static*/var associatedForRouterRetainKey: UInt8 = 0
14 |
15 | public class Screen
16 | {
17 | public let view: View
18 | public let presenter: Presenter
19 |
20 | public init(view: View, presenter: Presenter) {
21 | self.view = view
22 | self.presenter = presenter
23 |
24 | // yes it's not good, but optimization all code
25 | objc_setAssociatedObject(view, &associatedForRetainKey, presenter, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
26 | }
27 |
28 | public func setRouter(_ router: IRouter) {
29 | // yes it's not good, but optimization all code
30 | objc_setAssociatedObject(view, &associatedForRouterRetainKey, router, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Modules/Domain/Entities/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Entities",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Entities", targets: ["Entities"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../Common"),
14 | ],
15 | targets: [
16 | .target(name: "Entities", dependencies: [
17 | .product(name: "Common"),
18 | ], path: "./Sources"),
19 | ]
20 | )
21 |
--------------------------------------------------------------------------------
/Modules/Domain/Entities/README.md:
--------------------------------------------------------------------------------
1 | # Entities
2 |
--------------------------------------------------------------------------------
/Modules/Domain/Entities/Sources/ServiceCompletion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ServiceCompletion.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 03/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 |
10 | public enum ServiceCompletion
11 | {
12 | // Данные взяты из базы данных
13 |
14 | case notRelevant(T)
15 | case db(T)
16 |
17 | // Данные взяты по сети
18 |
19 | case network(T)
20 | case failure(ServiceError)
21 |
22 | public var content: T? {
23 | switch self {
24 | case .notRelevant(let content):
25 | return content
26 | case .db(let content):
27 | return content
28 | case .network(let content):
29 | return content
30 | case .failure:
31 | return nil
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Modules/Domain/Entities/Sources/ServiceError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ServiceError.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public enum ServiceError: Error {
10 | case notConnection
11 | case incorrectResponse
12 | case customError(Error)
13 | case cancelled
14 | }
15 |
--------------------------------------------------------------------------------
/Modules/Domain/Entities/Sources/SteamIDs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamIDs.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 28/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public typealias SteamID = UInt64
10 | public typealias AccountID = UInt32
11 |
12 | public typealias SteamGameID = UInt32
13 |
14 | public typealias SteamAchievementID = String
15 | public typealias SteamStatID = String
16 |
17 |
18 | private let magicSteamIDMapNumber: SteamID = 76561197960265728
19 |
20 | extension SteamID {
21 | public var accountId: AccountID { AccountID(self - magicSteamIDMapNumber) }
22 | }
23 |
24 | extension AccountID {
25 | public var steamId: SteamID { SteamID(self) + magicSteamIDMapNumber }
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/Domain/Entities/Sources/SteamLocalization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamLocalization.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 24/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 |
11 | public enum SteamLocalization
12 | {
13 | case en
14 | case ru
15 |
16 | public static var current: SteamLocalization {
17 | return SteamLocalization(current: ())
18 | }
19 | }
20 |
21 | extension SteamLocalization
22 | {
23 | public init(current: Void) {
24 | switch loc.languageCode {
25 | case "en":
26 | self = .en
27 | case "ru":
28 | self = .ru
29 | default:
30 | self = .en
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Modules/Domain/Entities/Sources/StorageResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StorageResult.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 28/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public enum StorageResult: ExpressibleByNilLiteral
10 | {
11 | case none
12 | case noRelevant(Content)
13 | case done(Content)
14 |
15 | public init(nilLiteral: ()) {
16 | self = .none
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "UseCases",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "UseCases", targets: ["UseCases"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../Core"),
14 | .package(path: "../Entities"),
15 | ],
16 | targets: [
17 | .target(name: "UseCases", dependencies: [
18 | .product(name: "Core"),
19 | .product(name: "Entities"),
20 | ], path: "./Sources"),
21 | ]
22 | )
23 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/README.md:
--------------------------------------------------------------------------------
1 | # UseCases
2 |
3 | Пакет содержит сервисы - то есть протоколый нужные для обеспечения бизнес логики.
4 | Также в пакете хранятся структуры данных необходимые для поддержания этой бизнес логики.
5 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/ImageService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageService.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | public protocol ImageService: class
13 | {
14 | func fetch(url: URL, to changeableImage: ChangeableImage)
15 | }
16 |
17 | extension ImageService
18 | {
19 | public func fetch(url: URL?, to changeableImage: ChangeableImage) {
20 | if let url = url {
21 | fetch(url: url, to: changeableImage)
22 | }
23 | }
24 |
25 | public func deferredFetch(url: URL?, to changeableImage: ChangeableImage) {
26 | if let url = url {
27 | changeableImage.fetcher = { [weak self, weak changeableImage] in
28 | if let changeableImage = changeableImage {
29 | self?.fetch(url: url, to: changeableImage)
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/auth/SteamAuthService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamAuthService.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 20/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 |
11 | /// Протокол содержащий бизнес логику авторизации через стим.
12 | public protocol SteamAuthService: class
13 | {
14 | /// Залогинен ли пользователь
15 | var isLogined: Bool { get }
16 | /// Уникальный id пользователя. Пустое только если пользователь не залогинен
17 | var steamId: SteamID? { get }
18 | /// Уникальный id пользователя - используется в играх. Пустое только если пользователь не залогинен
19 | var accountId: AccountID? { get }
20 |
21 | /// Позволяет начать процесс авторизации в стим.
22 | /// - Parameter completion: Результат авторизации. В случае успеха вернется SteamID
23 | func login(completion: @escaping (Result) -> Void)
24 | /// Позволяет разлогинится из steam.
25 | /// - Parameter completion: оповещает об завершении выхода. В случае успеха вернет SteamID с которого был произведен выход
26 | func logout(completion: @escaping (Result) -> Void)
27 | }
28 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/auth/models/SteamAuthErrors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamAuthErrors.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 20/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 |
10 | public enum SteamLoginError: Error
11 | {
12 | case yourLoggedIn
13 | case userCancel
14 | case applicationIncorrectConfigured
15 | case incorrectResponse
16 | }
17 |
18 | public enum SteamLogoutError: Error
19 | {
20 | case yourLoggedOut
21 | }
22 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/SteamDotaCompletion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamDotaCompletion.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 03/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | public enum SteamDotaCompletion
13 | {
14 | case notActual(T)
15 | case actual(T)
16 | case failure(ServiceError)
17 | }
18 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/SteamDotaService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamDotaService.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 24/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | /// Позволяет получить более подробную информацию о Dota 2
13 | public protocol SteamDotaService: class
14 | {
15 | /// Возвращает идентификатор игры "Dota 2"
16 | var gameId: SteamGameID { get }
17 |
18 | /// Возвращает сколько игр было сыграно за последние две недели
19 | func matchesInLast2weeks(for accountId: AccountID, completion: @escaping (SteamDotaCompletion) -> Void)
20 |
21 | /// Возвращает информацию по последней сыгранной игре. nil если игр нет
22 | func lastMatch(for accountId: AccountID, completion: @escaping (SteamDotaCompletion) -> Void)
23 |
24 | /// Возвращает детализацию за последние две недели
25 | func detailsInLast2weeks(for accountId: AccountID, completion: @escaping (SteamDotaCompletion<[DotaMatchDetails]>) -> Void)
26 |
27 | /// Возвращает детализацию за период
28 | func detailsInPeriod(for accountId: AccountID, from: Date, to: Date, completion: @escaping (SteamDotaCompletion<[DotaMatchDetails]>) -> Void)
29 |
30 | /// Возвращает описание героя. nil если по запрашиваему id героя нет
31 | func getHero(for heroId: DotaHeroID, loc: SteamLocalization, completion: @escaping (SteamDotaCompletion) -> Void)
32 | }
33 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/SteamDotaServiceCalculator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamDotaService.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 04/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 |
11 | /// Различные функции подсчета данных
12 | public protocol SteamDotaServiceCalculator: class
13 | {
14 | func player(for accountId: AccountID, in details: DotaMatchDetails) -> DotaMatchDetails.Player?
15 |
16 | /// Подсчитывает статистику побед/поражений из переданных игр. unknown это то количество, в скольких играх не нашелся данный игрок
17 | func winLoseCount(for accountId: AccountID, details: [DotaMatchDetails]) -> DotaWinLose
18 |
19 | /// Подсчитывает статистику усредненных показателей из переданных игр.
20 | func avgScores(for accountId: AccountID, details: [DotaMatchDetails]) -> DotaAvgScores
21 | }
22 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/models/DotaHero.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaHero.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 02/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | public typealias DotaHeroID = UInt16
13 |
14 | public typealias DotaHeroesResult = Result<[DotaHero], ServiceError>
15 |
16 | public struct DotaHero
17 | {
18 | public let id: DotaHeroID
19 | public let name: String
20 |
21 | /// 205x105
22 | public let iconURL: URL?
23 | /// 256x144
24 | public let iconFullHorizontalURL: URL?
25 | /// 235x272
26 | public let iconFullVerticalURL: URL?
27 |
28 | private init() { fatalError("Not support empty initialization") }
29 | }
30 |
31 | extension DotaHero
32 | {
33 | public init(
34 | id: DotaHeroID,
35 | name: String,
36 | iconURL: URL?,
37 | iconFullHorizontalURL: URL?,
38 | iconFullVerticalURL: URL?
39 | ) {
40 | self.id = id
41 | self.name = name
42 | self.iconURL = iconURL
43 | self.iconFullHorizontalURL = iconFullHorizontalURL
44 | self.iconFullVerticalURL = iconFullVerticalURL
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/models/DotaItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaItem.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 02/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public typealias DotaItemID = Int
10 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/models/DotaLeaverStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaLeaverStatus.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 02/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public enum DotaLeaverStatus
10 | {
11 | case notLeave
12 | case disconnected
13 | case disconnectedTooLong // more 5 minutes
14 | case abandoned
15 | case afk
16 | case neverConnected
17 | case neverConnectedTooLong
18 | }
19 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/models/DotaLobby.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaLobby.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 02/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 |
10 | public enum DotaLobby
11 | {
12 | case `public`
13 | case practice
14 | case tournament
15 | case tutorial
16 | case coopWithBots
17 | case teamMatch
18 | case soloQueue
19 | case ranked
20 | case soloMid1v1
21 | }
22 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/models/DotaMatch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaMatch.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 02/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | public typealias DotaMatchID = UInt64
13 |
14 | public typealias DotaMatchHistoryResult = Result<[DotaMatch], ServiceError>
15 |
16 | public struct DotaMatch
17 | {
18 | public struct Player {
19 | /// Иногда он не приходит, а иногда приходит равным UInt32.max... для простоты все к UInt32.max свожу
20 | public let accountId: AccountID
21 | public let heroId: DotaHeroID
22 | public let side: DotaSide
23 |
24 | private init() { fatalError("Not support empty initialization") }
25 | }
26 |
27 | public let matchId: DotaMatchID
28 | public let startTime: Date
29 | public let seqNumber: Int64
30 | public let lobby: DotaLobby?
31 | public let players: [Player]
32 |
33 | private init() { fatalError("Not support empty initialization") }
34 | }
35 |
36 | extension DotaMatch
37 | {
38 | public init(
39 | matchId: DotaMatchID,
40 | startTime: Date,
41 | seqNumber: Int64,
42 | lobby: DotaLobby?,
43 | players: [Player]
44 | ) {
45 | self.matchId = matchId
46 | self.startTime = startTime
47 | self.seqNumber = seqNumber
48 | self.lobby = lobby
49 | self.players = players
50 | }
51 | }
52 |
53 | extension DotaMatch.Player
54 | {
55 | public init(
56 | accountId: AccountID,
57 | heroId: DotaHeroID,
58 | side: DotaSide
59 | ) {
60 | self.accountId = accountId
61 | self.heroId = heroId
62 | self.side = side
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/models/DotaSide.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaSide.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 02/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public enum DotaSide
10 | {
11 | case radiant
12 | case dire
13 | }
14 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/models/SteamDotaError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamDotaError.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 03/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public enum SteamDotaError: Error
12 | {
13 | /// Пользователь не разрешил просматривать данные по игре.
14 | case notAllowed
15 | }
16 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/models/calculated/DotaAvgScores.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaAvgScores.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 05/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public struct DotaAvgScores
10 | {
11 | public let kills: Double
12 | public let deaths: Double
13 | public let assists: Double
14 | public let lastHits: Double
15 | public let denies: Double
16 | /// gold per minute
17 | public let gpm: Double
18 | /// xp per minute
19 | public let xpm: Double
20 |
21 | public let level: Double
22 |
23 | private init() { fatalError("Not support empty initialization") }
24 | }
25 |
26 | extension DotaAvgScores
27 | {
28 | public init(
29 | kills: Double,
30 | deaths: Double,
31 | assists: Double,
32 | lastHits: Double,
33 | denies: Double,
34 | gpm: Double,
35 | xpm: Double,
36 | level: Double
37 | ) {
38 | self.kills = kills
39 | self.deaths = deaths
40 | self.assists = assists
41 | self.lastHits = lastHits
42 | self.denies = denies
43 | self.gpm = gpm
44 | self.xpm = xpm
45 | self.level = level
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/models/calculated/DotaPopularHero.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaPopularHero.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 02/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public enum DotaPopularHero
10 | {
11 | case one(DotaHeroID)
12 | case multi
13 | case fewGames
14 | }
15 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/dota/models/calculated/DotaWinLose.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaWinLose.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 05/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public struct DotaWinLose
10 | {
11 | public let win: Int
12 | public let lose: Int
13 | public let unknown: Int
14 |
15 | private init() { fatalError("Not support empty initialization") }
16 | }
17 |
18 | extension DotaWinLose
19 | {
20 | public init(
21 | win: Int,
22 | lose: Int,
23 | unknown: Int
24 | ) {
25 | self.win = win
26 | self.lose = lose
27 | self.unknown = unknown
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/game/SteamAchievementService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamAchievementService.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 27/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Entities
11 |
12 | /// Позволяет получить общую универсальную информацию по любой игре
13 | public protocol SteamAchievementService: class
14 | {
15 | /// Позволяет получить суммарную информацию об ачивках, и реагировать на ее изменение
16 | func getAchievementsSummaryNotifier(for gameId: SteamGameID, steamId: SteamID) -> Notifier
17 | /// Обновляет суммарную информацию об ачивках. Обновление состоит из получения схемы и игрового прогресса.
18 | func refreshAchievementsSummary(for gameId: SteamGameID, steamId: SteamID, completion: ((Bool) -> Void)?)
19 | }
20 |
21 | extension SteamAchievementService
22 | {
23 | public func refreshAchievementsSummary(for gameId: SteamGameID, steamId: SteamID) {
24 | refreshAchievementsSummary(for: gameId, steamId: steamId, completion: nil)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/game/SteamGameService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamGameService.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 24/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Entities
11 |
12 | /// Позволяет получить общую универсальную информацию по любой игре
13 | public protocol SteamGameService: class
14 | {
15 | /// Схема выдает основную информацию о внутренностях игры - список ачивок, и список показателей.
16 | /// Универсально для любой игры.
17 | /// Схема не меняется, поэтому ей всяких нотификаторов не надо.
18 | func getScheme(for gameId: SteamGameID, loc: SteamLocalization, completion: @escaping (SteamGameSchemeCompletion) -> Void)
19 |
20 | /// Позволяет получить универсальный прогресс по игре - ачивки, и некоторую универсальную статистиску
21 | func getGameProgressNotifier(for gameId: SteamGameID, steamId: SteamID) -> Notifier
22 | /// Обновляет универсальный прогресс по игре - ачивки, и некоторую универсальную статистиску
23 | func refreshGameProgress(for gameId: SteamGameID, steamId: SteamID, completion: ((Bool) -> Void)?)
24 |
25 | /// Выдает универсальную историю прогресса - то есть как менялось состояние ачивок, и универсальной статистики
26 | func getGameProgressHistory(for gameId: SteamGameID, steamId: SteamID,
27 | completion: @escaping (SteamGameProgressHistoryCompletion) -> Void)
28 | }
29 |
30 | extension SteamGameService
31 | {
32 | public func refreshGameProgress(for gameId: SteamGameID, steamId: SteamID) {
33 | refreshGameProgress(for: gameId, steamId: steamId, completion: nil)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/game/models/SteamAchievementsSummary.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamAchievementsSummary.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 27/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 |
11 | public typealias SteamAchievementsSummaryResult = Result
12 |
13 | public struct SteamAchievementsSummary {
14 | public let current: Set
15 | public let any: Set
16 | public let onlyVisible: Set
17 |
18 | public init(current: Set,
19 | any: Set,
20 | onlyVisible: Set) {
21 | self.current = current
22 | self.any = any
23 | self.onlyVisible = onlyVisible
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/game/models/SteamGameInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamGameInfo.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 24/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | public struct SteamGameInfo
13 | {
14 | public let gameId: SteamGameID
15 | public let name: String
16 | public let iconUrl: URL?
17 | public let logoUrl: URL?
18 |
19 | private init() { fatalError("Not support empty initialization") }
20 | }
21 |
22 | extension SteamGameInfo
23 | {
24 | public init(
25 | gameId: SteamGameID,
26 | name: String,
27 | iconUrl: URL?,
28 | logoUrl: URL?
29 | ) {
30 | self.gameId = gameId
31 | self.name = name
32 | self.iconUrl = iconUrl
33 | self.logoUrl = logoUrl
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/game/models/SteamGameProgress.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamGameAchievementProgress.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 24/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | public typealias SteamGameProgressResult = Result
13 | public typealias SteamGameProgressHistoryCompletion = Result<[SteamGameProgress], ServiceError>
14 |
15 | public struct SteamGameProgress: Equatable
16 | {
17 | public struct Achievement: Equatable {
18 | public let achieved: Bool
19 |
20 | private init() { fatalError("Not support empty initialization") }
21 | }
22 |
23 | public struct Stat: Equatable {
24 | public let count: Double
25 |
26 | private init() { fatalError("Not support empty initialization") }
27 | }
28 |
29 | public let gameId: SteamGameID
30 | public let achievements: [SteamAchievementID: Achievement]
31 | public let stats: [SteamStatID: Stat]
32 | /// Начальное время когда точно было такое состояние прогресса
33 | public let stateDateBegin: Date
34 | /// Конечное время когда точно было такое состояние прогресса
35 | public let stateDateEnd: Date
36 |
37 | private init() { fatalError("Not support empty initialization") }
38 |
39 | public static func ==(lhs: SteamGameProgress, rhs: SteamGameProgress) -> Bool {
40 | return lhs.gameId == rhs.gameId && lhs.achievements == rhs.achievements && lhs.stats == rhs.stats
41 | }
42 | }
43 |
44 | extension SteamGameProgress
45 | {
46 | public init(
47 | gameId: SteamGameID,
48 | achievements: [SteamAchievementID: Achievement],
49 | stats: [SteamStatID: Stat],
50 | stateDateBegin: Date,
51 | stateDateEnd: Date
52 | ) {
53 | self.gameId = gameId
54 | self.achievements = achievements
55 | self.stats = stats
56 | self.stateDateBegin = stateDateBegin
57 | self.stateDateEnd = stateDateEnd
58 | }
59 | }
60 |
61 | extension SteamGameProgress.Achievement
62 | {
63 | public init(achieved: Bool) {
64 | self.achieved = achieved
65 | }
66 | }
67 |
68 | extension SteamGameProgress.Stat
69 | {
70 | public init(count: Double) {
71 | self.count = count
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/game/models/SteamGameScheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamGameScheme.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 24/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | public typealias SteamGameSchemeResult = Result
13 | public typealias SteamGameSchemeCompletion = ServiceCompletion
14 |
15 | public struct SteamGameScheme
16 | {
17 | public struct Achivement {
18 | public let id: SteamAchievementID
19 | public let hidden: Bool
20 |
21 | public let localizedName: String
22 | public let localizedDescription: String
23 |
24 | public let iconUrl: URL?
25 | public let iconGrayUrl: URL?
26 |
27 | private init() { fatalError("Not support empty initialization") }
28 | }
29 |
30 | public struct Stat {
31 | public let id: SteamStatID
32 | public let localizedName: String
33 |
34 | private init() { fatalError("Not support empty initialization") }
35 | }
36 |
37 | public let gameId: SteamGameID
38 |
39 | public let achivements: [Achivement]
40 | public let stats: [Stat]
41 |
42 | private init() { fatalError("Not support empty initialization") }
43 | }
44 |
45 | extension SteamGameScheme
46 | {
47 | public init(
48 | gameId: SteamGameID,
49 | achivements: [Achivement],
50 | stats: [Stat]
51 | ) {
52 | self.gameId = gameId
53 | self.achivements = achivements
54 | self.stats = stats
55 | }
56 | }
57 |
58 | extension SteamGameScheme.Achivement
59 | {
60 | public init(
61 | id: String,
62 | hidden: Bool,
63 | localizedName: String,
64 | localizedDescription: String,
65 | iconUrl: URL?,
66 | iconGrayUrl: URL?
67 | ) {
68 | self.id = id
69 | self.hidden = hidden
70 | self.localizedName = localizedName
71 | self.localizedDescription = localizedDescription
72 | self.iconUrl = iconUrl
73 | self.iconGrayUrl = iconGrayUrl
74 | }
75 | }
76 |
77 | extension SteamGameScheme.Stat
78 | {
79 | public init(
80 | id: String,
81 | localizedName: String
82 | ) {
83 | self.id = id
84 | self.localizedName = localizedName
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/sessions/SteamSessionsService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamSessionsService.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Entities
11 |
12 | /// Позволяет получить информацию о последних сессиях. В реальности пока это последние игры в которые заходил игрок
13 | public protocol SteamSessionsService: class
14 | {
15 | func getSessionsNotifier(for steamId: SteamID) -> Notifier
16 |
17 | func refreshSessions(for steamId: SteamID, completion: ((Bool) -> Void)?)
18 | }
19 |
20 | extension SteamSessionsService
21 | {
22 | public func refreshSessions(for steamId: SteamID) {
23 | refreshSessions(for: steamId, completion: nil)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/games/sessions/models/SteamSession.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamSession.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | public typealias SteamSessionsResult = Result<[SteamSession], ServiceError>
13 |
14 | public struct SteamSession
15 | {
16 | public let gameInfo: SteamGameInfo
17 |
18 | public let playtimeForever: TimeInterval
19 | public let playtime2weeks: TimeInterval
20 |
21 | private init() { fatalError("Not support empty initialization") }
22 | }
23 |
24 | extension SteamSession
25 | {
26 | public init(gameInfo: SteamGameInfo,
27 | playtimeForever: TimeInterval,
28 | playtime2weeks: TimeInterval) {
29 | self.gameInfo = gameInfo
30 | self.playtimeForever = playtimeForever
31 | self.playtime2weeks = playtime2weeks
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/profile/SteamFriendsService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamFriendsService.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 30/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Entities
11 |
12 | public protocol SteamFriendsService: class
13 | {
14 | func getNotifier(for steamId: SteamID) -> Notifier
15 |
16 | func refresh(for steamId: SteamID, completion: ((Bool) -> Void)?)
17 | }
18 |
19 |
20 | extension SteamFriendsService
21 | {
22 | public func refresh(for steamId: SteamID) {
23 | refresh(for: steamId, completion: nil)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/profile/SteamProfileGamesService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamProfileGamesService.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Entities
11 |
12 | public protocol SteamProfileGamesService: class
13 | {
14 | func getGameNotifier(for steamId: SteamID, gameId: SteamGameID) -> Notifier
15 | func refreshGame(for steamId: SteamID, gameId: SteamGameID, completion: ((Bool) -> Void)?)
16 |
17 | func getGamesNotifier(for steamId: SteamID) -> Notifier
18 | func refreshGames(for steamId: SteamID, completion: ((Bool) -> Void)?)
19 | }
20 |
21 | extension SteamProfileGamesService
22 | {
23 | public func refreshGame(for steamId: SteamID, gameId: SteamGameID) {
24 | refreshGame(for: steamId, gameId: gameId, completion: nil)
25 | }
26 |
27 | public func refreshGames(for steamId: SteamID) {
28 | refreshGames(for: steamId, completion: nil)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/profile/SteamProfileService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamProfileService.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Entities
11 |
12 | public protocol SteamProfileService: class
13 | {
14 | func getNotifier(for steamId: SteamID) -> Notifier
15 |
16 | func refresh(for steamId: SteamID, completion: ((Bool) -> Void)?)
17 |
18 | func getProfile(for steamId: SteamID, completion: @escaping (SteamProfileCompletion) -> Void)
19 | }
20 |
21 | extension SteamProfileService
22 | {
23 | public func refresh(for steamId: SteamID) {
24 | refresh(for: steamId, completion: nil)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/profile/models/SteamFriend.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamFriend.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 30/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | public typealias SteamFriendsResult = Result<[SteamFriend], ServiceError>
13 |
14 | public struct SteamFriend: Equatable
15 | {
16 | public let ownerSteamId: SteamID
17 |
18 | public let steamId: SteamID
19 | public let friendSince: Date?
20 |
21 | private init() { fatalError("Not support empty initialization") }
22 | }
23 |
24 | extension SteamFriend
25 | {
26 | public init(
27 | ownerSteamId: SteamID,
28 | steamId: SteamID,
29 | friendSince: Date?
30 | ) {
31 | self.ownerSteamId = ownerSteamId
32 | self.steamId = steamId
33 | self.friendSince = friendSince
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCases/Sources/profile/models/SteamProfileGameInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamProfileGameInfo.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | public typealias SteamProfileGamesInfoResult = Result<[SteamProfileGameInfo], ServiceError>
13 | public typealias SteamProfileGameInfoResult = Result
14 |
15 | public struct SteamProfileGameInfo
16 | {
17 | public let steamId: SteamID
18 |
19 | public let playtimeForever: TimeInterval
20 | public let playtime2weeks: TimeInterval
21 |
22 | public let gameInfo: SteamGameInfo
23 |
24 | private init() { fatalError("Not support empty initialization") }
25 | }
26 |
27 | extension SteamProfileGameInfo
28 | {
29 | public init(
30 | steamId: SteamID,
31 | playtimeForever: TimeInterval,
32 | playtime2weeks: TimeInterval,
33 | gameInfo: SteamGameInfo
34 | ) {
35 | self.steamId = steamId
36 | self.playtimeForever = playtimeForever
37 | self.playtime2weeks = playtime2weeks
38 | self.gameInfo = gameInfo
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "UseCasesContracts",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "UseCasesContracts", targets: ["UseCasesContracts"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../UseCases"),
14 | .package(path: "../Entities"),
15 | ],
16 | targets: [
17 | .target(name: "UseCasesContracts", dependencies: [
18 | .product(name: "UseCases"),
19 | .product(name: "Entities"),
20 | ], path: "./Sources"),
21 | ]
22 | )
23 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/README.md:
--------------------------------------------------------------------------------
1 | # UseCasesContracts
2 | Необходимые протоколы которые нужно реализовать, для работоспособности UseCases
3 |
4 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Sources/auth/SteamAuthNetwork.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamAuthNetwork.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 22/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 |
12 | public protocol SteamAuthNetwork: class
13 | {
14 | var loginUrl: URL { get }
15 |
16 | func isRedirect(url: URL) -> Bool
17 | func parseRedirect(url: URL) -> SteamID?
18 | }
19 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Sources/auth/SteamAuthStorage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamAuthStorage.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 22/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 |
11 | public protocol SteamAuthStorage: class
12 | {
13 | var steamId: SteamID? { set get }
14 | }
15 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Sources/games/dota/SteamDotaNetwork.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamDotaNetwork.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 02/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 | import UseCases
11 |
12 | public protocol SteamDotaNetwork: class
13 | {
14 | func requestHistory(accountId: AccountID, count: UInt, heroId: Int?, from: DotaMatchID?, completion: @escaping (DotaMatchHistoryResult) -> Void)
15 |
16 | func requestDetails(matchId: DotaMatchID, completion: @escaping (DotaMatchDetailsResult) -> Void)
17 |
18 | func requestHeroes(loc: SteamLocalization, completion: @escaping (DotaHeroesResult) -> Void)
19 | }
20 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Sources/games/dota/SteamDotaStorage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamDotaStorage.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 02/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 | import UseCases
11 |
12 | public protocol SteamDotaStorage: class
13 | {
14 | func put(matches: [DotaMatch], for accountId: AccountID)
15 | func put(details: DotaMatchDetails, for accountId: AccountID)
16 | func put(heroes: [DotaHero], loc: SteamLocalization)
17 |
18 | func fetchMatches(for accountId: AccountID) -> [DotaMatch]
19 | func fetchDetails(for accountId: AccountID, matchId: DotaMatchID) -> DotaMatchDetails?
20 | func fetchDetailsList(for accountId: AccountID) -> [DotaMatchDetails]
21 | func fetchHeroes(loc: SteamLocalization) -> StorageResult<[DotaHero]>
22 | }
23 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Sources/games/game/SteamGameNetwork.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamGameNetwork.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 24/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 | import UseCases
11 |
12 | public protocol SteamGameNetwork: class
13 | {
14 | func requestScheme(by gameId: SteamGameID, loc: SteamLocalization, completion: @escaping (SteamGameSchemeResult) -> Void)
15 |
16 | func requestGameProgress(by gameId: SteamGameID, steamId: SteamID, completion: @escaping (SteamGameProgressResult) -> Void)
17 | }
18 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Sources/games/game/SteamGameStorage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamGameStorage.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 24/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 | import UseCases
11 |
12 | public protocol SteamGameStorage: class
13 | {
14 | func put(scheme: SteamGameScheme, loc: SteamLocalization)
15 | func put(gameProgress: SteamGameProgress, steamId: SteamID)
16 |
17 | func fetchScheme(by gameId: SteamGameID, loc: SteamLocalization) -> StorageResult
18 | func fetchGameProgress(by gameId: SteamGameID, steamId: SteamID) -> StorageResult
19 | func fetchGameProgressHistory(by gameId: SteamGameID, steamId: SteamID) -> [SteamGameProgress]
20 | }
21 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Sources/games/sessions/SteamSessionsNetwork.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamSessionsNetwork.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 | import UseCases
11 |
12 | public protocol SteamSessionsNetwork: class
13 | {
14 | func requestSessions(for steamId: SteamID, completion: @escaping (SteamSessionsResult) -> Void)
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Sources/games/sessions/SteamSessionsStorage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamSessionsStorage.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 | import UseCases
11 |
12 | public protocol SteamSessionsStorage: class
13 | {
14 | func put(sessions: [SteamSession], steamId: SteamID)
15 |
16 | func fetchSessions(for steamId: SteamID) -> StorageResult<[SteamSession]>
17 | }
18 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Sources/profile/SteamProfileNetwork.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamProfileNetwork.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 22/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 | import UseCases
11 |
12 | public protocol SteamProfileNetwork: class
13 | {
14 | func requestProfile(by steamId: SteamID, completion: @escaping (SteamProfileResult) -> Void)
15 |
16 | func requestFriends(for steamId: SteamID, completion: @escaping (SteamFriendsResult) -> Void)
17 |
18 | func requestGames(by steamId: SteamID, completion: @escaping (SteamProfileGamesInfoResult) -> Void)
19 |
20 | func requestGame(by steamId: SteamID, gameId: SteamGameID, completion: @escaping (SteamProfileGameInfoResult) -> Void)
21 | }
22 |
--------------------------------------------------------------------------------
/Modules/Domain/UseCasesContracts/Sources/profile/SteamProfileStorage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamProfileStorage.swift
3 | // UseCases
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Entities
11 | import UseCases
12 |
13 | public protocol SteamProfileStorage: class
14 | {
15 | func put(profile: SteamProfile)
16 | func put(friends: [SteamFriend])
17 | func put(games: [SteamProfileGameInfo])
18 | func put(game: SteamProfileGameInfo)
19 |
20 | func fetchProfile(by steamId: SteamID) -> StorageResult
21 | func fetchFriends(for steamId: SteamID) -> StorageResult<[SteamFriend]>
22 | func fetchGames(by steamId: SteamID) -> StorageResult<[SteamProfileGameInfo]>
23 | func fetchGame(by steamId: SteamID, gameId: SteamGameID) -> StorageResult
24 | }
25 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Network/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Network",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Network", targets: ["Network"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../Domain/UseCasesContracts"),
14 | .package(path: "../../Core"),
15 | .package(path: "../../Common"),
16 | ],
17 | targets: [
18 | .target(name: "Network", dependencies: [
19 | "UseCasesContracts",
20 | "Core",
21 | "Common"
22 | ], path: "./Sources"),
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Network/README.md:
--------------------------------------------------------------------------------
1 | # Network
2 |
3 | Реализация протоколов по работе с получением данных по сети.
4 | На прямую к этому пакету никто не должен обращаться - все через протоколы объявленные уровнем ниже.
5 |
6 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Network/Sources/NetworkDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkDependency.swift
3 | // Network
4 | //
5 | // Created by Alexander Ivlev on 22/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 | import UseCasesContracts
11 |
12 | final class NetworkDependency: DIFramework
13 | {
14 | static func load(container: DIContainer) {
15 | container.register(NetworkSession.init)
16 | .lifetime(.perRun(.strong))
17 |
18 | container.register(SteamAuthNetworkImpl.init)
19 | .as(SteamAuthNetwork.self)
20 | .lifetime(.prototype)
21 |
22 | container.register(SteamProfileNetworkImpl.init)
23 | .as(SteamProfileNetwork.self)
24 | .lifetime(.prototype)
25 |
26 | container.register(SteamGameNetworkImpl.init)
27 | .as(SteamGameNetwork.self)
28 | .lifetime(.prototype)
29 |
30 | container.register(SteamSessionsNetworkImpl.init)
31 | .as(SteamSessionsNetwork.self)
32 | .lifetime(.prototype)
33 |
34 | // Games
35 | container.register(SteamDotaNetworkImpl.init)
36 | .as(SteamDotaNetwork.self)
37 | .lifetime(.prototype)
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Network/Sources/NetworkStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkStartPoint.swift
3 | // Network
4 | //
5 | // Created by Alexander Ivlev on 22/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import DITranquillity
11 |
12 | public final class NetworkStartPoint: CommonStartPoint
13 | {
14 | public init() {
15 |
16 | }
17 |
18 | public func configure() {
19 |
20 | }
21 |
22 | public func reg(container: DIContainer) {
23 | container.append(framework: NetworkDependency.self)
24 | }
25 |
26 | public func initialize() {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Network/Sources/root/Int64+ToDate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Int64+ToDate.swift
3 | // Network
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Int64 {
12 | var unixTimeToDate: Date {
13 | return Date(timeIntervalSince1970: TimeInterval(self))
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Network/Sources/root/Request.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Request.swift
3 | // Network
4 | //
5 | // Created by Alexander Ivlev on 27/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol RequestInfo {
12 | var interface: String { get }
13 | var method: String { get }
14 | var version: Int { get }
15 |
16 | var fields: [String: Any] { get }
17 | }
18 |
19 | class Request: RequestInfo {
20 | let interface: String
21 | let method: String
22 | let version: Int
23 |
24 | let fields: [String: Any]
25 | let completion: (Result) -> Void
26 |
27 | init(interface: String,
28 | method: String,
29 | version: Int,
30 | fields: [String: Any] = [:],
31 | completion: @escaping (Result) -> Void) {
32 | self.interface = interface
33 | self.method = method
34 | self.version = version
35 | self.fields = fields
36 | self.completion = completion
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Network/Sources/root/Response.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Response.swift
3 | // Network
4 | //
5 | // Created by Alexander Ivlev on 22/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Response: Decodable {
12 | let response: Type
13 | }
14 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Network/Sources/root/SteamLocalization+ToString.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamLocalization+ToString.swift
3 | // Network
4 | //
5 | // Created by Alexander Ivlev on 27/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 |
11 | extension SteamLocalization
12 | {
13 | var toString: String {
14 | switch self {
15 | case .en:
16 | return "english" // or not?
17 | case .ru:
18 | return "russian"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Network/Sources/root/SteamRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamRequest.swift
3 | // Network
4 | //
5 | // Created by Alexander Ivlev on 27/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 |
11 | class SteamRequest: Request {
12 | init(interface: String,
13 | method: String,
14 | version: Int,
15 | fields: [String: Any] = [:],
16 | parse: @escaping (Content) -> Result,
17 | completion: @escaping (Result) -> Void) {
18 | super.init(interface: interface, method: method, version: version, fields: fields, completion: { result in
19 | switch result {
20 | case .success(let content):
21 | completion(parse(content))
22 | case .failure(.cancelled):
23 | completion(.failure(.cancelled))
24 | case .failure(.notConnection), .failure(.timeout):
25 | completion(.failure(.notConnection))
26 | case .failure:
27 | completion(.failure(.incorrectResponse))
28 | }
29 | })
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Storage",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Storage", targets: ["Storage"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../Domain/UseCasesContracts"),
14 | .package(path: "../../Core"),
15 | .package(path: "../../Common"),
16 | .package(url: "https://github.com/realm/realm-cocoa.git", from: "4.1.1")
17 | ],
18 | targets: [
19 | .target(name: "Storage", dependencies: [
20 | "UseCasesContracts",
21 | "Core",
22 | "Common",
23 | "RealmSwift"
24 | ], path: "./Sources"),
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/README.md:
--------------------------------------------------------------------------------
1 | # Storage
2 |
3 | Реализация протоколов по работе с хранением данных.
4 | На прямую к этому пакету никто не должен обращаться - все через протоколы объявленные уровнем ниже.
5 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/StorageDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StorageDependency.swift
3 | // Storage
4 | //
5 | // Created by Alexander Ivlev on 22/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 | import UseCasesContracts
11 | import RealmSwift
12 |
13 | final class StorageDependency: DIFramework
14 | {
15 | static func load(container: DIContainer) {
16 | /// В силу ограничений времени, запись в БД игнорирует ошибки - то есть если не удалось сохранить, то этот факт просто проигнорируется
17 | /// Благо оно всегда записывается... Да и в прочем, а что еще делать? не выдавать же пользователю ошибку...
18 | container.register {
19 | try! Realm()
20 | }.lifetime(.perRun(.strong))
21 |
22 | container.register(SteamAuthStorageImpl.init)
23 | .as(SteamAuthStorage.self)
24 | .lifetime(.prototype)
25 |
26 | container.register(SteamProfileStorageImpl.init)
27 | .as(SteamProfileStorage.self)
28 | .lifetime(.prototype)
29 |
30 | container.register(SteamGameStorageImpl.init)
31 | .as(SteamGameStorage.self)
32 | .lifetime(.prototype)
33 |
34 | container.register(SteamSessionsStorageImpl.init)
35 | .as(SteamSessionsStorage.self)
36 | .lifetime(.prototype)
37 |
38 | container.register(SteamDotaStorageImpl.init)
39 | .as(SteamDotaStorage.self)
40 | .lifetime(.prototype)
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/StorageStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StorageStartPoint.swift
3 | // Storage
4 | //
5 | // Created by Alexander Ivlev on 22/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import DITranquillity
11 |
12 | public final class StorageStartPoint: CommonStartPoint
13 | {
14 | public init() {
15 |
16 | }
17 |
18 | public func configure() {
19 |
20 | }
21 |
22 | public func reg(container: DIContainer) {
23 | container.append(framework: StorageDependency.self)
24 | }
25 |
26 | public func initialize() {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/auth/SteamAuthData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamAuthData.swift
3 | // Storage
4 | //
5 | // Created by Alexander Ivlev on 28/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RealmSwift
11 | import Entities
12 |
13 | final class SteamAuthData: Object {
14 | static let singlePrimaryKey = 1
15 | // sessionId нужен просто, чтобы сохранять один объект. Ну или в будущем возможно сделать мульти вход...
16 | @objc dynamic var _sessionId: Int = singlePrimaryKey
17 |
18 | @objc dynamic var _steamId: String = ""
19 |
20 | override static func primaryKey() -> String? {
21 | return "_sessionId"
22 | }
23 | }
24 |
25 | extension SteamAuthData {
26 | convenience init(steamId: SteamID) {
27 | self.init()
28 | _steamId = "\(steamId)"
29 | }
30 |
31 | var steamId: SteamID? {
32 | return SteamID(_steamId)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/auth/SteamAuthStorageImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamAuthStorageImpl.swift
3 | // Storage
4 | //
5 | // Created by Alexander Ivlev on 22/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RealmSwift
11 | import Entities
12 | import UseCasesContracts
13 |
14 | class SteamAuthStorageImpl: SteamAuthStorage {
15 | //var steamId: SteamID? = 76561198073561699 // my steam id
16 | //var steamId: SteamID? = 76561197960434622 // more games steam id
17 | //var steamId: SteamID? = 76561198047329396 // core2duo steam id
18 |
19 | var steamId: SteamID? {
20 | set {
21 | if let steamId = newValue {
22 | realm.threadSafeWrite { realm in
23 | let data = SteamAuthData(steamId: steamId)
24 | realm.add(data, update: .all)
25 | }
26 | } else if let object = realm.ts?.object(ofType: SteamAuthData.self, forPrimaryKey: SteamAuthData.singlePrimaryKey) {
27 | realm.threadSafeWrite { realm in
28 | realm.delete(object)
29 | }
30 | }
31 | }
32 | get {
33 | realm.ts?.object(ofType: SteamAuthData.self, forPrimaryKey: SteamAuthData.singlePrimaryKey)?.steamId
34 | }
35 | }
36 |
37 | private let realm: Realm
38 |
39 | init(realm: Realm) {
40 | self.realm = realm
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/games/dota/DotaLobby+Convert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaLobby+Convert.swift
3 | // Storage
4 | //
5 | // Created by Alexander Ivlev on 03/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UseCases
10 |
11 | extension DotaLobby {
12 | init?(_ int: Int) {
13 | switch int {
14 | case 0: self = .public
15 | case 1: self = .practice
16 | case 2: self = .tournament
17 | case 3: self = .tutorial
18 | case 4: self = .coopWithBots
19 | case 5: self = .teamMatch
20 | case 6: self = .soloQueue
21 | case 7: self = .ranked
22 | case 8: self = .soloMid1v1
23 | default:
24 | return nil
25 | }
26 | }
27 |
28 | var toInt: Int {
29 | switch self {
30 | case .`public`: return 0
31 | case .practice: return 1
32 | case .tournament: return 2
33 | case .tutorial: return 3
34 | case .coopWithBots: return 4
35 | case .teamMatch: return 5
36 | case .soloQueue: return 6
37 | case .ranked: return 7
38 | case .soloMid1v1: return 8
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/games/dota/DotaSide+Convert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaSide+Convert.swift
3 | // Storage
4 | //
5 | // Created by Alexander Ivlev on 03/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UseCases
10 |
11 | extension DotaSide {
12 | init?(_ int: Int) {
13 | switch int {
14 | case 0: self = .radiant
15 | case 1: self = .dire
16 | default:
17 | return nil
18 | }
19 | }
20 |
21 | var toInt: Int {
22 | switch self {
23 | case .radiant: return 0
24 | case .dire: return 1
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/games/dota/SteamDotaHeroData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamDotaHeroData.swift
3 | // Storage
4 | //
5 | // Created by Alexander Ivlev on 03/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RealmSwift
11 | import Entities
12 | import UseCases
13 |
14 | final class SteamDotaHeroData: Object, LimitedUpdated {
15 | static func generatePrimaryKey(heroId: DotaHeroID, loc: SteamLocalization) -> String {
16 | return "\(heroId)_\(loc)"
17 | }
18 |
19 | @objc dynamic var _primaryKey: String = ""
20 | @objc dynamic var _heroId: Int = 0
21 | @objc dynamic var _loc: String = ""
22 | @objc dynamic var _name: String = ""
23 |
24 | @objc dynamic var _iconURL: String? = nil
25 | @objc dynamic var _iconFullHorizontalURL: String? = nil
26 | @objc dynamic var _iconFullVerticalURL: String? = nil
27 |
28 | @objc dynamic var lastUpdateTime: Date = Date()
29 |
30 | override static func primaryKey() -> String? {
31 | return "_primaryKey"
32 | }
33 | }
34 |
35 |
36 | extension SteamDotaHeroData
37 | {
38 | convenience init(hero: DotaHero, loc: SteamLocalization) {
39 | self.init()
40 |
41 | _primaryKey = Self.generatePrimaryKey(heroId: hero.id, loc: loc)
42 | _heroId = Int(hero.id)
43 | _loc = "\(loc)"
44 | _name = hero.name
45 |
46 | _iconURL = hero.iconURL?.absoluteString
47 | _iconFullHorizontalURL = hero.iconFullHorizontalURL?.absoluteString
48 | _iconFullVerticalURL = hero.iconFullVerticalURL?.absoluteString
49 | }
50 |
51 | var hero: DotaHero? {
52 | return DotaHero(id: DotaHeroID(_heroId),
53 | name: _name,
54 | iconURL: _iconURL.flatMap { URL(string: $0) },
55 | iconFullHorizontalURL: _iconFullHorizontalURL.flatMap { URL(string: $0) },
56 | iconFullVerticalURL: _iconFullVerticalURL.flatMap { URL(string: $0) })
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/games/sessions/SteamSessionsStorageImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamSessionsStorageImpl.swift
3 | // Storage
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RealmSwift
11 | import Entities
12 | import UseCases
13 | import UseCasesContracts
14 |
15 | private let sessionsUpdateInterval: TimeInterval = .minutes(5)
16 |
17 | final class SteamSessionsStorageImpl: SteamSessionsStorage
18 | {
19 | private let realm: Realm
20 |
21 | init(realm: Realm) {
22 | self.realm = realm
23 | }
24 |
25 | // MARK: - sessions
26 |
27 | func put(sessions: [SteamSession], steamId: SteamID) {
28 | realm.threadSafeWrite { realm in
29 | let data = SteamSessionsData(sessions: sessions, steamId: steamId)
30 | realm.add(data, update: .all)
31 | }
32 | }
33 |
34 | func fetchSessions(for steamId: SteamID) -> StorageResult<[SteamSession]> {
35 | let primaryKey = "\(steamId)"
36 | let data = realm.ts?.object(ofType: SteamSessionsData.self, forPrimaryKey: primaryKey)
37 | return dataToResult(data, updateInterval: sessionsUpdateInterval, map: { $0.sessions })
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/profile/SteamFriendData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamFriendData.swift
3 | // Storage
4 | //
5 | // Created by Alexander Ivlev on 30/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RealmSwift
11 | import Entities
12 | import UseCases
13 |
14 | final class SteamFriendData: Object, LimitedUpdated {
15 | static func generatePrimaryKey(ownerSteamId: SteamID, steamId: SteamID) -> String {
16 | return "\(ownerSteamId)_\(steamId)"
17 | }
18 |
19 | @objc dynamic var _ownerSteamId: String = ""
20 | @objc dynamic var _steamId: String = ""
21 | @objc dynamic var _primaryKey: String = ""
22 |
23 | @objc dynamic var _friendSince: Date? = nil
24 |
25 | @objc dynamic var lastUpdateTime: Date = Date()
26 |
27 | override static func primaryKey() -> String? {
28 | return "_primaryKey"
29 | }
30 | }
31 |
32 | extension SteamFriendData {
33 | convenience init(friend: SteamFriend) {
34 | self.init()
35 |
36 | _ownerSteamId = "\(friend.ownerSteamId)"
37 | _steamId = "\(friend.steamId)"
38 | _primaryKey = Self.generatePrimaryKey(ownerSteamId: friend.ownerSteamId, steamId: friend.steamId)
39 |
40 | _friendSince = friend.friendSince
41 | }
42 |
43 | var friend: SteamFriend? {
44 | guard let ownerSteamId = SteamID(_ownerSteamId), let steamId = SteamID(_steamId) else {
45 | return nil
46 | }
47 |
48 | return SteamFriend(ownerSteamId: ownerSteamId, steamId: steamId, friendSince: _friendSince)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/support/Realm+threadSafe.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexander Ivlev on 29/11/2019.
6 | //
7 |
8 | import Foundation
9 | import RealmSwift
10 | import Common
11 |
12 | extension Realm {
13 | var ts: Realm? { threadSafe }
14 |
15 | var threadSafe: Realm? {
16 | if Thread.isMainThread {
17 | return self
18 | }
19 |
20 | guard let realm = try? Realm(configuration: self.configuration) else {
21 | log.assert("Can't make realm for thread safe")
22 | return nil
23 | }
24 | return realm
25 | }
26 |
27 | func threadSafeWrite(_ closure: (_ realm: Realm) -> Void) {
28 | guard let realm = self.threadSafe else {
29 | return
30 | }
31 | try? realm.write {
32 | closure(realm)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/Storage/Sources/support/TimeInterval+minutes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimeInterval+minutes.swift
3 | // Storage
4 | //
5 | // Created by Alexander Ivlev on 28/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension TimeInterval
12 | {
13 | static func minutes(_ minutes: Int) -> TimeInterval {
14 | return TimeInterval(minutes) * 60.0
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/UseCasesImpl/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "UseCasesImpl",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "UseCasesImpl", targets: ["UseCasesImpl"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../Domain/UseCases"),
14 | .package(path: "../../Domain/UseCasesContracts"),
15 | .package(path: "../../Domain/Entities"),
16 | .package(path: "../../Core"),
17 | .package(path: "../../Common"),
18 | ],
19 | targets: [
20 | .target(name: "UseCasesImpl", dependencies: [
21 | .product(name: "Entities"),
22 | .product(name: "UseCases"),
23 | .product(name: "UseCasesContracts"),
24 | .product(name: "Core"),
25 | .product(name: "Common")
26 | ], path: "./Sources"),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/UseCasesImpl/README.md:
--------------------------------------------------------------------------------
1 | # UseCasesImpl
2 |
3 | Реализация всех сервисов. Конечно в идеальном мире реализации нужно хранить в отдельных модулях.
4 | Но для проекта на три недели плодить кучу пакетов, с реализациями по 1-2 класса не удобно.
5 |
6 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/UseCasesImpl/Sources/UseCasesImplDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UseCasesImplDependency.swift
3 | // UseCasesImpl
4 | //
5 | // Created by Alexander Ivlev on 20/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 | import UseCases
11 |
12 | final class UseCasesImplDependency: DIFramework
13 | {
14 | static func load(container: DIContainer) {
15 | container.register(ImageServiceImpl.init)
16 | .as(ImageService.self)
17 | .lifetime(.perRun(.weak))
18 |
19 | container.register(SteamAuthServiceImpl.init)
20 | .as(SteamAuthService.self)
21 | .lifetime(.perRun(.weak))
22 |
23 | container.register(SteamProfileServiceImpl.init)
24 | .as(SteamProfileService.self)
25 | .lifetime(.perRun(.weak))
26 |
27 | container.register(SteamProfileGamesServiceImpl.init)
28 | .as(SteamProfileGamesService.self)
29 | .lifetime(.perRun(.weak))
30 |
31 | container.register(SteamGameServiceImpl.init)
32 | .as(SteamGameService.self)
33 | .lifetime(.perRun(.weak))
34 |
35 | container.register(SteamAchievementServiceImpl.init)
36 | .as(SteamAchievementService.self)
37 | .lifetime(.perRun(.weak))
38 |
39 | container.register(SteamFriendsServiceImpl.init)
40 | .as(SteamFriendsService.self)
41 | .lifetime(.perRun(.weak))
42 |
43 | container.register(SteamSessionsServiceImpl.init)
44 | .as(SteamSessionsService.self)
45 | .lifetime(.perRun(.weak))
46 |
47 | container.register(SteamDotaServiceImpl.init)
48 | .as(SteamDotaService.self)
49 | .lifetime(.perRun(.weak))
50 | container.register(SteamDotaServiceCalculatorImpl.init)
51 | .as(SteamDotaServiceCalculator.self)
52 | .lifetime(.prototype)
53 |
54 | container.register { SteamDotaHistorySynchronizer(network: $0, storage: $1, accountId: arg($2)) }
55 | .lifetime(.prototype)
56 | container.register { SteamDotaDetailsSynchronizer(network: $0, storage: $1, accountId: arg($2)) }
57 | .lifetime(.prototype)
58 | container.register(SteamDotaSynchronizers.init)
59 | .lifetime(.perRun(.strong))
60 | }
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/UseCasesImpl/Sources/UseCasesImplStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UseCasesImplStartPoint.swift
3 | // UseCasesImpl
4 | //
5 | // Created by Alexander Ivlev on 20/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import DITranquillity
11 |
12 | public final class UseCasesImplStartPoint: CommonStartPoint
13 | {
14 | public init() {
15 |
16 | }
17 |
18 | public func configure() {
19 |
20 | }
21 |
22 | public func reg(container: DIContainer) {
23 | container.append(framework: UseCasesImplDependency.self)
24 | }
25 |
26 | public func initialize() {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/UseCasesImpl/Sources/usecases/games/dota/SteamDotaSynchonizers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamDotaSynchronizer.swift
3 | // UseCasesImpl
4 | //
5 | // Created by Alexander Ivlev on 04/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 | import Entities
12 | import UseCases
13 | import SwiftLazy
14 |
15 | final class SteamDotaSynchronizers {
16 | private let historySynchronizerProvider: Provider1
17 | private let detailsSynchronizerProvider: Provider1
18 |
19 | private var historySynchonizersByAccount: [AccountID: SteamDotaHistorySynchronizer] = [:]
20 | private var detailsSynchonizersByAccount: [AccountID: SteamDotaDetailsSynchronizer] = [:]
21 |
22 | private var lock = FastLock()
23 |
24 | init(historySynchronizerProvider: Provider1,
25 | detailsSynchronizerProvider: Provider1) {
26 | self.historySynchronizerProvider = historySynchronizerProvider
27 | self.detailsSynchronizerProvider = detailsSynchronizerProvider
28 | }
29 |
30 | func historySynchronizer(for accountId: AccountID) -> SteamDotaHistorySynchronizer {
31 | lock.lock()
32 | defer { lock.unlock() }
33 |
34 | if let synchronizer = historySynchonizersByAccount[accountId] {
35 | return synchronizer
36 | }
37 | let synchronizer = historySynchronizerProvider.value(accountId)
38 | historySynchonizersByAccount[accountId] = synchronizer
39 |
40 | return synchronizer
41 | }
42 |
43 | func detailsSynchronizer(for accountId: AccountID) -> SteamDotaDetailsSynchronizer {
44 | lock.lock()
45 | defer { lock.unlock() }
46 |
47 | if let synchronizer = detailsSynchonizersByAccount[accountId] {
48 | return synchronizer
49 | }
50 | let synchronizer = detailsSynchronizerProvider.value(accountId)
51 | detailsSynchonizersByAccount[accountId] = synchronizer
52 |
53 | return synchronizer
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/UseCasesImpl/Sources/usecases/games/sessions/SteamSessionsServiceImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamSessionsServiceImpl.swift
3 | // UseCasesImpl
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 | import Entities
12 | import UseCases
13 | import UseCasesContracts
14 |
15 | final class SteamSessionsServiceImpl: SteamSessionsService
16 | {
17 | private lazy var universalService = { [unowned self] in
18 | UniversalServiceImpl(
19 | fetcher: { steamId in
20 | self.storage.fetchSessions(for: steamId)
21 | }, updater: { (steamId, completion) in
22 | self.network.requestSessions(for: steamId, completion: completion)
23 | }, saver: { (steamId, sessions) in
24 | self.storage.put(sessions: sessions, steamId: steamId)
25 | }
26 | )
27 | }()
28 |
29 | private let network: SteamSessionsNetwork
30 | private let storage: SteamSessionsStorage
31 |
32 | init(network: SteamSessionsNetwork, storage: SteamSessionsStorage) {
33 | self.network = network
34 | self.storage = storage
35 | }
36 |
37 | func refreshSessions(for steamId: SteamID, completion: ((Bool) -> Void)?) {
38 | universalService.refresh(for: steamId, completion: completion)
39 | }
40 |
41 | func getSessionsNotifier(for steamId: SteamID) -> Notifier {
42 | universalService.getNotifier(for: steamId)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/UseCasesImpl/Sources/usecases/profile/SteamFriendsServiceImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamFriendsServiceImpl.swift
3 | // UseCasesImpl
4 | //
5 | // Created by Alexander Ivlev on 30/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 | import Entities
12 | import UseCases
13 | import UseCasesContracts
14 |
15 | final class SteamFriendsServiceImpl: SteamFriendsService
16 | {
17 |
18 | private lazy var universalService = { [unowned self] in
19 | UniversalServiceImpl(
20 | fetcher: { steamId in
21 | self.storage.fetchFriends(for: steamId)
22 | }, updater: { (steamId, completion) in
23 | self.network.requestFriends(for: steamId, completion: completion)
24 | }, saver: { (_, friends) in
25 | self.storage.put(friends: friends)
26 | }
27 | )
28 | }()
29 |
30 | private let network: SteamProfileNetwork
31 | private let storage: SteamProfileStorage
32 |
33 | init(network: SteamProfileNetwork, storage: SteamProfileStorage) {
34 | self.network = network
35 | self.storage = storage
36 | }
37 |
38 | func refresh(for steamId: SteamID, completion: ((Bool) -> Void)?) {
39 | universalService.refresh(for: steamId, completion: completion)
40 | }
41 |
42 | func getNotifier(for steamId: SteamID) -> Notifier {
43 | universalService.getNotifier(for: steamId)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Modules/Infrastructure/UseCasesImpl/Sources/usecases/profile/SteamProfileServiceImpl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamProfileServiceImpl.swift
3 | // UseCasesImpl
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 | import Entities
12 | import UseCases
13 | import UseCasesContracts
14 |
15 | final class SteamProfileServiceImpl: SteamProfileService
16 | {
17 |
18 | private lazy var universalService = { [unowned self] in
19 | UniversalServiceImpl(
20 | fetcher: { steamId in
21 | self.storage.fetchProfile(by: steamId)
22 | }, updater: { (steamId, completion) in
23 | self.network.requestProfile(by: steamId, completion: completion)
24 | }, saver: { (_, profile) in
25 | self.storage.put(profile: profile)
26 | }
27 | )
28 | }()
29 |
30 | private let network: SteamProfileNetwork
31 | private let storage: SteamProfileStorage
32 |
33 | init(network: SteamProfileNetwork, storage: SteamProfileStorage) {
34 | self.network = network
35 | self.storage = storage
36 | }
37 |
38 | func refresh(for steamId: SteamID, completion: ((Bool) -> Void)?) {
39 | universalService.refresh(for: steamId, completion: completion)
40 | }
41 |
42 | func getNotifier(for steamId: SteamID) -> Notifier {
43 | universalService.getNotifier(for: steamId)
44 | }
45 |
46 | func getProfile(for steamId: SteamID, completion: @escaping (SteamProfileCompletion) -> Void) {
47 | universalService.refresh(for: steamId, contentCompletion: completion)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Modules/UIs/AppUIComponents/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "AppUIComponents",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "AppUIComponents", targets: ["AppUIComponents"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../Domain/UseCases"),
14 | .package(path: "../UIComponents"),
15 | .package(path: "../Design"),
16 | .package(path: "../../Core"),
17 | .package(path: "../../Common"),
18 | ],
19 | targets: [
20 | .target(name: "AppUIComponents", dependencies: [
21 | .product(name: "UseCases"),
22 | .product(name: "UIComponents"),
23 | .product(name: "Design"),
24 | .product(name: "Core"),
25 | .product(name: "Common"),
26 | ], path: "./Sources"),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Modules/UIs/AppUIComponents/README.md:
--------------------------------------------------------------------------------
1 | # UIComponents
2 |
3 | Пакет с UI компонентами. Компоненты никак не привязаны к бизнес логике, и могут спокойно переноситься из проекта в проект.
4 | Максиум на что они завязаны - на дизайн, который может иногда меняться не только в цветах, но и в списке разрешенных имен.
5 |
--------------------------------------------------------------------------------
/Modules/UIs/AppUIComponents/Sources/AppUIComponentsStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppUIComponentsStartPoint.swift
3 | // AppUIComponents
4 | //
5 | // Created by Alexander Ivlev on 25/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import DITranquillity
11 | import Core
12 | import UIComponents
13 |
14 | public final class AppUIComponentsStartPoint: CommonStartPoint
15 | {
16 | public init() {
17 |
18 | }
19 |
20 | public func configure() {
21 |
22 | }
23 |
24 | public func reg(container: DIContainer) {
25 | }
26 |
27 | public func initialize() {
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Modules/UIs/AppUIComponents/Sources/SteamGameImageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SteamGameImageView.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import UIComponents
11 | import Design
12 |
13 | public class SteamGameImageView: IdImageView
14 | {
15 | public func apply(use style: Style, size: CGFloat) {
16 | backgroundColor = style.colors.shadowColor
17 | layer.cornerRadius = 0.12 * size
18 | clipsToBounds = true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Design",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Design", targets: ["Design"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../Core"),
14 | .package(path: "../../Common"),
15 | ],
16 | targets: [
17 | .target(name: "Design", dependencies: [
18 | .product(name: "Core"),
19 | .product(name: "Common"),
20 | ], path: "./Sources"),
21 | ]
22 | )
23 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/README.md:
--------------------------------------------------------------------------------
1 | # Design
2 |
3 | Пакет с дизайном. Тут в первую очередь цветовая палитра, используемые шрифты, настройки анимации, и базовый лайаутинг.
4 | Переносим между проектами, но скорей всего потребует модификаций - всеже универсальный список вариантов цветов/шрифтов сделать сложно.
5 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/Sources/Common/Gradient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Gradient.swift
3 | // Design
4 | //
5 | // Created by Alexander Ivlev on 25/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct Gradient: Equatable
12 | {
13 | public let from: UIColor
14 | public let to: UIColor
15 |
16 | public init(from: UIColor, to: UIColor) {
17 | self.from = from
18 | self.to = to
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/Sources/Common/TimeInterval+String.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimeInterval+String.swift
3 | // Design
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | extension TimeInterval {
13 | public var adaptiveString: String {
14 | if self < 60 { // seconds
15 | let seconds = Int(self)
16 | return "\(seconds) \(loc["TimeInterval.short.seconds"])"
17 | } else if self < 60 * 60 { // minutes
18 | let minutes = Int(self / 60)
19 | return "\(minutes) \(loc["TimeInterval.short.minutes"])"
20 | } else { // hours
21 | let hours = Int(self / (60 * 60))
22 | return "\(hours) \(loc["TimeInterval.short.hours"])"
23 | }
24 | }
25 |
26 | public var detailsAdaptiveString: String {
27 | let hours: Int = Int(self / (60.0 * 60.0))
28 | let minutes: Int = Int(self / 60.0) - (hours * 60)
29 | let seconds: Int = Int(self.rounded()) % 60
30 |
31 | if hours > 0 {
32 | return "\(hours):\(minutes):\(seconds)"
33 | }
34 |
35 | if minutes > 0 {
36 | return "\(minutes):\(seconds)"
37 | }
38 |
39 | return "\(seconds) \(loc["TimeInterval.short.seconds"])"
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/Sources/Common/UIColor+Hex.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Hex.swift
3 | // Design
4 | //
5 | // Created by Alexander Ivlev on 25/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIColor
12 | {
13 | public convenience init(hex24 hex: UInt32, alpha: CGFloat = 1.0) {
14 | let r: UInt32 = (hex >> 16) & 0x000000FF
15 | let g: UInt32 = (hex >> 8) & 0x000000FF
16 | let b: UInt32 = (hex >> 0) & 0x000000FF
17 | self.init(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: alpha)
18 | }
19 |
20 | public convenience init(hex32 hex: UInt32) {
21 | let r: UInt32 = (hex >> 24) & 0x000000FF
22 | let g: UInt32 = (hex >> 16) & 0x000000FF
23 | let b: UInt32 = (hex >> 8) & 0x000000FF
24 | let a: UInt32 = (hex >> 0) & 0x000000FF
25 | self.init(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: CGFloat(a) / 255.0)
26 | }
27 | }
28 |
29 | func color(hex24: UInt32, alpha: CGFloat = 1.0) -> UIColor {
30 | return UIColor(hex24: hex24, alpha: alpha)
31 | }
32 |
33 | func color(hex32: UInt32) -> UIColor {
34 | return UIColor(hex32: hex32)
35 | }
36 |
37 | func gradient(from: UInt32, to: UInt32) -> Gradient {
38 | return Gradient(from: UIColor(hex24: from), to: UIColor(hex24: to))
39 | }
40 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/Sources/DesignStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DesignStartPoint.swift
3 | // Design
4 | //
5 | // Created by Alexander Ivlev on 15/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import DITranquillity
11 |
12 | public final class DesignStartPoint: CommonStartPoint
13 | {
14 | public init() {
15 |
16 | }
17 |
18 | public func configure() {
19 |
20 | }
21 |
22 | public func reg(container: DIContainer) {
23 | }
24 |
25 | public func initialize() {
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/Sources/Style/StylizingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StylizingView.swift
3 | // Design
4 | //
5 | // Created by Alexander Ivlev on 27/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 |
11 | public protocol StylizingView: class
12 | {
13 | var stylizingSubviews: WeakArray { get }
14 |
15 | func apply(use style: Style)
16 | }
17 |
18 | extension StylizingView {
19 | public var stylizingSubviews: WeakArray { WeakArray() }
20 | }
21 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/Sources/Style/StylizingViewsContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StylizingViewsContainer.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 26/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Common
11 |
12 | public final class StylizingViewsContainer
13 | {
14 | private var style: Style?
15 |
16 | private var viewsForStylizing = WeakArray()
17 |
18 | public init() {
19 |
20 | }
21 |
22 | public func addView(_ view: StylizingView, immediately: Bool) {
23 | if !viewsForStylizing.contains(where: { $0 === view }) {
24 | viewsForStylizing.append(view)
25 | }
26 |
27 | if let style = style, immediately {
28 | view.apply(use: style)
29 | apply(style, viewsForStylizing: view.stylizingSubviews)
30 | }
31 | }
32 |
33 | public func styleDidChange(_ style: Style) {
34 | self.style = style
35 |
36 | apply(style, viewsForStylizing: viewsForStylizing)
37 | }
38 |
39 | private func apply(_ style: Style, viewsForStylizing: WeakArray) {
40 | for stylizingView in viewsForStylizing.reversed() {
41 | stylizingView.apply(use: style)
42 | apply(style, viewsForStylizing: stylizingView.stylizingSubviews)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/Sources/Style/consts/ConstColors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConstColors.swift
3 | // Design
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public enum ConstColors
12 | {
13 | public static let defaultColors = Style.Colors(
14 | background: color(hex24: 0x000000),
15 | accent: color(hex24: 0x1c1c1e),
16 | separator: color(hex24: 0x2c2c2e),
17 | skeleton: gradient(from: 0x39383e, to: 0x707070),
18 | skeletonFailed: color(hex24: 0xc73225),
19 | barStyle: .black,
20 | tint: color(hex24: 0xFFFFFF),
21 | mainText: color(hex24: 0xFFFFFF),
22 | notAccentText: color(hex24: 0xc3c3c3),
23 | contentText: color(hex24: 0xc3c3c3),
24 | blurStyle: .dark,
25 | preferredStatusBarStyle: .lightContent,
26 |
27 | shadowColor: color(hex24: 0x000000),
28 | shadowOpacity: 1.0
29 | )
30 |
31 | public static let darkColors = Style.Colors(
32 | background: color(hex24: 0x000000),
33 | accent: color(hex24: 0x1c1c1e),
34 | separator: color(hex24: 0x2c2c2e),
35 | skeleton: gradient(from: 0x39383e, to: 0x707070),
36 | skeletonFailed: color(hex24: 0xc73225),
37 | barStyle: .default,
38 | tint: color(hex24: 0xFFFFFF),
39 | mainText: color(hex24: 0xFFFFFF),
40 | notAccentText: color(hex24: 0xc3c3c3),
41 | contentText: color(hex24: 0xc3c3c3),
42 | blurStyle: .dark,
43 | preferredStatusBarStyle: .default,
44 |
45 | shadowColor: color(hex24: 0x000000),
46 | shadowOpacity: 1.0
47 | )
48 |
49 | public static let lightColors = Style.Colors(
50 | background: color(hex24: 0xf3f2f8),
51 | accent: color(hex24: 0xffffff),
52 | separator: color(hex24: 0xe6e5eb),
53 | skeleton: gradient(from: 0xbdbdbd, to: 0xe0e0e0),
54 | skeletonFailed: color(hex24: 0xf64d3f),
55 | barStyle: .default,
56 | tint: color(hex24: 0x000000),
57 | mainText: color(hex24: 0x000000),
58 | notAccentText: color(hex24: 0x3c3c3c),
59 | contentText: color(hex24: 0x3c3c3c),
60 | blurStyle: .light,
61 | preferredStatusBarStyle: .default,
62 |
63 | shadowColor: color(hex24: 0x000000),
64 | shadowOpacity: 1.0
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/Sources/Style/consts/ConstsAnimation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConstsAnimation.swift
3 | // Design
4 | //
5 | // Created by Alexander Ivlev on 26/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public enum ConstsAnimation
12 | {
13 | public static let `default` = Style.Animation(
14 | animationTime: 0.25,
15 | transitionTime: 0.5
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/Modules/UIs/Design/Sources/Style/consts/ConstsFonts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConstsFonts.swift
3 | // Design
4 | //
5 | // Created by Alexander Ivlev on 25/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public enum ConstsFonts
12 | {
13 | public static let `default` = Style.Fonts(
14 | navLarge: UIFont.systemFont(ofSize: 36.0),
15 | navDefault: UIFont.systemFont(ofSize: 30.0),
16 | large: UIFont.systemFont(ofSize: 28.0),
17 | avatar: UIFont.systemFont(ofSize: 20.0, weight: .bold),
18 | title: UIFont.systemFont(ofSize: 18.0),
19 | subtitle: UIFont.systemFont(ofSize: 14.0),
20 | content: UIFont.systemFont(ofSize: 12.0)
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Auth/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Auth",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Auth", targets: ["Auth"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../UIComponents"),
14 | .package(path: "../../Design"),
15 | .package(path: "../../../Core"),
16 | .package(path: "../../../Common"),
17 | ],
18 | targets: [
19 | .target(name: "Auth", dependencies: [
20 | .product(name: "UIComponents"),
21 | .product(name: "Design"),
22 | .product(name: "Core"),
23 | .product(name: "Common"),
24 | ], path: "./Sources")
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Auth/README.md:
--------------------------------------------------------------------------------
1 | # Profile
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Auth/Sources/AuthDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthDependency.swift
3 | // Auth
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 |
11 | final class AuthDependency: DIFramework
12 | {
13 | static func load(container: DIContainer) {
14 | container.register { AuthRouter(navigator: arg($0), authService: $1) }
15 | .injection(\.authScreenProvider)
16 | .lifetime(.objectGraph)
17 |
18 | container.register(AuthScreen.init)
19 | .lifetime(.prototype)
20 | container.register { AuthScreenView() }
21 | .as(AuthScreenViewContract.self)
22 | .lifetime(.objectGraph)
23 | container.register(AuthScreenPresenter.init)
24 | .lifetime(.objectGraph)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Auth/Sources/AuthRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthRouter.swift
3 | // Auth
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Core
11 | import Common
12 | import SwiftLazy
13 | import UIComponents
14 | import UseCases
15 |
16 | typealias AuthScreen = Screen
17 |
18 | final class AuthRouter: IRouter
19 | {
20 | let authSuccessNotifier = Notifier()
21 |
22 | /*dependency*/var authScreenProvider = Provider()
23 |
24 | private let navigator: Navigator
25 | private let authService: SteamAuthService
26 |
27 | init(navigator: Navigator, authService: SteamAuthService) {
28 | self.navigator = navigator
29 | self.authService = authService
30 | }
31 |
32 | func start(parameters: RoutingParamaters) {
33 | log.assert(!authService.isLogined, "Unsupport show auth if your logined")
34 |
35 | showAuthScreen(parameters: parameters)
36 | }
37 |
38 | private func showAuthScreen(parameters: RoutingParamaters) {
39 | let screen = authScreenProvider.value
40 | screen.setRouter(self)
41 | screen.presenter.authSuccessNotifier.join(authSuccessNotifier, map: { _ in
42 | return parameters
43 | })
44 |
45 | navigator.push(screen.view)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Auth/Sources/AuthStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthStartPoint.swift
3 | // Auth
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Core
11 | import DITranquillity
12 | import SwiftLazy
13 |
14 | public final class AuthStartPoint: UIStartPoint
15 | {
16 | public static let name: UIModuleName = .auth
17 |
18 | public struct Subscribers {
19 | public let authSuccessNotifier: Notifier
20 | }
21 | public var subscribersFiller: (_ navigator: Navigator, _ subscribers: Subscribers) -> Void = { _, _ in }
22 |
23 | private var routerProvider = Provider1()
24 |
25 | public init() {
26 |
27 | }
28 |
29 | public func configure() {
30 |
31 | }
32 |
33 | public func reg(container: DIContainer) {
34 | container.append(framework: AuthDependency.self)
35 | routerProvider = container.resolve()
36 | }
37 |
38 | public func initialize() {
39 |
40 | }
41 |
42 | public func isSupportOpen(with parameters: RoutingParamaters) -> Bool {
43 | return parameters.moduleName == Self.name
44 | }
45 |
46 | public func makeRouter(use navigator: Navigator) -> IRouter {
47 | let router = routerProvider.value(navigator)
48 | subscribersFiller(navigator, Subscribers(
49 | authSuccessNotifier: router.authSuccessNotifier
50 | ))
51 |
52 | return router
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Dota",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Dota", targets: ["Dota"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../GameInformation")
14 | ],
15 | targets: [
16 | .target(name: "Dota", dependencies: [
17 | .product(name: "GameInformation"),
18 | ], path: "./Sources"),
19 | ]
20 | )
21 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/README.md:
--------------------------------------------------------------------------------
1 | # Dota
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/Sources/DotaDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaDependency.swift
3 | // Dota
4 | //
5 | // Created by Alexander Ivlev on 06/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 | import GameInformation //TODO: CustomGameInformation
11 |
12 | public final class DotaDependency: DIFramework
13 | {
14 | public static func load(container: DIContainer) {
15 | container.register(DotaRouter.init)
16 | .as(CustomGameInfoRouter.self)
17 | .injection(\.statisticsProvider)
18 | .lifetime(.objectGraph)
19 |
20 | container.register(DotaGameInfoPresenter.init)
21 | .lifetime(.objectGraph)
22 |
23 | container.register(DotaStatisticsScreen.init)
24 | .lifetime(.prototype)
25 | container.register(DotaStatisticsScreenPresenter.init)
26 | .lifetime(.objectGraph)
27 | container.register { DotaStatisticsScreenView() }
28 | .as(DotaStatisticsScreenViewContract.self)
29 | .lifetime(.objectGraph)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/Sources/DotaRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaRouter.swift
3 | // Dota
4 | //
5 | // Created by Alexander Ivlev on 06/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Core
11 | import Entities
12 | import UseCases
13 | import SwiftLazy
14 | import GameInformation
15 |
16 | typealias DotaStatisticsScreen = Screen
17 |
18 | final class DotaRouter: CustomGameInfoRouter
19 | {
20 | var statisticsProvider = Provider()
21 |
22 | private var navigator: Navigator?
23 | private let gameInfoPresenter: DotaGameInfoPresenter
24 | private let dotaService: SteamDotaService
25 |
26 | init(gameInfoPresenter: DotaGameInfoPresenter, dotaService: SteamDotaService) {
27 | self.gameInfoPresenter = gameInfoPresenter
28 | self.dotaService = dotaService
29 | }
30 |
31 | func configure(navigator: Navigator, steamId: SteamID, gameId: SteamGameID) -> [CustomGameInfoPresenter] {
32 | if gameId == dotaService.gameId {
33 | self.navigator = navigator
34 | configureCustomPresenters(steamId: steamId, gameId: gameId)
35 | return [gameInfoPresenter]
36 | }
37 | return []
38 | }
39 |
40 | private func showDotaStatistics(for steamId: SteamID) {
41 | log.assert(navigator != nil, "Call show dota statistics but navigator is nil")
42 |
43 | let screen = statisticsProvider.value
44 |
45 | screen.presenter.configure(steamId: steamId)
46 |
47 | navigator?.push(screen.view, animated: true)
48 | }
49 |
50 | private func configureCustomPresenters(steamId: SteamID, gameId: SteamGameID) {
51 | gameInfoPresenter.tapSummaryNotifier.weakJoin(listener: { (self, _) in
52 | self.showDotaStatistics(for: steamId)
53 | }, owner: self)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/Sources/DotaStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaStartPoint.swift
3 | // Dota
4 | //
5 | // Created by Alexander Ivlev on 07/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import DITranquillity
11 |
12 | public final class DotaStartPoint: CommonStartPoint
13 | {
14 | public init() {
15 |
16 | }
17 |
18 | public func configure() {
19 |
20 | }
21 |
22 | public func reg(container: DIContainer) {
23 | container.append(framework: DotaDependency.self)
24 | }
25 |
26 | public func initialize() {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/Sources/gameInfo/last2WeeksSummary/Dota2WeeksDetailsViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dota2WeeksDetailsViewModel.swift
3 | // Dota
4 | //
5 | // Created by Alexander Ivlev on 05/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | struct Dota2WeeksDetailsViewModel
13 | {
14 | let winPrefix: String
15 | let win: Int
16 |
17 | let avgKillsPrefix: String
18 | let avgKills: Int
19 |
20 | let avgDeathsPrefix: String
21 | let avgDeaths: Int
22 |
23 | let avgAssistsPrefix: String
24 | let avgAssists: Int
25 |
26 | let avgLastHitsPrefix: String
27 | let avgLastHits: Int
28 |
29 | let avgDeniesPrefix: String
30 | let avgDenies: Int
31 |
32 | let avgGPMPrefix: String
33 | let avgGPM: Int
34 | }
35 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/Sources/gameInfo/last2WeeksSummary/Dota2WeeksGamesCountViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dota2WeeksGamesCountViewModel.swift
3 | // Dota
4 | //
5 | // Created by Alexander Ivlev on 05/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | struct Dota2WeeksGamesCountViewModel
13 | {
14 | let prefix: String
15 | let count: Int
16 | }
17 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/Sources/gameInfo/last2WeeksSummary/Dota2WeeksSummaryConfigurator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dota2WeeksSummaryConfigurator.swift
3 | // Dota
4 | //
5 | // Created by Alexander Ivlev on 05/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import UIComponents
11 | import Common
12 | import GameInformation //TODO: CustomGameInformation
13 |
14 | final class Dota2WeeksSummaryConfigurator: CustomTableCellConfigurator
15 | {
16 | var gamesCountViewModel: SkeletonViewModel = .loading
17 | var detailsViewModel: SkeletonViewModel = .loading
18 |
19 | let tapNotifier = Notifier()
20 |
21 | var isSelectable: Bool {
22 | /// Пока не загрузилось количество игр, то нажимать нельзя. А детали, уже не обязательно - на следующем экране всеравно их больше чем две недели будет.
23 | if case .done = gamesCountViewModel {
24 | return tapNotifier.hasListeners()
25 | }
26 | return false
27 | }
28 |
29 | var registeredCell: (type: UITableViewCell.Type, identifier: String) {
30 | return (type: DotaLast2WeeksSummaryCell.self, identifier: DotaLast2WeeksSummaryCell.identifier)
31 | }
32 |
33 | func calculateHeightCell() -> CGFloat {
34 | return DotaLast2WeeksSummaryCell.preferredHeight
35 | }
36 |
37 | func makeCell(in tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
38 | return tableView.dequeueReusableCell(withIdentifier: DotaLast2WeeksSummaryCell.identifier, for: indexPath)
39 | }
40 |
41 | func configureCell(_ cell: UITableViewCell) {
42 | if let summaryCell = cell as? DotaLast2WeeksSummaryCell {
43 | summaryCell.configure(gamesCountViewModel)
44 | summaryCell.configure(detailsViewModel)
45 | return
46 | }
47 | }
48 |
49 | func select() {
50 | DispatchQueue.main.async { [tapNotifier] in
51 | tapNotifier.notify(())
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/Sources/gameInfo/lastGame/DotaLastMatchConfigurator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaLastMatchConfigurator.swift
3 | // Dota
4 | //
5 | // Created by Alexander Ivlev on 05/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import UIComponents
11 | import GameInformation //TODO: CustomGameInformation
12 |
13 | final class DotaLastMatchConfigurator: CustomTableCellConfigurator
14 | {
15 | var lastMatchViewModel: SkeletonViewModel = .loading
16 |
17 | let isSelectable: Bool = false
18 |
19 | var registeredCell: (type: UITableViewCell.Type, identifier: String) {
20 | return (type: DotaLastMatchCell.self, identifier: DotaLastMatchCell.identifier)
21 | }
22 |
23 | func calculateHeightCell() -> CGFloat {
24 | return DotaLastMatchCell.preferredHeight
25 | }
26 |
27 | func makeCell(in tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
28 | return tableView.dequeueReusableCell(withIdentifier: DotaLastMatchCell.identifier, for: indexPath)
29 | }
30 |
31 | func configureCell(_ cell: UITableViewCell) {
32 | if let lastMatchCell = cell as? DotaLastMatchCell {
33 | lastMatchCell.configure(lastMatchViewModel)
34 | return
35 | }
36 | }
37 |
38 | func select() {
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/Sources/gameInfo/lastGame/DotaLastMatchViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaLastMatchViewModel.swift
3 | // Dota
4 | //
5 | // Created by Alexander Ivlev on 05/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | struct DotaLastMatchViewModel
13 | {
14 | let heroImage: ChangeableImage
15 | let heroName: String
16 |
17 | /// kills, deaths, assists
18 | let kdaText: String
19 | let kills: Int
20 | let deaths: Int
21 | let assists: Int
22 |
23 | let startTime: Date
24 | let durationText: String
25 | let duration: TimeInterval
26 |
27 | let resultText: String
28 | let isWin: Bool
29 | let winText: String
30 | let loseText: String
31 | }
32 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/Dota/Sources/statistics/view/DotaStatisticsScreenView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotaStatisticsScreenView.swift
3 | // Dota
4 | //
5 | // Created by Alexander Ivlev on 06/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Common
11 | import Design
12 | import UIComponents
13 |
14 | final class DotaStatisticsScreenView: ApViewController, DotaStatisticsScreenViewContract
15 | {
16 | private let tableView = DotaStatisticsTableView()
17 |
18 | override init() {
19 | super.init()
20 | }
21 |
22 | required init?(coder: NSCoder) {
23 | fatalError("init(coder:) has not been implemented")
24 | }
25 |
26 | override func viewDidLoad() {
27 | super.viewDidLoad()
28 |
29 | title = loc["Games.Dota2.GameStatistic.title"]
30 |
31 | configureViews()
32 | }
33 |
34 | func setStatistics(_ viewModels: [DotaStatisticViewModel]) {
35 | tableView.updateStatistics(viewModels)
36 | }
37 |
38 | func showError(_ text: String) {
39 | ErrorAlert.show(text, on: self)
40 | }
41 |
42 | private func configureViews() {
43 | view.addSubview(tableView)
44 |
45 | tableView.clipsToBounds = true
46 | tableView.backgroundColor = .clear
47 |
48 | addViewForStylizing(tableView)
49 |
50 | tableView.snp.makeConstraints { maker in
51 | maker.top.equalToSuperview()
52 | maker.left.equalToSuperview()
53 | maker.right.equalToSuperview()
54 | maker.bottom.equalToSuperview()
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "GameInformation",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "GameInformation", targets: ["GameInformation"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../../AppUIComponents"),
14 | .package(path: "../../../UIComponents"),
15 | .package(path: "../../../Design"),
16 | .package(path: "../../../../Core"),
17 | .package(path: "../../../../Common"),
18 | ],
19 | targets: [
20 | .target(name: "GameInformation", dependencies: [
21 | .product(name: "AppUIComponents"),
22 | .product(name: "UIComponents"),
23 | .product(name: "Design"),
24 | .product(name: "Core"),
25 | .product(name: "Common"),
26 | ], path: "./Sources"),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/README.md:
--------------------------------------------------------------------------------
1 | # GameInformation
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Sources/GameInfoDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameInfoDependency.swift
3 | // GameInformation
4 | //
5 | // Created by Alexander Ivlev on 26/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 |
11 | final class GameInfoDependency: DIFramework
12 | {
13 | static func load(container: DIContainer) {
14 | container.register { GameInfoRouter(navigator: arg($0)) }
15 | .injection(\.gameInfoScreenProvider)
16 | .lifetime(.objectGraph)
17 |
18 | container.register { GameInfoScreen(screen: $0, routerProviders: many($1)) }
19 | .lifetime(.prototype)
20 | container.register(EmbeddedGameInfoScreen.init)
21 | .lifetime(.prototype)
22 |
23 | container.register { GameInfoScreenView() }
24 | .as(GameInfoScreenViewContract.self)
25 | .as(CustomGameInfoViewContract.self)
26 | .lifetime(.objectGraph)
27 |
28 | container.register(GameInfoScreenPresenter.init)
29 | .lifetime(.objectGraph)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Sources/GameInfoRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameInfoRouter.swift
3 | // GameInformation
4 | //
5 | // Created by Alexander Ivlev on 26/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Core
11 | import Common
12 | import SwiftLazy
13 | import UIComponents
14 | import Entities
15 |
16 | final class GameInfoRouter: IRouter
17 | {
18 | /*dependency*/var gameInfoScreenProvider = Provider()
19 |
20 | private let navigator: Navigator
21 |
22 | init(navigator: Navigator) {
23 | self.navigator = navigator
24 | }
25 |
26 | func start(parameters: RoutingParamaters) {
27 | let steamIdKey = GameInfoStartPoint.RoutingOptions.steamId
28 | let gameIdKey = GameInfoStartPoint.RoutingOptions.gameId
29 |
30 | if let steamId = parameters.options[steamIdKey].flatMap({ SteamID($0) }),
31 | let gameId = parameters.options[gameIdKey].flatMap({ SteamGameID($0) }) {
32 | showGameInfoScreen(steamId: steamId, gameId: gameId)
33 | } else {
34 | log.fatal("Unsupport show game info without steamId and game in options")
35 | }
36 | }
37 |
38 | private func showGameInfoScreen(steamId: SteamID, gameId: SteamGameID) {
39 | let screen = makeGameInfoScreen(steamId: steamId, gameId: gameId)
40 |
41 | navigator.push(screen.view)
42 | }
43 |
44 | private func makeGameInfoScreen(steamId: SteamID, gameId: SteamGameID) -> GameInfoScreen {
45 | let screen = gameInfoScreenProvider.value
46 | screen.setRouter(self)
47 |
48 | let presentersConfigurator = screen.configureRouters(navigator: navigator, steamId: steamId, gameId: gameId)
49 | screen.presenter.configure(steamId: steamId, gameId: gameId, presentersConfigurator: presentersConfigurator)
50 |
51 | return screen
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Sources/GameInfoStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameInfoStartPoint.swift
3 | // GameInformation
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import DITranquillity
11 | import SwiftLazy
12 | import Entities
13 |
14 | public final class GameInfoStartPoint: UIStartPoint
15 | {
16 | public enum RoutingOptions {
17 | public static let steamId = "SteamId"
18 | public static let gameId = "GameId"
19 | }
20 |
21 | public static let name: UIModuleName = .gameInfo
22 |
23 | private var routerProvider = Provider1()
24 |
25 | public init() {
26 |
27 | }
28 |
29 | public func makeParams(steamId: SteamID, gameId: SteamGameID) -> RoutingParamaters {
30 | return RoutingParamaters(moduleName: Self.name, options: [
31 | RoutingOptions.steamId: "\(steamId)",
32 | RoutingOptions.gameId: "\(gameId)"
33 | ])
34 | }
35 |
36 | public func configure() {
37 |
38 | }
39 |
40 | public func reg(container: DIContainer) {
41 | container.append(framework: GameInfoDependency.self)
42 | routerProvider = container.resolve()
43 | }
44 |
45 | public func initialize() {
46 |
47 | }
48 |
49 | public func isSupportOpen(with parameters: RoutingParamaters) -> Bool {
50 | return parameters.moduleName == Self.name
51 | }
52 |
53 | public func makeRouter(use navigator: Navigator) -> IRouter {
54 | return routerProvider.value(navigator)
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Sources/custom/CustomGameInfoPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomGameInfoPresenter.swift
3 | // GameInformation
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 |
11 | public protocol CustomGameInfoPresenter: class
12 | {
13 | var priority: Int { get }
14 |
15 | var orders: [UInt] { set get }
16 |
17 | // Говорит сколько секций он должен занимать. Вернет ноль, если не поддерживает информацию об игре
18 | func requestSectionsCount(gameId: SteamGameID) -> UInt
19 |
20 | func configure(steamId: SteamID, gameId: SteamGameID)
21 | func refresh(steamId: SteamID, gameId: SteamGameID)
22 | }
23 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Sources/custom/CustomGameInfoRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomGameInfoRouter.swift
3 | // GameInformation
4 | //
5 | // Created by Alexander Ivlev on 07/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Entities
10 | import Core
11 |
12 | // TODO: весь custom лучше бы в отдельный модуль, дабы Dota и другие кастомные модули не завязывались на прямую на GameInformation
13 | public protocol CustomGameInfoRouter: class
14 | {
15 | func configure(navigator: Navigator, steamId: SteamID, gameId: SteamGameID) -> [CustomGameInfoPresenter]
16 | }
17 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Sources/custom/CustomGameInfoViewContract.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomGameInfoViewContract.swift
3 | // GameInformation
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | public protocol CustomGameInfoViewContract: class
10 | {
11 | func addCustomSection(title: String?, order: UInt, configurators: [CustomTableCellConfigurator])
12 | func removeCustomSection(order: UInt)
13 |
14 | func updateCustom(configurator: CustomTableCellConfigurator)
15 |
16 | func showError(_ text: String)
17 | }
18 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Sources/custom/CustomTableCellConfigurator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomGameInfoViewContract.swift
3 | // GameInformation
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import UIComponents
11 |
12 | public protocol CustomTableCellConfigurator: class
13 | {
14 | var registeredCell: (type: UITableViewCell.Type, identifier: String) { get }
15 | var isSelectable: Bool { get }
16 |
17 | func calculateHeightCell() -> CGFloat
18 | func makeCell(in tableView: UITableView, indexPath: IndexPath) -> UITableViewCell
19 | func configureCell(_ cell: UITableViewCell)
20 |
21 | func select()
22 | }
23 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Sources/custom/universal/CustomGameInfoPresenterConfigurator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomGameInfoPresenterConfigurator.swift
3 | // GameInformation
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Entities
11 |
12 | final class CustomGameInfoPresenterConfigurator
13 | {
14 | private let customPresenters: [CustomGameInfoPresenter]
15 |
16 | init(customPresenters: [CustomGameInfoPresenter]) {
17 | self.customPresenters = customPresenters.sorted(by: { $0.priority > $1.priority })
18 | }
19 |
20 | func configure(steamId: SteamID, gameId: SteamGameID) {
21 | var order: UInt = 0
22 |
23 | for presenter in customPresenters {
24 | var sectionCount = presenter.requestSectionsCount(gameId: gameId)
25 | presenter.orders = []
26 | while sectionCount > 0 {
27 | presenter.orders.append(order)
28 | order += 1
29 | sectionCount -= 1
30 | }
31 | }
32 |
33 | for presenter in customPresenters {
34 | if !presenter.orders.isEmpty {
35 | presenter.configure(steamId: steamId, gameId: gameId)
36 | }
37 | }
38 | }
39 |
40 | func refresh(steamId: SteamID, gameId: SteamGameID) {
41 | for presenter in customPresenters {
42 | if !presenter.orders.isEmpty {
43 | presenter.refresh(steamId: steamId, gameId: gameId)
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Sources/gameInfo/AchievementsSummaryViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AchievementsSummaryViewModel.swift
3 | // GameInformation
4 | //
5 | // Created by Alexander Ivlev on 27/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | struct AchievementsSummaryViewModel
13 | {
14 | let prefix: String
15 | let current: Int
16 | let any: Int
17 | }
18 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/GameInformation/GameInformation/Sources/gameInfo/GameInfoViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameInfoViewModel.swift
3 | // GameInformation
4 | //
5 | // Created by Alexander Ivlev on 26/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | struct GameInfoViewModel
13 | {
14 | let icon: ChangeableImage
15 | let name: String
16 |
17 | let playtimeForeverPrefix: String
18 | let playtimeForever: TimeInterval
19 |
20 | let playtime2weeksPrefix: String
21 | let playtime2weeks: TimeInterval
22 | let playtime2weeksSuffix: String
23 | }
24 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Menu/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Menu",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Menu", targets: ["Menu"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../UIComponents"),
14 | .package(path: "../../Design"),
15 | .package(path: "../../../Core"),
16 | .package(path: "../../../Common"),
17 | ],
18 | targets: [
19 | .target(name: "Menu", dependencies: [
20 | .product(name: "UIComponents"),
21 | .product(name: "Design"),
22 | .product(name: "Core"),
23 | .product(name: "Common"),
24 | ], path: "./Sources"),
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Menu/README.md:
--------------------------------------------------------------------------------
1 | # Menu
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Menu/Sources/MenuDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuDependency.swift
3 | // Menu
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 |
11 | final class MenuDependency: DIFramework
12 | {
13 | static func load(container: DIContainer) {
14 | container.register { MenuRouter(navigator: arg($0)) }
15 | .injection(\.menuScreenProvider)
16 | .lifetime(.objectGraph)
17 |
18 | container.register(MenuScreen.init)
19 | .lifetime(.prototype)
20 | container.register{ MenuScreenView(navigatorProvider: $0) }
21 | .as(MenuScreenViewContract.self)
22 | .lifetime(.objectGraph)
23 | container.register(MenuScreenPresenter.init)
24 | .lifetime(.objectGraph)
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Menu/Sources/MenuRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuRouter.swift
3 | // Menu
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 |
10 | import UIKit
11 | import Core
12 | import UIComponents
13 | import Common
14 | import SwiftLazy
15 |
16 |
17 | typealias MenuScreen = Screen
18 |
19 | final class MenuRouter: IRouter
20 | {
21 | let newsGetter = Getter()
22 | let myProfileGetter = Getter()
23 | let sessionsGetter = Getter()
24 |
25 | /*dependency*/var menuScreenProvider = Lazy()
26 |
27 | private let navigator: Navigator
28 |
29 | init(navigator: Navigator) {
30 | self.navigator = navigator
31 | }
32 |
33 | func start(parameters: RoutingParamaters) {
34 | let screen = menuScreenProvider.value
35 | configure(screen)
36 |
37 | navigator.push(screen.view)
38 | }
39 |
40 | private func configure(_ screen: MenuScreen) {
41 | screen.setRouter(self)
42 |
43 | screen.presenter.newsGetter.take(from: self.newsGetter)
44 | screen.presenter.myProfileGetter.take(from: self.myProfileGetter)
45 | screen.presenter.sessionsGetter.take(from: self.sessionsGetter)
46 |
47 | screen.presenter.start()
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Menu/Sources/MenuStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuStartPoint.swift
3 | // Menu
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import Common
11 | import DITranquillity
12 | import SwiftLazy
13 |
14 | public final class MenuStartPoint: UIStartPoint
15 | {
16 | public static let name: UIModuleName = .menu
17 |
18 | public struct Subscribers {
19 | public let newsGetter: Getter
20 | public let myProfileGetter: Getter
21 | public let sessionsGetter: Getter
22 | }
23 | public var subscribersFiller: (_ navigator: Navigator, _ subscribers: Subscribers) -> Void = { _, _ in }
24 |
25 | private var routerProvider = Provider1()
26 |
27 | public init() {
28 |
29 | }
30 |
31 | public func configure() {
32 |
33 | }
34 |
35 | public func reg(container: DIContainer) {
36 | container.append(framework: MenuDependency.self)
37 | routerProvider = container.resolve()
38 | }
39 |
40 | public func initialize() {
41 |
42 | }
43 |
44 | public func isSupportOpen(with parameters: RoutingParamaters) -> Bool {
45 | return parameters.moduleName == Self.name
46 | }
47 |
48 | public func makeRouter(use navigator: Navigator) -> IRouter {
49 | let router = routerProvider.value(navigator)
50 | subscribersFiller(navigator, Subscribers(
51 | newsGetter: router.newsGetter,
52 | myProfileGetter: router.myProfileGetter,
53 | sessionsGetter: router.sessionsGetter
54 | ))
55 |
56 | return router
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Menu/Sources/menu/MenuScreenPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuScreenPresenter.swift
3 | // Menu
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import Common
11 |
12 | protocol MenuScreenViewContract: class
13 | {
14 | var viewModels: [MenuViewModel] { get set }
15 | }
16 |
17 | final class MenuScreenPresenter
18 | {
19 | let newsGetter = Getter()
20 | let myProfileGetter = Getter()
21 | let sessionsGetter = Getter()
22 |
23 | private weak var view: MenuScreenViewContract?
24 |
25 | init(view: MenuScreenViewContract) {
26 | self.view = view
27 | }
28 |
29 | func start() {
30 | configure()
31 | }
32 |
33 | private func configure() {
34 | view?.viewModels = [
35 | MenuViewModel(
36 | icon: ConstImage(named: "tabbarNews"),
37 | title: loc["SteamMenu.News"],
38 | selected: false,
39 | viewGetter: newsGetter
40 | ),
41 | MenuViewModel(
42 | icon: ConstImage(named: "tabbarProfile"),
43 | title: loc["SteamMenu.Profile"],
44 | selected: true,
45 | viewGetter: myProfileGetter
46 | ),
47 | MenuViewModel(
48 | icon: ConstImage(named: "tabbarSessions"),
49 | title: loc["SteamMenu.Sessions"],
50 | selected: false,
51 | viewGetter: sessionsGetter
52 | ),
53 | ]
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Menu/Sources/menu/MenuScreenView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuScreenView.swift
3 | // Menu
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Core
11 | import Common
12 | import UIComponents
13 | import Design
14 | import SwiftLazy
15 |
16 | final class MenuScreenView: ApTabBarController, UITabBarControllerDelegate, MenuScreenViewContract
17 | {
18 | var viewModels: [MenuViewModel] = [] {
19 | didSet {
20 | configureViewControllers()
21 | }
22 | }
23 |
24 | private let navigatorProvider: Provider
25 |
26 | init(navigatorProvider: Provider) {
27 | self.navigatorProvider = navigatorProvider
28 | super.init(nibName: nil, bundle: nil)
29 | }
30 |
31 | required init?(coder: NSCoder) {
32 | fatalError("init(coder:) has not been implemented")
33 | }
34 |
35 | override func styleDidChange(_ style: Style) {
36 | super.styleDidChange(style)
37 |
38 | tabBar.barStyle = style.colors.barStyle
39 | tabBar.tintColor = style.colors.tint
40 | }
41 |
42 | override func viewDidLoad() {
43 | super.viewDidLoad()
44 |
45 | self.delegate = self
46 |
47 | styleDidChange(style)
48 | }
49 |
50 | private func configureViewControllers() {
51 | var selectedIndex: Int = 0
52 | var newViewControllers: [UIViewController] = []
53 | for viewModel in viewModels {
54 | if !viewModel.viewGetter.hasCallback() {
55 | continue
56 | }
57 |
58 | let navigator = navigatorProvider.value
59 | guard let router = viewModel.viewGetter.get(navigator) else {
60 | continue
61 | }
62 | router.start()
63 |
64 | let vc = navigator.controller
65 | vc.tabBarItem = UITabBarItem(title: viewModel.title, image: viewModel.icon.image, tag: 0)
66 |
67 | if viewModel.selected {
68 | selectedIndex = newViewControllers.count
69 | }
70 |
71 | newViewControllers.append(vc)
72 | }
73 |
74 | viewControllers = newViewControllers
75 | self.selectedIndex = selectedIndex
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Menu/Sources/menu/MenuViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuViewModel.swift
3 | // Menu
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import Common
11 |
12 | struct MenuViewModel {
13 | let icon: ConstImage
14 | let title: String
15 | let selected: Bool
16 |
17 | let viewGetter: Getter
18 | }
19 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/News/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "News",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "News", targets: ["News"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../AppUIComponents"),
14 | .package(path: "../../UIComponents"),
15 | .package(path: "../../Design"),
16 | .package(path: "../../../Core"),
17 | .package(path: "../../../Common"),
18 | ],
19 | targets: [
20 | .target(name: "News", dependencies: [
21 | .product(name: "AppUIComponents"),
22 | .product(name: "UIComponents"),
23 | .product(name: "Design"),
24 | .product(name: "Core"),
25 | .product(name: "Common"),
26 | ], path: "./Sources"),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/News/README.md:
--------------------------------------------------------------------------------
1 | # News
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/News/Sources/NewsDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsDependency.swift
3 | // News
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 |
11 | final class NewsDependency: DIFramework
12 | {
13 | static func load(container: DIContainer) {
14 | container.register { NewsRouter(navigator: arg($0)) }
15 | .injection(\.ribbonScreenProvider)
16 | .lifetime(.objectGraph)
17 |
18 | container.register(RibbonScreen.init)
19 | .lifetime(.prototype)
20 | container.register { RibbonScreenView() }
21 | .as(RibbonScreenViewContract.self)
22 | .lifetime(.objectGraph)
23 | container.register(RibbonScreenPresenter.init)
24 | .lifetime(.objectGraph)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/News/Sources/NewsRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsRouter.swift
3 | // News
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Core
11 | import Common
12 | import SwiftLazy
13 | import UIComponents
14 |
15 | typealias RibbonScreen = Screen
16 |
17 | final class NewsRouter: IRouter
18 | {
19 | /*dependency*/var ribbonScreenProvider = Provider()
20 |
21 | private let navigator: Navigator
22 |
23 | init(navigator: Navigator) {
24 | self.navigator = navigator
25 | }
26 |
27 | func start(parameters: RoutingParamaters) {
28 | let screen = ribbonScreenProvider.value
29 | configure(screen)
30 | navigator.push(screen.view)
31 | }
32 |
33 | private func configure(_ screen: RibbonScreen) {
34 | screen.setRouter(self)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/News/Sources/NewsStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsStartPoint.swift
3 | // News
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Core
10 | import DITranquillity
11 | import SwiftLazy
12 |
13 | public final class NewsStartPoint: UIStartPoint
14 | {
15 | public static let name: UIModuleName = .news
16 |
17 | private var routerProvider = Provider1()
18 |
19 | public init() {
20 |
21 | }
22 |
23 | public func configure() {
24 |
25 | }
26 |
27 | public func reg(container: DIContainer) {
28 | container.append(framework: NewsDependency.self)
29 | routerProvider = container.resolve()
30 | }
31 |
32 | public func initialize() {
33 |
34 | }
35 |
36 | public func isSupportOpen(with parameters: RoutingParamaters) -> Bool {
37 | return parameters.moduleName == Self.name
38 | }
39 |
40 | public func makeRouter(use navigator: Navigator) -> IRouter {
41 | return routerProvider.value(navigator)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/News/Sources/ribbon/RibbonScreenPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RibbonScreenPresenter.swift
3 | // News
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 |
11 | protocol RibbonScreenViewContract: class
12 | {
13 | }
14 |
15 | final class RibbonScreenPresenter
16 | {
17 | private weak var view: RibbonScreenViewContract?
18 |
19 | init(view: RibbonScreenViewContract) {
20 | self.view = view
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/News/Sources/ribbon/view/RibbonScreenView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RibbonScreenView.swift
3 | // News
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Common
11 | import UIComponents
12 |
13 | final class RibbonScreenView: ApViewController, RibbonScreenViewContract
14 | {
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 |
18 | title = "Новости"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Profile/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Profile",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Profile", targets: ["Profile"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../AppUIComponents"),
14 | .package(path: "../../UIComponents"),
15 | .package(path: "../../Design"),
16 | .package(path: "../../../Core"),
17 | .package(path: "../../../Common"),
18 | ],
19 | targets: [
20 | .target(name: "Profile", dependencies: [
21 | .product(name: "AppUIComponents"),
22 | .product(name: "UIComponents"),
23 | .product(name: "Design"),
24 | .product(name: "Core"),
25 | .product(name: "Common"),
26 | ], path: "./Sources")
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Profile/README.md:
--------------------------------------------------------------------------------
1 | # Profile
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Profile/Sources/ProfileDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileDependency.swift
3 | // Profile
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 |
11 | final class ProfileDependency: DIFramework
12 | {
13 | static func load(container: DIContainer) {
14 | container.register { ProfileRouter(navigator: arg($0), authService: $1) }
15 | .injection(\.profileScreenProvider)
16 | .injection(\.friendsScreenProvider)
17 | .lifetime(.objectGraph)
18 |
19 | container.register(ProfileScreen.init)
20 | .lifetime(.prototype)
21 | container.register { ProfileScreenView() }
22 | .as(ProfileScreenViewContract.self)
23 | .lifetime(.objectGraph)
24 | container.register(ProfileScreenPresenter.init)
25 | .lifetime(.objectGraph)
26 |
27 | // Вначале друзья были отдельным модулем, но потом я подумал, и решил - а зачем?
28 | container.register(FriendsScreen.init)
29 | .lifetime(.prototype)
30 | container.register { FriendsScreenView() }
31 | .as(FriendsScreenViewContract.self)
32 | .lifetime(.objectGraph)
33 | container.register(FriendsScreenPresenter.init)
34 | .lifetime(.objectGraph)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Profile/Sources/ProfileStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileStartPoint.swift
3 | // Profile
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Core
11 | import DITranquillity
12 | import SwiftLazy
13 | import Entities
14 |
15 | public final class ProfileStartPoint: UIStartPoint
16 | {
17 | public enum RoutingOptions {
18 | public static let steamId = "SteamId"
19 | // TODO: в будущем можно будет поддержать переход сразу на друга, от себя (то есть с полной навигацией)
20 | }
21 |
22 | public struct Subscribers {
23 | public let tapOnGameNotifier: Notifier<(SteamID, SteamGameID, Navigator)>
24 | }
25 | public var subscribersFiller: (_ navigator: Navigator, _ subscribers: Subscribers) -> Void = { _, _ in }
26 |
27 | public static let name: UIModuleName = .profile
28 |
29 | private var routerProvider = Provider1()
30 |
31 | public init() {
32 |
33 | }
34 |
35 | public func makeParams(steamId: SteamID) -> RoutingParamaters {
36 | return RoutingParamaters(moduleName: Self.name, options: [
37 | RoutingOptions.steamId: "\(steamId)"
38 | ])
39 | }
40 |
41 | public func configure() {
42 |
43 | }
44 |
45 | public func reg(container: DIContainer) {
46 | container.append(framework: ProfileDependency.self)
47 | routerProvider = container.resolve()
48 | }
49 |
50 | public func initialize() {
51 |
52 | }
53 |
54 | public func isSupportOpen(with parameters: RoutingParamaters) -> Bool {
55 | return parameters.moduleName == Self.name
56 | }
57 |
58 | public func makeRouter(use navigator: Navigator) -> IRouter {
59 | let router = routerProvider.value(navigator)
60 | subscribersFiller(navigator, Subscribers(
61 | tapOnGameNotifier: router.tapOnGameNotifier
62 | ))
63 |
64 | return router
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Profile/Sources/friends/FriendViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FriendViewModel.swift
3 | // Profile
4 | //
5 | // Created by Alexander Ivlev on 30/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 | import Entities
12 |
13 | struct FriendViewModel: Equatable
14 | {
15 | struct Content: Equatable {
16 | let avatar: ChangeableImage
17 | let avatarURL: URL? // только ради сравнения
18 | let avatarLetter: String
19 |
20 | let nick: String
21 |
22 | let tapNotifier: Notifier
23 |
24 | static func ==(lhs: Content, rhs: Content) -> Bool {
25 | return lhs.avatarURL == rhs.avatarURL && lhs.nick == rhs.nick
26 | }
27 | }
28 |
29 | enum State: Equatable {
30 | case loading
31 | case failed
32 | case done(Content)
33 | }
34 |
35 | let steamId: SteamID
36 | let state: State
37 | let needUpdateCallback: (() -> Void)?
38 |
39 | static func ==(lhs: FriendViewModel, rhs: FriendViewModel) -> Bool {
40 | return lhs.steamId == rhs.steamId && lhs.state == rhs.state
41 | }
42 | }
43 |
44 | extension FriendViewModel
45 | {
46 | init(empty state: State) {
47 | self.steamId = 0
48 | self.state = state
49 | self.needUpdateCallback = nil
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Profile/Sources/friends/view/FriendsScreenView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FriendsScreenView.swift
3 | // Profile
4 | //
5 | // Created by Alexander Ivlev on 26/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AppUIComponents
11 | import UIComponents
12 | import Common
13 | import Design
14 | import SnapKit
15 | import UseCases
16 |
17 | final class FriendsScreenView: ApViewController, FriendsScreenViewContract
18 | {
19 | let needUpdateNotifier = Notifier()
20 |
21 | private let tableView = FriendsTableView()
22 |
23 | override init() {
24 | super.init()
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 |
31 | override func viewDidLoad() {
32 | super.viewDidLoad()
33 |
34 | title = loc["SteamFriends.Title"]
35 |
36 | configureViews()
37 | }
38 |
39 | override func viewWillAppear(_ animated: Bool) {
40 | super.viewWillAppear(animated)
41 |
42 | needUpdateNotifier.notify(())
43 | }
44 |
45 | func setTitle(_ text: String) {
46 | title = text
47 | }
48 |
49 | func beginLoading() {
50 | tableView.updateFriends(Array(repeating: FriendViewModel(empty: .loading), count: 8))
51 | }
52 |
53 | func failedLoading() {
54 | tableView.updateFriends(Array(repeating: FriendViewModel(empty: .failed), count: 8))
55 | }
56 |
57 | func updateFriends(_ friends: [FriendViewModel]) {
58 | tableView.updateFriends(friends)
59 | }
60 |
61 | func updateFriend(_ friend: FriendViewModel) {
62 | tableView.updateFriend(friend)
63 | }
64 |
65 | // MARK: - other
66 |
67 | func showError(_ text: String) {
68 | ErrorAlert.show(text, on: self)
69 | }
70 |
71 | private func configureViews() {
72 | view.addSubview(tableView)
73 |
74 | tableView.clipsToBounds = true
75 | tableView.backgroundColor = .clear
76 |
77 | addViewForStylizing(tableView)
78 |
79 | tableView.snp.makeConstraints { maker in
80 | maker.top.equalToSuperview()
81 | maker.left.equalToSuperview()
82 | maker.right.equalToSuperview()
83 | maker.bottom.equalToSuperview()
84 | }
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Profile/Sources/profile/ProfileGameInfoViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileGameViewModel.swift
3 | // Profile
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | struct ProfileGameInfoViewModel
13 | {
14 | let icon: ChangeableImage
15 | let name: String
16 |
17 | let playtimePrefix: String
18 | let playtime: TimeInterval
19 |
20 | let tapNotifier: Notifier
21 | }
22 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Profile/Sources/profile/ProfileViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileViewModel.swift
3 | // Profile
4 | //
5 | // Created by Alexander Ivlev on 22/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 |
11 | struct ProfileViewModel
12 | {
13 | let avatar: ChangeableImage
14 | let avatarLetter: String
15 |
16 | let nick: String
17 |
18 | var isPrivate: Bool = true
19 | var realName: String = ""
20 | let privateText: String
21 | let notSetRealNameText: String
22 |
23 | let tapNotifier: Notifier
24 | }
25 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Sessions/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Sessions",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "Sessions", targets: ["Sessions"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../../AppUIComponents"),
14 | .package(path: "../../UIComponents"),
15 | .package(path: "../../Design"),
16 | .package(path: "../../../Core"),
17 | .package(path: "../../../Common"),
18 | ],
19 | targets: [
20 | .target(name: "Sessions", dependencies: [
21 | .product(name: "AppUIComponents"),
22 | .product(name: "UIComponents"),
23 | .product(name: "Design"),
24 | .product(name: "Core"),
25 | .product(name: "Common"),
26 | ], path: "./Sources"),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Sessions/README.md:
--------------------------------------------------------------------------------
1 | # Sessions
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Sessions/Sources/SessionsDependency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionsDependency.swift
3 | // Sessions
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import DITranquillity
10 |
11 | final class SessionsDependency: DIFramework
12 | {
13 | static func load(container: DIContainer) {
14 | container.register { SessionsRouter(navigator: arg($0), authService: $1) }
15 | .injection(\.sessionsScreenProvider)
16 | .lifetime(.objectGraph)
17 |
18 | container.register(SessionsScreen.init)
19 | .lifetime(.prototype)
20 | container.register { SessionsScreenView() }
21 | .as(SessionsScreenViewContract.self)
22 | .lifetime(.objectGraph)
23 | container.register(SessionsScreenPresenter.init)
24 | .lifetime(.objectGraph)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Sessions/Sources/SessionsRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionsRouter.swift
3 | // Sessions
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Core
11 | import UIComponents
12 | import Common
13 | import Entities
14 | import UseCases
15 | import SwiftLazy
16 |
17 | typealias SessionsScreen = Screen
18 |
19 | final class SessionsRouter: IRouter
20 | {
21 | let tapOnGameNotifier = Notifier<(SteamID, SteamGameID, Navigator)>()
22 |
23 | /*dependency*/var sessionsScreenProvider = Provider()
24 |
25 | private let navigator: Navigator
26 | private let authService: SteamAuthService
27 |
28 | private var currentRoutingParamaters: RoutingParamaters?
29 |
30 | init(navigator: Navigator, authService: SteamAuthService) {
31 | self.navigator = navigator
32 | self.authService = authService
33 | }
34 |
35 | func start(parameters: RoutingParamaters) {
36 | if currentRoutingParamaters == parameters {
37 | return
38 | }
39 | currentRoutingParamaters = parameters
40 |
41 | let steamIdKey = SessionsStartPoint.RoutingOptions.steamId
42 | if let steamId = parameters.options[steamIdKey].flatMap({ SteamID($0) }) {
43 | showSessionsScreen(steamId: steamId, on: navigator)
44 | } else if let steamId = authService.steamId {
45 | showSessionsScreen(steamId: steamId, on: navigator)
46 | } else {
47 | log.fatal("Unsupport show sessions without steamId for options or auth")
48 | }
49 | }
50 |
51 | private func showSessionsScreen(steamId: SteamID, on navigator: Navigator) {
52 | let screen = sessionsScreenProvider.value
53 | screen.setRouter(self)
54 |
55 | screen.presenter.tapOnGameNotifier.join(tapOnGameNotifier) { (steamId, gameId) in
56 | return (steamId, gameId, navigator)
57 | }
58 |
59 | screen.presenter.configure(steamId: steamId)
60 |
61 | navigator.push(screen.view)
62 | }
63 |
64 | }
65 |
66 |
67 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Sessions/Sources/SessionsStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionsStartPoint.swift
3 | // Sessions
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Common
10 | import Core
11 | import DITranquillity
12 | import SwiftLazy
13 | import Entities
14 |
15 | public final class SessionsStartPoint: UIStartPoint
16 | {
17 | public enum RoutingOptions {
18 | public static let steamId = "SteamId"
19 | // TODO: в будущем можно будет поддержать переход сразу на друга, от себя (то есть с полной навигацией)
20 | }
21 |
22 | public struct Subscribers {
23 | public let tapOnGameNotifier: Notifier<(SteamID, SteamGameID, Navigator)>
24 | }
25 | public var subscribersFiller: (_ navigator: Navigator, _ subscribers: Subscribers) -> Void = { _, _ in }
26 |
27 | public static let name: UIModuleName = .sessions
28 |
29 | private var routerProvider = Provider1()
30 |
31 | public init() {
32 |
33 | }
34 |
35 | public func makeParams(steamId: SteamID) -> RoutingParamaters {
36 | return RoutingParamaters(moduleName: Self.name, options: [
37 | RoutingOptions.steamId: "\(steamId)"
38 | ])
39 | }
40 |
41 | public func configure() {
42 | }
43 |
44 | public func reg(container: DIContainer) {
45 | container.append(framework: SessionsDependency.self)
46 | routerProvider = container.resolve()
47 | }
48 |
49 | public func initialize() {
50 | // TODO: setup style
51 | }
52 |
53 | public func isSupportOpen(with parameters: RoutingParamaters) -> Bool {
54 | return parameters.moduleName == Self.name
55 | }
56 |
57 | public func makeRouter(use navigator: Navigator) -> IRouter {
58 | let router = routerProvider.value(navigator)
59 | subscribersFiller(navigator, Subscribers(
60 | tapOnGameNotifier: router.tapOnGameNotifier
61 | ))
62 |
63 | return router
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Sessions/Sources/sessions/SessionViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionViewModel.swift
3 | // Sessions
4 | //
5 | // Created by Alexander Ivlev on 01/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Common
11 |
12 | struct SessionViewModel
13 | {
14 | let icon: ChangeableImage
15 | let name: String
16 |
17 | let playtimeForeverPrefix: String
18 | let playtimeForever: TimeInterval
19 |
20 | let playtime2weeksPrefix: String
21 | let playtime2weeks: TimeInterval
22 | let playtime2weeksSuffix: String
23 |
24 | let tapNotifier: Notifier
25 | }
26 |
--------------------------------------------------------------------------------
/Modules/UIs/Modules/Sessions/Sources/sessions/view/SessionsScreenView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionsScreenView.swift
3 | // Sessions
4 | //
5 | // Created by Alexander Ivlev on 24/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 |
10 | import UIKit
11 | import Common
12 | import UIComponents
13 |
14 | final class SessionsScreenView: ApViewController, SessionsScreenViewContract
15 | {
16 | let needUpdateNotifier = Notifier()
17 |
18 | private let tableView = SessionsTableView()
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 |
23 | title = loc["SteamSessions.Title"]
24 |
25 | configureViews()
26 | }
27 |
28 | override func viewWillAppear(_ animated: Bool) {
29 | super.viewWillAppear(animated)
30 |
31 | needUpdateNotifier.notify(())
32 | }
33 |
34 | func beginLoading() {
35 | tableView.updateSessions(Array(repeating: .loading, count: 8))
36 | }
37 |
38 | func failedLoadingSessions() {
39 | tableView.updateSessions(Array(repeating: .failed, count: 8))
40 | }
41 |
42 | func showSessions(_ sessions: [SessionViewModel]) {
43 | tableView.updateSessions(sessions.map { .done($0) })
44 | }
45 |
46 | // MARK: - other
47 |
48 | func showError(_ text: String) {
49 | ErrorAlert.show(text, on: self)
50 | }
51 |
52 | private func configureViews() {
53 | view.addSubview(tableView)
54 |
55 | tableView.clipsToBounds = true
56 | tableView.backgroundColor = .clear
57 |
58 | addViewForStylizing(tableView)
59 |
60 | tableView.snp.makeConstraints { maker in
61 | maker.top.equalToSuperview()
62 | maker.left.equalToSuperview()
63 | maker.right.equalToSuperview()
64 | maker.bottom.equalToSuperview()
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "UIComponents",
8 | platforms: [.iOS(.v10)],
9 | products: [
10 | .library(name: "UIComponents", targets: ["UIComponents"]),
11 | ],
12 | dependencies: [
13 | .package(path: "../Design"),
14 | .package(path: "../../Core"),
15 | .package(path: "../../Common"),
16 | .package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.0.1")
17 | ],
18 | targets: [
19 | .target(name: "UIComponents", dependencies: [
20 | .product(name: "Design"),
21 | .product(name: "Core"),
22 | .product(name: "Common"),
23 | .product(name: "SnapKit"),
24 | ], path: "./Sources"),
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/README.md:
--------------------------------------------------------------------------------
1 | # UIComponents
2 |
3 | Пакет с UI компонентами. Компоненты никак не привязаны к бизнес логике, и могут спокойно переноситься из проекта в проект.
4 | Максиум на что они завязаны - на дизайн, который может иногда меняться не только в цветах, но и в списке разрешенных имен.
5 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/UIComponentsStartPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIComponentsStartPoint.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 04/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import DITranquillity
11 | import Core
12 |
13 | public final class UIComponentsStartPoint: CommonStartPoint
14 | {
15 | public init() {
16 |
17 | }
18 |
19 | public func configure() {
20 |
21 | }
22 |
23 | public func reg(container: DIContainer) {
24 | container.register { ApNavigationController(nibName: nil, bundle: nil) }
25 | .as(UINavigationController.self)
26 | .lifetime(.prototype)
27 | }
28 |
29 | public func initialize() {
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/alerts/ErrorAlert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ErrorAlert.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 21/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Common
11 |
12 | public final class ErrorAlert
13 | {
14 | private static var onShownViewController = WeakArray()
15 |
16 | public static func show(_ text: String, on viewController: UIViewController) {
17 | log.assert(Thread.isMainThread, "Thread.isMainThread")
18 |
19 | let onShownVC = viewController
20 |
21 | if onShownViewController.contains(where: { $0 === onShownVC }) {
22 | return
23 | }
24 |
25 | // Да не модно и не молодежно, но зато писать или подключать ничего не надо
26 | let alertController = AlertController(title: loc["Alert.Error"],
27 | message: text,
28 | preferredStyle: .alert)
29 | alertController.statusBarStyle = viewController.preferredStatusBarStyle
30 | alertController.addAction(UIAlertAction(
31 | title: loc["Alert.Ok"],
32 | style: .default,
33 | handler: { _ in
34 | onShownViewController.remove(onShownVC)
35 | }
36 | ))
37 |
38 | onShownViewController.append(onShownVC)
39 | onShownVC.present(alertController, animated: true)
40 | }
41 | }
42 |
43 | private final class AlertController: UIAlertController
44 | {
45 | override var preferredStatusBarStyle: UIStatusBarStyle {
46 | return statusBarStyle
47 | }
48 |
49 | var statusBarStyle: UIStatusBarStyle = .default
50 | }
51 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/base/ApHapticFeedback.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApHapticFeedback.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 03/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreHaptics
11 |
12 | public enum ApFeedbackGenerator {
13 | case impact(UIImpactFeedbackGenerator, intensity: CGFloat)
14 | case notification(UINotificationFeedbackGenerator, type: UINotificationFeedbackGenerator.FeedbackType)
15 | case selection(UISelectionFeedbackGenerator)
16 | }
17 |
18 | public class AppHapticFeedback {
19 | private let feedback: ApFeedbackGenerator
20 |
21 | public static func test() {
22 | }
23 |
24 | public static func preview() -> AppHapticFeedback {
25 | // TODO: need use CoreHaptic for improve
26 | return AppHapticFeedback(feedback: .impact(UIImpactFeedbackGenerator(style: .heavy), intensity: 1.0))
27 | }
28 |
29 | public func noise() {
30 | switch feedback {
31 | case let .impact(generator, intensity):
32 | if #available(iOS 13.0, *) {
33 | generator.impactOccurred(intensity: intensity)
34 | } else {
35 | generator.impactOccurred()
36 | }
37 | case let .notification(generator, type):
38 | generator.notificationOccurred(type)
39 | case let .selection(generator):
40 | generator.selectionChanged()
41 | }
42 | }
43 |
44 | public init(feedback: ApFeedbackGenerator) {
45 | self.feedback = feedback
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/base/ApNavigationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApNavigationController.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 03/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class ApNavigationController: UINavigationController, UIGestureRecognizerDelegate
12 | {
13 | open override var preferredStatusBarStyle: UIStatusBarStyle {
14 | return (presentedViewController ?? topViewController)?.preferredStatusBarStyle ?? .default
15 | }
16 |
17 | open override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | interactivePopGestureRecognizer?.delegate = self
21 | }
22 |
23 | open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
24 | return viewControllers.count > 1
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/base/ApTabBarController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApTabBarController.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 19/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Design
11 |
12 | open class ApTabBarController: UITabBarController
13 | {
14 | public private(set) lazy var style: Style = styleMaker.makeStyle(for: self)
15 |
16 | public override var preferredStatusBarStyle: UIStatusBarStyle {
17 | guard let tabVCs = viewControllers, !tabVCs.isEmpty else {
18 | return style.colors.preferredStatusBarStyle
19 | }
20 | return tabVCs[selectedIndex].preferredStatusBarStyle
21 | }
22 |
23 | private let styleMaker: StyleMaker = StyleMaker()
24 |
25 | open func styleDidChange(_ style: Style) {
26 | view.backgroundColor = style.colors.background
27 | }
28 |
29 | open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
30 | super.traitCollectionDidChange(previousTraitCollection)
31 |
32 | style = styleMaker.makeStyle(for: self)
33 | styleDidChange(style)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/base/table/ApCollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApCollectionView.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 08/12/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Design
11 | import Common
12 |
13 | open class ApCollectionView: UICollectionView, StylizingView
14 | {
15 | private let stylizingViewsContainer = StylizingViewsContainer()
16 |
17 | public func addViewForStylizing(_ view: StylizingView, immediately: Bool = true) {
18 | stylizingViewsContainer.addView(view, immediately: immediately)
19 | }
20 |
21 | open func apply(use style: Design.Style) {
22 | backgroundColor = style.colors.background
23 |
24 | stylizingViewsContainer.styleDidChange(style)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/base/table/ApSectionTitleView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApSectionTitleView.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 26/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Design
11 | import Common
12 |
13 | public class ApSectionTitleView: UIView
14 | {
15 | public static let preferredHeight: CGFloat = 30.0
16 |
17 | private let titleLabel: UILabel = UILabel(frame: .zero)
18 |
19 | public init(text: String) {
20 | super.init(frame: .zero)
21 |
22 | commonInit(text: text)
23 | }
24 |
25 | required init?(coder: NSCoder) {
26 | fatalError("init(coder:) has not been implemented")
27 | }
28 |
29 | private func commonInit(text: String) {
30 | addSubview(titleLabel)
31 |
32 | titleLabel.text = text
33 | }
34 | }
35 |
36 | extension ApSectionTitleView: StylizingView
37 | {
38 | public func apply(use style: Style) {
39 | backgroundColor = .clear
40 |
41 | titleLabel.font = style.fonts.title
42 | titleLabel.textColor = style.colors.mainText
43 |
44 | titleLabel.snp.remakeConstraints { maker in
45 | maker.top.equalToSuperview().offset(4.0)
46 | maker.left.equalToSuperview().offset(style.layout.cellInnerInsets.left)
47 | maker.right.equalToSuperview().offset(-style.layout.cellInnerInsets.right)
48 | maker.bottom.equalToSuperview().offset(-4.0)
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/base/table/ApTableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApTableView.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 26/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Design
11 | import Common
12 |
13 | open class ApTableView: UITableView, StylizingView
14 | {
15 | public struct TableSection {
16 | public var title: String?
17 | public var content: Content
18 |
19 | public init(content: Content) {
20 | self.content = content
21 | }
22 | }
23 |
24 | private let stylizingViewsContainer = StylizingViewsContainer()
25 |
26 | public func addViewForStylizing(_ view: StylizingView, immediately: Bool = true) {
27 | stylizingViewsContainer.addView(view, immediately: immediately)
28 | }
29 |
30 | open func apply(use style: Design.Style) {
31 | backgroundColor = style.colors.background
32 | separatorColor = style.colors.separator
33 |
34 | separatorInset = .zero
35 |
36 | stylizingViewsContainer.styleDidChange(style)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/base/table/ApTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApTableViewCell.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 26/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Design
11 | import Common
12 |
13 | open class ApTableViewCell: UITableViewCell, StylizingView
14 | {
15 | public var stylizingSubviews = WeakArray()
16 |
17 | open func apply(use style: Design.Style) {
18 | self.backgroundColor = style.colors.accent
19 |
20 | selectionStyle = .default
21 | let selectedView = UIView()
22 | selectedView.backgroundColor = style.colors.separator
23 | selectedBackgroundView = selectedView
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/skeleton/SkeletonViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SkeletonViewModel.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 26/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 |
10 | public enum SkeletonViewModel
11 | {
12 | case loading
13 | case done(ViewModel)
14 | case failed
15 | }
16 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/skeleton/UIView+Skeleton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Skeleton.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SnapKit
11 |
12 | extension UIView {
13 | public var skeletonView: SkeletonView {
14 | let skeletons = subviews.compactMap { $0 as? SkeletonView}
15 | if let skeleton = skeletons.last {
16 | skeletons.forEach { bringSubviewToFront($0) }
17 | return skeleton
18 | }
19 |
20 | let skeleton = SkeletonView()
21 | addSubview(skeleton)
22 |
23 | skeleton.snp.makeConstraints { maker in
24 | maker.edges.equalToSuperview()
25 | }
26 |
27 | return skeleton
28 | }
29 |
30 | public func startSkeleton() {
31 | if isHidden {
32 | return
33 | }
34 |
35 | let skeletonViews = subviews.compactMap { $0 as? SkeletonView }
36 | UIView.animate(withDuration: 0.15, animations: {
37 | for subview in skeletonViews {
38 | subview.start()
39 | }
40 | })
41 | }
42 |
43 | public func endSkeleton() {
44 | let skeletonViews = subviews.compactMap { $0 as? SkeletonView }
45 | UIView.animate(withDuration: 0.15, animations: {
46 | for subview in skeletonViews {
47 | subview.end()
48 | }
49 | })
50 | }
51 |
52 | public func failedSkeleton() {
53 | let skeletonViews = subviews.compactMap { $0 as? SkeletonView }
54 | UIView.animate(withDuration: 0.25, animations: {
55 | for subview in skeletonViews {
56 | subview.failed()
57 | }
58 | })
59 | }
60 |
61 | public func endSkeleton(success: Bool) {
62 | if success {
63 | endSkeleton()
64 | } else {
65 | failedSkeleton()
66 | }
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/views/AvatarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AvatarView.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 28/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Common
11 | import Design
12 |
13 | open class AvatarView: IdImageView {
14 |
15 | public init() {
16 | super.init(frame: .zero)
17 | }
18 |
19 | public required init?(coder: NSCoder) {
20 | fatalError("init(coder:) has not been implemented")
21 | }
22 | }
23 |
24 | extension AvatarView
25 | {
26 | public static func generateAvatar(letter: String, size: CGFloat, style: Style) -> UIImage {
27 | return UIGraphicsImageRenderer(size: CGSize(width: size, height: size)).image { context in
28 | let label = UILabel(frame: CGRect(x: 0, y: 0, width: size, height: size))
29 | label.textAlignment = .center
30 | label.textColor = style.colors.mainText
31 | label.backgroundColor = style.colors.accent
32 | label.font = style.fonts.avatar.withSize(size * 0.5)
33 | label.text = letter
34 |
35 | label.layer.render(in: context.cgContext)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/views/GradientView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientItemView.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 27/10/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Design
11 | import Common
12 |
13 | public class GradientView: UIView {
14 | public override class var layerClass: AnyClass {
15 | return CAGradientLayer.classForCoder()
16 | }
17 |
18 | public var gradient: Gradient? {
19 | didSet { updateGradient() }
20 | }
21 |
22 | public var startPoint: CGPoint = CGPoint(x: 0.5, y: 0.0) {
23 | didSet { updateGradient() }
24 | }
25 |
26 | public var endPoint: CGPoint = CGPoint(x: 0.5, y: 1.0) {
27 | didSet { updateGradient() }
28 | }
29 |
30 | public override init(frame: CGRect = .zero) {
31 | super.init(frame: frame)
32 | }
33 |
34 | required init?(coder: NSCoder) {
35 | fatalError("init(coder:) has not been implemented")
36 | }
37 |
38 | private func updateGradient() {
39 | guard let layer = self.layer as? CAGradientLayer else {
40 | log.assert("Gradient view layer have incorrect type")
41 | return
42 | }
43 |
44 | guard let gradient = self.gradient else {
45 | layer.colors = [UIColor.clear.cgColor, UIColor.clear.cgColor]
46 | return
47 | }
48 |
49 | layer.startPoint = startPoint
50 | layer.endPoint = endPoint
51 | layer.colors = [gradient.from.cgColor, gradient.to.cgColor]
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/Modules/UIs/UIComponents/Sources/views/IdImageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IdImageView.swift
3 | // UIComponents
4 | //
5 | // Created by Alexander Ivlev on 23/11/2019.
6 | // Copyright © 2019 ApostleLife. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Common
11 |
12 | open class IdImageView: UIImageView
13 | {
14 | fileprivate var owner: ChangeableImage?
15 | fileprivate var unique = AbstractImageUnique()
16 | }
17 |
18 | // MARK: - UIImageView
19 | extension ChangeableImage {
20 | public func join(imageView: IdImageView, completion: (() -> Void)? = nil, file: String = #file, line: UInt = #line) {
21 | imageView.image = image
22 | imageView.owner = self
23 |
24 | imageView.unique = AbstractImageUnique()
25 | weakJoin(listener: { [weak self, weak imageView] (_, image) in
26 | if imageView?.owner === self {
27 | imageView?.image = image
28 | completion?()
29 | }
30 | }, owner: imageView.unique, file: file, line: line)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------