diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..7d905699 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,83 @@ +name: "LINE SDK CI" + +on: [push, pull_request] + +jobs: + test_sdk: + name: Test LINE SDK + runs-on: macos-14 + strategy: + matrix: + swift-version: ["5.0", "4.2"] + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Bundle Install + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + - name: Test SDK + env: + SWIFT_VERSION: ${{ matrix.swift-version }} + run: bundle exec fastlane sdk_tests + lint_pod: + name: Lint CocoaPods + runs-on: macos-14 + strategy: + matrix: + swift-version: ["5.0", "4.2"] + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Bundle Install + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + - name: Lint CocoaPods + env: + SWIFT_VERSION: ${{ matrix.swift-version }} + run: bundle exec fastlane lint_pod + lint_spm: + name: Lint Swift Package Manager + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Bundle Install + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + - name: Lint Swift Package Manager + run: bundle exec fastlane lint_spm + xcframework: + name: Build XCFramework + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Bundle Install + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + - name: Build XCFramework + run: bundle exec fastlane xcframework version:ci diff --git a/.gitignore b/.gitignore index ffc0258c..6c34899f 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,6 @@ docs beta_config script/to_beta.rb fastlane/.env +/.bundle/ +.swiftpm +vendor/bundle \ No newline at end of file diff --git a/.jazzy-internal.yaml b/.jazzy-internal.yaml index 26a8b3c1..46ced84c 100644 --- a/.jazzy-internal.yaml +++ b/.jazzy-internal.yaml @@ -1,4 +1,4 @@ -author: LINE Corporation +author: LY Corporation author_url: https://line.me clean: true github_url: https://github.com/line/line-sdk-ios-swift diff --git a/.jazzy.yaml b/.jazzy.yaml index 38673171..ffafb32d 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -1,4 +1,4 @@ -author: LINE Corporation +author: LY Corporation author_url: https://line.me clean: true github_url: https://github.com/line/line-sdk-ios-swift @@ -13,4 +13,7 @@ readme: REFERENCETOP.md exclude: - "LineSDK/LineSDK/Messaging/*" - "LineSDK/LineSDK/Graph/*" - - "LineSDK/LineSDK/Networking/API+Internal.swift" + - "LineSDK/LineSDK/OpenChat/*" + - "LineSDK/LineSDK/Networking/API/API+Internal.swift" + - "LineSDK/LineSDK/LineSDKUI/SharingUI/*" + - "LineSDK/LineSDK/Utils/Result.swift" \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index 437459cd..0aec50e6 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.5.0 +3.1.4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e7cf4ba..05870d66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,254 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [5.11.2] - 2024-09-26 + +### Fixed + +- Some compiler warning when building with Xcode 16.0. [#214](https://github.com/line/line-sdk-ios-swift/pull/214) + +## [5.11.1] - 2024-05-22 + +### Fixed + +- Now the `xcframework` binaries do not contain bit code anymore. [#210](https://github.com/line/line-sdk-ios-swift/pull/210) + +## [5.11.0] - 2023-11-01 + +### Added + +- Add digital signature to the xcframework artifacts. Now the xcframework binaries are signed with certificate of LINE Developer team. [#199](https://github.com/line/line-sdk-ios-swift/pull/199) +- Add the privacy manifest file to the resource bundle. [#200](https://github.com/line/line-sdk-ios-swift/pull/200) + +## [5.10.1] - 2023-10-02 + +### Fixed + +- Update license holder name in all source code files. Now LY Corporation is the license holder of LINE SDK Swift. The license content and terms itself is not changed so you can still use the SDK under the same condition as before. [#193](https://github.com/line/line-sdk-ios-swift/pull/193) +- As the removal of general support of iOS 12 and earlier in Xcode, now LINE SDK Swift also drops support for iOS 12 and earlier. [#190](https://github.com/line/line-sdk-ios-swift/pull/190) + +## [5.10.0] - 2023-07-19 + +### Added + +- An option `promptBotID` to append related query for internal partner use. [#188](https://github.com/line/line-sdk-ios-swift/pull/188). + +## [5.9.1] - 2023-06-08 + +### Fixed + +- Append `IDTokenRaw` property to ObjC's `LineSDKAccessToken` class. This allows retrieve the raw value of the received ID token when using the Objective-C wrapper. [#185](https://github.com/line/line-sdk-ios-swift/issues/185) + +## [5.9.0] - 2022-09-20 + +This version drops supports for iOS 10 since Xcode 14 is not supporting 32-bit devices anymore. For more information, please refer to [32-bit app support has been discontinued in Xcode 14](https://developer.apple.com/news/upcoming-requirements/?id=06062022a). + +### Added + +- SwiftUI sample app. If you are using SwiftUI and want to integrate LINE SDK Swift into your SwiftUI project, please refer to the SwiftUI sample app to get started. [#177](https://github.com/line/line-sdk-ios-swift/pull/177) +- Support for Xcode 14 and iOS 16. [#178](https://github.com/line/line-sdk-ios-swift/pull/178) + +### Fixed + +- Drops iOS 10 support. Now LINE SDK Swift requires iOS 11 or later. If you have to continue to support iOS 10, use LINE SDK Swift 5.8.2. + +## [5.8.2] - 2022-05-24 + +### Fixed + +- Xcode 13.4 support. Fixed an issue that when built with Xcode 13.4, there is a chance that linking fails due to reference cycle. [#174](https://github.com/line/line-sdk-ios-swift/pull/174) +- Modernize the project to the latest Xcode and Swift version. +- Now the xcframework contains debug symbols (dSYMs and bitcode map) in the framework. Integrating LINE SDK by xcframework now provides easier setup steps. [#175](https://github.com/line/line-sdk-ios-swift/pull/175) + +## [5.8.1] - 2021-11-17 + +### Fixed + +- Upgrade project settings for Xcode 13 and other tool dependencies. [#170](https://github.com/line/line-sdk-ios-swift/pull/170) + +## [5.8.0] - 2021-08-19 + +### Added + +- New permissions in `LoginPermission` for Open Chat Plug features. [#168](https://github.com/line/line-sdk-ios-swift/pull/168) + +## [5.7.0] - 2020-09-16 + +### Added + +- Xcode 12 support. The new Swift Package Manager support. Now the built-in UI of LINE SDK is also available when integrated with SPM. [#161](https://github.com/line/line-sdk-ios-swift/pull/161) + +## [5.6.2] - 2020-08-14 + +### Fixed + +- Auto retry `NSURLErrorNetworkConnectionLost` error once when exchanging token. This is a workaround for a long-last iOS bug [discussed here](https://github.com/line/line-sdk-ios-swift/blob/18b713af91f8b46c5aa4674e0af9c424339b96ae/LineSDK/LineSDK/Login/LoginProcess.swift#L246-L261). [#159](https://github.com/line/line-sdk-ios-swift/pull/159) + +## [5.6.1] - 2020-08-05 + +### Fixed + +- The completion handler for logging out was not called for HTTP status code 400. It now should be always called and not causing a hanging. [#158](https://github.com/line/line-sdk-ios-swift/pull/158) + +## [5.6.0] - 2020-06-11 + +### Added + +- Open Chat support. You can create an Open Chat room with LINE SDK for Swift now. There are also some other public APIs to help checking room availability or user's memebership for a given Open Chat room. Check the [official documentation](https://developers.line.biz/en/docs/ios-sdk/swift/overview/) and [API reference](https://developers.line.biz/en/reference/ios-sdk-swift/) of `OpenChatCreatingController` for more information. [#132](https://github.com/line/line-sdk-ios-swift/pull/132) + +### Fixed + +- Lots of improvement of reference and documentation comment. + +## [5.5.2] - 2020-04-30 + +### Fixed + +- Now explicitly log in with web view does not trigger the "LINE is not installed" warning on the login screen page. + +## [5.5.1] - 2020-02-27 + +### Fixed + +- Use standard parameter names from [PKCE for OAuth 2.0](https://oauth.net/2/pkce/) to replace the original One-Time-Password mechanism. [#133](https://github.com/line/line-sdk-ios-swift/pull/133) +- An issue that some symbols cannot be found in XCFramework binary for Objective-C wrapper. [#140](https://github.com/line/line-sdk-ios-swift/pull/140) +- Fix several dangling pointer warning for Xcode 11.4. [#141](https://github.com/line/line-sdk-ios-swift/pull/141) +- Improve security for `state` and `nonce` generating to use a better random generator from Security.framework. [#137](https://github.com/line/line-sdk-ios-swift/pull/137) + +## [5.5.0] - 2019-12-17 + +### Added + +- Add the `displayNameOverridden` and `displayNameOriginal` properties to `User` when getting friends list. Currently the `User.displayName` is a combination of `displayNameOverridden` and `displayNameOriginal`. It is a preferred version of user's name for displaying and searching. [#125](https://github.com/line/line-sdk-ios-swift/pull/125) +- Support for `xcframework`. Now you can download binary format of LINE SDK and LINE SDK Objective-C wrapper as `xcframework`, as well as the related dSYMs and symbol map files from the release page. To implement this feature, we modified a bit for the exposed Objective-C wrapper module, to make sure the binary compatibility not broken in future releases. [#126](https://github.com/line/line-sdk-ios-swift/pull/126) + +### Fixed + +- A missing localization for pt-BR when searching in sharing list panel. [#127](https://github.com/line/line-sdk-ios-swift/pull/127) + +## [5.4.0] - 2019-11-29 + +### Added + +- Sharing UI support. Now you can request `.oneTimeShare` permission and present a `ShareViewController` to let users select messages and share these messages to their friends or groups. LINE SDK provides a pre-defined UI for sharing messages. You can also build your own UI based on public methods in the SDK. [#79](https://github.com/line/line-sdk-ios-swift/pull/79) +- Properties in `Friend` and `Group` to retrieve the "large" version and "small" version of a profile image. [#30](https://github.com/line/line-sdk-ios-swift/pull/30) +- A new `relation` sort option to get graph list sorted by relationship between current user and friends. [#30](https://github.com/line/line-sdk-ios-swift/pull/30) +- Support for macCatalyst as a build target. [#123](https://github.com/line/line-sdk-ios-swift/pull/123) +- Support for building against Swift Package Manager. Currently SPM does not support adding resource, so all UI related parts (such as `LoginButton` and `ShareViewController`) are eliminated from SPM build. [#70](https://github.com/line/line-sdk-ios-swift/pull/70) +- Replace `LoginManagerOptions` with `LoginManager.Parameters` for flexible parameter configuration while login. [#119](https://github.com/line/line-sdk-ios-swift/pull/119) +- Provide a way to set customized `IDTokenNonce` as the `nonce` value in ID Token. [#119](https://github.com/line/line-sdk-ios-swift/pull/119) +- Now message payload setting provides more public setter. You can create a customized message payload much easier. [#90](https://github.com/line/line-sdk-ios-swift/pull/90) +- `APIErrorDetail` is now public, so you can get the detail error information when a `.invalidHTTPStatusAPIError` error happens. [#115](https://github.com/line/line-sdk-ios-swift/pull/115) +- Dark mode is supported now for iOS 13 or later. Although all parts of LINE SDK is compatible with the dark mode, the login page and consent pages are not yet. They will be prepared eventually without a native SDK release. [#105](https://github.com/line/line-sdk-ios-swift/pull/105) + +### Fixed + +- Now `resource_bundles` is used instead of `resources` when integrated by CocoaPods. [#77](https://github.com/line/line-sdk-ios-swift/pull/77) + +### Deprecated + +- `LoginManagerOptions` and the related login method is deprecated. Use `LoginManager.Parameters` instead. [#119](https://github.com/line/line-sdk-ios-swift/pull/119/files#diff-f055b8fa041c67b8c8f2bd173ba83669) +- `preferredWebPageLanguage` is deprecated. Use the property with the same name in `LoginManager.Parameters` instead. [#119](https://github.com/line/line-sdk-ios-swift/pull/119/files#diff-f055b8fa041c67b8c8f2bd173ba83669) +- The general error type (`Error`) version of error handling delegate method in `LoginButtonDelegate` is deprecated. Use the specific `LineSDKError` version instead. [#120](https://github.com/line/line-sdk-ios-swift/pull/120) +- All token related APIs in `API` are now deprecated. They are moved to `API.Auth` to distinguish from the normal public APIs. Not like `API`, methods in `API.Auth` will not try to automatically refresh your access token. [#118](https://github.com/line/line-sdk-ios-swift/pull/118) + + +## [5.3.1] - 2019-10-25 + +### Fixed + +- Web page preference language for Japanese now works properly with correct language code. [#113](https://github.com/line/line-sdk-ios-swift/pull/113) + +## [5.3.0] - 2019-09-17 + +### Added + +- Add `IDTokenNonce` to `LoginResult`. This value can be used against the ID token verification API as a parameter. + +### Fixed + +- Some improvement in documentation spelling and grammar. + + +## [5.2.4] - 2019-08-23 + +### Fixed + +- Source application validation is removed. Login with LINE app now works correctly on iOS 13. [#97](https://github.com/line/line-sdk-ios-swift/pull/97) + +## [5.2.3] - 2019-08-01 + +### Fixed + +- An issue that the stored ID Token will be overwritten when a refreshed token is issued. [#88](https://github.com/line/line-sdk-ios-swift/pull/88) + +## [5.2.2] - 2019-07-29 + +### Fixed + +- When verifying token, get the provider metadata `issuer` from open ID discovery document, instead of a fixed value. [#86](https://github.com/line/line-sdk-ios-swift/pull/86) + +## [5.2.1] - 2019-07-19 + +### Fixed + +- Align the behavior of `LineSDKLoginButton` (wrapper class) to LoginButton, when user click login, will only return if login process is ongoing. [#78](https://github.com/line/line-sdk-ios-swift/pull/78) + +## [5.2.0] - 2019-06-12 + +### Added + +- Support for customizing the language used when login through web page. Set `preferredWebPageLanguage` of `LoginManager` to apply the required language. The default behavior (using the system language on user's device) is not changed. [#61](https://github.com/line/line-sdk-ios-swift/pull/61) +- Support for accessing AMR (Authentication Methods References) value in ID Token. [#63](https://github.com/line/line-sdk-ios-swift/pull/63) +- Now you can use either Swift 4.2 or Swift 5.0 when integrating LINE SDK with CocoaPods. [#60](https://github.com/line/line-sdk-ios-swift/pull/60) + +### Fixed + +- The `refreshToken` in `AccessToken` is now marked as `private`. We do not encourage you to use or store the refresh token yourself. Instead, always use the refresh token API from client when you want to get a new access token. + + +## [5.1.2] - 2019-04-15 + +### Fixed + +- Logging out a user now revokes refresh token and its corresponding access tokens, instead of the current access token only. [#45](https://github.com/line/line-sdk-ios-swift/pull/45) + +## [5.1.1] - 2019-03-28 + +### Fixed + +- Allow additional application bundle ID of LINE apps to grant authorization code. + +## [5.1.0] - 2019-02-26 + +### Added + +- Some model types also support `Encodable` now for easier serialization. +- Support JSON conversion for Objective-C model wrapper classes for future features. +- Now you can get the raw ID Token value for server verification purpose. +- Add compatibility for Swift 5.0 and Xcode 10.2. + +## [5.0.3] - 2019-01-17 + +### Fixed + +- Build LineSDKObjC with Carthage now works properly with all targets included. [#13](https://github.com/line/line-sdk-ios-swift/issues/13) + +## [5.0.2] - 2018-12-18 + +### Fixed + +- A compiling crash when using Swift 5.0 tool chain to compile LINE SDK. [#6](https://github.com/line/line-sdk-ios-swift/issues/6), [SR-9375](https://bugs.swift.org/browse/SR-9375), [Swift #21296](https://github.com/apple/swift/pull/21296) +- An internal improvement on JWK handling. +- Improvement on documentation spelling and grammar. [#9](https://github.com/line/line-sdk-ios-swift/pull/9) + +## [5.0.1] - 2018-11-29 + +### Fixed + +- Improve ID Token signature verifying code to use latest Security framework API. [#4](https://github.com/line/line-sdk-ios-swift/pull/4) +- Hide an implementation detail in the sample app. [#2](https://github.com/line/line-sdk-ios-swift/pull/2) + ## [5.0.0] - 2018-11-20 Initial release of LINE SDK Swift. Now the LINE SDK is an open source project. @@ -22,4 +270,35 @@ LINE SDK version 5 is not compatible with version 4.x. To upgrade to version 5, - A potential issue which causes authorizing from LINE app may fail on devices with iOS 12. - The automatically token refreshing should now work properly when receives a token expiring error from LINE Login Server. -[5.0.0]: https://github.com/line/line-sdk-ios-swift/tree/5.0.0 +[5.0.0]: https://github.com/line/line-sdk-ios-swift/releases/tag/5.0.0 +[5.0.1]: https://github.com/line/line-sdk-ios-swift/compare/5.0.0...5.0.1 +[5.0.2]: https://github.com/line/line-sdk-ios-swift/compare/5.0.1...5.0.2 +[5.0.3]: https://github.com/line/line-sdk-ios-swift/compare/5.0.2...5.0.3 +[5.1.0]: https://github.com/line/line-sdk-ios-swift/compare/5.0.3...5.1.0 +[5.1.1]: https://github.com/line/line-sdk-ios-swift/compare/5.1.0...5.1.1 +[5.1.2]: https://github.com/line/line-sdk-ios-swift/compare/5.1.1...5.1.2 +[5.2.0]: https://github.com/line/line-sdk-ios-swift/compare/5.1.2...5.2.0 +[5.2.1]: https://github.com/line/line-sdk-ios-swift/compare/5.2.0...5.2.1 +[5.2.2]: https://github.com/line/line-sdk-ios-swift/compare/5.2.1...5.2.2 +[5.2.3]: https://github.com/line/line-sdk-ios-swift/compare/5.2.2...5.2.3 +[5.2.4]: https://github.com/line/line-sdk-ios-swift/compare/5.2.3...5.2.4 +[5.3.0]: https://github.com/line/line-sdk-ios-swift/compare/5.2.4...5.3.0 +[5.3.1]: https://github.com/line/line-sdk-ios-swift/compare/5.3.0...5.3.1 +[5.4.0]: https://github.com/line/line-sdk-ios-swift/compare/5.3.1...5.4.0 +[5.5.0]: https://github.com/line/line-sdk-ios-swift/compare/5.4.0...5.5.0 +[5.5.1]: https://github.com/line/line-sdk-ios-swift/compare/5.5.0...5.5.1 +[5.5.2]: https://github.com/line/line-sdk-ios-swift/compare/5.5.1...5.5.2 +[5.6.0]: https://github.com/line/line-sdk-ios-swift/compare/5.5.2...5.6.0 +[5.6.1]: https://github.com/line/line-sdk-ios-swift/compare/5.6.0...5.6.1 +[5.6.2]: https://github.com/line/line-sdk-ios-swift/compare/5.6.1...5.6.2 +[5.7.0]: https://github.com/line/line-sdk-ios-swift/compare/5.6.2...5.7.0 +[5.8.0]: https://github.com/line/line-sdk-ios-swift/compare/5.7.0...5.8.0 +[5.8.1]: https://github.com/line/line-sdk-ios-swift/compare/5.8.0...5.8.1 +[5.8.2]: https://github.com/line/line-sdk-ios-swift/compare/5.8.1...5.8.2 +[5.9.0]: https://github.com/line/line-sdk-ios-swift/compare/5.8.2...5.9.0 +[5.9.1]: https://github.com/line/line-sdk-ios-swift/compare/5.9.0...5.9.1 +[5.9.1]: https://github.com/line/line-sdk-ios-swift/compare/5.9.1...5.10.0 +[5.10.1]: https://github.com/line/line-sdk-ios-swift/compare/5.10.0...5.10.1 +[5.11.0]: https://github.com/line/line-sdk-ios-swift/compare/5.10.1...5.11.0 +[5.11.1]: https://github.com/line/line-sdk-ios-swift/compare/5.11.0...5.11.1 +[5.11.2]: https://github.com/line/line-sdk-ios-swift/compare/5.11.1...5.11.2 diff --git a/Gemfile b/Gemfile index 81fbd35c..4a27c64a 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source "https://rubygems.org" gem "fastlane" gem "jazzy" gem "xcode-install" +gem "cocoapods" plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index d1bc6260..598877c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,232 +1,324 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.0) - activesupport (4.2.10) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) + CFPropertyList (3.0.7) + base64 + nkf + rexml + activesupport (7.2.1.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + artifactory (3.0.17) atomos (0.1.3) - babosa (1.0.2) - claide (1.0.2) - cocoapods (1.5.3) - activesupport (>= 4.0.2, < 5) + aws-eventstream (1.3.0) + aws-partitions (1.998.0) + aws-sdk-core (3.211.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.95.0) + aws-sdk-core (~> 3, >= 3.210.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.169.0) + aws-sdk-core (~> 3, >= 3.210.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.10.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.2.0) + bigdecimal (3.1.8) + claide (1.1.0) + cocoapods (1.16.1) + addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.5.3) - cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.0, < 2.0) + cocoapods-core (= 1.16.1) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (~> 2.0.1) + fourflusher (>= 2.3.0, < 3.0) gh_inspector (~> 1.0) - molinillo (~> 0.6.5) + molinillo (~> 0.8.0) nap (~> 1.0) - ruby-macho (~> 1.1) - xcodeproj (>= 1.5.7, < 2.0) - cocoapods-core (1.5.3) - activesupport (>= 4.0.2, < 6) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.26.0, < 2.0) + cocoapods-core (1.16.1) + activesupport (>= 5.0, < 8) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.2) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (2.1) cocoapods-plugins (1.0.0) nap - cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.1) + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored (1.2) colored2 (3.1.2) - commander-fastlane (4.4.6) - highline (~> 1.7.2) - concurrent-ruby (1.1.3) - declarative (0.0.10) - declarative-option (0.1.0) - domain_name (0.5.20180417) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.5.0) - emoji_regex (0.1.1) + commander (4.6.0) + highline (~> 2.0.0) + concurrent-ruby (1.3.4) + connection_pool (2.4.1) + declarative (0.0.20) + digest-crc (0.6.5) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + drb (2.2.1) + emoji_regex (3.2.3) escape (0.0.4) - excon (0.62.0) - faraday (0.15.3) - multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) + ethon (0.16.0) + ffi (>= 1.15.0) + excon (0.112.0) + faraday (1.10.4) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) http-cookie (~> 1.0.0) - faraday_middleware (0.12.2) - faraday (>= 0.7.4, < 1.0) - fastimage (2.1.4) - fastlane (2.108.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.3.1) + fastlane (2.225.0) CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) - babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 2.0.0) - colored - commander-fastlane (>= 4.4.6, < 5.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (~> 0.1) - excon (>= 0.45.0, < 1.0.0) - faraday (~> 0.9) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 0.9) + faraday_middleware (~> 1.0) fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.21.2, < 0.24.0) - highline (>= 1.7.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) json (< 3.0.0) - mini_magick (~> 4.5.1) - multi_json - multi_xml (~> 0.5) - multipart-post (~> 2.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) - public_suffix (~> 2.0.0) - rubyzip (>= 1.2.2, < 2.0.0) - security (= 0.1.3) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 1.6.2, < 2.0.0) - terminal-table (>= 1.4.5, < 2.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) - xcodeproj (>= 1.6.0, < 2.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-changelog (0.12.0) - ffi (1.9.25) - fourflusher (2.0.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-changelog (0.16.0) + fastlane-plugin-create_xcframework (1.1.2) + fastlane-sirp (1.0.0) + sysrandom (~> 1.0) + ffi (1.17.0-arm64-darwin) + fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-api-client (0.23.9) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.5, < 0.7.0) - httpclient (>= 2.8.1, < 3.0) - mime-types (~> 3.0) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.9) - googleauth (0.6.7) - faraday (~> 0.12) + retriable (>= 2.0, < 4.a) + rexml + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.7.1) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.7) - highline (1.7.10) - http-cookie (1.0.3) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.7) domain_name (~> 0.5) httpclient (2.8.3) - i18n (0.9.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) - jazzy (0.9.4) - cocoapods (~> 1.0) - mustache (~> 0.99) - open4 - redcarpet (~> 3.2) - rouge (>= 2.0.6, < 4.0) - sass (~> 3.4) + jazzy (0.15.3) + cocoapods (~> 1.5) + mustache (~> 1.1) + open4 (~> 1.3) + redcarpet (~> 3.4) + rexml (>= 3.2.7, < 4.0) + rouge (>= 2.0.6, < 5.0) + sassc (~> 2.1) sqlite3 (~> 1.3) xcinvoke (~> 0.3.0) - json (2.1.0) - jwt (2.1.0) + jmespath (1.6.2) + json (2.7.4) + jwt (2.9.3) + base64 liferaft (0.0.6) - memoist (0.16.0) - mime-types (3.2.2) - mime-types-data (~> 3.2015) - mime-types-data (3.2018.0812) - mini_magick (4.5.1) - minitest (5.11.3) - molinillo (0.6.6) - multi_json (1.13.1) - multi_xml (0.6.0) - multipart-post (2.0.0) - mustache (0.99.8) - nanaimo (0.2.6) + logger (1.6.1) + mini_magick (4.13.2) + mini_mime (1.1.5) + minitest (5.25.1) + molinillo (0.8.0) + multi_json (1.15.0) + multipart-post (2.4.1) + mustache (1.1.1) + nanaimo (0.4.0) nap (1.1.0) - naturally (2.2.0) + naturally (2.2.1) netrc (0.11.0) + nkf (0.2.0) open4 (1.3.4) - os (1.0.0) - plist (3.4.0) - public_suffix (2.0.5) - rb-fsevent (0.10.3) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - redcarpet (3.4.0) - representable (3.0.4) + optparse (0.5.0) + os (1.1.4) + plist (3.7.1) + public_suffix (4.0.7) + rake (13.2.1) + redcarpet (3.6.0) + representable (3.2.0) declarative (< 0.1.0) - declarative-option (< 0.2.0) + trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) + rexml (3.3.9) rouge (2.0.7) - ruby-macho (1.3.1) - rubyzip (1.2.2) - sass (3.7.2) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - security (0.1.3) - signet (0.11.0) - addressable (~> 2.3) - faraday (~> 0.9) + ruby-macho (2.5.1) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + sassc (2.4.0) + ffi (~> 1.9) + securerandom (0.3.1) + security (0.1.5) + signet (0.19.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.5) + simctl (1.6.10) CFPropertyList naturally - slack-notifier (2.3.2) - sqlite3 (1.3.13) - terminal-notifier (1.8.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - tty-cursor (0.6.0) - tty-screen (0.6.5) - tty-spinner (0.8.0) - tty-cursor (>= 0.5.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) + sqlite3 (1.7.3-arm64-darwin) + sysrandom (1.0.5) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + typhoeus (1.4.1) + ethon (>= 0.9.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.5) - unicode-display_width (1.4.0) + unicode-display_width (2.6.0) word_wrap (1.0.0) xcinvoke (0.3.0) liferaft (~> 0.0.6) - xcode-install (2.4.4) - claide (>= 0.9.1, < 1.1.0) + xcode-install (2.8.1) + claide (>= 0.9.1) fastlane (>= 2.1.0, < 3.0.0) - xcodeproj (1.7.0) + xcodeproj (1.26.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.6) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) + xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) PLATFORMS - ruby + arm64-darwin-22 DEPENDENCIES + cocoapods fastlane fastlane-plugin-changelog + fastlane-plugin-create_xcframework jazzy xcode-install BUNDLED WITH - 1.17.1 + 2.3.26 diff --git a/LineSDK.xcworkspace/contents.xcworkspacedata b/LineSDK.xcworkspace/contents.xcworkspacedata index fa42b136..d3061975 100644 --- a/LineSDK.xcworkspace/contents.xcworkspacedata +++ b/LineSDK.xcworkspace/contents.xcworkspacedata @@ -7,4 +7,7 @@ + + diff --git a/LineSDK.xcworkspace/xcshareddata/IDETemplateMacros.plist b/LineSDK.xcworkspace/xcshareddata/IDETemplateMacros.plist index c627dff6..6322a8f8 100644 --- a/LineSDK.xcworkspace/xcshareddata/IDETemplateMacros.plist +++ b/LineSDK.xcworkspace/xcshareddata/IDETemplateMacros.plist @@ -6,13 +6,13 @@ // ___FILENAME___ // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Resource.bundle/Info.plist b/LineSDK.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 62% rename from LineSDK/LineSDK/Resource.bundle/Info.plist rename to LineSDK.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index cd6eeeec..54782e32 100644 --- a/LineSDK/LineSDK/Resource.bundle/Info.plist +++ b/LineSDK.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -2,9 +2,7 @@ - CFBundleShortVersionString - 5.0.0 - CFBundleVersion - 402 + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + diff --git a/LineSDK/LineSDK.xcodeproj/project.pbxproj b/LineSDK/LineSDK.xcodeproj/project.pbxproj index b7914a7f..c426942f 100644 --- a/LineSDK/LineSDK.xcodeproj/project.pbxproj +++ b/LineSDK/LineSDK.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -12,16 +12,22 @@ 3F946A212126D13A009914ED /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3F946A202126D13A009914ED /* Default-568h@2x.png */; }; 3FE8E370214A6E70009E91BD /* LineSDKLoginButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8E36F214A6E70009E91BD /* LineSDKLoginButton.swift */; }; 4B08F9C22119863500B140DF /* LineSDKErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08F9C12119863500B140DF /* LineSDKErrorTests.swift */; }; + 4B0FD5822330D0270054A4E8 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0FD5812330D0270054A4E8 /* Colors.swift */; }; 4B15EEF4211D466D00866E6C /* TextMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B15EEF3211D466D00866E6C /* TextMessage.swift */; }; 4B15EEF6211D46FF00866E6C /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B15EEF5211D46FF00866E6C /* Message.swift */; }; 4B15EEF8211D473400866E6C /* MessageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B15EEF7211D473400866E6C /* MessageSender.swift */; }; 4B15EEFB211D5BF800866E6C /* TextMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B15EEFA211D5BF800866E6C /* TextMessageTests.swift */; }; 4B15EEFD211D628000866E6C /* MessageSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B15EEFC211D628000866E6C /* MessageSample.swift */; }; + 4B1658052252EF3C0008E441 /* DownloadableImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1658042252EF3C0008E441 /* DownloadableImageView.swift */; }; + 4B19DC952398974900E51458 /* OptionSelectingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B19DC942398974900E51458 /* OptionSelectingViewController.swift */; }; + 4B19DC982398A08400E51458 /* StyleNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B19DC972398A08400E51458 /* StyleNavigationController.swift */; }; + 4B1A4EF722EAA0390030F560 /* PostMessageSendingTokenRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1A4EF622EAA0390030F560 /* PostMessageSendingTokenRequestTests.swift */; }; 4B1C44652137E0CA0094C1D4 /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1C44642137E0CA0094C1D4 /* JWT.swift */; }; 4B1C44692137E1EE0094C1D4 /* JWTHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1C44682137E1EE0094C1D4 /* JWTHelpers.swift */; }; 4B1C446B2137E98D0094C1D4 /* JWTCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1C446A2137E98D0094C1D4 /* JWTCoder.swift */; }; 4B2422EF2134FBE8007200C2 /* LineSDKErrorConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2422EE2134FBE8007200C2 /* LineSDKErrorConstant.swift */; }; 4B25A815213687A400C74B87 /* RSA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B25A814213687A400C74B87 /* RSA.swift */; }; + 4B26F6AF221D266D00F33BF4 /* ResultUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B26F6AE221D266D00F33BF4 /* ResultUtils.swift */; }; 4B2D14E3212F8EDA000DD5BE /* LineSDKUserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D14E2212F8EDA000DD5BE /* LineSDKUserProfile.swift */; }; 4B2D14E9212F9270000DD5BE /* LineSDKAccessTokenVerifyResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D14E8212F9270000DD5BE /* LineSDKAccessTokenVerifyResult.swift */; }; 4B2D14ED212F931D000DD5BE /* LineSDKModelInterfaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D14EC212F931D000DD5BE /* LineSDKModelInterfaceTests.m */; }; @@ -32,6 +38,10 @@ 4B2D1501212FA9EE000DD5BE /* LineSDKHexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D1500212FA9EE000DD5BE /* LineSDKHexColor.swift */; }; 4B2D1503212FAB5A000DD5BE /* LineSDKAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D1502212FAB5A000DD5BE /* LineSDKAPIError.swift */; }; 4B2D1505212FABD2000DD5BE /* LineSDKAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D1504212FABD2000DD5BE /* LineSDKAPI.swift */; }; + 4B32D62423FFBDA500052485 /* Resource.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 4BBAFC4A2101CE8100E7BFF6 /* Resource.bundle */; }; + 4B32D62523FFBDAD00052485 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3F75522E2123D214004AC047 /* Assets.xcassets */; }; + 4B392662224DC06F006485B4 /* ShareTargetTableViewStyling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B392661224DC06F006485B4 /* ShareTargetTableViewStyling.swift */; }; + 4B392664224DE2E1006485B4 /* ShareTargetSelectingSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B392663224DE2E1006485B4 /* ShareTargetSelectingSectionHeaderView.swift */; }; 4B39D1DE211044B000A45510 /* PostTokenExchangeRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B39D1DD211044B000A45510 /* PostTokenExchangeRequestTests.swift */; }; 4B39D1E02110496C00A45510 /* GetUserProfileRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B39D1DF2110496C00A45510 /* GetUserProfileRequestTests.swift */; }; 4B39D1E221104C7A00A45510 /* LoginManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B39D1E121104C7A00A45510 /* LoginManagerExtension.swift */; }; @@ -42,30 +52,50 @@ 4B3CCB9A21523A2600F51D76 /* CryptoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CCB9921523A2600F51D76 /* CryptoKey.swift */; }; 4B3CCB9C21523B1300F51D76 /* ECDSA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CCB9B21523B1300F51D76 /* ECDSA.swift */; }; 4B3CCB9E2152449400F51D76 /* ECDSAKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CCB9D2152449400F51D76 /* ECDSAKeyTests.swift */; }; + 4B3D78BB22420BEA00DE27D1 /* PageTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3D78BA22420BEA00DE27D1 /* PageTabView.swift */; }; 4B414D5C210EF12C00FD19BC /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B414D5B210EF12C00FD19BC /* APIError.swift */; }; 4B414D5E210F077700FD19BC /* RequestStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B414D5D210F077700FD19BC /* RequestStubs.swift */; }; + 4B415938225AE40E003AE63A /* ShareViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B415937225AE40E003AE63A /* ShareViewControllerDelegate.swift */; }; + 4B41593A225AE464003AE63A /* MessageShareTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B415939225AE464003AE63A /* MessageShareTargetType.swift */; }; + 4B41593C225AE497003AE63A /* LineSDKShareViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41593B225AE497003AE63A /* LineSDKShareViewControllerDelegate.swift */; }; + 4B41593E225AE4E0003AE63A /* LineSDKMessageShareTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41593D225AE4E0003AE63A /* LineSDKMessageShareTargetType.swift */; }; + 4B415940225AE65A003AE63A /* LineSDKShareTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41593F225AE65A003AE63A /* LineSDKShareTarget.swift */; }; + 4B415942225AF024003AE63A /* LineSDKAuthorizationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B415941225AF024003AE63A /* LineSDKAuthorizationStatus.swift */; }; + 4B42816621ED9CE6004A0846 /* JSONConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B42816521ED9CE6004A0846 /* JSONConverter.swift */; }; 4B4464E8212D093D008D3624 /* TemplateMessageProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4464E7212D093D008D3624 /* TemplateMessageProperties.swift */; }; 4B45256B2101810D00A39D4F /* LoginProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B45256A2101810D00A39D4F /* LoginProcess.swift */; }; 4B45256D2101829F00A39D4F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B45256C2101829F00A39D4F /* Helpers.swift */; }; 4B45256F210188C300A39D4F /* LoginPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B45256E210188C300A39D4F /* LoginPermission.swift */; }; 4B4525712101938F00A39D4F /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4525702101938F00A39D4F /* Request.swift */; }; 4B45257621019EFB00A39D4F /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B45257521019EFB00A39D4F /* Session.swift */; }; - 4B45257821019FCC00A39D4F /* PostOTPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B45257721019FCC00A39D4F /* PostOTPRequest.swift */; }; - 4B45257B2101A4E900A39D4F /* OneTimePassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B45257A2101A4E900A39D4F /* OneTimePassword.swift */; }; 4B4A9AF32159FCF700915054 /* LineSDKConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A9AF22159FCF700915054 /* LineSDKConstant.swift */; }; + 4B4B363723E7B3AA0016E2C0 /* StringExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B363623E7B3AA0016E2C0 /* StringExtensionTests.swift */; }; 4B4F0B7E21080C51006D17F5 /* PostExchangeTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F0B7D21080C51006D17F5 /* PostExchangeTokenRequest.swift */; }; 4B4F0B8021080D69006D17F5 /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F0B7F21080D69006D17F5 /* AccessToken.swift */; }; 4B4F0B8221083CDB006D17F5 /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F0B8121083CDB006D17F5 /* Constant.swift */; }; 4B4F0B8421084755006D17F5 /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F0B8321084755006D17F5 /* UserProfile.swift */; }; 4B4F0B8D210870EF006D17F5 /* GetUserProfileRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F0B8C210870EF006D17F5 /* GetUserProfileRequest.swift */; }; + 4B5B0EF32241DEA900BA59A0 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B0EF22241DEA900BA59A0 /* ShareViewController.swift */; }; + 4B5B0EF52241DF3E00BA59A0 /* PageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B0EF42241DF3E00BA59A0 /* PageViewController.swift */; }; 4B5EE2DD212BAF2C0009DF2E /* FlexSpacerComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5EE2DC212BAF2C0009DF2E /* FlexSpacerComponent.swift */; }; 4B5EE2DF212BAFA00009DF2E /* FlexSpacerComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5EE2DE212BAFA00009DF2E /* FlexSpacerComponentTests.swift */; }; 4B5EE2E1212BB53F0009DF2E /* FlexBoxComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5EE2E0212BB53F0009DF2E /* FlexBoxComponentTests.swift */; }; 4B5EE2E4212BCE1A0009DF2E /* FlexBlockStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5EE2E3212BCE1A0009DF2E /* FlexBlockStyle.swift */; }; 4B5EE2E6212BD53E0009DF2E /* FlexBubbleContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5EE2E5212BD53E0009DF2E /* FlexBubbleContainerTests.swift */; }; 4B5EE2E8212BE0D00009DF2E /* FlexCarouselContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5EE2E7212BE0D00009DF2E /* FlexCarouselContainerTests.swift */; }; + 4B603F032396342800B81E56 /* FormEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B603F022396342800B81E56 /* FormEntry.swift */; }; + 4B603F05239638CF00B81E56 /* FormSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B603F04239638CF00B81E56 /* FormSection.swift */; }; + 4B61C6602394CE87005D7C21 /* OpenChatCreatingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B61C65F2394CE87005D7C21 /* OpenChatCreatingNavigationController.swift */; }; + 4B62425222CAF2000092B92F /* PostMessageSendingTokenIssueRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B62425122CAF2000092B92F /* PostMessageSendingTokenIssueRequest.swift */; }; 4B63F4732106FDCC003D1BF1 /* LoginProcessURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B63F4722106FDCC003D1BF1 /* LoginProcessURLResponse.swift */; }; 4B6508B8211812CE001796E0 /* LoginManagerOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6508B7211812CE001796E0 /* LoginManagerOptions.swift */; }; + 4B67EA3823E2754000A6ADCD /* LineSDKOpenChatCreatingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67EA3723E2754000A6ADCD /* LineSDKOpenChatCreatingController.swift */; }; + 4B67EA3A23E2758800A6ADCD /* LineSDKOpenChatCreatingControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67EA3923E2758800A6ADCD /* LineSDKOpenChatCreatingControllerDelegate.swift */; }; + 4B67EA3C23E2765400A6ADCD /* LineSDKOpenChatRoomInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67EA3B23E2765400A6ADCD /* LineSDKOpenChatRoomInfo.swift */; }; + 4B67EA3E23E2770900A6ADCD /* LineSDKOpenChatRoomCreatingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67EA3D23E2770900A6ADCD /* LineSDKOpenChatRoomCreatingItem.swift */; }; + 4B67EA4023E280D700A6ADCD /* AuthorizationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67EA3F23E280D700A6ADCD /* AuthorizationStatus.swift */; }; + 4B688E3E226F029B002575CC /* LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B688E3D226F029B002575CC /* LoadingIndicator.swift */; }; + 4B6972972396433C0079B7D2 /* OpenChatRoomNameTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6972962396433C0079B7D2 /* OpenChatRoomNameTableViewCell.swift */; }; 4B6A0FF1212A99AD00B3ED1F /* FlexTextComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A0FF0212A99AD00B3ED1F /* FlexTextComponentTests.swift */; }; 4B6A0FF3212AA15D00B3ED1F /* FlexButtonComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A0FF2212AA15D00B3ED1F /* FlexButtonComponent.swift */; }; 4B6A0FF5212AA82500B3ED1F /* HexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A0FF4212AA82500B3ED1F /* HexColor.swift */; }; @@ -74,9 +104,10 @@ 4B6A7026210956B600D71C66 /* KeychainStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A7025210956B600D71C66 /* KeychainStore.swift */; }; 4B6A702921096E3700D71C66 /* KeychainStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A702821096E3700D71C66 /* KeychainStoreTests.swift */; }; 4B6A70312109793500D71C66 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A70302109793500D71C66 /* AppDelegate.swift */; }; + 4B6CACC7239F44D100BD6C35 /* OpenChatControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6CACC6239F44D100BD6C35 /* OpenChatControllerTests.swift */; }; + 4B6CACC9239F4A2B00BD6C35 /* AssertionHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6CACC8239F4A2B00BD6C35 /* AssertionHelpers.swift */; }; 4B792FB121102D9200EDDD1E /* LoginProcessURLResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B792FB021102D9200EDDD1E /* LoginProcessURLResponseTests.swift */; }; 4B792FB321103A0200EDDD1E /* LoginManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B792FB221103A0200EDDD1E /* LoginManagerTests.swift */; }; - 4B792FB621103D2200EDDD1E /* PostOTPRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B792FB521103D2200EDDD1E /* PostOTPRequestTests.swift */; }; 4B792FB821103D4B00EDDD1E /* ResponseDataStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B792FB721103D4B00EDDD1E /* ResponseDataStub.swift */; }; 4B7D8C252137B9FA00C6855A /* public_base_64_newline in Resources */ = {isa = PBXBuildFile; fileRef = 4B7D8C1D2137B9FA00C6855A /* public_base_64_newline */; }; 4B7D8C262137B9FA00C6855A /* public_base_64_header in Resources */ = {isa = PBXBuildFile; fileRef = 4B7D8C1E2137B9FA00C6855A /* public_base_64_header */; }; @@ -87,8 +118,14 @@ 4B7D8C2B2137B9FA00C6855A /* public.der in Resources */ = {isa = PBXBuildFile; fileRef = 4B7D8C232137B9FA00C6855A /* public.der */; }; 4B7D8C2C2137B9FA00C6855A /* test_public.cer in Resources */ = {isa = PBXBuildFile; fileRef = 4B7D8C242137B9FA00C6855A /* test_public.cer */; }; 4B7D8C2E2137BD8900C6855A /* private.der in Resources */ = {isa = PBXBuildFile; fileRef = 4B7D8C2D2137BD8900C6855A /* private.der */; }; + 4B7FEDC6221E4245003F7369 /* ResultUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF3044E221D3620005ADADD /* ResultUtils.swift */; }; + 4B7FEE0F221E466E003F7369 /* ResultExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7FEE0E221E466E003F7369 /* ResultExtensions.swift */; }; 4B813C5A2101B1450091F78E /* RequestAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B813C592101B1450091F78E /* RequestAdapter.swift */; }; 4B813C5C2101B65D0091F78E /* ParametersAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B813C5B2101B65D0091F78E /* ParametersAdapter.swift */; }; + 4B825623224CC36000D9F63E /* ShareTargetSearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B825622224CC36000D9F63E /* ShareTargetSearchController.swift */; }; + 4B8469A623EAA7AA00EE49FA /* LineSDKOpenChatRoomStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8469A523EAA7AA00EE49FA /* LineSDKOpenChatRoomStatus.swift */; }; + 4B8469A823EAA7BD00EE49FA /* LineSDKOpenChatRoomMembershipState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8469A723EAA7BD00EE49FA /* LineSDKOpenChatRoomMembershipState.swift */; }; + 4B85BB6B22C4A8E600DFE299 /* ResourceLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B85BB6A22C4A8E600DFE299 /* ResourceLoading.swift */; }; 4B8A965721100A5800760219 /* LoginConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8A965621100A5800760219 /* LoginConfigurationTests.swift */; }; 4B8FF4862105656500890AEF /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FF4852105656500890AEF /* Delegate.swift */; }; 4B8FF48B21056B3800890AEF /* CallbackQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FF48A21056B3800890AEF /* CallbackQueue.swift */; }; @@ -97,11 +134,10 @@ 4B8FF4932105C49200890AEF /* LoginFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FF4922105C49200890AEF /* LoginFlowTests.swift */; }; 4B90588221006E5D004D717F /* LineSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B90587821006E5C004D717F /* LineSDK.framework */; }; 4B90588721006E5D004D717F /* LineSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B90588621006E5D004D717F /* LineSDKTests.swift */; }; - 4B90588921006E5D004D717F /* LineSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B90587B21006E5C004D717F /* LineSDK.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4B9058D421007394004D717F /* LoginConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9058D321007394004D717F /* LoginConfiguration.swift */; }; 4B9058D6210073E6004D717F /* LoginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9058D5210073E6004D717F /* LoginManager.swift */; }; - 4B9058D8210078FB004D717F /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9058D7210078FB004D717F /* Result.swift */; }; 4B9058DC21007C8C004D717F /* LoginResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9058DB21007C8C004D717F /* LoginResult.swift */; }; + 4B93F2D223CD5307003B955D /* OpenChatCreatingControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B93F2D123CD5307003B955D /* OpenChatCreatingControllerDelegate.swift */; }; 4B94D06721533D4D0049DE68 /* ECDSATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B94D06621533D4D0049DE68 /* ECDSATests.swift */; }; 4B94D0692153678D0049DE68 /* JWTRSATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B94D0682153678D0049DE68 /* JWTRSATests.swift */; }; 4B94D06B2153681E0049DE68 /* JWTECTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B94D06A2153681E0049DE68 /* JWTECTests.swift */; }; @@ -135,13 +171,17 @@ 4BA8E44A210ED82B00355F03 /* AdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA8E449210ED82B00355F03 /* AdapterTests.swift */; }; 4BA8E44C210EDB5100355F03 /* ParameterEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA8E44B210EDB5100355F03 /* ParameterEncoderTests.swift */; }; 4BA8E44E210EE79B00355F03 /* PipelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA8E44D210EE79B00355F03 /* PipelineTests.swift */; }; + 4BB002C62244758000FB8BD8 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB002C52244758000FB8BD8 /* ImageDownloader.swift */; }; + 4BB002C82244788A00FB8BD8 /* ImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB002C72244788A00FB8BD8 /* ImageManager.swift */; }; + 4BB0E256239F5C8A008C7F3F /* FormEntryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0E255239F5C8A008C7F3F /* FormEntryTests.swift */; }; + 4BB0E258239F6728008C7F3F /* CountLimitedTextViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0E257239F6728008C7F3F /* CountLimitedTextViewTests.swift */; }; + 4BB0E25A239F6BE9008C7F3F /* OpenChatCreatingFormItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0E259239F6BE9008C7F3F /* OpenChatCreatingFormItemTests.swift */; }; 4BB2E6612135188900885687 /* LineSDKMessagingModelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2E6602135188900885687 /* LineSDKMessagingModelTests.m */; }; 4BB95E46215B7C2F00D213AD /* API+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB95E45215B7C2F00D213AD /* API+Internal.swift */; }; 4BBAFC452101BB9E00E7BFF6 /* LineSDKError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBAFC442101BB9E00E7BFF6 /* LineSDKError.swift */; }; 4BBAFC472101C8A100E7BFF6 /* AccessTokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBAFC462101C8A100E7BFF6 /* AccessTokenStore.swift */; }; 4BBAFC4B2101CE8100E7BFF6 /* Resource.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 4BBAFC4A2101CE8100E7BFF6 /* Resource.bundle */; }; 4BBAFC502101D31300E7BFF6 /* ConstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBAFC4F2101D31300E7BFF6 /* ConstantTests.swift */; }; - 4BBEA996212EAF9F00858627 /* LineSDKObjC.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BBEA994212EAF9F00858627 /* LineSDKObjC.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4BBEA9A3212EAFBB00858627 /* LineSDKObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BBEA992212EAF9F00858627 /* LineSDKObjC.framework */; }; 4BBEA9AC212EAFE400858627 /* LineSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B90587821006E5C004D717F /* LineSDK.framework */; }; 4BBEA9B0212EB03200858627 /* LineSDKLoginPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBEA9AF212EB03200858627 /* LineSDKLoginPermission.swift */; }; @@ -154,12 +194,196 @@ 4BC4B3DF212FEF8600750794 /* LineSDKLocationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC4B3DE212FEF8600750794 /* LineSDKLocationMessage.swift */; }; 4BC4B3E1212FF39D00750794 /* LineSDKTemplateMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC4B3E0212FF39D00750794 /* LineSDKTemplateMessage.swift */; }; 4BC4B3E4212FF86100750794 /* LineSDKAudioMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC4B3E3212FF86100750794 /* LineSDKAudioMessage.swift */; }; + 4BC66AE522EA8A6900546FB6 /* LineSDKMessageSendingToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC66AE422EA8A6900546FB6 /* LineSDKMessageSendingToken.swift */; }; 4BC9B2D9212FADD60071C736 /* LinsSDKCallbackQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2D8212FADD60071C736 /* LinsSDKCallbackQueue.swift */; }; 4BC9B2DB212FB0210071C736 /* LineSDKAPIInterfaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2DA212FB0210071C736 /* LineSDKAPIInterfaceTests.m */; }; 4BC9B2DE212FB7230071C736 /* LineSDKUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2DD212FB7230071C736 /* LineSDKUser.swift */; }; 4BC9B2E0212FB7B60071C736 /* LineSDKGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2DF212FB7B60071C736 /* LineSDKGroup.swift */; }; 4BC9B2E2212FB8390071C736 /* LineSDKGraphResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2E1212FB8390071C736 /* LineSDKGraphResponse.swift */; }; 4BC9B2E7212FCA130071C736 /* LineSDKMessagingResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2E6212FCA130071C736 /* LineSDKMessagingResponse.swift */; }; + 4BCD24C823FFA74100D4B6BD /* LineSDKAccessTokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBEA9B3212EB35000858627 /* LineSDKAccessTokenStore.swift */; }; + 4BCD24C923FFA74100D4B6BD /* LinsSDKCallbackQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2D8212FADD60071C736 /* LinsSDKCallbackQueue.swift */; }; + 4BCD24CA23FFA74100D4B6BD /* LineSDKTemplateImageCarouselPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C1B2133B97600DD563D /* LineSDKTemplateImageCarouselPayload.swift */; }; + 4BCD24CB23FFA74100D4B6BD /* LineSDKTemplateMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC4B3E0212FF39D00750794 /* LineSDKTemplateMessage.swift */; }; + 4BCD24CC23FFA74100D4B6BD /* ResultExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7FEE0E221E466E003F7369 /* ResultExtensions.swift */; }; + 4BCD24CD23FFA74100D4B6BD /* LineSDKAuthAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD33EDD238B72E500C1E8A9 /* LineSDKAuthAPI.swift */; }; + 4BCD24CE23FFA74100D4B6BD /* LineSDKAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D1504212FABD2000DD5BE /* LineSDKAPI.swift */; }; + 4BCD24CF23FFA74100D4B6BD /* LineSDKTemplateButtonsPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C132133954800DD563D /* LineSDKTemplateButtonsPayload.swift */; }; + 4BCD24D023FFA74100D4B6BD /* LineSDKJWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD24CF6213E517800606411 /* LineSDKJWT.swift */; }; + 4BCD24D123FFA74100D4B6BD /* LineSDKTemplateConfirmPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C172133A26900DD563D /* LineSDKTemplateConfirmPayload.swift */; }; + 4BCD24D223FFA74100D4B6BD /* LineSDKGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2DF212FB7B60071C736 /* LineSDKGroup.swift */; }; + 4BCD24D323FFA74100D4B6BD /* LineSDKConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A9AF22159FCF700915054 /* LineSDKConstant.swift */; }; + 4BCD24D423FFA74100D4B6BD /* LineSDKFlexBoxComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C372133F34300DD563D /* LineSDKFlexBoxComponent.swift */; }; + 4BCD24D523FFA74100D4B6BD /* LineSDKAccessTokenVerifyResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D14E8212F9270000DD5BE /* LineSDKAccessTokenVerifyResult.swift */; }; + 4BCD24D623FFA74100D4B6BD /* LineSDKTemplateCarouselPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C192133A5E300DD563D /* LineSDKTemplateCarouselPayload.swift */; }; + 4BCD24D723FFA74100D4B6BD /* LineSDKUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2DD212FB7230071C736 /* LineSDKUser.swift */; }; + 4BCD24D823FFA74100D4B6BD /* LineSDKMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC4B3D6212FD76C00750794 /* LineSDKMessage.swift */; }; + 4BCD24D923FFA74100D4B6BD /* LineSDKGraphResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2E1212FB8390071C736 /* LineSDKGraphResponse.swift */; }; + 4BCD24DA23FFA74100D4B6BD /* LineSDKFlexCarouselContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C242133CA1800DD563D /* LineSDKFlexCarouselContainer.swift */; }; + 4BCD24DB23FFA74100D4B6BD /* LineSDKFlexTextComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C292133DD9300DD563D /* LineSDKFlexTextComponent.swift */; }; + 4BCD24DC23FFA74100D4B6BD /* LineSDKUserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D14E2212F8EDA000DD5BE /* LineSDKUserProfile.swift */; }; + 4BCD24DD23FFA74100D4B6BD /* LineSDKLoginManagerOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D14EE212F9468000DD5BE /* LineSDKLoginManagerOptions.swift */; }; + 4BCD24DE23FFA74100D4B6BD /* LineSDKAuthorizationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B415941225AF024003AE63A /* LineSDKAuthorizationStatus.swift */; }; + 4BCD24DF23FFA74100D4B6BD /* LineSDKShareTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41593F225AE65A003AE63A /* LineSDKShareTarget.swift */; }; + 4BCD24E023FFA74100D4B6BD /* LineSDKTextMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC4B3D8212FEB9000750794 /* LineSDKTextMessage.swift */; }; + 4BCD24E123FFA74100D4B6BD /* LineSDKLoginManagerParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF79914238BB71E0009C315 /* LineSDKLoginManagerParameters.swift */; }; + 4BCD24E223FFA74100D4B6BD /* LineSDKShareViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41593B225AE497003AE63A /* LineSDKShareViewControllerDelegate.swift */; }; + 4BCD24E323FFA74100D4B6BD /* LineSDKFlexMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C1D2133C15300DD563D /* LineSDKFlexMessage.swift */; }; + 4BCD24E423FFA74100D4B6BD /* LineSDKMessagingResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9B2E6212FCA130071C736 /* LineSDKMessagingResponse.swift */; }; + 4BCD24E523FFA74100D4B6BD /* LineSDKImageMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC4B3DA212FEBA600750794 /* LineSDKImageMessage.swift */; }; + 4BCD24E623FFA74100D4B6BD /* LineSDKFlexButtonComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C2B2133F2D400DD563D /* LineSDKFlexButtonComponent.swift */; }; + 4BCD24E723FFA74100D4B6BD /* LineSDKFlexSeparatorComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C332133F32200DD563D /* LineSDKFlexSeparatorComponent.swift */; }; + 4BCD24E823FFA74100D4B6BD /* LineSDKSpacerComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C352133F32F00DD563D /* LineSDKSpacerComponent.swift */; }; + 4BCD24E923FFA74100D4B6BD /* LineSDKAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D1502212FAB5A000DD5BE /* LineSDKAPIError.swift */; }; + 4BCD24EA23FFA74100D4B6BD /* LineSDKLocationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC4B3DE212FEF8600750794 /* LineSDKLocationMessage.swift */; }; + 4BCD24EB23FFA74100D4B6BD /* LineSDKFlexMessageComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C272133D2B300DD563D /* LineSDKFlexMessageComponent.swift */; }; + 4BCD24EC23FFA74100D4B6BD /* LineSDKFlexImageComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C2D2133F2EA00DD563D /* LineSDKFlexImageComponent.swift */; }; + 4BCD24ED23FFA74100D4B6BD /* LineSDKFlexIconComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C312133F30A00DD563D /* LineSDKFlexIconComponent.swift */; }; + 4BCD24EE23FFA74100D4B6BD /* LineSDKAccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBEA9B5212EB41000858627 /* LineSDKAccessToken.swift */; }; + 4BCD24EF23FFA74100D4B6BD /* LineSDKVideoMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC4B3DC212FED4300750794 /* LineSDKVideoMessage.swift */; }; + 4BCD24F023FFA74100D4B6BD /* LineSDKMessageSendingToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC66AE422EA8A6900546FB6 /* LineSDKMessageSendingToken.swift */; }; + 4BCD24F123FFA74100D4B6BD /* LineSDKAudioMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC4B3E3212FF86100750794 /* LineSDKAudioMessage.swift */; }; + 4BCD24F223FFA74100D4B6BD /* LineSDKFlexMessageContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C202133C1CD00DD563D /* LineSDKFlexMessageContainer.swift */; }; + 4BCD24F323FFA74100D4B6BD /* LineSDKMessageShareTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41593D225AE4E0003AE63A /* LineSDKMessageShareTargetType.swift */; }; + 4BCD24F423FFA74100D4B6BD /* LineSDKFlexBubbleContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C222133C61F00DD563D /* LineSDKFlexBubbleContainer.swift */; }; + 4BCD24F523FFA74100D4B6BD /* LineSDKGetBotFriendshipStatusResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D102A6A321646AE200804C7C /* LineSDKGetBotFriendshipStatusResponse.swift */; }; + 4BCD24F623FFA74100D4B6BD /* LineSDKShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F4E715225743F3002639AC /* LineSDKShareViewController.swift */; }; + 4BCD24F723FFA74100D4B6BD /* LineSDKMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C1121338B9800DD563D /* LineSDKMessageAction.swift */; }; + 4BCD24F823FFA74100D4B6BD /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C152133970800DD563D /* Log.swift */; }; + 4BCD24F923FFA74100D4B6BD /* LineSDKFlexFillerComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C2F2133F2FA00DD563D /* LineSDKFlexFillerComponent.swift */; }; + 4BCD24FA23FFA74100D4B6BD /* JSONConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B42816521ED9CE6004A0846 /* JSONConverter.swift */; }; + 4BCD24FB23FFA74100D4B6BD /* LineSDKHexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D1500212FA9EE000DD5BE /* LineSDKHexColor.swift */; }; + 4BCD24FC23FFA74100D4B6BD /* LineSDKLoginResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D14F0212F95B3000DD5BE /* LineSDKLoginResult.swift */; }; + 4BCD24FD23FFA74100D4B6BD /* LineSDKLoginProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D14F2212F96A7000DD5BE /* LineSDKLoginProcess.swift */; }; + 4BCD24FE23FFA74100D4B6BD /* LineSDKErrorConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2422EE2134FBE8007200C2 /* LineSDKErrorConstant.swift */; }; + 4BCD24FF23FFA74100D4B6BD /* LineSDKLoginPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBEA9AF212EB03200858627 /* LineSDKLoginPermission.swift */; }; + 4BCD250023FFA74100D4B6BD /* LineSDKLoginButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8E36F214A6E70009E91BD /* LineSDKLoginButton.swift */; }; + 4BCD250123FFA74100D4B6BD /* LineSDKLoginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D14F4212F97A6000DD5BE /* LineSDKLoginManager.swift */; }; + 4BCD250F23FFAABA00D4B6BD /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B0EF22241DEA900BA59A0 /* ShareViewController.swift */; }; + 4BCD251023FFAABA00D4B6BD /* ShareViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B415937225AE40E003AE63A /* ShareViewControllerDelegate.swift */; }; + 4BCD251123FFAABA00D4B6BD /* MessageShareTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B415939225AE464003AE63A /* MessageShareTargetType.swift */; }; + 4BCD251223FFAABA00D4B6BD /* ShareTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16D5323224872A300BAA3B4 /* ShareTarget.swift */; }; + 4BCD251323FFAABA00D4B6BD /* ColumnDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16D5327224876B900BAA3B4 /* ColumnDataStore.swift */; }; + 4BCD251423FFAABA00D4B6BD /* PageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B0EF42241DF3E00BA59A0 /* PageViewController.swift */; }; + 4BCD251523FFAABA00D4B6BD /* PageTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3D78BA22420BEA00DE27D1 /* PageTabView.swift */; }; + 4BCD251623FFAABA00D4B6BD /* ShareRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB115EF92254660C00C16B0C /* ShareRootViewController.swift */; }; + 4BCD251723FFAABA00D4B6BD /* ShareTargetSelectingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16D5325224873C700BAA3B4 /* ShareTargetSelectingViewController.swift */; }; + 4BCD251823FFAABA00D4B6BD /* ShareTargetSelectingTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16D53292248B01600BAA3B4 /* ShareTargetSelectingTableCell.swift */; }; + 4BCD251923FFAABA00D4B6BD /* ShareTargetSearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B825622224CC36000D9F63E /* ShareTargetSearchController.swift */; }; + 4BCD251A23FFAABA00D4B6BD /* ShareTargetTableViewStyling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B392661224DC06F006485B4 /* ShareTargetTableViewStyling.swift */; }; + 4BCD251B23FFAABA00D4B6BD /* ShareTargetSearchResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB115EF722537F1000C16B0C /* ShareTargetSearchResultViewController.swift */; }; + 4BCD251C23FFAABA00D4B6BD /* ShareTargetSearchResultTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCD203224CAF6100CD9544 /* ShareTargetSearchResultTableViewController.swift */; }; + 4BCD251D23FFAABA00D4B6BD /* ShareTargetSelectingSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B392663224DE2E1006485B4 /* ShareTargetSelectingSectionHeaderView.swift */; }; + 4BCD251E23FFAABA00D4B6BD /* SelectedTargetPanelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFBD438224B85F500A74D44 /* SelectedTargetPanelViewController.swift */; }; + 4BCD251F23FFAABA00D4B6BD /* SelectedTargetPanelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFBD43E224E7D0500A74D44 /* SelectedTargetPanelCell.swift */; }; + 4BCD252023FFAABA00D4B6BD /* LoginButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F75523021244502004AC047 /* LoginButton.swift */; }; + 4BCD252123FFAABA00D4B6BD /* ResourceLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B85BB6A22C4A8E600DFE299 /* ResourceLoading.swift */; }; + 4BCD252223FFAABA00D4B6BD /* LineSDKError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBAFC442101BB9E00E7BFF6 /* LineSDKError.swift */; }; + 4BCD252323FFAABA00D4B6BD /* PostExchangeTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F0B7D21080C51006D17F5 /* PostExchangeTokenRequest.swift */; }; + 4BCD252423FFAABA00D4B6BD /* GetUserProfileRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F0B8C210870EF006D17F5 /* GetUserProfileRequest.swift */; }; + 4BCD252523FFAABA00D4B6BD /* PostRefreshTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFBEA7A211057050044C2B6 /* PostRefreshTokenRequest.swift */; }; + 4BCD252623FFAABA00D4B6BD /* PostRevokeTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191B521115ED800E2C481 /* PostRevokeTokenRequest.swift */; }; + 4BCD252723FFAABA00D4B6BD /* GetVerifyTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD278D2113E9A900B90D8F /* GetVerifyTokenRequest.swift */; }; + 4BCD252823FFAABA00D4B6BD /* PKCE.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB86930F23D5619E0011AEB9 /* PKCE.swift */; }; + 4BCD252923FFAABA00D4B6BD /* AccessTokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBAFC462101C8A100E7BFF6 /* AccessTokenStore.swift */; }; + 4BCD252A23FFAABA00D4B6BD /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F0B7F21080D69006D17F5 /* AccessToken.swift */; }; + 4BCD252B23FFAABA00D4B6BD /* AccessTokenVerifyResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD278F2113EA1300B90D8F /* AccessTokenVerifyResult.swift */; }; + 4BCD252C23FFAABA00D4B6BD /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F0B8321084755006D17F5 /* UserProfile.swift */; }; + 4BCD252D23FFAABA00D4B6BD /* LoginConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9058D321007394004D717F /* LoginConfiguration.swift */; }; + 4BCD252E23FFAABA00D4B6BD /* LoginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9058D5210073E6004D717F /* LoginManager.swift */; }; + 4BCD252F23FFAABA00D4B6BD /* LoginManagerOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6508B7211812CE001796E0 /* LoginManagerOptions.swift */; }; + 4BCD253023FFAABA00D4B6BD /* LoginManagerParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD33EE2238B825600C1E8A9 /* LoginManagerParameters.swift */; }; + 4BCD253123FFAABA00D4B6BD /* LoginResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9058DB21007C8C004D717F /* LoginResult.swift */; }; + 4BCD253223FFAABA00D4B6BD /* LoginProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B45256A2101810D00A39D4F /* LoginProcess.swift */; }; + 4BCD253323FFAABA00D4B6BD /* LoginProcessURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B63F4722106FDCC003D1BF1 /* LoginProcessURLResponse.swift */; }; + 4BCD253423FFAABA00D4B6BD /* LoginPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B45256E210188C300A39D4F /* LoginPermission.swift */; }; + 4BCD253523FFAABA00D4B6BD /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE4E36C211D6C1A00184D66 /* User.swift */; }; + 4BCD253623FFAABA00D4B6BD /* Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF20273212C114D00780358 /* Group.swift */; }; + 4BCD253723FFAABA00D4B6BD /* GetFriendsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE4E36F211D6CF500184D66 /* GetFriendsRequest.swift */; }; + 4BCD253823FFAABA00D4B6BD /* GetGroupsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF20275212C11A600780358 /* GetGroupsRequest.swift */; }; + 4BCD253923FFAABA00D4B6BD /* GetApproversInFriendsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCFD312126BD7200E8327A /* GetApproversInFriendsRequest.swift */; }; + 4BCD253A23FFAABA00D4B6BD /* GetApproversInGroupRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF20277212C137D00780358 /* GetApproversInGroupRequest.swift */; }; + 4BCD253B23FFAABA00D4B6BD /* GetShareFriendsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF3B0D5236143F70059CE93 /* GetShareFriendsRequest.swift */; }; + 4BCD253C23FFAABA00D4B6BD /* GetShareGroupsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF3B0D7236145530059CE93 /* GetShareGroupsRequest.swift */; }; + 4BCD253D23FFAABA00D4B6BD /* PostSendMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9B9AA62123C6E400325B9B /* PostSendMessagesRequest.swift */; }; + 4BCD253E23FFAABA00D4B6BD /* PostMultisendMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B95F40121252D2F00AD3A81 /* PostMultisendMessagesRequest.swift */; }; + 4BCD253F23FFAABA00D4B6BD /* PostMultisendMessagesWithTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEC048322CDA65300C95B3B /* PostMultisendMessagesWithTokenRequest.swift */; }; + 4BCD254023FFAABA00D4B6BD /* PostMessageSendingTokenIssueRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B62425122CAF2000092B92F /* PostMessageSendingTokenIssueRequest.swift */; }; + 4BCD254123FFAABA00D4B6BD /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B15EEF5211D46FF00866E6C /* Message.swift */; }; + 4BCD254223FFAABA00D4B6BD /* MessageProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A303A212121FC00174C6F /* MessageProtocols.swift */; }; + 4BCD254323FFAABA00D4B6BD /* MessageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B15EEF7211D473400866E6C /* MessageSender.swift */; }; + 4BCD254423FFAABA00D4B6BD /* TextMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B15EEF3211D466D00866E6C /* TextMessage.swift */; }; + 4BCD254523FFAABA00D4B6BD /* ImageMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A303021210FE700174C6F /* ImageMessage.swift */; }; + 4BCD254623FFAABA00D4B6BD /* VideoMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A30342121182400174C6F /* VideoMessage.swift */; }; + 4BCD254723FFAABA00D4B6BD /* AudioMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A303821211EC400174C6F /* AudioMessage.swift */; }; + 4BCD254823FFAABA00D4B6BD /* LocationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A303E2121277400174C6F /* LocationMessage.swift */; }; + 4BCD254923FFAABA00D4B6BD /* TemplateMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A3046212139C000174C6F /* TemplateMessage.swift */; }; + 4BCD254A23FFAABA00D4B6BD /* FlexMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF53538212A7C9100EA602A /* FlexMessage.swift */; }; + 4BCD254B23FFAABA00D4B6BD /* MessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A304D2121405300174C6F /* MessageAction.swift */; }; + 4BCD254C23FFAABA00D4B6BD /* TemplateMessagePayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A304A21213DBB00174C6F /* TemplateMessagePayload.swift */; }; + 4BCD254D23FFAABA00D4B6BD /* TemplateMessageProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4464E7212D093D008D3624 /* TemplateMessageProperties.swift */; }; + 4BCD254E23FFAABA00D4B6BD /* TemplateButtonsPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A304821213CE100174C6F /* TemplateButtonsPayload.swift */; }; + 4BCD254F23FFAABA00D4B6BD /* TemplateConfirmPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A305321215A1A00174C6F /* TemplateConfirmPayload.swift */; }; + 4BCD255023FFAABA00D4B6BD /* TemplateCarouselPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A30572121613500174C6F /* TemplateCarouselPayload.swift */; }; + 4BCD255123FFAABA00D4B6BD /* TemplateImageCarouselPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9A305B21216D2E00174C6F /* TemplateImageCarouselPayload.swift */; }; + 4BCD255223FFAABA00D4B6BD /* FlexBlockStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5EE2E3212BCE1A0009DF2E /* FlexBlockStyle.swift */; }; + 4BCD255323FFAABA00D4B6BD /* FlexMessageContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF53536212A7C8500EA602A /* FlexMessageContainer.swift */; }; + 4BCD255423FFAABA00D4B6BD /* FlexBubbleContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF5353A212A7DB200EA602A /* FlexBubbleContainer.swift */; }; + 4BCD255523FFAABA00D4B6BD /* FlexCarouselContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF5353C212A7E8E00EA602A /* FlexCarouselContainer.swift */; }; + 4BCD255623FFAABA00D4B6BD /* FlexMessageComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFD6061212A8F55009E9838 /* FlexMessageComponent.swift */; }; + 4BCD255723FFAABA00D4B6BD /* FlexMessageProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFD605F212A8F00009E9838 /* FlexMessageProperties.swift */; }; + 4BCD255823FFAABA00D4B6BD /* FlexBoxComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFD6059212A875F009E9838 /* FlexBoxComponent.swift */; }; + 4BCD255923FFAABA00D4B6BD /* FlexTextComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFD605B212A8775009E9838 /* FlexTextComponent.swift */; }; + 4BCD255A23FFAABA00D4B6BD /* FlexButtonComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A0FF2212AA15D00B3ED1F /* FlexButtonComponent.swift */; }; + 4BCD255B23FFAABA00D4B6BD /* FlexImageComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB4919212B94BC00BA946A /* FlexImageComponent.swift */; }; + 4BCD255C23FFAABA00D4B6BD /* FlexFillerComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB491D212B9E5500BA946A /* FlexFillerComponent.swift */; }; + 4BCD255D23FFAABA00D4B6BD /* FlexIconComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB4921212BA05500BA946A /* FlexIconComponent.swift */; }; + 4BCD255E23FFAABA00D4B6BD /* FlexSeparatorComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB4925212BA2FD00BA946A /* FlexSeparatorComponent.swift */; }; + 4BCD255F23FFAABA00D4B6BD /* FlexSpacerComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5EE2DC212BAF2C0009DF2E /* FlexSpacerComponent.swift */; }; + 4BCD256023FFAABA00D4B6BD /* GetBotFriendshipStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D102A69D2164658D00804C7C /* GetBotFriendshipStatus.swift */; }; + 4BCD256123FFAABA00D4B6BD /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191B3211154DF00E2C481 /* API.swift */; }; + 4BCD256223FFAABA00D4B6BD /* API+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB95E45215B7C2F00D213AD /* API+Internal.swift */; }; + 4BCD256323FFAABA00D4B6BD /* API+Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD33ED1238B627200C1E8A9 /* API+Auth.swift */; }; + 4BCD256423FFAABA00D4B6BD /* API+Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD33ED6238B6E8800C1E8A9 /* API+Deprecated.swift */; }; + 4BCD256523FFAABA00D4B6BD /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB002C52244758000FB8BD8 /* ImageDownloader.swift */; }; + 4BCD256623FFAABA00D4B6BD /* ImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB002C72244788A00FB8BD8 /* ImageManager.swift */; }; + 4BCD256723FFAABA00D4B6BD /* DownloadableImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1658042252EF3C0008E441 /* DownloadableImageView.swift */; }; + 4BCD256823FFAABA00D4B6BD /* ChainedPaginatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF5337D222E33E30080D138 /* ChainedPaginatedRequest.swift */; }; + 4BCD256923FFAABA00D4B6BD /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4525702101938F00A39D4F /* Request.swift */; }; + 4BCD256A23FFAABA00D4B6BD /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B45257521019EFB00A39D4F /* Session.swift */; }; + 4BCD256B23FFAABA00D4B6BD /* RequestAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B813C592101B1450091F78E /* RequestAdapter.swift */; }; + 4BCD256C23FFAABA00D4B6BD /* ParametersAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B813C5B2101B65D0091F78E /* ParametersAdapter.swift */; }; + 4BCD256D23FFAABA00D4B6BD /* ResponsePipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FF48C210598F500890AEF /* ResponsePipeline.swift */; }; + 4BCD256E23FFAABA00D4B6BD /* CodingExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD27922113EC6C00B90D8F /* CodingExtension.swift */; }; + 4BCD256F23FFAABA00D4B6BD /* HexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A0FF4212AA82500B3ED1F /* HexColor.swift */; }; + 4BCD257023FFAABA00D4B6BD /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B414D5B210EF12C00FD19BC /* APIError.swift */; }; + 4BCD257123FFAABA00D4B6BD /* Unit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191B721116C4C00E2C481 /* Unit.swift */; }; + 4BCD257223FFAABA00D4B6BD /* CryptoData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CCB752152295300F51D76 /* CryptoData.swift */; }; + 4BCD257323FFAABA00D4B6BD /* CryptoAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CCB77215229DC00F51D76 /* CryptoAlgorithm.swift */; }; + 4BCD257423FFAABA00D4B6BD /* CryptoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CCB9921523A2600F51D76 /* CryptoKey.swift */; }; + 4BCD257523FFAABA00D4B6BD /* CryptoHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF45A85213783D000CCD28E /* CryptoHelpers.swift */; }; + 4BCD257623FFAABA00D4B6BD /* CryptoError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF45A8121377DCF00CCD28E /* CryptoError.swift */; }; + 4BCD257723FFAABA00D4B6BD /* GetDiscoveryDocumentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFC09F2213CF09A00F4594D /* GetDiscoveryDocumentRequest.swift */; }; + 4BCD257823FFAABA00D4B6BD /* GetJWKSetRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFC09F6213CF68000F4594D /* GetJWKSetRequest.swift */; }; + 4BCD257923FFAABA00D4B6BD /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1C44642137E0CA0094C1D4 /* JWT.swift */; }; + 4BCD257A23FFAABA00D4B6BD /* JWTCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1C446A2137E98D0094C1D4 /* JWTCoder.swift */; }; + 4BCD257B23FFAABA00D4B6BD /* JWTHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1C44682137E1EE0094C1D4 /* JWTHelpers.swift */; }; + 4BCD257C23FFAABA00D4B6BD /* JWK.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A591262139489E00AC080D /* JWK.swift */; }; + 4BCD257D23FFAABA00D4B6BD /* JWA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD24CF4213E278700606411 /* JWA.swift */; }; + 4BCD257E23FFAABA00D4B6BD /* JWKSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A591282139495B00AC080D /* JWKSet.swift */; }; + 4BCD257F23FFAABA00D4B6BD /* RSA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B25A814213687A400C74B87 /* RSA.swift */; }; + 4BCD258023FFAABA00D4B6BD /* ECDSA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CCB9B21523B1300F51D76 /* ECDSA.swift */; }; + 4BCD258123FFAABA00D4B6BD /* ResultUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF3044E221D3620005ADADD /* ResultUtils.swift */; }; + 4BCD258323FFAABA00D4B6BD /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B45256C2101829F00A39D4F /* Helpers.swift */; }; + 4BCD258423FFAABA00D4B6BD /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FF4852105656500890AEF /* Delegate.swift */; }; + 4BCD258523FFAABA00D4B6BD /* CallbackQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FF48A21056B3800890AEF /* CallbackQueue.swift */; }; + 4BCD258623FFAABA00D4B6BD /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FF48E2105B27600890AEF /* URLs.swift */; }; + 4BCD258723FFAABA00D4B6BD /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F0B8121083CDB006D17F5 /* Constant.swift */; }; + 4BCD258823FFAABA00D4B6BD /* KeychainStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A7025210956B600D71C66 /* KeychainStore.swift */; }; + 4BCD258923FFAABA00D4B6BD /* NotificationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE5B34210AC2D000846C6D /* NotificationToken.swift */; }; + 4BCD258A23FFAABA00D4B6BD /* KeyboardObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5035BA22571D3B003CD0B9 /* KeyboardObservable.swift */; }; + 4BCD258B23FFAABA00D4B6BD /* LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B688E3D226F029B002575CC /* LoadingIndicator.swift */; }; + 4BCD258C23FFAABA00D4B6BD /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0FD5812330D0270054A4E8 /* Colors.swift */; }; 4BCD278E2113E9A900B90D8F /* GetVerifyTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD278D2113E9A900B90D8F /* GetVerifyTokenRequest.swift */; }; 4BCD27902113EA1300B90D8F /* AccessTokenVerifyResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD278F2113EA1300B90D8F /* AccessTokenVerifyResult.swift */; }; 4BCD27932113EC6C00B90D8F /* CodingExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD27922113EC6C00B90D8F /* CodingExtension.swift */; }; @@ -170,8 +394,19 @@ 4BD191B821116C4C00E2C481 /* Unit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191B721116C4C00E2C481 /* Unit.swift */; }; 4BD191BA21116CF700E2C481 /* PostRevokeTokenRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191B921116CF700E2C481 /* PostRevokeTokenRequestTests.swift */; }; 4BD191BC211188D500E2C481 /* RefreshTokenPipelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191BB211188D500E2C481 /* RefreshTokenPipelineTests.swift */; }; + 4BD1B2222397425000437892 /* VerticallyCenteredTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD1B2212397425000437892 /* VerticallyCenteredTextView.swift */; }; + 4BD1B224239748F700437892 /* CountLimitedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD1B223239748F700437892 /* CountLimitedTextView.swift */; }; + 4BD1B228239796BC00437892 /* OpenChatRoomDescriptionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD1B227239796BC00437892 /* OpenChatRoomDescriptionTableViewCell.swift */; }; 4BD24CF5213E278700606411 /* JWA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD24CF4213E278700606411 /* JWA.swift */; }; 4BD24CF7213E517800606411 /* LineSDKJWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD24CF6213E517800606411 /* LineSDKJWT.swift */; }; + 4BD2E52F239DFA8A00AB3C2D /* UserDefaultsValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD2E52E239DFA8A00AB3C2D /* UserDefaultsValue.swift */; }; + 4BD33ED2238B627200C1E8A9 /* API+Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD33ED1238B627200C1E8A9 /* API+Auth.swift */; }; + 4BD33ED7238B6E8800C1E8A9 /* API+Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD33ED6238B6E8800C1E8A9 /* API+Deprecated.swift */; }; + 4BD33EDE238B72E500C1E8A9 /* LineSDKAuthAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD33EDD238B72E500C1E8A9 /* LineSDKAuthAPI.swift */; }; + 4BD33EE3238B825600C1E8A9 /* LoginManagerParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD33EE2238B825600C1E8A9 /* LoginManagerParameters.swift */; }; + 4BD91E17239A202600E229C2 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD91E16239A202600E229C2 /* String.swift */; }; + 4BD91E1B239A3F1300E229C2 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD91E1A239A3F1300E229C2 /* ToastView.swift */; }; + 4BD91E30239A50FF00E229C2 /* GetOpenChatRoomMembershipStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD91E2F239A50FF00E229C2 /* GetOpenChatRoomMembershipStateRequest.swift */; }; 4BDD2C1221338B9800DD563D /* LineSDKMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C1121338B9800DD563D /* LineSDKMessageAction.swift */; }; 4BDD2C142133954800DD563D /* LineSDKTemplateButtonsPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C132133954800DD563D /* LineSDKTemplateButtonsPayload.swift */; }; 4BDD2C162133970800DD563D /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C152133970800DD563D /* Log.swift */; }; @@ -191,23 +426,39 @@ 4BDD2C342133F32200DD563D /* LineSDKFlexSeparatorComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C332133F32200DD563D /* LineSDKFlexSeparatorComponent.swift */; }; 4BDD2C362133F32F00DD563D /* LineSDKSpacerComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C352133F32F00DD563D /* LineSDKSpacerComponent.swift */; }; 4BDD2C382133F34300DD563D /* LineSDKFlexBoxComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDD2C372133F34300DD563D /* LineSDKFlexBoxComponent.swift */; }; + 4BE4E0FC2251CEE10071FC60 /* ColumnDataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE4E0FB2251CEE10071FC60 /* ColumnDataStoreTests.swift */; }; + 4BE56FBF239504C800C9C415 /* OpenChatRoomCreatingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE56FBE239504C800C9C415 /* OpenChatRoomCreatingItem.swift */; }; 4BEB491A212B94BC00BA946A /* FlexImageComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB4919212B94BC00BA946A /* FlexImageComponent.swift */; }; 4BEB491C212B992B00BA946A /* FlexImageComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB491B212B992B00BA946A /* FlexImageComponentTests.swift */; }; 4BEB491E212B9E5500BA946A /* FlexFillerComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB491D212B9E5500BA946A /* FlexFillerComponent.swift */; }; 4BEB4920212B9F6900BA946A /* FlexFillerComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB491F212B9F6900BA946A /* FlexFillerComponentTests.swift */; }; 4BEB4922212BA05500BA946A /* FlexIconComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB4921212BA05500BA946A /* FlexIconComponent.swift */; }; - 4BEB4924212BA0A300BA946A /* FlexIconCompomentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB4923212BA0A300BA946A /* FlexIconCompomentTests.swift */; }; + 4BEB4924212BA0A300BA946A /* FlexIconComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB4923212BA0A300BA946A /* FlexIconComponentTests.swift */; }; 4BEB4926212BA2FD00BA946A /* FlexSeparatorComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB4925212BA2FD00BA946A /* FlexSeparatorComponent.swift */; }; 4BEB4928212BA8CF00BA946A /* FlexSeparatorComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEB4927212BA8CF00BA946A /* FlexSeparatorComponentTests.swift */; }; + 4BEC048422CDA65300C95B3B /* PostMultisendMessagesWithTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEC048322CDA65300C95B3B /* PostMultisendMessagesWithTokenRequest.swift */; }; 4BEE5B35210AC2D000846C6D /* NotificationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE5B34210AC2D000846C6D /* NotificationToken.swift */; }; + 4BEFF644226DCD960046DB66 /* ShareControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEFF643226DCD960046DB66 /* ShareControllerTests.swift */; }; + 4BF3B0D6236143F70059CE93 /* GetShareFriendsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF3B0D5236143F70059CE93 /* GetShareFriendsRequest.swift */; }; + 4BF3B0D8236145530059CE93 /* GetShareGroupsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF3B0D7236145530059CE93 /* GetShareGroupsRequest.swift */; }; 4BF45A8221377DCF00CCD28E /* CryptoError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF45A8121377DCF00CCD28E /* CryptoError.swift */; }; 4BF45A86213783D000CCD28E /* CryptoHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF45A85213783D000CCD28E /* CryptoHelpers.swift */; }; 4BF45A892137B29300CCD28E /* RSATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF45A882137B29300CCD28E /* RSATests.swift */; }; 4BF45A8B2137B2C500CCD28E /* RSAKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF45A8A2137B2C500CCD28E /* RSAKeyTests.swift */; }; + 4BF487F32394A29A00A5DC77 /* GetOpenChatTermAgreementStatusRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF487F22394A29A00A5DC77 /* GetOpenChatTermAgreementStatusRequest.swift */; }; + 4BF487F52394A47A00A5DC77 /* PostOpenChatCreateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF487F42394A47A00A5DC77 /* PostOpenChatCreateRequest.swift */; }; + 4BF487F92394A80100A5DC77 /* OpenChatCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF487F82394A80100A5DC77 /* OpenChatCategory.swift */; }; + 4BF487FB2394AC3800A5DC77 /* GetOpenChatRoomStatusRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF487FA2394AC3800A5DC77 /* GetOpenChatRoomStatusRequest.swift */; }; + 4BF488012394C99400A5DC77 /* OpenChatCreatingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF488002394C99400A5DC77 /* OpenChatCreatingController.swift */; }; + 4BF488062394CB9C00A5DC77 /* OpenChatRoomInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF488052394CB9C00A5DC77 /* OpenChatRoomInfoViewController.swift */; }; + 4BF488082394CBFF00A5DC77 /* OpenChatUserProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF488072394CBFF00A5DC77 /* OpenChatUserProfileViewController.swift */; }; + 4BF5337E222E33E30080D138 /* ChainedPaginatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF5337D222E33E30080D138 /* ChainedPaginatedRequest.swift */; }; + 4BF53380222E3DB80080D138 /* ChainedPaginatedRequestsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF5337F222E3DB80080D138 /* ChainedPaginatedRequestsTests.swift */; }; 4BF53537212A7C8500EA602A /* FlexMessageContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF53536212A7C8500EA602A /* FlexMessageContainer.swift */; }; 4BF53539212A7C9100EA602A /* FlexMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF53538212A7C9100EA602A /* FlexMessage.swift */; }; 4BF5353B212A7DB200EA602A /* FlexBubbleContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF5353A212A7DB200EA602A /* FlexBubbleContainer.swift */; }; 4BF5353D212A7E8E00EA602A /* FlexCarouselContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF5353C212A7E8E00EA602A /* FlexCarouselContainer.swift */; }; + 4BF79915238BB71E0009C315 /* LineSDKLoginManagerParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF79914238BB71E0009C315 /* LineSDKLoginManagerParameters.swift */; }; 4BFBEA7B211057050044C2B6 /* PostRefreshTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFBEA7A211057050044C2B6 /* PostRefreshTokenRequest.swift */; }; 4BFC09EF213CCE7700F4594D /* JWKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFC09EE213CCE7700F4594D /* JWKTests.swift */; }; 4BFC09F1213CDFC300F4594D /* JWKDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFC09F0213CDFC300F4594D /* JWKDataTests.swift */; }; @@ -215,6 +466,7 @@ 4BFC09F5213CF5B200F4594D /* GetDiscoveryDocumentRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFC09F4213CF5B200F4594D /* GetDiscoveryDocumentRequestTests.swift */; }; 4BFC09F7213CF68000F4594D /* GetJWKSetRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFC09F6213CF68000F4594D /* GetJWKSetRequest.swift */; }; 4BFC09F9213CFB6000F4594D /* GetJWKSetRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFC09F8213CFB6000F4594D /* GetJWKSetRequestTests.swift */; }; + 4BFCD204224CAF6100CD9544 /* ShareTargetSearchResultTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCD203224CAF6100CD9544 /* ShareTargetSearchResultTableViewController.swift */; }; 4BFD605A212A875F009E9838 /* FlexBoxComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFD6059212A875F009E9838 /* FlexBoxComponent.swift */; }; 4BFD605C212A8775009E9838 /* FlexTextComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFD605B212A8775009E9838 /* FlexTextComponent.swift */; }; 4BFD605E212A8BA6009E9838 /* FlexComponentMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFD605D212A8BA6009E9838 /* FlexComponentMessageTests.swift */; }; @@ -223,8 +475,61 @@ D102A69E2164658D00804C7C /* GetBotFriendshipStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D102A69D2164658D00804C7C /* GetBotFriendshipStatus.swift */; }; D102A6A0216467FE00804C7C /* GetBotFriendshipStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D102A69F216467FE00804C7C /* GetBotFriendshipStatusTests.swift */; }; D102A6A421646AE200804C7C /* LineSDKGetBotFriendshipStatusResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D102A6A321646AE200804C7C /* LineSDKGetBotFriendshipStatusResponse.swift */; }; + D16D5324224872A300BAA3B4 /* ShareTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16D5323224872A300BAA3B4 /* ShareTarget.swift */; }; + D16D5326224873C700BAA3B4 /* ShareTargetSelectingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16D5325224873C700BAA3B4 /* ShareTargetSelectingViewController.swift */; }; + D16D5328224876B900BAA3B4 /* ColumnDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16D5327224876B900BAA3B4 /* ColumnDataStore.swift */; }; + D16D532A2248B01600BAA3B4 /* ShareTargetSelectingTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16D53292248B01600BAA3B4 /* ShareTargetSelectingTableCell.swift */; }; + D17C6FB022237CB1007BA517 /* ResultUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D17C6FAF22237CB1007BA517 /* ResultUtilsTests.swift */; }; D1A591272139489E00AC080D /* JWK.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A591262139489E00AC080D /* JWK.swift */; }; D1A591292139495B00AC080D /* JWKSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A591282139495B00AC080D /* JWKSet.swift */; }; + D1CB0D0F24481987001F9238 /* LineSDKOpenChatRoomJoinType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CB0D0E24481987001F9238 /* LineSDKOpenChatRoomJoinType.swift */; }; + D1CB0D1024481A1A001F9238 /* LineSDKOpenChatRoomJoinType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CB0D0E24481987001F9238 /* LineSDKOpenChatRoomJoinType.swift */; }; + D1D22C8E2408A8D70028A9E0 /* StyleNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B19DC972398A08400E51458 /* StyleNavigationController.swift */; }; + D1D22C8F2408A8E60028A9E0 /* OpenChatCreatingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF488002394C99400A5DC77 /* OpenChatCreatingController.swift */; }; + D1D22C902408A8E90028A9E0 /* OpenChatCreatingControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B93F2D123CD5307003B955D /* OpenChatCreatingControllerDelegate.swift */; }; + D1D22C912408A8EC0028A9E0 /* OpenChatCreatingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B61C65F2394CE87005D7C21 /* OpenChatCreatingNavigationController.swift */; }; + D1D22C922408A8EF0028A9E0 /* OpenChatRoomInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF488052394CB9C00A5DC77 /* OpenChatRoomInfoViewController.swift */; }; + D1D22C932408A8F10028A9E0 /* OptionSelectingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B19DC942398974900E51458 /* OptionSelectingViewController.swift */; }; + D1D22C942408A8F30028A9E0 /* OpenChatUserProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF488072394CBFF00A5DC77 /* OpenChatUserProfileViewController.swift */; }; + D1D22C962408A8FA0028A9E0 /* FormEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B603F022396342800B81E56 /* FormEntry.swift */; }; + D1D22C972408A8FD0028A9E0 /* FormSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B603F04239638CF00B81E56 /* FormSection.swift */; }; + D1D22C982408A9000028A9E0 /* OpenChatRoomNameTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6972962396433C0079B7D2 /* OpenChatRoomNameTableViewCell.swift */; }; + D1D22C992408A9030028A9E0 /* OpenChatRoomDescriptionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD1B227239796BC00437892 /* OpenChatRoomDescriptionTableViewCell.swift */; }; + D1D22C9A2408A9050028A9E0 /* VerticallyCenteredTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD1B2212397425000437892 /* VerticallyCenteredTextView.swift */; }; + D1D22C9B2408A9080028A9E0 /* CountLimitedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD1B223239748F700437892 /* CountLimitedTextView.swift */; }; + D1D22C9C2408A9120028A9E0 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD91E1A239A3F1300E229C2 /* ToastView.swift */; }; + D1D22C9D2408A91B0028A9E0 /* AuthorizationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67EA3F23E280D700A6ADCD /* AuthorizationStatus.swift */; }; + D1D22C9E2408A94E0028A9E0 /* OpenChatCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF487F82394A80100A5DC77 /* OpenChatCategory.swift */; }; + D1D22C9F2408A9510028A9E0 /* OpenChatRoomCreatingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE56FBE239504C800C9C415 /* OpenChatRoomCreatingItem.swift */; }; + D1D22CA02408A9570028A9E0 /* GetOpenChatTermAgreementStatusRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF487F22394A29A00A5DC77 /* GetOpenChatTermAgreementStatusRequest.swift */; }; + D1D22CA12408A9570028A9E0 /* PostOpenChatCreateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF487F42394A47A00A5DC77 /* PostOpenChatCreateRequest.swift */; }; + D1D22CA22408A9570028A9E0 /* GetOpenChatRoomStatusRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF487FA2394AC3800A5DC77 /* GetOpenChatRoomStatusRequest.swift */; }; + D1D22CA32408A9570028A9E0 /* GetOpenChatRoomMembershipStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD91E2F239A50FF00E229C2 /* GetOpenChatRoomMembershipStateRequest.swift */; }; + D1D22CA42408A97A0028A9E0 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD91E16239A202600E229C2 /* String.swift */; }; + D1D22CA52408A97E0028A9E0 /* UserDefaultsValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD2E52E239DFA8A00AB3C2D /* UserDefaultsValue.swift */; }; + D1D22CA62408AA2F0028A9E0 /* LineSDKOpenChatCreatingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67EA3723E2754000A6ADCD /* LineSDKOpenChatCreatingController.swift */; }; + D1D22CA72408AA2F0028A9E0 /* LineSDKOpenChatCreatingControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67EA3923E2758800A6ADCD /* LineSDKOpenChatCreatingControllerDelegate.swift */; }; + D1D22CA82408AA2F0028A9E0 /* LineSDKOpenChatRoomInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67EA3B23E2765400A6ADCD /* LineSDKOpenChatRoomInfo.swift */; }; + D1D22CA92408AA2F0028A9E0 /* LineSDKOpenChatRoomCreatingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B67EA3D23E2770900A6ADCD /* LineSDKOpenChatRoomCreatingItem.swift */; }; + D1D22CAA2408AA440028A9E0 /* LineSDKOpenChatRoomStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8469A523EAA7AA00EE49FA /* LineSDKOpenChatRoomStatus.swift */; }; + D1D22CAB2408AA480028A9E0 /* LineSDKOpenChatRoomMembershipState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8469A723EAA7BD00EE49FA /* LineSDKOpenChatRoomMembershipState.swift */; }; + D1F20A762500BBF4005E359E /* OpenChatCreatingFormItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F20A752500BBF4005E359E /* OpenChatCreatingFormItem.swift */; }; + D1F20A782500BBFD005E359E /* OpenChatCreatingFormItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F20A752500BBF4005E359E /* OpenChatCreatingFormItem.swift */; }; + D1F20A7A2500BC12005E359E /* OpenChatCategoryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F20A792500BC11005E359E /* OpenChatCategoryExtensions.swift */; }; + D1F20A7B2500BC12005E359E /* OpenChatCategoryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F20A792500BC11005E359E /* OpenChatCategoryExtensions.swift */; }; + D1F4E716225743F3002639AC /* LineSDKShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F4E715225743F3002639AC /* LineSDKShareViewController.swift */; }; + D1F4E718225744CE002639AC /* LineSDKViewControllerInterfaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D1F4E717225744CE002639AC /* LineSDKViewControllerInterfaceTests.m */; }; + D1F6B581243C16DF00024910 /* PostOpenChatRoomJoinRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F6B580243C16DF00024910 /* PostOpenChatRoomJoinRequest.swift */; }; + D1F6B589243C2BE000024910 /* GetOpenChatRoomJoinTypeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F6B588243C2BE000024910 /* GetOpenChatRoomJoinTypeRequest.swift */; }; + D1F6B58A243C421100024910 /* PostOpenChatRoomJoinRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F6B580243C16DF00024910 /* PostOpenChatRoomJoinRequest.swift */; }; + D1F6B58B243C421400024910 /* GetOpenChatRoomJoinTypeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F6B588243C2BE000024910 /* GetOpenChatRoomJoinTypeRequest.swift */; }; + DB09851523D5AF9D0001A3B8 /* PKCETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB09851423D5AF9D0001A3B8 /* PKCETests.swift */; }; + DB0AFF612247FB2E002729AD /* PageTabViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AFF602247FB2E002729AD /* PageTabViewTests.swift */; }; + DB115EF822537F1000C16B0C /* ShareTargetSearchResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB115EF722537F1000C16B0C /* ShareTargetSearchResultViewController.swift */; }; + DB115EFA2254660C00C16B0C /* ShareRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB115EF92254660C00C16B0C /* ShareRootViewController.swift */; }; + DB5035BB22571D3B003CD0B9 /* KeyboardObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5035BA22571D3B003CD0B9 /* KeyboardObservable.swift */; }; + DB75650928CB254A001A25A5 /* UIColorExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB75650828CB254A001A25A5 /* UIColorExtensionTests.swift */; }; + DB86931023D5619E0011AEB9 /* PKCE.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB86930F23D5619E0011AEB9 /* PKCE.swift */; }; DBDCFD322126BD7200E8327A /* GetApproversInFriendsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCFD312126BD7200E8327A /* GetApproversInFriendsRequest.swift */; }; DBDCFD342126CB6300E8327A /* GetApproversInFriendsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCFD332126CB6300E8327A /* GetApproversInFriendsTests.swift */; }; DBE4E36D211D6C1A00184D66 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE4E36C211D6C1A00184D66 /* User.swift */; }; @@ -235,6 +540,8 @@ DBF20278212C137D00780358 /* GetApproversInGroupRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF20277212C137D00780358 /* GetApproversInGroupRequest.swift */; }; DBF2027B212D58FC00780358 /* GetGroupsRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF2027A212D58FC00780358 /* GetGroupsRequestTests.swift */; }; DBF2027D212D5B5200780358 /* GetApproversInGroupRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF2027C212D5B5200780358 /* GetApproversInGroupRequestTests.swift */; }; + DBFBD439224B85F500A74D44 /* SelectedTargetPanelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFBD438224B85F500A74D44 /* SelectedTargetPanelViewController.swift */; }; + DBFBD43F224E7D0500A74D44 /* SelectedTargetPanelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFBD43E224E7D0500A74D44 /* SelectedTargetPanelCell.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -274,16 +581,22 @@ 3F946A202126D13A009914ED /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 3FE8E36F214A6E70009E91BD /* LineSDKLoginButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKLoginButton.swift; sourceTree = ""; }; 4B08F9C12119863500B140DF /* LineSDKErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineSDKErrorTests.swift; sourceTree = ""; }; + 4B0FD5812330D0270054A4E8 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; 4B15EEF3211D466D00866E6C /* TextMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessage.swift; sourceTree = ""; }; 4B15EEF5211D46FF00866E6C /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; 4B15EEF7211D473400866E6C /* MessageSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSender.swift; sourceTree = ""; }; 4B15EEFA211D5BF800866E6C /* TextMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageTests.swift; sourceTree = ""; }; 4B15EEFC211D628000866E6C /* MessageSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSample.swift; sourceTree = ""; }; + 4B1658042252EF3C0008E441 /* DownloadableImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadableImageView.swift; sourceTree = ""; }; + 4B19DC942398974900E51458 /* OptionSelectingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionSelectingViewController.swift; sourceTree = ""; }; + 4B19DC972398A08400E51458 /* StyleNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleNavigationController.swift; sourceTree = ""; }; + 4B1A4EF622EAA0390030F560 /* PostMessageSendingTokenRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostMessageSendingTokenRequestTests.swift; sourceTree = ""; }; 4B1C44642137E0CA0094C1D4 /* JWT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWT.swift; sourceTree = ""; }; 4B1C44682137E1EE0094C1D4 /* JWTHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWTHelpers.swift; sourceTree = ""; }; 4B1C446A2137E98D0094C1D4 /* JWTCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWTCoder.swift; sourceTree = ""; }; 4B2422EE2134FBE8007200C2 /* LineSDKErrorConstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKErrorConstant.swift; sourceTree = ""; }; 4B25A814213687A400C74B87 /* RSA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSA.swift; sourceTree = ""; }; + 4B26F6AE221D266D00F33BF4 /* ResultUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultUtils.swift; sourceTree = ""; }; 4B2D14E2212F8EDA000DD5BE /* LineSDKUserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKUserProfile.swift; sourceTree = ""; }; 4B2D14E8212F9270000DD5BE /* LineSDKAccessTokenVerifyResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKAccessTokenVerifyResult.swift; sourceTree = ""; }; 4B2D14EC212F931D000DD5BE /* LineSDKModelInterfaceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LineSDKModelInterfaceTests.m; sourceTree = ""; }; @@ -294,6 +607,8 @@ 4B2D1500212FA9EE000DD5BE /* LineSDKHexColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKHexColor.swift; sourceTree = ""; }; 4B2D1502212FAB5A000DD5BE /* LineSDKAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LineSDKAPIError.swift; path = LineSDKObjC/Networking/LineSDKAPIError.swift; sourceTree = SOURCE_ROOT; }; 4B2D1504212FABD2000DD5BE /* LineSDKAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKAPI.swift; sourceTree = ""; }; + 4B392661224DC06F006485B4 /* ShareTargetTableViewStyling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTargetTableViewStyling.swift; sourceTree = ""; }; + 4B392663224DE2E1006485B4 /* ShareTargetSelectingSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTargetSelectingSectionHeaderView.swift; sourceTree = ""; }; 4B39D1DD211044B000A45510 /* PostTokenExchangeRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTokenExchangeRequestTests.swift; sourceTree = ""; }; 4B39D1DF2110496C00A45510 /* GetUserProfileRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUserProfileRequestTests.swift; sourceTree = ""; }; 4B39D1E121104C7A00A45510 /* LoginManagerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginManagerExtension.swift; sourceTree = ""; }; @@ -304,30 +619,50 @@ 4B3CCB9921523A2600F51D76 /* CryptoKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoKey.swift; sourceTree = ""; }; 4B3CCB9B21523B1300F51D76 /* ECDSA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECDSA.swift; sourceTree = ""; }; 4B3CCB9D2152449400F51D76 /* ECDSAKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECDSAKeyTests.swift; sourceTree = ""; }; + 4B3D78BA22420BEA00DE27D1 /* PageTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageTabView.swift; sourceTree = ""; }; 4B414D5B210EF12C00FD19BC /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; 4B414D5D210F077700FD19BC /* RequestStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestStubs.swift; sourceTree = ""; }; + 4B415937225AE40E003AE63A /* ShareViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewControllerDelegate.swift; sourceTree = ""; }; + 4B415939225AE464003AE63A /* MessageShareTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageShareTargetType.swift; sourceTree = ""; }; + 4B41593B225AE497003AE63A /* LineSDKShareViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKShareViewControllerDelegate.swift; sourceTree = ""; }; + 4B41593D225AE4E0003AE63A /* LineSDKMessageShareTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKMessageShareTargetType.swift; sourceTree = ""; }; + 4B41593F225AE65A003AE63A /* LineSDKShareTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKShareTarget.swift; sourceTree = ""; }; + 4B415941225AF024003AE63A /* LineSDKAuthorizationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKAuthorizationStatus.swift; sourceTree = ""; }; + 4B42816521ED9CE6004A0846 /* JSONConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONConverter.swift; sourceTree = ""; }; 4B4464E7212D093D008D3624 /* TemplateMessageProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateMessageProperties.swift; sourceTree = ""; }; 4B45256A2101810D00A39D4F /* LoginProcess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginProcess.swift; sourceTree = ""; }; 4B45256C2101829F00A39D4F /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; 4B45256E210188C300A39D4F /* LoginPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPermission.swift; sourceTree = ""; }; 4B4525702101938F00A39D4F /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; 4B45257521019EFB00A39D4F /* Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; - 4B45257721019FCC00A39D4F /* PostOTPRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOTPRequest.swift; sourceTree = ""; }; - 4B45257A2101A4E900A39D4F /* OneTimePassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneTimePassword.swift; sourceTree = ""; }; 4B4A9AF22159FCF700915054 /* LineSDKConstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKConstant.swift; sourceTree = ""; }; + 4B4B363623E7B3AA0016E2C0 /* StringExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensionTests.swift; sourceTree = ""; }; 4B4F0B7D21080C51006D17F5 /* PostExchangeTokenRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostExchangeTokenRequest.swift; sourceTree = ""; }; 4B4F0B7F21080D69006D17F5 /* AccessToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessToken.swift; sourceTree = ""; }; 4B4F0B8121083CDB006D17F5 /* Constant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constant.swift; sourceTree = ""; }; 4B4F0B8321084755006D17F5 /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; 4B4F0B8C210870EF006D17F5 /* GetUserProfileRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUserProfileRequest.swift; sourceTree = ""; }; + 4B5B0EF22241DEA900BA59A0 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; + 4B5B0EF42241DF3E00BA59A0 /* PageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageViewController.swift; sourceTree = ""; }; 4B5EE2DC212BAF2C0009DF2E /* FlexSpacerComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexSpacerComponent.swift; sourceTree = ""; }; 4B5EE2DE212BAFA00009DF2E /* FlexSpacerComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexSpacerComponentTests.swift; sourceTree = ""; }; 4B5EE2E0212BB53F0009DF2E /* FlexBoxComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexBoxComponentTests.swift; sourceTree = ""; }; 4B5EE2E3212BCE1A0009DF2E /* FlexBlockStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexBlockStyle.swift; sourceTree = ""; }; 4B5EE2E5212BD53E0009DF2E /* FlexBubbleContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexBubbleContainerTests.swift; sourceTree = ""; }; 4B5EE2E7212BE0D00009DF2E /* FlexCarouselContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexCarouselContainerTests.swift; sourceTree = ""; }; + 4B603F022396342800B81E56 /* FormEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormEntry.swift; sourceTree = ""; }; + 4B603F04239638CF00B81E56 /* FormSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormSection.swift; sourceTree = ""; }; + 4B61C65F2394CE87005D7C21 /* OpenChatCreatingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatCreatingNavigationController.swift; sourceTree = ""; }; + 4B62425122CAF2000092B92F /* PostMessageSendingTokenIssueRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostMessageSendingTokenIssueRequest.swift; sourceTree = ""; }; 4B63F4722106FDCC003D1BF1 /* LoginProcessURLResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginProcessURLResponse.swift; sourceTree = ""; }; 4B6508B7211812CE001796E0 /* LoginManagerOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginManagerOptions.swift; sourceTree = ""; }; + 4B67EA3723E2754000A6ADCD /* LineSDKOpenChatCreatingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKOpenChatCreatingController.swift; sourceTree = ""; }; + 4B67EA3923E2758800A6ADCD /* LineSDKOpenChatCreatingControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKOpenChatCreatingControllerDelegate.swift; sourceTree = ""; }; + 4B67EA3B23E2765400A6ADCD /* LineSDKOpenChatRoomInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKOpenChatRoomInfo.swift; sourceTree = ""; }; + 4B67EA3D23E2770900A6ADCD /* LineSDKOpenChatRoomCreatingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKOpenChatRoomCreatingItem.swift; sourceTree = ""; }; + 4B67EA3F23E280D700A6ADCD /* AuthorizationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationStatus.swift; sourceTree = ""; }; + 4B688E3D226F029B002575CC /* LoadingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicator.swift; sourceTree = ""; }; + 4B6972962396433C0079B7D2 /* OpenChatRoomNameTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatRoomNameTableViewCell.swift; sourceTree = ""; }; 4B6A0FF0212A99AD00B3ED1F /* FlexTextComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexTextComponentTests.swift; sourceTree = ""; }; 4B6A0FF2212AA15D00B3ED1F /* FlexButtonComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexButtonComponent.swift; sourceTree = ""; }; 4B6A0FF4212AA82500B3ED1F /* HexColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexColor.swift; sourceTree = ""; }; @@ -338,9 +673,10 @@ 4B6A702E2109793500D71C66 /* TestHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestHost.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4B6A70302109793500D71C66 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4B6A703C2109793700D71C66 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4B6CACC6239F44D100BD6C35 /* OpenChatControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatControllerTests.swift; sourceTree = ""; }; + 4B6CACC8239F4A2B00BD6C35 /* AssertionHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssertionHelpers.swift; sourceTree = ""; }; 4B792FB021102D9200EDDD1E /* LoginProcessURLResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginProcessURLResponseTests.swift; sourceTree = ""; }; 4B792FB221103A0200EDDD1E /* LoginManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginManagerTests.swift; sourceTree = ""; }; - 4B792FB521103D2200EDDD1E /* PostOTPRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOTPRequestTests.swift; sourceTree = ""; }; 4B792FB721103D4B00EDDD1E /* ResponseDataStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseDataStub.swift; sourceTree = ""; }; 4B7D8C1D2137B9FA00C6855A /* public_base_64_newline */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = public_base_64_newline; sourceTree = ""; }; 4B7D8C1E2137B9FA00C6855A /* public_base_64_header */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = public_base_64_header; sourceTree = ""; }; @@ -351,8 +687,13 @@ 4B7D8C232137B9FA00C6855A /* public.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = public.der; sourceTree = ""; }; 4B7D8C242137B9FA00C6855A /* test_public.cer */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = test_public.cer; sourceTree = ""; }; 4B7D8C2D2137BD8900C6855A /* private.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = private.der; sourceTree = ""; }; + 4B7FEE0E221E466E003F7369 /* ResultExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultExtensions.swift; sourceTree = ""; }; 4B813C592101B1450091F78E /* RequestAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestAdapter.swift; sourceTree = ""; }; 4B813C5B2101B65D0091F78E /* ParametersAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParametersAdapter.swift; sourceTree = ""; }; + 4B825622224CC36000D9F63E /* ShareTargetSearchController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTargetSearchController.swift; sourceTree = ""; }; + 4B8469A523EAA7AA00EE49FA /* LineSDKOpenChatRoomStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKOpenChatRoomStatus.swift; sourceTree = ""; }; + 4B8469A723EAA7BD00EE49FA /* LineSDKOpenChatRoomMembershipState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKOpenChatRoomMembershipState.swift; sourceTree = ""; }; + 4B85BB6A22C4A8E600DFE299 /* ResourceLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceLoading.swift; sourceTree = ""; }; 4B8A965621100A5800760219 /* LoginConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginConfigurationTests.swift; sourceTree = ""; }; 4B8FF4852105656500890AEF /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = ""; }; 4B8FF48A21056B3800890AEF /* CallbackQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallbackQueue.swift; sourceTree = ""; }; @@ -360,15 +701,14 @@ 4B8FF48E2105B27600890AEF /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = ""; }; 4B8FF4922105C49200890AEF /* LoginFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginFlowTests.swift; sourceTree = ""; }; 4B90587821006E5C004D717F /* LineSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LineSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 4B90587B21006E5C004D717F /* LineSDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LineSDK.h; sourceTree = ""; }; 4B90587C21006E5C004D717F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4B90588121006E5D004D717F /* LineSDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LineSDKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 4B90588621006E5D004D717F /* LineSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKTests.swift; sourceTree = ""; }; 4B90588821006E5D004D717F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4B9058D321007394004D717F /* LoginConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginConfiguration.swift; sourceTree = ""; }; 4B9058D5210073E6004D717F /* LoginManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginManager.swift; sourceTree = ""; }; - 4B9058D7210078FB004D717F /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 4B9058DB21007C8C004D717F /* LoginResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginResult.swift; sourceTree = ""; }; + 4B93F2D123CD5307003B955D /* OpenChatCreatingControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatCreatingControllerDelegate.swift; sourceTree = ""; }; 4B94D06621533D4D0049DE68 /* ECDSATests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECDSATests.swift; sourceTree = ""; }; 4B94D0682153678D0049DE68 /* JWTRSATests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JWTRSATests.swift; sourceTree = ""; }; 4B94D06A2153681E0049DE68 /* JWTECTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWTECTests.swift; sourceTree = ""; }; @@ -402,6 +742,11 @@ 4BA8E449210ED82B00355F03 /* AdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdapterTests.swift; sourceTree = ""; }; 4BA8E44B210EDB5100355F03 /* ParameterEncoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParameterEncoderTests.swift; sourceTree = ""; }; 4BA8E44D210EE79B00355F03 /* PipelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PipelineTests.swift; sourceTree = ""; }; + 4BB002C52244758000FB8BD8 /* ImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = ""; }; + 4BB002C72244788A00FB8BD8 /* ImageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = ""; }; + 4BB0E255239F5C8A008C7F3F /* FormEntryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormEntryTests.swift; sourceTree = ""; }; + 4BB0E257239F6728008C7F3F /* CountLimitedTextViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountLimitedTextViewTests.swift; sourceTree = ""; }; + 4BB0E259239F6BE9008C7F3F /* OpenChatCreatingFormItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatCreatingFormItemTests.swift; sourceTree = ""; }; 4BB2E6602135188900885687 /* LineSDKMessagingModelTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LineSDKMessagingModelTests.m; sourceTree = ""; }; 4BB95E45215B7C2F00D213AD /* API+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "API+Internal.swift"; sourceTree = ""; }; 4BBAFC442101BB9E00E7BFF6 /* LineSDKError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKError.swift; sourceTree = ""; }; @@ -409,7 +754,6 @@ 4BBAFC4A2101CE8100E7BFF6 /* Resource.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Resource.bundle; sourceTree = ""; }; 4BBAFC4F2101D31300E7BFF6 /* ConstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantTests.swift; sourceTree = ""; }; 4BBEA992212EAF9F00858627 /* LineSDKObjC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LineSDKObjC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 4BBEA994212EAF9F00858627 /* LineSDKObjC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LineSDKObjC.h; sourceTree = ""; }; 4BBEA995212EAF9F00858627 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4BBEA99E212EAFBB00858627 /* LineSDKObjCInterfaceTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LineSDKObjCInterfaceTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 4BBEA9A2212EAFBB00858627 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -423,12 +767,14 @@ 4BC4B3DE212FEF8600750794 /* LineSDKLocationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKLocationMessage.swift; sourceTree = ""; }; 4BC4B3E0212FF39D00750794 /* LineSDKTemplateMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKTemplateMessage.swift; sourceTree = ""; }; 4BC4B3E3212FF86100750794 /* LineSDKAudioMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKAudioMessage.swift; sourceTree = ""; }; + 4BC66AE422EA8A6900546FB6 /* LineSDKMessageSendingToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKMessageSendingToken.swift; sourceTree = ""; }; 4BC9B2D8212FADD60071C736 /* LinsSDKCallbackQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinsSDKCallbackQueue.swift; sourceTree = ""; }; 4BC9B2DA212FB0210071C736 /* LineSDKAPIInterfaceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LineSDKAPIInterfaceTests.m; sourceTree = ""; }; 4BC9B2DD212FB7230071C736 /* LineSDKUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKUser.swift; sourceTree = ""; }; 4BC9B2DF212FB7B60071C736 /* LineSDKGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKGroup.swift; sourceTree = ""; }; 4BC9B2E1212FB8390071C736 /* LineSDKGraphResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKGraphResponse.swift; sourceTree = ""; }; 4BC9B2E6212FCA130071C736 /* LineSDKMessagingResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKMessagingResponse.swift; sourceTree = ""; }; + 4BCD250A23FFA74100D4B6BD /* LineSDKObjC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LineSDKObjC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4BCD278D2113E9A900B90D8F /* GetVerifyTokenRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetVerifyTokenRequest.swift; sourceTree = ""; }; 4BCD278F2113EA1300B90D8F /* AccessTokenVerifyResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessTokenVerifyResult.swift; sourceTree = ""; }; 4BCD27922113EC6C00B90D8F /* CodingExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodingExtension.swift; sourceTree = ""; }; @@ -439,8 +785,19 @@ 4BD191B721116C4C00E2C481 /* Unit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Unit.swift; sourceTree = ""; }; 4BD191B921116CF700E2C481 /* PostRevokeTokenRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostRevokeTokenRequestTests.swift; sourceTree = ""; }; 4BD191BB211188D500E2C481 /* RefreshTokenPipelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshTokenPipelineTests.swift; sourceTree = ""; }; + 4BD1B2212397425000437892 /* VerticallyCenteredTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticallyCenteredTextView.swift; sourceTree = ""; }; + 4BD1B223239748F700437892 /* CountLimitedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountLimitedTextView.swift; sourceTree = ""; }; + 4BD1B227239796BC00437892 /* OpenChatRoomDescriptionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatRoomDescriptionTableViewCell.swift; sourceTree = ""; }; 4BD24CF4213E278700606411 /* JWA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWA.swift; sourceTree = ""; }; 4BD24CF6213E517800606411 /* LineSDKJWT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKJWT.swift; sourceTree = ""; }; + 4BD2E52E239DFA8A00AB3C2D /* UserDefaultsValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsValue.swift; sourceTree = ""; }; + 4BD33ED1238B627200C1E8A9 /* API+Auth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "API+Auth.swift"; sourceTree = ""; }; + 4BD33ED6238B6E8800C1E8A9 /* API+Deprecated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "API+Deprecated.swift"; sourceTree = ""; }; + 4BD33EDD238B72E500C1E8A9 /* LineSDKAuthAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKAuthAPI.swift; sourceTree = ""; }; + 4BD33EE2238B825600C1E8A9 /* LoginManagerParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginManagerParameters.swift; sourceTree = ""; }; + 4BD91E16239A202600E229C2 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; + 4BD91E1A239A3F1300E229C2 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; + 4BD91E2F239A50FF00E229C2 /* GetOpenChatRoomMembershipStateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetOpenChatRoomMembershipStateRequest.swift; sourceTree = ""; }; 4BDD2C1121338B9800DD563D /* LineSDKMessageAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKMessageAction.swift; sourceTree = ""; }; 4BDD2C132133954800DD563D /* LineSDKTemplateButtonsPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKTemplateButtonsPayload.swift; sourceTree = ""; }; 4BDD2C152133970800DD563D /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; @@ -460,23 +817,40 @@ 4BDD2C332133F32200DD563D /* LineSDKFlexSeparatorComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKFlexSeparatorComponent.swift; sourceTree = ""; }; 4BDD2C352133F32F00DD563D /* LineSDKSpacerComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKSpacerComponent.swift; sourceTree = ""; }; 4BDD2C372133F34300DD563D /* LineSDKFlexBoxComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKFlexBoxComponent.swift; sourceTree = ""; }; + 4BE4E0FB2251CEE10071FC60 /* ColumnDataStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnDataStoreTests.swift; sourceTree = ""; }; + 4BE56FBE239504C800C9C415 /* OpenChatRoomCreatingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatRoomCreatingItem.swift; sourceTree = ""; }; 4BEB4919212B94BC00BA946A /* FlexImageComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexImageComponent.swift; sourceTree = ""; }; 4BEB491B212B992B00BA946A /* FlexImageComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexImageComponentTests.swift; sourceTree = ""; }; 4BEB491D212B9E5500BA946A /* FlexFillerComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexFillerComponent.swift; sourceTree = ""; }; 4BEB491F212B9F6900BA946A /* FlexFillerComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexFillerComponentTests.swift; sourceTree = ""; }; 4BEB4921212BA05500BA946A /* FlexIconComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexIconComponent.swift; sourceTree = ""; }; - 4BEB4923212BA0A300BA946A /* FlexIconCompomentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexIconCompomentTests.swift; sourceTree = ""; }; + 4BEB4923212BA0A300BA946A /* FlexIconComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexIconComponentTests.swift; sourceTree = ""; }; 4BEB4925212BA2FD00BA946A /* FlexSeparatorComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexSeparatorComponent.swift; sourceTree = ""; }; 4BEB4927212BA8CF00BA946A /* FlexSeparatorComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexSeparatorComponentTests.swift; sourceTree = ""; }; + 4BEC048322CDA65300C95B3B /* PostMultisendMessagesWithTokenRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostMultisendMessagesWithTokenRequest.swift; sourceTree = ""; }; 4BEE5B34210AC2D000846C6D /* NotificationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationToken.swift; sourceTree = ""; }; + 4BEFF643226DCD960046DB66 /* ShareControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareControllerTests.swift; sourceTree = ""; }; + 4BF3044E221D3620005ADADD /* ResultUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultUtils.swift; sourceTree = ""; }; + 4BF3B0D5236143F70059CE93 /* GetShareFriendsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetShareFriendsRequest.swift; sourceTree = ""; }; + 4BF3B0D7236145530059CE93 /* GetShareGroupsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetShareGroupsRequest.swift; sourceTree = ""; }; 4BF45A8121377DCF00CCD28E /* CryptoError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoError.swift; sourceTree = ""; }; 4BF45A85213783D000CCD28E /* CryptoHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoHelpers.swift; sourceTree = ""; }; 4BF45A882137B29300CCD28E /* RSATests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSATests.swift; sourceTree = ""; }; 4BF45A8A2137B2C500CCD28E /* RSAKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSAKeyTests.swift; sourceTree = ""; }; + 4BF487F22394A29A00A5DC77 /* GetOpenChatTermAgreementStatusRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetOpenChatTermAgreementStatusRequest.swift; sourceTree = ""; }; + 4BF487F42394A47A00A5DC77 /* PostOpenChatCreateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOpenChatCreateRequest.swift; sourceTree = ""; }; + 4BF487F82394A80100A5DC77 /* OpenChatCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatCategory.swift; sourceTree = ""; }; + 4BF487FA2394AC3800A5DC77 /* GetOpenChatRoomStatusRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetOpenChatRoomStatusRequest.swift; sourceTree = ""; }; + 4BF488002394C99400A5DC77 /* OpenChatCreatingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatCreatingController.swift; sourceTree = ""; }; + 4BF488052394CB9C00A5DC77 /* OpenChatRoomInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatRoomInfoViewController.swift; sourceTree = ""; }; + 4BF488072394CBFF00A5DC77 /* OpenChatUserProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatUserProfileViewController.swift; sourceTree = ""; }; + 4BF5337D222E33E30080D138 /* ChainedPaginatedRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChainedPaginatedRequest.swift; sourceTree = ""; }; + 4BF5337F222E3DB80080D138 /* ChainedPaginatedRequestsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainedPaginatedRequestsTests.swift; sourceTree = ""; }; 4BF53536212A7C8500EA602A /* FlexMessageContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexMessageContainer.swift; sourceTree = ""; }; 4BF53538212A7C9100EA602A /* FlexMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexMessage.swift; sourceTree = ""; }; 4BF5353A212A7DB200EA602A /* FlexBubbleContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexBubbleContainer.swift; sourceTree = ""; }; 4BF5353C212A7E8E00EA602A /* FlexCarouselContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexCarouselContainer.swift; sourceTree = ""; }; + 4BF79914238BB71E0009C315 /* LineSDKLoginManagerParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKLoginManagerParameters.swift; sourceTree = ""; }; 4BFBEA7A211057050044C2B6 /* PostRefreshTokenRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostRefreshTokenRequest.swift; sourceTree = ""; }; 4BFC09EE213CCE7700F4594D /* JWKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKTests.swift; sourceTree = ""; }; 4BFC09F0213CDFC300F4594D /* JWKDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKDataTests.swift; sourceTree = ""; }; @@ -484,6 +858,7 @@ 4BFC09F4213CF5B200F4594D /* GetDiscoveryDocumentRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetDiscoveryDocumentRequestTests.swift; sourceTree = ""; }; 4BFC09F6213CF68000F4594D /* GetJWKSetRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetJWKSetRequest.swift; sourceTree = ""; }; 4BFC09F8213CFB6000F4594D /* GetJWKSetRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetJWKSetRequestTests.swift; sourceTree = ""; }; + 4BFCD203224CAF6100CD9544 /* ShareTargetSearchResultTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTargetSearchResultTableViewController.swift; sourceTree = ""; }; 4BFD6059212A875F009E9838 /* FlexBoxComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexBoxComponent.swift; sourceTree = ""; }; 4BFD605B212A8775009E9838 /* FlexTextComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexTextComponent.swift; sourceTree = ""; }; 4BFD605D212A8BA6009E9838 /* FlexComponentMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexComponentMessageTests.swift; sourceTree = ""; }; @@ -492,8 +867,27 @@ D102A69D2164658D00804C7C /* GetBotFriendshipStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetBotFriendshipStatus.swift; sourceTree = ""; }; D102A69F216467FE00804C7C /* GetBotFriendshipStatusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetBotFriendshipStatusTests.swift; sourceTree = ""; }; D102A6A321646AE200804C7C /* LineSDKGetBotFriendshipStatusResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKGetBotFriendshipStatusResponse.swift; sourceTree = ""; }; + D16D5323224872A300BAA3B4 /* ShareTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTarget.swift; sourceTree = ""; }; + D16D5325224873C700BAA3B4 /* ShareTargetSelectingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTargetSelectingViewController.swift; sourceTree = ""; }; + D16D5327224876B900BAA3B4 /* ColumnDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnDataStore.swift; sourceTree = ""; }; + D16D53292248B01600BAA3B4 /* ShareTargetSelectingTableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTargetSelectingTableCell.swift; sourceTree = ""; }; + D17C6FAF22237CB1007BA517 /* ResultUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultUtilsTests.swift; sourceTree = ""; }; D1A591262139489E00AC080D /* JWK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWK.swift; sourceTree = ""; }; D1A591282139495B00AC080D /* JWKSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKSet.swift; sourceTree = ""; }; + D1CB0D0E24481987001F9238 /* LineSDKOpenChatRoomJoinType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKOpenChatRoomJoinType.swift; sourceTree = ""; }; + D1F20A752500BBF4005E359E /* OpenChatCreatingFormItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenChatCreatingFormItem.swift; sourceTree = ""; }; + D1F20A792500BC11005E359E /* OpenChatCategoryExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenChatCategoryExtensions.swift; sourceTree = ""; }; + D1F4E715225743F3002639AC /* LineSDKShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKShareViewController.swift; sourceTree = ""; }; + D1F4E717225744CE002639AC /* LineSDKViewControllerInterfaceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LineSDKViewControllerInterfaceTests.m; sourceTree = ""; }; + D1F6B580243C16DF00024910 /* PostOpenChatRoomJoinRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOpenChatRoomJoinRequest.swift; sourceTree = ""; }; + D1F6B588243C2BE000024910 /* GetOpenChatRoomJoinTypeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetOpenChatRoomJoinTypeRequest.swift; sourceTree = ""; }; + DB09851423D5AF9D0001A3B8 /* PKCETests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKCETests.swift; sourceTree = ""; }; + DB0AFF602247FB2E002729AD /* PageTabViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageTabViewTests.swift; sourceTree = ""; }; + DB115EF722537F1000C16B0C /* ShareTargetSearchResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTargetSearchResultViewController.swift; sourceTree = ""; }; + DB115EF92254660C00C16B0C /* ShareRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareRootViewController.swift; sourceTree = ""; }; + DB5035BA22571D3B003CD0B9 /* KeyboardObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardObservable.swift; sourceTree = ""; }; + DB75650828CB254A001A25A5 /* UIColorExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtensionTests.swift; sourceTree = ""; }; + DB86930F23D5619E0011AEB9 /* PKCE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKCE.swift; sourceTree = ""; }; DBDCFD312126BD7200E8327A /* GetApproversInFriendsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetApproversInFriendsRequest.swift; sourceTree = ""; }; DBDCFD332126CB6300E8327A /* GetApproversInFriendsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetApproversInFriendsTests.swift; sourceTree = ""; }; DBE4E36C211D6C1A00184D66 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; @@ -504,6 +898,8 @@ DBF20277212C137D00780358 /* GetApproversInGroupRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetApproversInGroupRequest.swift; sourceTree = ""; }; DBF2027A212D58FC00780358 /* GetGroupsRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetGroupsRequestTests.swift; sourceTree = ""; }; DBF2027C212D5B5200780358 /* GetApproversInGroupRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetApproversInGroupRequestTests.swift; sourceTree = ""; }; + DBFBD438224B85F500A74D44 /* SelectedTargetPanelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedTargetPanelViewController.swift; sourceTree = ""; }; + DBFBD43E224E7D0500A74D44 /* SelectedTargetPanelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedTargetPanelCell.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -545,6 +941,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4BCD250223FFA74100D4B6BD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -552,6 +955,7 @@ isa = PBXGroup; children = ( 4B6A0FF8212AACEC00B3ED1F /* HexColorTests.swift */, + DB75650828CB254A001A25A5 /* UIColorExtensionTests.swift */, 4B15EEFA211D5BF800866E6C /* TextMessageTests.swift */, 4B9A3032212111BE00174C6F /* ImageMessageTests.swift */, 4B9A303621211CC100174C6F /* VideoMessageTests.swift */, @@ -567,7 +971,7 @@ 4B6A0FF6212AACCF00B3ED1F /* FlexButtonComponentTests.swift */, 4BEB491B212B992B00BA946A /* FlexImageComponentTests.swift */, 4BEB491F212B9F6900BA946A /* FlexFillerComponentTests.swift */, - 4BEB4923212BA0A300BA946A /* FlexIconCompomentTests.swift */, + 4BEB4923212BA0A300BA946A /* FlexIconComponentTests.swift */, 4BEB4927212BA8CF00BA946A /* FlexSeparatorComponentTests.swift */, 4B5EE2DE212BAFA00009DF2E /* FlexSpacerComponentTests.swift */, 4B5EE2E0212BB53F0009DF2E /* FlexBoxComponentTests.swift */, @@ -577,6 +981,14 @@ path = Message; sourceTree = ""; }; + 4B19DC962398A06D00E51458 /* Base */ = { + isa = PBXGroup; + children = ( + 4B19DC972398A08400E51458 /* StyleNavigationController.swift */, + ); + path = Base; + sourceTree = ""; + }; 4B1C44632137E0AF0094C1D4 /* JWT */ = { isa = PBXGroup; children = ( @@ -627,6 +1039,7 @@ 4B2D14FF212FA9CA000DD5BE /* CustomizeCoding */, 4B2D1502212FAB5A000DD5BE /* LineSDKAPIError.swift */, 4B2D1504212FABD2000DD5BE /* LineSDKAPI.swift */, + 4BD33EDD238B72E500C1E8A9 /* LineSDKAuthAPI.swift */, ); path = Networking; sourceTree = ""; @@ -652,6 +1065,7 @@ 4B45257C2101A77300A39D4F /* Client */ = { isa = PBXGroup; children = ( + 4BF5337D222E33E30080D138 /* ChainedPaginatedRequest.swift */, 4B4525702101938F00A39D4F /* Request.swift */, 4B45257521019EFB00A39D4F /* Session.swift */, 4B813C592101B1450091F78E /* RequestAdapter.swift */, @@ -661,6 +1075,19 @@ path = Client; sourceTree = ""; }; + 4B5B0EF12241DE4600BA59A0 /* SharingUI */ = { + isa = PBXGroup; + children = ( + 4BB080CC226720EF00783C60 /* Public */, + 4BB080D0226721C600783C60 /* Model */, + 4BB080CE2267215000783C60 /* PageViewController */, + 4BB080CD2267211700783C60 /* TargetSelecting */, + 4BB080CF2267218500783C60 /* TargetSearch */, + 4BB080D1226721DE00783C60 /* SelectedPanel */, + ); + path = SharingUI; + sourceTree = ""; + }; 4B5EE2E2212BCE030009DF2E /* Style */ = { isa = PBXGroup; children = ( @@ -669,6 +1096,39 @@ path = Style; sourceTree = ""; }; + 4B61C6612394CF2A005D7C21 /* Model */ = { + isa = PBXGroup; + children = ( + D1F20A792500BC11005E359E /* OpenChatCategoryExtensions.swift */, + 4B603F022396342800B81E56 /* FormEntry.swift */, + 4B603F04239638CF00B81E56 /* FormSection.swift */, + ); + path = Model; + sourceTree = ""; + }; + 4B67EA3623E2750200A6ADCD /* OpenChatUI */ = { + isa = PBXGroup; + children = ( + 4B67EA3723E2754000A6ADCD /* LineSDKOpenChatCreatingController.swift */, + 4B67EA3923E2758800A6ADCD /* LineSDKOpenChatCreatingControllerDelegate.swift */, + 4B67EA3B23E2765400A6ADCD /* LineSDKOpenChatRoomInfo.swift */, + 4B67EA3D23E2770900A6ADCD /* LineSDKOpenChatRoomCreatingItem.swift */, + ); + path = OpenChatUI; + sourceTree = ""; + }; + 4B6972952396430D0079B7D2 /* View */ = { + isa = PBXGroup; + children = ( + 4B6972962396433C0079B7D2 /* OpenChatRoomNameTableViewCell.swift */, + 4BD1B227239796BC00437892 /* OpenChatRoomDescriptionTableViewCell.swift */, + 4BD1B2212397425000437892 /* VerticallyCenteredTextView.swift */, + 4BD1B223239748F700437892 /* CountLimitedTextView.swift */, + 4BD91E1A239A3F1300E229C2 /* ToastView.swift */, + ); + path = View; + sourceTree = ""; + }; 4B6A702F2109793500D71C66 /* TestHost */ = { isa = PBXGroup; children = ( @@ -679,10 +1139,20 @@ path = TestHost; sourceTree = ""; }; + 4B6CACC5239F424500BD6C35 /* OpenChat */ = { + isa = PBXGroup; + children = ( + 4B6CACC6239F44D100BD6C35 /* OpenChatControllerTests.swift */, + 4BB0E255239F5C8A008C7F3F /* FormEntryTests.swift */, + 4BB0E257239F6728008C7F3F /* CountLimitedTextViewTests.swift */, + 4BB0E259239F6BE9008C7F3F /* OpenChatCreatingFormItemTests.swift */, + ); + path = OpenChat; + sourceTree = ""; + }; 4B792FB421103CFD00EDDD1E /* API */ = { isa = PBXGroup; children = ( - 4B792FB521103D2200EDDD1E /* PostOTPRequestTests.swift */, 4B39D1DF2110496C00A45510 /* GetUserProfileRequestTests.swift */, DBE4E371211D7C9B00184D66 /* GetFriendsRequestTests.swift */, DBDCFD332126CB6300E8327A /* GetApproversInFriendsTests.swift */, @@ -695,10 +1165,32 @@ 4B95F3FE21251BB300AD3A81 /* PostSendMessagesRequestTests.swift */, 4B95F4032125364500AD3A81 /* PostMultisendMessagesRequestTests.swift */, D102A69F216467FE00804C7C /* GetBotFriendshipStatusTests.swift */, + 4B1A4EF622EAA0390030F560 /* PostMessageSendingTokenRequestTests.swift */, ); path = API; sourceTree = ""; }; + 4B8469A423EAA77100EE49FA /* OpenChat */ = { + isa = PBXGroup; + children = ( + 4B8469A523EAA7AA00EE49FA /* LineSDKOpenChatRoomStatus.swift */, + 4B8469A723EAA7BD00EE49FA /* LineSDKOpenChatRoomMembershipState.swift */, + D1CB0D0E24481987001F9238 /* LineSDKOpenChatRoomJoinType.swift */, + ); + path = OpenChat; + sourceTree = ""; + }; + 4B87E44822E55755005AA4D2 /* LineSDKUI */ = { + isa = PBXGroup; + children = ( + 3FE8E36F214A6E70009E91BD /* LineSDKLoginButton.swift */, + 4B415941225AF024003AE63A /* LineSDKAuthorizationStatus.swift */, + D1F4E714225743D3002639AC /* SharingUI */, + 4B67EA3623E2750200A6ADCD /* OpenChatUI */, + ); + path = LineSDKUI; + sourceTree = ""; + }; 4B8A9655211009A400760219 /* Login */ = { isa = PBXGroup; children = ( @@ -706,6 +1198,7 @@ 4B8A965621100A5800760219 /* LoginConfigurationTests.swift */, 4B792FB021102D9200EDDD1E /* LoginProcessURLResponseTests.swift */, 4B792FB221103A0200EDDD1E /* LoginManagerTests.swift */, + DB09851423D5AF9D0001A3B8 /* PKCETests.swift */, ); path = Login; sourceTree = ""; @@ -731,6 +1224,7 @@ 4B6A702E2109793500D71C66 /* TestHost.app */, 4BBEA992212EAF9F00858627 /* LineSDKObjC.framework */, 4BBEA99E212EAFBB00858627 /* LineSDKObjCInterfaceTests.xctest */, + 4BCD250A23FFA74100D4B6BD /* LineSDKObjC.framework */, ); name = Products; sourceTree = ""; @@ -739,16 +1233,17 @@ isa = PBXGroup; children = ( 4BBAFC4A2101CE8100E7BFF6 /* Resource.bundle */, + 4BE7E81822C4A14A00362661 /* LineSDKUI */, 4BBAFC432101BB8200E7BFF6 /* General */, 4B9058CE210072B9004D717F /* Login */, DBE4E36E211D6C2600184D66 /* Graph */, 4BF53530212A79C800EA602A /* Messaging */, D102A6992164650A00804C7C /* Friendship */, + 4BF487EF2394A24D00A5DC77 /* OpenChat */, 4B9058CD210072B0004D717F /* Networking */, 4B25A811213684F000C74B87 /* Crypto */, 4B9058CF210072C3004D717F /* Utils */, 3F75522E2123D214004AC047 /* Assets.xcassets */, - 4B90587B21006E5C004D717F /* LineSDK.h */, 4B90587C21006E5C004D717F /* Info.plist */, ); path = LineSDK; @@ -757,6 +1252,8 @@ 4B90588521006E5D004D717F /* LineSDKTests */ = { isa = PBXGroup; children = ( + 4B6CACC5239F424500BD6C35 /* OpenChat */, + 4BE4E0FA2251C9090071FC60 /* Sharing */, 4B15EEF9211D5BE800866E6C /* Message */, 4B792FB421103CFD00EDDD1E /* API */, 4B8A9655211009A400760219 /* Login */, @@ -775,10 +1272,10 @@ 4B9058CD210072B0004D717F /* Networking */ = { isa = PBXGroup; children = ( + 4BD33ED5238B6E6600C1E8A9 /* API */, + 4BB002C4224474F800FB8BD8 /* Images */, 4B45257C2101A77300A39D4F /* Client */, 4B4525792101A4DE00A39D4F /* Model */, - 4BD191B3211154DF00E2C481 /* API.swift */, - 4BB95E45215B7C2F00D213AD /* API+Internal.swift */, ); path = Networking; sourceTree = ""; @@ -786,12 +1283,13 @@ 4B9058CE210072B9004D717F /* Login */ = { isa = PBXGroup; children = ( - 3F75523021244502004AC047 /* LoginButton.swift */, 4BF53534212A7A0700EA602A /* Request */, + DB86931123D561A40011AEB9 /* PKCE */, 4BF53533212A79FA00EA602A /* Model */, 4B9058D321007394004D717F /* LoginConfiguration.swift */, 4B9058D5210073E6004D717F /* LoginManager.swift */, 4B6508B7211812CE001796E0 /* LoginManagerOptions.swift */, + 4BD33EE2238B825600C1E8A9 /* LoginManagerParameters.swift */, 4B9058DB21007C8C004D717F /* LoginResult.swift */, 4B45256A2101810D00A39D4F /* LoginProcess.swift */, 4B63F4722106FDCC003D1BF1 /* LoginProcessURLResponse.swift */, @@ -803,7 +1301,7 @@ 4B9058CF210072C3004D717F /* Utils */ = { isa = PBXGroup; children = ( - 4B9058D7210078FB004D717F /* Result.swift */, + 4BF3044E221D3620005ADADD /* ResultUtils.swift */, 4B45256C2101829F00A39D4F /* Helpers.swift */, 4B8FF4852105656500890AEF /* Delegate.swift */, 4B8FF48A21056B3800890AEF /* CallbackQueue.swift */, @@ -811,6 +1309,11 @@ 4B4F0B8121083CDB006D17F5 /* Constant.swift */, 4B6A7025210956B600D71C66 /* KeychainStore.swift */, 4BEE5B34210AC2D000846C6D /* NotificationToken.swift */, + DB5035BA22571D3B003CD0B9 /* KeyboardObservable.swift */, + 4B688E3D226F029B002575CC /* LoadingIndicator.swift */, + 4B0FD5812330D0270054A4E8 /* Colors.swift */, + 4BD91E16239A202600E229C2 /* String.swift */, + 4BD2E52E239DFA8A00AB3C2D /* UserDefaultsValue.swift */, ); path = Utils; sourceTree = ""; @@ -854,6 +1357,10 @@ 4B39D1E321104DBA00A45510 /* APITests.swift */, 4B39D1E521104F3B00A45510 /* ViewControllerCompatibleTest.swift */, 4B15EEFC211D628000866E6C /* MessageSample.swift */, + 4B26F6AE221D266D00F33BF4 /* ResultUtils.swift */, + D17C6FAF22237CB1007BA517 /* ResultUtilsTests.swift */, + 4B6CACC8239F4A2B00BD6C35 /* AssertionHelpers.swift */, + 4B4B363623E7B3AA0016E2C0 /* StringExtensionTests.swift */, ); path = Utils; sourceTree = ""; @@ -866,10 +1373,80 @@ 4BA8E44B210EDB5100355F03 /* ParameterEncoderTests.swift */, 4BA8E44D210EE79B00355F03 /* PipelineTests.swift */, 4BD191BB211188D500E2C481 /* RefreshTokenPipelineTests.swift */, + 4BF5337F222E3DB80080D138 /* ChainedPaginatedRequestsTests.swift */, ); path = Networking; sourceTree = ""; }; + 4BB002C4224474F800FB8BD8 /* Images */ = { + isa = PBXGroup; + children = ( + 4BB002C52244758000FB8BD8 /* ImageDownloader.swift */, + 4BB002C72244788A00FB8BD8 /* ImageManager.swift */, + 4B1658042252EF3C0008E441 /* DownloadableImageView.swift */, + ); + path = Images; + sourceTree = ""; + }; + 4BB080CC226720EF00783C60 /* Public */ = { + isa = PBXGroup; + children = ( + 4B5B0EF22241DEA900BA59A0 /* ShareViewController.swift */, + 4B415937225AE40E003AE63A /* ShareViewControllerDelegate.swift */, + 4B415939225AE464003AE63A /* MessageShareTargetType.swift */, + D16D5323224872A300BAA3B4 /* ShareTarget.swift */, + ); + path = Public; + sourceTree = ""; + }; + 4BB080CD2267211700783C60 /* TargetSelecting */ = { + isa = PBXGroup; + children = ( + DB115EF92254660C00C16B0C /* ShareRootViewController.swift */, + D16D5325224873C700BAA3B4 /* ShareTargetSelectingViewController.swift */, + D16D53292248B01600BAA3B4 /* ShareTargetSelectingTableCell.swift */, + 4B825622224CC36000D9F63E /* ShareTargetSearchController.swift */, + 4B392661224DC06F006485B4 /* ShareTargetTableViewStyling.swift */, + ); + path = TargetSelecting; + sourceTree = ""; + }; + 4BB080CE2267215000783C60 /* PageViewController */ = { + isa = PBXGroup; + children = ( + 4B5B0EF42241DF3E00BA59A0 /* PageViewController.swift */, + 4B3D78BA22420BEA00DE27D1 /* PageTabView.swift */, + ); + path = PageViewController; + sourceTree = ""; + }; + 4BB080CF2267218500783C60 /* TargetSearch */ = { + isa = PBXGroup; + children = ( + DB115EF722537F1000C16B0C /* ShareTargetSearchResultViewController.swift */, + 4BFCD203224CAF6100CD9544 /* ShareTargetSearchResultTableViewController.swift */, + 4B392663224DE2E1006485B4 /* ShareTargetSelectingSectionHeaderView.swift */, + ); + path = TargetSearch; + sourceTree = ""; + }; + 4BB080D0226721C600783C60 /* Model */ = { + isa = PBXGroup; + children = ( + D16D5327224876B900BAA3B4 /* ColumnDataStore.swift */, + ); + path = Model; + sourceTree = ""; + }; + 4BB080D1226721DE00783C60 /* SelectedPanel */ = { + isa = PBXGroup; + children = ( + DBFBD438224B85F500A74D44 /* SelectedTargetPanelViewController.swift */, + DBFBD43E224E7D0500A74D44 /* SelectedTargetPanelCell.swift */, + ); + path = SelectedPanel; + sourceTree = ""; + }; 4BBAFC432101BB8200E7BFF6 /* General */ = { isa = PBXGroup; children = ( @@ -881,14 +1458,16 @@ 4BBEA993212EAF9F00858627 /* LineSDKObjC */ = { isa = PBXGroup; children = ( + 4B87E44822E55755005AA4D2 /* LineSDKUI */, D102A6A221646AA100804C7C /* Friendship */, 4B2422ED2134FBD6007200C2 /* General */, 4BBEA9AD212EAFFB00858627 /* Login */, 4BC9B2DC212FB7100071C736 /* Social */, + 4BC66AE322EA8A4500546FB6 /* Sharing */, + 4B8469A423EAA77100EE49FA /* OpenChat */, 4BC9B2E5212FC9E30071C736 /* Messaging */, 4B2D14FB212F9FA5000DD5BE /* Networking */, 4BC9B2D7212FAD9E0071C736 /* Utils */, - 4BBEA994212EAF9F00858627 /* LineSDKObjC.h */, 4BBEA995212EAF9F00858627 /* Info.plist */, ); path = LineSDKObjC; @@ -901,6 +1480,7 @@ 4B2D14EC212F931D000DD5BE /* LineSDKModelInterfaceTests.m */, 4BB2E6602135188900885687 /* LineSDKMessagingModelTests.m */, 4BC9B2DA212FB0210071C736 /* LineSDKAPIInterfaceTests.m */, + D1F4E717225744CE002639AC /* LineSDKViewControllerInterfaceTests.m */, ); path = LineSDKObjCInterfaceTests; sourceTree = ""; @@ -918,10 +1498,10 @@ 4BBEA9AE212EB01C00858627 /* Model */, 4B2D14F4212F97A6000DD5BE /* LineSDKLoginManager.swift */, 4B2D14EE212F9468000DD5BE /* LineSDKLoginManagerOptions.swift */, + 4BF79914238BB71E0009C315 /* LineSDKLoginManagerParameters.swift */, 4B2D14F0212F95B3000DD5BE /* LineSDKLoginResult.swift */, 4B2D14F2212F96A7000DD5BE /* LineSDKLoginProcess.swift */, 4BBEA9AF212EB03200858627 /* LineSDKLoginPermission.swift */, - 3FE8E36F214A6E70009E91BD /* LineSDKLoginButton.swift */, ); path = Login; sourceTree = ""; @@ -949,12 +1529,22 @@ path = Template; sourceTree = ""; }; + 4BC66AE322EA8A4500546FB6 /* Sharing */ = { + isa = PBXGroup; + children = ( + 4BC66AE422EA8A6900546FB6 /* LineSDKMessageSendingToken.swift */, + ); + path = Sharing; + sourceTree = ""; + }; 4BC9B2D7212FAD9E0071C736 /* Utils */ = { isa = PBXGroup; children = ( 4BC9B2D8212FADD60071C736 /* LinsSDKCallbackQueue.swift */, 4BDD2C152133970800DD563D /* Log.swift */, 4B4A9AF22159FCF700915054 /* LineSDKConstant.swift */, + 4B42816521ED9CE6004A0846 /* JSONConverter.swift */, + 4B7FEE0E221E466E003F7369 /* ResultExtensions.swift */, ); path = Utils; sourceTree = ""; @@ -997,6 +1587,17 @@ path = CustomizeCoding; sourceTree = ""; }; + 4BD33ED5238B6E6600C1E8A9 /* API */ = { + isa = PBXGroup; + children = ( + 4BD191B3211154DF00E2C481 /* API.swift */, + 4BB95E45215B7C2F00D213AD /* API+Internal.swift */, + 4BD33ED1238B627200C1E8A9 /* API+Auth.swift */, + 4BD33ED6238B6E8800C1E8A9 /* API+Deprecated.swift */, + ); + path = API; + sourceTree = ""; + }; 4BDD2C1021338B8600DD563D /* Actions */ = { isa = PBXGroup; children = ( @@ -1032,6 +1633,29 @@ path = Component; sourceTree = ""; }; + 4BE4E0FA2251C9090071FC60 /* Sharing */ = { + isa = PBXGroup; + children = ( + DB0AFF602247FB2E002729AD /* PageTabViewTests.swift */, + 4BEFF643226DCD960046DB66 /* ShareControllerTests.swift */, + 4BE4E0FB2251CEE10071FC60 /* ColumnDataStoreTests.swift */, + ); + path = Sharing; + sourceTree = ""; + }; + 4BE7E81822C4A14A00362661 /* LineSDKUI */ = { + isa = PBXGroup; + children = ( + 4B19DC962398A06D00E51458 /* Base */, + 4B5B0EF12241DE4600BA59A0 /* SharingUI */, + 4BF487FE2394C64600A5DC77 /* OpenChatUI */, + 3F75523021244502004AC047 /* LoginButton.swift */, + 4B85BB6A22C4A8E600DFE299 /* ResourceLoading.swift */, + 4B67EA3F23E280D700A6ADCD /* AuthorizationStatus.swift */, + ); + path = LineSDKUI; + sourceTree = ""; + }; 4BF45A872137B27F00CCD28E /* Crypto */ = { isa = PBXGroup; children = ( @@ -1066,6 +1690,69 @@ path = SampleRSAKeys; sourceTree = ""; }; + 4BF487EF2394A24D00A5DC77 /* OpenChat */ = { + isa = PBXGroup; + children = ( + 4BF487F02394A26100A5DC77 /* Model */, + 4BF487F12394A26600A5DC77 /* Request */, + ); + path = OpenChat; + sourceTree = ""; + }; + 4BF487F02394A26100A5DC77 /* Model */ = { + isa = PBXGroup; + children = ( + 4BF487F82394A80100A5DC77 /* OpenChatCategory.swift */, + D1F20A752500BBF4005E359E /* OpenChatCreatingFormItem.swift */, + 4BE56FBE239504C800C9C415 /* OpenChatRoomCreatingItem.swift */, + ); + path = Model; + sourceTree = ""; + }; + 4BF487F12394A26600A5DC77 /* Request */ = { + isa = PBXGroup; + children = ( + 4BF487F22394A29A00A5DC77 /* GetOpenChatTermAgreementStatusRequest.swift */, + 4BF487F42394A47A00A5DC77 /* PostOpenChatCreateRequest.swift */, + 4BF487FA2394AC3800A5DC77 /* GetOpenChatRoomStatusRequest.swift */, + 4BD91E2F239A50FF00E229C2 /* GetOpenChatRoomMembershipStateRequest.swift */, + D1F6B580243C16DF00024910 /* PostOpenChatRoomJoinRequest.swift */, + D1F6B588243C2BE000024910 /* GetOpenChatRoomJoinTypeRequest.swift */, + ); + path = Request; + sourceTree = ""; + }; + 4BF487FE2394C64600A5DC77 /* OpenChatUI */ = { + isa = PBXGroup; + children = ( + 4BF487FF2394C98000A5DC77 /* Public */, + 4BF488042394CB8400A5DC77 /* ViewControllers */, + 4B61C6612394CF2A005D7C21 /* Model */, + 4B6972952396430D0079B7D2 /* View */, + ); + path = OpenChatUI; + sourceTree = ""; + }; + 4BF487FF2394C98000A5DC77 /* Public */ = { + isa = PBXGroup; + children = ( + 4BF488002394C99400A5DC77 /* OpenChatCreatingController.swift */, + 4B93F2D123CD5307003B955D /* OpenChatCreatingControllerDelegate.swift */, + ); + path = Public; + sourceTree = ""; + }; + 4BF488042394CB8400A5DC77 /* ViewControllers */ = { + isa = PBXGroup; + children = ( + 4B61C65F2394CE87005D7C21 /* OpenChatCreatingNavigationController.swift */, + 4BF488052394CB9C00A5DC77 /* OpenChatRoomInfoViewController.swift */, + 4B19DC942398974900E51458 /* OptionSelectingViewController.swift */, + 4BF488072394CBFF00A5DC77 /* OpenChatUserProfileViewController.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; 4BF53530212A79C800EA602A /* Messaging */ = { isa = PBXGroup; children = ( @@ -1100,6 +1787,8 @@ children = ( 4B9B9AA62123C6E400325B9B /* PostSendMessagesRequest.swift */, 4B95F40121252D2F00AD3A81 /* PostMultisendMessagesRequest.swift */, + 4BEC048322CDA65300C95B3B /* PostMultisendMessagesWithTokenRequest.swift */, + 4B62425122CAF2000092B92F /* PostMessageSendingTokenIssueRequest.swift */, ); path = Request; sourceTree = ""; @@ -1107,7 +1796,6 @@ 4BF53533212A79FA00EA602A /* Model */ = { isa = PBXGroup; children = ( - 4B45257A2101A4E900A39D4F /* OneTimePassword.swift */, 4BBAFC462101C8A100E7BFF6 /* AccessTokenStore.swift */, 4B4F0B7F21080D69006D17F5 /* AccessToken.swift */, 4BCD278F2113EA1300B90D8F /* AccessTokenVerifyResult.swift */, @@ -1119,7 +1807,6 @@ 4BF53534212A7A0700EA602A /* Request */ = { isa = PBXGroup; children = ( - 4B45257721019FCC00A39D4F /* PostOTPRequest.swift */, 4B4F0B7D21080C51006D17F5 /* PostExchangeTokenRequest.swift */, 4B4F0B8C210870EF006D17F5 /* GetUserProfileRequest.swift */, 4BFBEA7A211057050044C2B6 /* PostRefreshTokenRequest.swift */, @@ -1181,6 +1868,8 @@ DBF20275212C11A600780358 /* GetGroupsRequest.swift */, DBDCFD312126BD7200E8327A /* GetApproversInFriendsRequest.swift */, DBF20277212C137D00780358 /* GetApproversInGroupRequest.swift */, + 4BF3B0D5236143F70059CE93 /* GetShareFriendsRequest.swift */, + 4BF3B0D7236145530059CE93 /* GetShareGroupsRequest.swift */, ); path = Request; sourceTree = ""; @@ -1220,6 +1909,25 @@ path = JWK; sourceTree = ""; }; + D1F4E714225743D3002639AC /* SharingUI */ = { + isa = PBXGroup; + children = ( + D1F4E715225743F3002639AC /* LineSDKShareViewController.swift */, + 4B41593B225AE497003AE63A /* LineSDKShareViewControllerDelegate.swift */, + 4B41593F225AE65A003AE63A /* LineSDKShareTarget.swift */, + 4B41593D225AE4E0003AE63A /* LineSDKMessageShareTargetType.swift */, + ); + path = SharingUI; + sourceTree = ""; + }; + DB86931123D561A40011AEB9 /* PKCE */ = { + isa = PBXGroup; + children = ( + DB86930F23D5619E0011AEB9 /* PKCE.swift */, + ); + path = PKCE; + sourceTree = ""; + }; DBE4E36E211D6C2600184D66 /* Graph */ = { isa = PBXGroup; children = ( @@ -1236,7 +1944,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 4B90588921006E5D004D717F /* LineSDK.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1244,7 +1951,13 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 4BBEA996212EAF9F00858627 /* LineSDKObjC.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4BCD250423FFA74100D4B6BD /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1342,36 +2055,58 @@ productReference = 4BBEA99E212EAFBB00858627 /* LineSDKObjCInterfaceTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 4BCD24C423FFA74100D4B6BD /* LineSDKObjCBinary */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4BCD250723FFA74100D4B6BD /* Build configuration list for PBXNativeTarget "LineSDKObjCBinary" */; + buildPhases = ( + 4BCD24C723FFA74100D4B6BD /* Sources */, + 4BCD250223FFA74100D4B6BD /* Frameworks */, + 4BCD250423FFA74100D4B6BD /* Headers */, + 4BCD250623FFA74100D4B6BD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = LineSDKObjCBinary; + productName = LineSDKObjC; + productReference = 4BCD250A23FFA74100D4B6BD /* LineSDKObjC.framework */; + productType = "com.apple.product-type.framework"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 4B90586F21006E5C004D717F /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0940; - LastUpgradeCheck = 0940; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "LINE Corp"; TargetAttributes = { 4B6A702D2109793500D71C66 = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; }; 4B90587721006E5C004D717F = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1340; }; 4B90588021006E5D004D717F = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; TestTargetID = 4B6A702D2109793500D71C66; }; 4BBEA991212EAF9F00858627 = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 0940; + LastSwiftMigration = 1340; }; 4BBEA99D212EAFBB00858627 = { CreatedOnToolsVersion = 9.4.1; }; + 4BCD24C423FFA74100D4B6BD = { + LastSwiftMigration = 1340; + }; }; }; buildConfigurationList = 4B90587221006E5C004D717F /* Build configuration list for PBXProject "LineSDK" */; @@ -1409,6 +2144,7 @@ 4B6A702D2109793500D71C66 /* TestHost */, 4BBEA991212EAF9F00858627 /* LineSDKObjC */, 4BBEA99D212EAFBB00858627 /* LineSDKObjCInterfaceTests */, + 4BCD24C423FFA74100D4B6BD /* LineSDKObjCBinary */, ); }; /* End PBXProject section */ @@ -1461,6 +2197,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4BCD250623FFA74100D4B6BD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B32D62423FFBDA500052485 /* Resource.bundle in Resources */, + 4B32D62523FFBDAD00052485 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1477,26 +2222,31 @@ buildActionMask = 2147483647; files = ( DBF20276212C11A600780358 /* GetGroupsRequest.swift in Sources */, + 4B5B0EF52241DF3E00BA59A0 /* PageViewController.swift in Sources */, 4BFC09F3213CF09A00F4594D /* GetDiscoveryDocumentRequest.swift in Sources */, - 4B45257B2101A4E900A39D4F /* OneTimePassword.swift in Sources */, 4B813C5A2101B1450091F78E /* RequestAdapter.swift in Sources */, 4B9A305C21216D2E00174C6F /* TemplateImageCarouselPayload.swift in Sources */, 4B8FF48D210598F500890AEF /* ResponsePipeline.swift in Sources */, 4B9A30582121613500174C6F /* TemplateCarouselPayload.swift in Sources */, + DB86931023D5619E0011AEB9 /* PKCE.swift in Sources */, 4B45256D2101829F00A39D4F /* Helpers.swift in Sources */, 4B9A304E2121405300174C6F /* MessageAction.swift in Sources */, 4B45256F210188C300A39D4F /* LoginPermission.swift in Sources */, 4BF53539212A7C9100EA602A /* FlexMessage.swift in Sources */, D1A591272139489E00AC080D /* JWK.swift in Sources */, + 4BD1B228239796BC00437892 /* OpenChatRoomDescriptionTableViewCell.swift in Sources */, 4BD191B821116C4C00E2C481 /* Unit.swift in Sources */, 4BCD27902113EA1300B90D8F /* AccessTokenVerifyResult.swift in Sources */, - 4B45257821019FCC00A39D4F /* PostOTPRequest.swift in Sources */, + 4B41593A225AE464003AE63A /* MessageShareTargetType.swift in Sources */, + 4BD33ED7238B6E8800C1E8A9 /* API+Deprecated.swift in Sources */, 4B1C446B2137E98D0094C1D4 /* JWTCoder.swift in Sources */, DBE4E370211D6CF500184D66 /* GetFriendsRequest.swift in Sources */, 4B6A0FF5212AA82500B3ED1F /* HexColor.swift in Sources */, 4B15EEF6211D46FF00866E6C /* Message.swift in Sources */, 4BBAFC452101BB9E00E7BFF6 /* LineSDKError.swift in Sources */, + 4B67EA4023E280D700A6ADCD /* AuthorizationStatus.swift in Sources */, 4BD24CF5213E278700606411 /* JWA.swift in Sources */, + D16D5326224873C700BAA3B4 /* ShareTargetSelectingViewController.swift in Sources */, 4BCD278E2113E9A900B90D8F /* GetVerifyTokenRequest.swift in Sources */, 4B15EEF8211D473400866E6C /* MessageSender.swift in Sources */, 4B813C5C2101B65D0091F78E /* ParametersAdapter.swift in Sources */, @@ -1505,67 +2255,120 @@ 4B4F0B8D210870EF006D17F5 /* GetUserProfileRequest.swift in Sources */, 4B6A7026210956B600D71C66 /* KeychainStore.swift in Sources */, 4B9A303F2121277400174C6F /* LocationMessage.swift in Sources */, + 4BF488062394CB9C00A5DC77 /* OpenChatRoomInfoViewController.swift in Sources */, 4B9A304B21213DBB00174C6F /* TemplateMessagePayload.swift in Sources */, 4BF45A86213783D000CCD28E /* CryptoHelpers.swift in Sources */, + 4BFCD204224CAF6100CD9544 /* ShareTargetSearchResultTableViewController.swift in Sources */, + 4B19DC982398A08400E51458 /* StyleNavigationController.swift in Sources */, + 4B3D78BB22420BEA00DE27D1 /* PageTabView.swift in Sources */, 4B45257621019EFB00A39D4F /* Session.swift in Sources */, 4B8FF4862105656500890AEF /* Delegate.swift in Sources */, 4B4F0B8221083CDB006D17F5 /* Constant.swift in Sources */, + 4BF5337E222E33E30080D138 /* ChainedPaginatedRequest.swift in Sources */, + 4B415938225AE40E003AE63A /* ShareViewControllerDelegate.swift in Sources */, 4BD191B4211154DF00E2C481 /* API.swift in Sources */, + 4B603F032396342800B81E56 /* FormEntry.swift in Sources */, 4BFD6060212A8F00009E9838 /* FlexMessageProperties.swift in Sources */, + 4B5B0EF32241DEA900BA59A0 /* ShareViewController.swift in Sources */, + 4BD91E1B239A3F1300E229C2 /* ToastView.swift in Sources */, DBE4E36D211D6C1A00184D66 /* User.swift in Sources */, 4B8FF48B21056B3800890AEF /* CallbackQueue.swift in Sources */, D102A69E2164658D00804C7C /* GetBotFriendshipStatus.swift in Sources */, 4B3CCB9A21523A2600F51D76 /* CryptoKey.swift in Sources */, + 4B19DC952398974900E51458 /* OptionSelectingViewController.swift in Sources */, + 4B6972972396433C0079B7D2 /* OpenChatRoomNameTableViewCell.swift in Sources */, + 4B93F2D223CD5307003B955D /* OpenChatCreatingControllerDelegate.swift in Sources */, + D1F6B589243C2BE000024910 /* GetOpenChatRoomJoinTypeRequest.swift in Sources */, 4BF53537212A7C8500EA602A /* FlexMessageContainer.swift in Sources */, + 4BF487F32394A29A00A5DC77 /* GetOpenChatTermAgreementStatusRequest.swift in Sources */, + 4BD1B224239748F700437892 /* CountLimitedTextView.swift in Sources */, + 4BB002C62244758000FB8BD8 /* ImageDownloader.swift in Sources */, 4BEB4926212BA2FD00BA946A /* FlexSeparatorComponent.swift in Sources */, + 4BD91E30239A50FF00E229C2 /* GetOpenChatRoomMembershipStateRequest.swift in Sources */, + 4B62425222CAF2000092B92F /* PostMessageSendingTokenIssueRequest.swift in Sources */, 4BF5353D212A7E8E00EA602A /* FlexCarouselContainer.swift in Sources */, + DB115EFA2254660C00C16B0C /* ShareRootViewController.swift in Sources */, 3F75523121244502004AC047 /* LoginButton.swift in Sources */, + 4B61C6602394CE87005D7C21 /* OpenChatCreatingNavigationController.swift in Sources */, 4BBAFC472101C8A100E7BFF6 /* AccessTokenStore.swift in Sources */, 4B9058DC21007C8C004D717F /* LoginResult.swift in Sources */, 4BB95E46215B7C2F00D213AD /* API+Internal.swift in Sources */, + 4B392662224DC06F006485B4 /* ShareTargetTableViewStyling.swift in Sources */, 4B9A3047212139C000174C6F /* TemplateMessage.swift in Sources */, 4B1C44652137E0CA0094C1D4 /* JWT.swift in Sources */, - 4B9058D8210078FB004D717F /* Result.swift in Sources */, + 4BB002C82244788A00FB8BD8 /* ImageManager.swift in Sources */, 4B9A30352121182400174C6F /* VideoMessage.swift in Sources */, 4B9A304921213CE100174C6F /* TemplateButtonsPayload.swift in Sources */, + 4B688E3E226F029B002575CC /* LoadingIndicator.swift in Sources */, 4B6508B8211812CE001796E0 /* LoginManagerOptions.swift in Sources */, 4BEB491E212B9E5500BA946A /* FlexFillerComponent.swift in Sources */, + D16D532A2248B01600BAA3B4 /* ShareTargetSelectingTableCell.swift in Sources */, + D16D5324224872A300BAA3B4 /* ShareTarget.swift in Sources */, + 4BF487F52394A47A00A5DC77 /* PostOpenChatCreateRequest.swift in Sources */, 4BD191B621115ED800E2C481 /* PostRevokeTokenRequest.swift in Sources */, + 4BF488082394CBFF00A5DC77 /* OpenChatUserProfileViewController.swift in Sources */, 4B6A0FF3212AA15D00B3ED1F /* FlexButtonComponent.swift in Sources */, 4B9A303921211EC400174C6F /* AudioMessage.swift in Sources */, + 4BF488012394C99400A5DC77 /* OpenChatCreatingController.swift in Sources */, + 4B825623224CC36000D9F63E /* ShareTargetSearchController.swift in Sources */, + 4BF3B0D8236145530059CE93 /* GetShareGroupsRequest.swift in Sources */, 4B8FF48F2105B27600890AEF /* URLs.swift in Sources */, + 4BF487F92394A80100A5DC77 /* OpenChatCategory.swift in Sources */, + 4BEC048422CDA65300C95B3B /* PostMultisendMessagesWithTokenRequest.swift in Sources */, + D16D5328224876B900BAA3B4 /* ColumnDataStore.swift in Sources */, 4B9058D421007394004D717F /* LoginConfiguration.swift in Sources */, 4B3CCB9C21523B1300F51D76 /* ECDSA.swift in Sources */, 4B9058D6210073E6004D717F /* LoginManager.swift in Sources */, + 4BD33ED2238B627200C1E8A9 /* API+Auth.swift in Sources */, 4B4F0B7E21080C51006D17F5 /* PostExchangeTokenRequest.swift in Sources */, + 4BF3B0D6236143F70059CE93 /* GetShareFriendsRequest.swift in Sources */, + 4BD2E52F239DFA8A00AB3C2D /* UserDefaultsValue.swift in Sources */, + 4BE56FBF239504C800C9C415 /* OpenChatRoomCreatingItem.swift in Sources */, 4B4F0B8021080D69006D17F5 /* AccessToken.swift in Sources */, 4BEB4922212BA05500BA946A /* FlexIconComponent.swift in Sources */, 4BEE5B35210AC2D000846C6D /* NotificationToken.swift in Sources */, 4B9A303B212121FC00174C6F /* MessageProtocols.swift in Sources */, + 4BD1B2222397425000437892 /* VerticallyCenteredTextView.swift in Sources */, + 4B1658052252EF3C0008E441 /* DownloadableImageView.swift in Sources */, 4BF5353B212A7DB200EA602A /* FlexBubbleContainer.swift in Sources */, + 4BD91E17239A202600E229C2 /* String.swift in Sources */, + DBFBD43F224E7D0500A74D44 /* SelectedTargetPanelCell.swift in Sources */, + 4B85BB6B22C4A8E600DFE299 /* ResourceLoading.swift in Sources */, + DB5035BB22571D3B003CD0B9 /* KeyboardObservable.swift in Sources */, 4B95F40221252D2F00AD3A81 /* PostMultisendMessagesRequest.swift in Sources */, + D1F20A7A2500BC12005E359E /* OpenChatCategoryExtensions.swift in Sources */, 4B9B9AA72123C6E400325B9B /* PostSendMessagesRequest.swift in Sources */, 4B3CCB762152295300F51D76 /* CryptoData.swift in Sources */, + D1F6B581243C16DF00024910 /* PostOpenChatRoomJoinRequest.swift in Sources */, 4BCD27932113EC6C00B90D8F /* CodingExtension.swift in Sources */, 4BEB491A212B94BC00BA946A /* FlexImageComponent.swift in Sources */, 4B1C44692137E1EE0094C1D4 /* JWTHelpers.swift in Sources */, 4B5EE2E4212BCE1A0009DF2E /* FlexBlockStyle.swift in Sources */, 4B63F4732106FDCC003D1BF1 /* LoginProcessURLResponse.swift in Sources */, + 4B392664224DE2E1006485B4 /* ShareTargetSelectingSectionHeaderView.swift in Sources */, 4B25A815213687A400C74B87 /* RSA.swift in Sources */, DBDCFD322126BD7200E8327A /* GetApproversInFriendsRequest.swift in Sources */, 4B5EE2DD212BAF2C0009DF2E /* FlexSpacerComponent.swift in Sources */, 4B414D5C210EF12C00FD19BC /* APIError.swift in Sources */, + 4B0FD5822330D0270054A4E8 /* Colors.swift in Sources */, 4B3CCB78215229DC00F51D76 /* CryptoAlgorithm.swift in Sources */, 4BFC09F7213CF68000F4594D /* GetJWKSetRequest.swift in Sources */, DBF20274212C114D00780358 /* Group.swift in Sources */, + 4B7FEDC6221E4245003F7369 /* ResultUtils.swift in Sources */, 4B4F0B8421084755006D17F5 /* UserProfile.swift in Sources */, 4BFD605A212A875F009E9838 /* FlexBoxComponent.swift in Sources */, + DB115EF822537F1000C16B0C /* ShareTargetSearchResultViewController.swift in Sources */, 4B4525712101938F00A39D4F /* Request.swift in Sources */, 4BFD605C212A8775009E9838 /* FlexTextComponent.swift in Sources */, + 4B603F05239638CF00B81E56 /* FormSection.swift in Sources */, 4B9A305421215A1A00174C6F /* TemplateConfirmPayload.swift in Sources */, 4BF45A8221377DCF00CCD28E /* CryptoError.swift in Sources */, + DBFBD439224B85F500A74D44 /* SelectedTargetPanelViewController.swift in Sources */, + 4BD33EE3238B825600C1E8A9 /* LoginManagerParameters.swift in Sources */, 4BFD6062212A8F55009E9838 /* FlexMessageComponent.swift in Sources */, 4B4464E8212D093D008D3624 /* TemplateMessageProperties.swift in Sources */, + 4BF487FB2394AC3800A5DC77 /* GetOpenChatRoomStatusRequest.swift in Sources */, + D1F20A762500BBF4005E359E /* OpenChatCreatingFormItem.swift in Sources */, 4B45256B2101810D00A39D4F /* LoginProcess.swift in Sources */, 4B9A303121210FE700174C6F /* ImageMessage.swift in Sources */, DBF20278212C137D00780358 /* GetApproversInGroupRequest.swift in Sources */, @@ -1579,10 +2382,14 @@ files = ( D102A6A0216467FE00804C7C /* GetBotFriendshipStatusTests.swift in Sources */, 4BD191B22111452E00E2C481 /* PostRefreshTokenRequestTests.swift in Sources */, + 4B6CACC7239F44D100BD6C35 /* OpenChatControllerTests.swift in Sources */, 4BA8E44E210EE79B00355F03 /* PipelineTests.swift in Sources */, 4B8FF4932105C49200890AEF /* LoginFlowTests.swift in Sources */, - 4B792FB621103D2200EDDD1E /* PostOTPRequestTests.swift in Sources */, + 4B6CACC9239F4A2B00BD6C35 /* AssertionHelpers.swift in Sources */, + 4BB0E256239F5C8A008C7F3F /* FormEntryTests.swift in Sources */, + DB0AFF612247FB2E002729AD /* PageTabViewTests.swift in Sources */, 4B9A303D2121238D00174C6F /* AudioMessageTests.swift in Sources */, + 4BF53380222E3DB80080D138 /* ChainedPaginatedRequestsTests.swift in Sources */, 4B8A965721100A5800760219 /* LoginConfigurationTests.swift in Sources */, 4BEB4928212BA8CF00BA946A /* FlexSeparatorComponentTests.swift in Sources */, 4B39D1DE211044B000A45510 /* PostTokenExchangeRequestTests.swift in Sources */, @@ -1598,26 +2405,34 @@ 4BA8E44C210EDB5100355F03 /* ParameterEncoderTests.swift in Sources */, DBDCFD342126CB6300E8327A /* GetApproversInFriendsTests.swift in Sources */, 4B39D1E621104F3B00A45510 /* ViewControllerCompatibleTest.swift in Sources */, + 4BB0E25A239F6BE9008C7F3F /* OpenChatCreatingFormItemTests.swift in Sources */, 4BA8E44A210ED82B00355F03 /* AdapterTests.swift in Sources */, + DB75650928CB254A001A25A5 /* UIColorExtensionTests.swift in Sources */, 4B6A0FF7212AACCF00B3ED1F /* FlexButtonComponentTests.swift in Sources */, + DB09851523D5AF9D0001A3B8 /* PKCETests.swift in Sources */, 4BBAFC502101D31300E7BFF6 /* ConstantTests.swift in Sources */, 4B9A305621215DED00174C6F /* TemplateConfirmPayloadTests.swift in Sources */, DBF2027D212D5B5200780358 /* GetApproversInGroupRequestTests.swift in Sources */, 4B5EE2E1212BB53F0009DF2E /* FlexBoxComponentTests.swift in Sources */, 4B5EE2DF212BAFA00009DF2E /* FlexSpacerComponentTests.swift in Sources */, 4B94D0692153678D0049DE68 /* JWTRSATests.swift in Sources */, + D17C6FB022237CB1007BA517 /* ResultUtilsTests.swift in Sources */, 4B9A3033212111BE00174C6F /* ImageMessageTests.swift in Sources */, 4BD191BC211188D500E2C481 /* RefreshTokenPipelineTests.swift in Sources */, + 4B26F6AF221D266D00F33BF4 /* ResultUtils.swift in Sources */, 4B9A30412121286900174C6F /* LocationMessageTests.swift in Sources */, 4BEB4920212B9F6900BA946A /* FlexFillerComponentTests.swift in Sources */, + 4B1A4EF722EAA0390030F560 /* PostMessageSendingTokenRequestTests.swift in Sources */, 4B792FB821103D4B00EDDD1E /* ResponseDataStub.swift in Sources */, 4B95F4042125364500AD3A81 /* PostMultisendMessagesRequestTests.swift in Sources */, + 4B4B363723E7B3AA0016E2C0 /* StringExtensionTests.swift in Sources */, 4B6A702921096E3700D71C66 /* KeychainStoreTests.swift in Sources */, 4B9A3052212142CA00174C6F /* TemplateMessageActionTests.swift in Sources */, 4BFC09F9213CFB6000F4594D /* GetJWKSetRequestTests.swift in Sources */, 4B39D1E421104DBA00A45510 /* APITests.swift in Sources */, 4B9A305A2121639600174C6F /* TemplateCarouselPayloadTests.swift in Sources */, 4B792FB321103A0200EDDD1E /* LoginManagerTests.swift in Sources */, + 4BEFF644226DCD960046DB66 /* ShareControllerTests.swift in Sources */, 4BFC09EF213CCE7700F4594D /* JWKTests.swift in Sources */, 4B3CCB9E2152449400F51D76 /* ECDSAKeyTests.swift in Sources */, 4B414D5E210F077700FD19BC /* RequestStubs.swift in Sources */, @@ -1626,8 +2441,10 @@ 4BA8E442210EBCFF00355F03 /* SessionTests.swift in Sources */, 4B39D1E221104C7A00A45510 /* LoginManagerExtension.swift in Sources */, 4BEB491C212B992B00BA946A /* FlexImageComponentTests.swift in Sources */, + 4BB0E258239F6728008C7F3F /* CountLimitedTextViewTests.swift in Sources */, 4B08F9C22119863500B140DF /* LineSDKErrorTests.swift in Sources */, 4B792FB121102D9200EDDD1E /* LoginProcessURLResponseTests.swift in Sources */, + 4BE4E0FC2251CEE10071FC60 /* ColumnDataStoreTests.swift in Sources */, 4B5EE2E6212BD53E0009DF2E /* FlexBubbleContainerTests.swift in Sources */, 4BF45A8B2137B2C500CCD28E /* RSAKeyTests.swift in Sources */, 4BFC09F5213CF5B200F4594D /* GetDiscoveryDocumentRequestTests.swift in Sources */, @@ -1636,7 +2453,7 @@ 4B6A0FF1212A99AD00B3ED1F /* FlexTextComponentTests.swift in Sources */, 4BA8E445210ED27900355F03 /* SessionDelegateStub.swift in Sources */, 4B9A30502121429C00174C6F /* TemplateButtonsPayloadTests.swift in Sources */, - 4BEB4924212BA0A300BA946A /* FlexIconCompomentTests.swift in Sources */, + 4BEB4924212BA0A300BA946A /* FlexIconComponentTests.swift in Sources */, 4B94D06B2153681E0049DE68 /* JWTECTests.swift in Sources */, 4B9A303721211CC100174C6F /* VideoMessageTests.swift in Sources */, DBF2027B212D58FC00780358 /* GetGroupsRequestTests.swift in Sources */, @@ -1653,6 +2470,8 @@ 4BC9B2D9212FADD60071C736 /* LinsSDKCallbackQueue.swift in Sources */, 4BDD2C1C2133B97600DD563D /* LineSDKTemplateImageCarouselPayload.swift in Sources */, 4BC4B3E1212FF39D00750794 /* LineSDKTemplateMessage.swift in Sources */, + 4B7FEE0F221E466E003F7369 /* ResultExtensions.swift in Sources */, + 4BD33EDE238B72E500C1E8A9 /* LineSDKAuthAPI.swift in Sources */, 4B2D1505212FABD2000DD5BE /* LineSDKAPI.swift in Sources */, 4BDD2C142133954800DD563D /* LineSDKTemplateButtonsPayload.swift in Sources */, 4BD24CF7213E517800606411 /* LineSDKJWT.swift in Sources */, @@ -1660,16 +2479,22 @@ 4BC9B2E0212FB7B60071C736 /* LineSDKGroup.swift in Sources */, 4B4A9AF32159FCF700915054 /* LineSDKConstant.swift in Sources */, 4BDD2C382133F34300DD563D /* LineSDKFlexBoxComponent.swift in Sources */, + 4B8469A623EAA7AA00EE49FA /* LineSDKOpenChatRoomStatus.swift in Sources */, 4B2D14E9212F9270000DD5BE /* LineSDKAccessTokenVerifyResult.swift in Sources */, 4BDD2C1A2133A5E300DD563D /* LineSDKTemplateCarouselPayload.swift in Sources */, 4BC9B2DE212FB7230071C736 /* LineSDKUser.swift in Sources */, 4BC4B3D7212FD76C00750794 /* LineSDKMessage.swift in Sources */, 4BC9B2E2212FB8390071C736 /* LineSDKGraphResponse.swift in Sources */, + 4B67EA3A23E2758800A6ADCD /* LineSDKOpenChatCreatingControllerDelegate.swift in Sources */, 4BDD2C252133CA1800DD563D /* LineSDKFlexCarouselContainer.swift in Sources */, 4BDD2C2A2133DD9300DD563D /* LineSDKFlexTextComponent.swift in Sources */, 4B2D14E3212F8EDA000DD5BE /* LineSDKUserProfile.swift in Sources */, 4B2D14EF212F9468000DD5BE /* LineSDKLoginManagerOptions.swift in Sources */, + 4B415942225AF024003AE63A /* LineSDKAuthorizationStatus.swift in Sources */, + 4B415940225AE65A003AE63A /* LineSDKShareTarget.swift in Sources */, 4BC4B3D9212FEB9000750794 /* LineSDKTextMessage.swift in Sources */, + 4BF79915238BB71E0009C315 /* LineSDKLoginManagerParameters.swift in Sources */, + 4B41593C225AE497003AE63A /* LineSDKShareViewControllerDelegate.swift in Sources */, 4BDD2C1E2133C15300DD563D /* LineSDKFlexMessage.swift in Sources */, 4BC9B2E7212FCA130071C736 /* LineSDKMessagingResponse.swift in Sources */, 4BC4B3DB212FEBA600750794 /* LineSDKImageMessage.swift in Sources */, @@ -1678,23 +2503,32 @@ 4BDD2C362133F32F00DD563D /* LineSDKSpacerComponent.swift in Sources */, 4B2D1503212FAB5A000DD5BE /* LineSDKAPIError.swift in Sources */, 4BC4B3DF212FEF8600750794 /* LineSDKLocationMessage.swift in Sources */, + D1CB0D0F24481987001F9238 /* LineSDKOpenChatRoomJoinType.swift in Sources */, 4BDD2C282133D2B300DD563D /* LineSDKFlexMessageComponent.swift in Sources */, 4BDD2C2E2133F2EA00DD563D /* LineSDKFlexImageComponent.swift in Sources */, 4BDD2C322133F30A00DD563D /* LineSDKFlexIconComponent.swift in Sources */, 4BBEA9B6212EB41000858627 /* LineSDKAccessToken.swift in Sources */, + 4B8469A823EAA7BD00EE49FA /* LineSDKOpenChatRoomMembershipState.swift in Sources */, 4BC4B3DD212FED4300750794 /* LineSDKVideoMessage.swift in Sources */, + 4BC66AE522EA8A6900546FB6 /* LineSDKMessageSendingToken.swift in Sources */, 4BC4B3E4212FF86100750794 /* LineSDKAudioMessage.swift in Sources */, 4BDD2C212133C1CD00DD563D /* LineSDKFlexMessageContainer.swift in Sources */, + 4B41593E225AE4E0003AE63A /* LineSDKMessageShareTargetType.swift in Sources */, 4BDD2C232133C61F00DD563D /* LineSDKFlexBubbleContainer.swift in Sources */, D102A6A421646AE200804C7C /* LineSDKGetBotFriendshipStatusResponse.swift in Sources */, + D1F4E716225743F3002639AC /* LineSDKShareViewController.swift in Sources */, 4BDD2C1221338B9800DD563D /* LineSDKMessageAction.swift in Sources */, 4BDD2C162133970800DD563D /* Log.swift in Sources */, 4BDD2C302133F2FA00DD563D /* LineSDKFlexFillerComponent.swift in Sources */, + 4B67EA3E23E2770900A6ADCD /* LineSDKOpenChatRoomCreatingItem.swift in Sources */, + 4B42816621ED9CE6004A0846 /* JSONConverter.swift in Sources */, 4B2D1501212FA9EE000DD5BE /* LineSDKHexColor.swift in Sources */, 4B2D14F1212F95B3000DD5BE /* LineSDKLoginResult.swift in Sources */, 4B2D14F3212F96A7000DD5BE /* LineSDKLoginProcess.swift in Sources */, + 4B67EA3C23E2765400A6ADCD /* LineSDKOpenChatRoomInfo.swift in Sources */, 4B2422EF2134FBE8007200C2 /* LineSDKErrorConstant.swift in Sources */, 4BBEA9B0212EB03200858627 /* LineSDKLoginPermission.swift in Sources */, + 4B67EA3823E2754000A6ADCD /* LineSDKOpenChatCreatingController.swift in Sources */, 3FE8E370214A6E70009E91BD /* LineSDKLoginButton.swift in Sources */, 4B2D14F5212F97A6000DD5BE /* LineSDKLoginManager.swift in Sources */, ); @@ -1705,11 +2539,236 @@ buildActionMask = 2147483647; files = ( 4B2D14ED212F931D000DD5BE /* LineSDKModelInterfaceTests.m in Sources */, + D1F4E718225744CE002639AC /* LineSDKViewControllerInterfaceTests.m in Sources */, 4BB2E6612135188900885687 /* LineSDKMessagingModelTests.m in Sources */, 4BC9B2DB212FB0210071C736 /* LineSDKAPIInterfaceTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 4BCD24C723FFA74100D4B6BD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D1D22C9B2408A9080028A9E0 /* CountLimitedTextView.swift in Sources */, + 4BCD251623FFAABA00D4B6BD /* ShareRootViewController.swift in Sources */, + 4BCD252323FFAABA00D4B6BD /* PostExchangeTokenRequest.swift in Sources */, + 4BCD24C823FFA74100D4B6BD /* LineSDKAccessTokenStore.swift in Sources */, + 4BCD257E23FFAABA00D4B6BD /* JWKSet.swift in Sources */, + 4BCD24C923FFA74100D4B6BD /* LinsSDKCallbackQueue.swift in Sources */, + 4BCD252823FFAABA00D4B6BD /* PKCE.swift in Sources */, + 4BCD252E23FFAABA00D4B6BD /* LoginManager.swift in Sources */, + 4BCD253723FFAABA00D4B6BD /* GetFriendsRequest.swift in Sources */, + 4BCD256F23FFAABA00D4B6BD /* HexColor.swift in Sources */, + 4BCD254323FFAABA00D4B6BD /* MessageSender.swift in Sources */, + 4BCD24CA23FFA74100D4B6BD /* LineSDKTemplateImageCarouselPayload.swift in Sources */, + 4BCD24CB23FFA74100D4B6BD /* LineSDKTemplateMessage.swift in Sources */, + D1D22CA32408A9570028A9E0 /* GetOpenChatRoomMembershipStateRequest.swift in Sources */, + 4BCD24CC23FFA74100D4B6BD /* ResultExtensions.swift in Sources */, + 4BCD24CD23FFA74100D4B6BD /* LineSDKAuthAPI.swift in Sources */, + 4BCD254D23FFAABA00D4B6BD /* TemplateMessageProperties.swift in Sources */, + 4BCD257323FFAABA00D4B6BD /* CryptoAlgorithm.swift in Sources */, + 4BCD258723FFAABA00D4B6BD /* Constant.swift in Sources */, + D1D22CA82408AA2F0028A9E0 /* LineSDKOpenChatRoomInfo.swift in Sources */, + 4BCD24CE23FFA74100D4B6BD /* LineSDKAPI.swift in Sources */, + 4BCD256823FFAABA00D4B6BD /* ChainedPaginatedRequest.swift in Sources */, + 4BCD24CF23FFA74100D4B6BD /* LineSDKTemplateButtonsPayload.swift in Sources */, + 4BCD254223FFAABA00D4B6BD /* MessageProtocols.swift in Sources */, + 4BCD255723FFAABA00D4B6BD /* FlexMessageProperties.swift in Sources */, + 4BCD256623FFAABA00D4B6BD /* ImageManager.swift in Sources */, + 4BCD24D023FFA74100D4B6BD /* LineSDKJWT.swift in Sources */, + 4BCD253B23FFAABA00D4B6BD /* GetShareFriendsRequest.swift in Sources */, + 4BCD253823FFAABA00D4B6BD /* GetGroupsRequest.swift in Sources */, + D1D22C972408A8FD0028A9E0 /* FormSection.swift in Sources */, + D1D22CAB2408AA480028A9E0 /* LineSDKOpenChatRoomMembershipState.swift in Sources */, + 4BCD251B23FFAABA00D4B6BD /* ShareTargetSearchResultViewController.swift in Sources */, + 4BCD256C23FFAABA00D4B6BD /* ParametersAdapter.swift in Sources */, + 4BCD256323FFAABA00D4B6BD /* API+Auth.swift in Sources */, + 4BCD254623FFAABA00D4B6BD /* VideoMessage.swift in Sources */, + 4BCD24D123FFA74100D4B6BD /* LineSDKTemplateConfirmPayload.swift in Sources */, + 4BCD253D23FFAABA00D4B6BD /* PostSendMessagesRequest.swift in Sources */, + 4BCD24D223FFA74100D4B6BD /* LineSDKGroup.swift in Sources */, + 4BCD254B23FFAABA00D4B6BD /* MessageAction.swift in Sources */, + 4BCD251323FFAABA00D4B6BD /* ColumnDataStore.swift in Sources */, + D1CB0D1024481A1A001F9238 /* LineSDKOpenChatRoomJoinType.swift in Sources */, + 4BCD24D323FFA74100D4B6BD /* LineSDKConstant.swift in Sources */, + 4BCD24D423FFA74100D4B6BD /* LineSDKFlexBoxComponent.swift in Sources */, + 4BCD24D523FFA74100D4B6BD /* LineSDKAccessTokenVerifyResult.swift in Sources */, + 4BCD253A23FFAABA00D4B6BD /* GetApproversInGroupRequest.swift in Sources */, + 4BCD251C23FFAABA00D4B6BD /* ShareTargetSearchResultTableViewController.swift in Sources */, + 4BCD24D623FFA74100D4B6BD /* LineSDKTemplateCarouselPayload.swift in Sources */, + 4BCD251923FFAABA00D4B6BD /* ShareTargetSearchController.swift in Sources */, + 4BCD256123FFAABA00D4B6BD /* API.swift in Sources */, + 4BCD258023FFAABA00D4B6BD /* ECDSA.swift in Sources */, + D1D22CA42408A97A0028A9E0 /* String.swift in Sources */, + 4BCD255923FFAABA00D4B6BD /* FlexTextComponent.swift in Sources */, + 4BCD252D23FFAABA00D4B6BD /* LoginConfiguration.swift in Sources */, + 4BCD24D723FFA74100D4B6BD /* LineSDKUser.swift in Sources */, + 4BCD24D823FFA74100D4B6BD /* LineSDKMessage.swift in Sources */, + 4BCD254823FFAABA00D4B6BD /* LocationMessage.swift in Sources */, + 4BCD24D923FFA74100D4B6BD /* LineSDKGraphResponse.swift in Sources */, + 4BCD253023FFAABA00D4B6BD /* LoginManagerParameters.swift in Sources */, + D1D22C8F2408A8E60028A9E0 /* OpenChatCreatingController.swift in Sources */, + 4BCD258123FFAABA00D4B6BD /* ResultUtils.swift in Sources */, + 4BCD258423FFAABA00D4B6BD /* Delegate.swift in Sources */, + 4BCD258B23FFAABA00D4B6BD /* LoadingIndicator.swift in Sources */, + 4BCD24DA23FFA74100D4B6BD /* LineSDKFlexCarouselContainer.swift in Sources */, + 4BCD250F23FFAABA00D4B6BD /* ShareViewController.swift in Sources */, + 4BCD251223FFAABA00D4B6BD /* ShareTarget.swift in Sources */, + D1F6B58B243C421400024910 /* GetOpenChatRoomJoinTypeRequest.swift in Sources */, + 4BCD254923FFAABA00D4B6BD /* TemplateMessage.swift in Sources */, + 4BCD257F23FFAABA00D4B6BD /* RSA.swift in Sources */, + 4BCD251523FFAABA00D4B6BD /* PageTabView.swift in Sources */, + D1D22CA22408A9570028A9E0 /* GetOpenChatRoomStatusRequest.swift in Sources */, + 4BCD258923FFAABA00D4B6BD /* NotificationToken.swift in Sources */, + 4BCD258623FFAABA00D4B6BD /* URLs.swift in Sources */, + 4BCD254523FFAABA00D4B6BD /* ImageMessage.swift in Sources */, + 4BCD257123FFAABA00D4B6BD /* Unit.swift in Sources */, + 4BCD24DB23FFA74100D4B6BD /* LineSDKFlexTextComponent.swift in Sources */, + 4BCD256E23FFAABA00D4B6BD /* CodingExtension.swift in Sources */, + 4BCD252623FFAABA00D4B6BD /* PostRevokeTokenRequest.swift in Sources */, + 4BCD24DC23FFA74100D4B6BD /* LineSDKUserProfile.swift in Sources */, + 4BCD24DD23FFA74100D4B6BD /* LineSDKLoginManagerOptions.swift in Sources */, + 4BCD256423FFAABA00D4B6BD /* API+Deprecated.swift in Sources */, + 4BCD257A23FFAABA00D4B6BD /* JWTCoder.swift in Sources */, + 4BCD254123FFAABA00D4B6BD /* Message.swift in Sources */, + D1D22C982408A9000028A9E0 /* OpenChatRoomNameTableViewCell.swift in Sources */, + D1D22C932408A8F10028A9E0 /* OptionSelectingViewController.swift in Sources */, + 4BCD252423FFAABA00D4B6BD /* GetUserProfileRequest.swift in Sources */, + 4BCD254023FFAABA00D4B6BD /* PostMessageSendingTokenIssueRequest.swift in Sources */, + 4BCD24DE23FFA74100D4B6BD /* LineSDKAuthorizationStatus.swift in Sources */, + 4BCD24DF23FFA74100D4B6BD /* LineSDKShareTarget.swift in Sources */, + 4BCD24E023FFA74100D4B6BD /* LineSDKTextMessage.swift in Sources */, + 4BCD253523FFAABA00D4B6BD /* User.swift in Sources */, + 4BCD24E123FFA74100D4B6BD /* LineSDKLoginManagerParameters.swift in Sources */, + 4BCD251023FFAABA00D4B6BD /* ShareViewControllerDelegate.swift in Sources */, + D1D22CA02408A9570028A9E0 /* GetOpenChatTermAgreementStatusRequest.swift in Sources */, + 4BCD255D23FFAABA00D4B6BD /* FlexIconComponent.swift in Sources */, + 4BCD257D23FFAABA00D4B6BD /* JWA.swift in Sources */, + 4BCD252A23FFAABA00D4B6BD /* AccessToken.swift in Sources */, + 4BCD256223FFAABA00D4B6BD /* API+Internal.swift in Sources */, + 4BCD252123FFAABA00D4B6BD /* ResourceLoading.swift in Sources */, + D1D22C962408A8FA0028A9E0 /* FormEntry.swift in Sources */, + 4BCD251A23FFAABA00D4B6BD /* ShareTargetTableViewStyling.swift in Sources */, + 4BCD24E223FFA74100D4B6BD /* LineSDKShareViewControllerDelegate.swift in Sources */, + 4BCD24E323FFA74100D4B6BD /* LineSDKFlexMessage.swift in Sources */, + 4BCD24E423FFA74100D4B6BD /* LineSDKMessagingResponse.swift in Sources */, + 4BCD255023FFAABA00D4B6BD /* TemplateCarouselPayload.swift in Sources */, + D1D22C912408A8EC0028A9E0 /* OpenChatCreatingNavigationController.swift in Sources */, + 4BCD257023FFAABA00D4B6BD /* APIError.swift in Sources */, + 4BCD253623FFAABA00D4B6BD /* Group.swift in Sources */, + 4BCD24E523FFA74100D4B6BD /* LineSDKImageMessage.swift in Sources */, + 4BCD255323FFAABA00D4B6BD /* FlexMessageContainer.swift in Sources */, + 4BCD253223FFAABA00D4B6BD /* LoginProcess.swift in Sources */, + D1D22CA92408AA2F0028A9E0 /* LineSDKOpenChatRoomCreatingItem.swift in Sources */, + 4BCD253423FFAABA00D4B6BD /* LoginPermission.swift in Sources */, + 4BCD24E623FFA74100D4B6BD /* LineSDKFlexButtonComponent.swift in Sources */, + 4BCD256B23FFAABA00D4B6BD /* RequestAdapter.swift in Sources */, + 4BCD253C23FFAABA00D4B6BD /* GetShareGroupsRequest.swift in Sources */, + 4BCD24E723FFA74100D4B6BD /* LineSDKFlexSeparatorComponent.swift in Sources */, + 4BCD258823FFAABA00D4B6BD /* KeychainStore.swift in Sources */, + 4BCD24E823FFA74100D4B6BD /* LineSDKSpacerComponent.swift in Sources */, + 4BCD24E923FFA74100D4B6BD /* LineSDKAPIError.swift in Sources */, + 4BCD24EA23FFA74100D4B6BD /* LineSDKLocationMessage.swift in Sources */, + D1D22C8E2408A8D70028A9E0 /* StyleNavigationController.swift in Sources */, + 4BCD258C23FFAABA00D4B6BD /* Colors.swift in Sources */, + 4BCD254A23FFAABA00D4B6BD /* FlexMessage.swift in Sources */, + 4BCD258A23FFAABA00D4B6BD /* KeyboardObservable.swift in Sources */, + 4BCD255223FFAABA00D4B6BD /* FlexBlockStyle.swift in Sources */, + 4BCD24EB23FFA74100D4B6BD /* LineSDKFlexMessageComponent.swift in Sources */, + 4BCD256723FFAABA00D4B6BD /* DownloadableImageView.swift in Sources */, + 4BCD253923FFAABA00D4B6BD /* GetApproversInFriendsRequest.swift in Sources */, + 4BCD24EC23FFA74100D4B6BD /* LineSDKFlexImageComponent.swift in Sources */, + 4BCD258523FFAABA00D4B6BD /* CallbackQueue.swift in Sources */, + 4BCD257823FFAABA00D4B6BD /* GetJWKSetRequest.swift in Sources */, + 4BCD254E23FFAABA00D4B6BD /* TemplateButtonsPayload.swift in Sources */, + 4BCD255F23FFAABA00D4B6BD /* FlexSpacerComponent.swift in Sources */, + D1F20A7B2500BC12005E359E /* OpenChatCategoryExtensions.swift in Sources */, + 4BCD24ED23FFA74100D4B6BD /* LineSDKFlexIconComponent.swift in Sources */, + 4BCD257623FFAABA00D4B6BD /* CryptoError.swift in Sources */, + 4BCD257C23FFAABA00D4B6BD /* JWK.swift in Sources */, + 4BCD251F23FFAABA00D4B6BD /* SelectedTargetPanelCell.swift in Sources */, + 4BCD252F23FFAABA00D4B6BD /* LoginManagerOptions.swift in Sources */, + 4BCD258323FFAABA00D4B6BD /* Helpers.swift in Sources */, + 4BCD24EE23FFA74100D4B6BD /* LineSDKAccessToken.swift in Sources */, + 4BCD252923FFAABA00D4B6BD /* AccessTokenStore.swift in Sources */, + D1F6B58A243C421100024910 /* PostOpenChatRoomJoinRequest.swift in Sources */, + 4BCD24EF23FFA74100D4B6BD /* LineSDKVideoMessage.swift in Sources */, + 4BCD251723FFAABA00D4B6BD /* ShareTargetSelectingViewController.swift in Sources */, + 4BCD24F023FFA74100D4B6BD /* LineSDKMessageSendingToken.swift in Sources */, + 4BCD252C23FFAABA00D4B6BD /* UserProfile.swift in Sources */, + D1D22C922408A8EF0028A9E0 /* OpenChatRoomInfoViewController.swift in Sources */, + 4BCD255B23FFAABA00D4B6BD /* FlexImageComponent.swift in Sources */, + 4BCD257B23FFAABA00D4B6BD /* JWTHelpers.swift in Sources */, + 4BCD256023FFAABA00D4B6BD /* GetBotFriendshipStatus.swift in Sources */, + 4BCD252B23FFAABA00D4B6BD /* AccessTokenVerifyResult.swift in Sources */, + 4BCD253F23FFAABA00D4B6BD /* PostMultisendMessagesWithTokenRequest.swift in Sources */, + 4BCD24F123FFA74100D4B6BD /* LineSDKAudioMessage.swift in Sources */, + 4BCD256D23FFAABA00D4B6BD /* ResponsePipeline.swift in Sources */, + 4BCD255A23FFAABA00D4B6BD /* FlexButtonComponent.swift in Sources */, + 4BCD24F223FFA74100D4B6BD /* LineSDKFlexMessageContainer.swift in Sources */, + D1D22CA62408AA2F0028A9E0 /* LineSDKOpenChatCreatingController.swift in Sources */, + D1D22C992408A9030028A9E0 /* OpenChatRoomDescriptionTableViewCell.swift in Sources */, + 4BCD254C23FFAABA00D4B6BD /* TemplateMessagePayload.swift in Sources */, + 4BCD256A23FFAABA00D4B6BD /* Session.swift in Sources */, + 4BCD251423FFAABA00D4B6BD /* PageViewController.swift in Sources */, + 4BCD252523FFAABA00D4B6BD /* PostRefreshTokenRequest.swift in Sources */, + D1D22C9D2408A91B0028A9E0 /* AuthorizationStatus.swift in Sources */, + 4BCD254423FFAABA00D4B6BD /* TextMessage.swift in Sources */, + 4BCD252223FFAABA00D4B6BD /* LineSDKError.swift in Sources */, + 4BCD256523FFAABA00D4B6BD /* ImageDownloader.swift in Sources */, + 4BCD24F323FFA74100D4B6BD /* LineSDKMessageShareTargetType.swift in Sources */, + D1D22CA12408A9570028A9E0 /* PostOpenChatCreateRequest.swift in Sources */, + D1D22C9E2408A94E0028A9E0 /* OpenChatCategory.swift in Sources */, + 4BCD252023FFAABA00D4B6BD /* LoginButton.swift in Sources */, + 4BCD255623FFAABA00D4B6BD /* FlexMessageComponent.swift in Sources */, + 4BCD24F423FFA74100D4B6BD /* LineSDKFlexBubbleContainer.swift in Sources */, + D1D22CA72408AA2F0028A9E0 /* LineSDKOpenChatCreatingControllerDelegate.swift in Sources */, + 4BCD255C23FFAABA00D4B6BD /* FlexFillerComponent.swift in Sources */, + 4BCD24F523FFA74100D4B6BD /* LineSDKGetBotFriendshipStatusResponse.swift in Sources */, + D1D22C902408A8E90028A9E0 /* OpenChatCreatingControllerDelegate.swift in Sources */, + 4BCD24F623FFA74100D4B6BD /* LineSDKShareViewController.swift in Sources */, + 4BCD251D23FFAABA00D4B6BD /* ShareTargetSelectingSectionHeaderView.swift in Sources */, + 4BCD24F723FFA74100D4B6BD /* LineSDKMessageAction.swift in Sources */, + 4BCD24F823FFA74100D4B6BD /* Log.swift in Sources */, + 4BCD253323FFAABA00D4B6BD /* LoginProcessURLResponse.swift in Sources */, + D1D22C9A2408A9050028A9E0 /* VerticallyCenteredTextView.swift in Sources */, + 4BCD257723FFAABA00D4B6BD /* GetDiscoveryDocumentRequest.swift in Sources */, + 4BCD24F923FFA74100D4B6BD /* LineSDKFlexFillerComponent.swift in Sources */, + 4BCD24FA23FFA74100D4B6BD /* JSONConverter.swift in Sources */, + 4BCD253123FFAABA00D4B6BD /* LoginResult.swift in Sources */, + 4BCD24FB23FFA74100D4B6BD /* LineSDKHexColor.swift in Sources */, + 4BCD255E23FFAABA00D4B6BD /* FlexSeparatorComponent.swift in Sources */, + D1D22CA52408A97E0028A9E0 /* UserDefaultsValue.swift in Sources */, + 4BCD24FC23FFA74100D4B6BD /* LineSDKLoginResult.swift in Sources */, + D1F20A782500BBFD005E359E /* OpenChatCreatingFormItem.swift in Sources */, + 4BCD255823FFAABA00D4B6BD /* FlexBoxComponent.swift in Sources */, + 4BCD255423FFAABA00D4B6BD /* FlexBubbleContainer.swift in Sources */, + 4BCD24FD23FFA74100D4B6BD /* LineSDKLoginProcess.swift in Sources */, + 4BCD24FE23FFA74100D4B6BD /* LineSDKErrorConstant.swift in Sources */, + 4BCD257223FFAABA00D4B6BD /* CryptoData.swift in Sources */, + D1D22CAA2408AA440028A9E0 /* LineSDKOpenChatRoomStatus.swift in Sources */, + D1D22C942408A8F30028A9E0 /* OpenChatUserProfileViewController.swift in Sources */, + 4BCD257523FFAABA00D4B6BD /* CryptoHelpers.swift in Sources */, + 4BCD252723FFAABA00D4B6BD /* GetVerifyTokenRequest.swift in Sources */, + 4BCD24FF23FFA74100D4B6BD /* LineSDKLoginPermission.swift in Sources */, + D1D22C9C2408A9120028A9E0 /* ToastView.swift in Sources */, + D1D22C9F2408A9510028A9E0 /* OpenChatRoomCreatingItem.swift in Sources */, + 4BCD257423FFAABA00D4B6BD /* CryptoKey.swift in Sources */, + 4BCD251E23FFAABA00D4B6BD /* SelectedTargetPanelViewController.swift in Sources */, + 4BCD253E23FFAABA00D4B6BD /* PostMultisendMessagesRequest.swift in Sources */, + 4BCD257923FFAABA00D4B6BD /* JWT.swift in Sources */, + 4BCD250023FFA74100D4B6BD /* LineSDKLoginButton.swift in Sources */, + 4BCD256923FFAABA00D4B6BD /* Request.swift in Sources */, + 4BCD255123FFAABA00D4B6BD /* TemplateImageCarouselPayload.swift in Sources */, + 4BCD254723FFAABA00D4B6BD /* AudioMessage.swift in Sources */, + 4BCD254F23FFAABA00D4B6BD /* TemplateConfirmPayload.swift in Sources */, + 4BCD250123FFA74100D4B6BD /* LineSDKLoginManager.swift in Sources */, + 4BCD255523FFAABA00D4B6BD /* FlexCarouselContainer.swift in Sources */, + 4BCD251823FFAABA00D4B6BD /* ShareTargetSelectingTableCell.swift in Sources */, + 4BCD251123FFAABA00D4B6BD /* MessageShareTargetType.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1742,7 +2801,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = TestHost/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1759,7 +2817,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = TestHost/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1797,6 +2854,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1805,7 +2863,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 402; + CURRENT_PROJECT_VERSION = 1208; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -1823,11 +2881,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG LineSDKXCProj"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; @@ -1862,6 +2920,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1870,7 +2929,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 402; + CURRENT_PROJECT_VERSION = 1208; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1882,9 +2941,10 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = LineSDKXCProj; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 4.2; @@ -1902,8 +2962,9 @@ CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 402; + DYLIB_CURRENT_VERSION = 1208; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = LineSDK/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -1911,10 +2972,14 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.LineSDK; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -1927,8 +2992,9 @@ CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 402; + DYLIB_CURRENT_VERSION = 1208; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = LineSDK/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -1936,9 +3002,13 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.LineSDK; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -1987,20 +3057,23 @@ CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 402; + DYLIB_CURRENT_VERSION = 1208; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = LineSDKObjC/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.LineSDKObjC; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -2013,19 +3086,22 @@ CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 402; + DYLIB_CURRENT_VERSION = 1208; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = LineSDKObjC/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.LineSDKObjC; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -2036,7 +3112,6 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = LineSDKObjCInterfaceTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2054,7 +3129,6 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = LineSDKObjCInterfaceTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2066,6 +3140,65 @@ }; name = Release; }; + 4BCD250823FFA74100D4B6BD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1208; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + INFOPLIST_FILE = LineSDKObjC/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; + OTHER_SWIFT_FLAGS = "-DLineSDKBinary"; + PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.LineSDKObjC; + PRODUCT_NAME = LineSDKObjC; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 4BCD250923FFA74100D4B6BD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1208; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + INFOPLIST_FILE = LineSDKObjC/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; + OTHER_SWIFT_FLAGS = "-DLineSDKBinary"; + PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.LineSDKObjC; + PRODUCT_NAME = LineSDKObjC; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -2123,6 +3256,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 4BCD250723FFA74100D4B6BD /* Build configuration list for PBXNativeTarget "LineSDKObjCBinary" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4BCD250823FFA74100D4B6BD /* Debug */, + 4BCD250923FFA74100D4B6BD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 4B90586F21006E5C004D717F /* Project object */; diff --git a/LineSDK/LineSDK.xcodeproj/xcshareddata/xcschemes/LineSDK.xcscheme b/LineSDK/LineSDK.xcodeproj/xcshareddata/xcschemes/LineSDK.xcscheme index 9b654e55..934d1be1 100644 --- a/LineSDK/LineSDK.xcodeproj/xcshareddata/xcschemes/LineSDK.xcscheme +++ b/LineSDK/LineSDK.xcodeproj/xcshareddata/xcschemes/LineSDK.xcscheme @@ -1,6 +1,6 @@ + onlyGenerateCoverageForSpecifiedTargets = "YES"> + + + + + + + + @@ -54,17 +73,6 @@ - - - - - - - - + + + + @@ -39,17 +48,6 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LineSDK/LineSDK/Assets.xcassets/friend_check_off.imageset/Contents.json b/LineSDK/LineSDK/Assets.xcassets/friend_check_off.imageset/Contents.json new file mode 100644 index 00000000..0b81f0f5 --- /dev/null +++ b/LineSDK/LineSDK/Assets.xcassets/friend_check_off.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "friend_check_off@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/LineSDK/LineSDK/Assets.xcassets/friend_check_off.imageset/friend_check_off@2x.png b/LineSDK/LineSDK/Assets.xcassets/friend_check_off.imageset/friend_check_off@2x.png new file mode 100644 index 00000000..873256f6 Binary files /dev/null and b/LineSDK/LineSDK/Assets.xcassets/friend_check_off.imageset/friend_check_off@2x.png differ diff --git a/LineSDK/LineSDK/Assets.xcassets/friend_check_on.imageset/Contents.json b/LineSDK/LineSDK/Assets.xcassets/friend_check_on.imageset/Contents.json new file mode 100644 index 00000000..45988dd4 --- /dev/null +++ b/LineSDK/LineSDK/Assets.xcassets/friend_check_on.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "friend_check_on@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/LineSDK/LineSDK/Assets.xcassets/friend_check_on.imageset/friend_check_on@2x.png b/LineSDK/LineSDK/Assets.xcassets/friend_check_on.imageset/friend_check_on@2x.png new file mode 100644 index 00000000..0f56c313 Binary files /dev/null and b/LineSDK/LineSDK/Assets.xcassets/friend_check_on.imageset/friend_check_on@2x.png differ diff --git a/LineSDK/LineSDK/Assets.xcassets/list_icon_delete_normal.imageset/Contents.json b/LineSDK/LineSDK/Assets.xcassets/list_icon_delete_normal.imageset/Contents.json new file mode 100644 index 00000000..46e589b1 --- /dev/null +++ b/LineSDK/LineSDK/Assets.xcassets/list_icon_delete_normal.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "list_icon_delete_normal@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/LineSDK/LineSDK/Assets.xcassets/list_icon_delete_normal.imageset/list_icon_delete_normal@2x.png b/LineSDK/LineSDK/Assets.xcassets/list_icon_delete_normal.imageset/list_icon_delete_normal@2x.png new file mode 100644 index 00000000..15100ed3 Binary files /dev/null and b/LineSDK/LineSDK/Assets.xcassets/list_icon_delete_normal.imageset/list_icon_delete_normal@2x.png differ diff --git a/LineSDK/LineSDK/Assets.xcassets/navi_icon_close.imageset/Contents.json b/LineSDK/LineSDK/Assets.xcassets/navi_icon_close.imageset/Contents.json new file mode 100644 index 00000000..af6a23cc --- /dev/null +++ b/LineSDK/LineSDK/Assets.xcassets/navi_icon_close.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "navi_icon_close@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/LineSDK/LineSDK/Assets.xcassets/navi_icon_close.imageset/navi_icon_close@2x.png b/LineSDK/LineSDK/Assets.xcassets/navi_icon_close.imageset/navi_icon_close@2x.png new file mode 100644 index 00000000..fcb5e846 Binary files /dev/null and b/LineSDK/LineSDK/Assets.xcassets/navi_icon_close.imageset/navi_icon_close@2x.png differ diff --git a/LineSDK/LineSDK/Assets.xcassets/setting_icon_delete_normal.imageset/Contents.json b/LineSDK/LineSDK/Assets.xcassets/setting_icon_delete_normal.imageset/Contents.json new file mode 100644 index 00000000..e95abf50 --- /dev/null +++ b/LineSDK/LineSDK/Assets.xcassets/setting_icon_delete_normal.imageset/Contents.json @@ -0,0 +1,52 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "setting_icon_delete_normal@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "setting_icon_delete_dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LineSDK/LineSDK/Assets.xcassets/setting_icon_delete_normal.imageset/setting_icon_delete_dark@2x.png b/LineSDK/LineSDK/Assets.xcassets/setting_icon_delete_normal.imageset/setting_icon_delete_dark@2x.png new file mode 100644 index 00000000..7ebf2449 Binary files /dev/null and b/LineSDK/LineSDK/Assets.xcassets/setting_icon_delete_normal.imageset/setting_icon_delete_dark@2x.png differ diff --git a/LineSDK/LineSDK/Assets.xcassets/setting_icon_delete_normal.imageset/setting_icon_delete_normal@2x.png b/LineSDK/LineSDK/Assets.xcassets/setting_icon_delete_normal.imageset/setting_icon_delete_normal@2x.png new file mode 100644 index 00000000..fc30d083 Binary files /dev/null and b/LineSDK/LineSDK/Assets.xcassets/setting_icon_delete_normal.imageset/setting_icon_delete_normal@2x.png differ diff --git a/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_01.imageset/Contents.json b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_01.imageset/Contents.json new file mode 100644 index 00000000..1d3f7da9 --- /dev/null +++ b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_01.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "unknown_user_small_01@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_01.imageset/unknown_user_small_01@2x.png b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_01.imageset/unknown_user_small_01@2x.png new file mode 100644 index 00000000..8890573e Binary files /dev/null and b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_01.imageset/unknown_user_small_01@2x.png differ diff --git a/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_02.imageset/Contents.json b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_02.imageset/Contents.json new file mode 100644 index 00000000..e55a5ffa --- /dev/null +++ b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_02.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "unknown_user_small_02@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_02.imageset/unknown_user_small_02@2x.png b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_02.imageset/unknown_user_small_02@2x.png new file mode 100644 index 00000000..92b16fce Binary files /dev/null and b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_02.imageset/unknown_user_small_02@2x.png differ diff --git a/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_03.imageset/Contents.json b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_03.imageset/Contents.json new file mode 100644 index 00000000..33c249eb --- /dev/null +++ b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_03.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "unknown_user_small_03@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_03.imageset/unknown_user_small_03@2x.png b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_03.imageset/unknown_user_small_03@2x.png new file mode 100644 index 00000000..41a8c803 Binary files /dev/null and b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_03.imageset/unknown_user_small_03@2x.png differ diff --git a/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_04.imageset/Contents.json b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_04.imageset/Contents.json new file mode 100644 index 00000000..20baa817 --- /dev/null +++ b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_04.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "unknown_user_small_04@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_04.imageset/unknown_user_small_04@2x.png b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_04.imageset/unknown_user_small_04@2x.png new file mode 100644 index 00000000..ebce0593 Binary files /dev/null and b/LineSDK/LineSDK/Assets.xcassets/unknown_user_small_04.imageset/unknown_user_small_04@2x.png differ diff --git a/LineSDK/LineSDK/Crypto/Algorithms/ECDSA.swift b/LineSDK/LineSDK/Crypto/Algorithms/ECDSA.swift index ccdd5763..ba77dbc4 100644 --- a/LineSDK/LineSDK/Crypto/Algorithms/ECDSA.swift +++ b/LineSDK/LineSDK/Crypto/Algorithms/ECDSA.swift @@ -1,13 +1,13 @@ // -// ECDRA.swift +// ECDSA.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -22,10 +22,10 @@ import Foundation import CommonCrypto -/// Namespace for ECDRA related things. +/// Namespace for ECDSA related things. struct ECDSA {} -/// ECDRA Digest Algorithms. +/// ECDSA Digest Algorithms. extension ECDSA { enum Curve: String, Decodable { case P256 = "P-256" @@ -52,18 +52,9 @@ extension ECDSA { } enum Algorithm: CryptoAlgorithm { + case sha1, sha224, sha256, sha384, sha512 - var length: CC_LONG { - switch self { - case .sha1: return CC_LONG(CC_SHA1_DIGEST_LENGTH) - case .sha224: return CC_LONG(CC_SHA224_DIGEST_LENGTH) - case .sha256: return CC_LONG(CC_SHA256_DIGEST_LENGTH) - case .sha384: return CC_LONG(CC_SHA384_DIGEST_LENGTH) - case .sha512: return CC_LONG(CC_SHA512_DIGEST_LENGTH) - } - } - var signatureAlgorithm: SecKeyAlgorithm { switch self { case .sha1: return .ecdsaSignatureMessageX962SHA1 @@ -73,28 +64,75 @@ extension ECDSA { case .sha512: return .ecdsaSignatureMessageX962SHA512 } } + + var digestAlgorithm: DigestAlgorithm { + switch self { + case .sha1: return .sha1 + case .sha224: return .sha224 + case .sha256: return .sha256 + case .sha384: return .sha384 + case .sha512: return .sha384 + } + } var encryptionAlgorithm: SecKeyAlgorithm { Log.fatalError("ECDSA should be only used for signing purpose.") } - - var digest: CryptoDigest { - switch self { - case .sha1: return CC_SHA1 - case .sha224: return CC_SHA224 - case .sha256: return CC_SHA256 - case .sha384: return CC_SHA384 - case .sha512: return CC_SHA512 - } + + func convertSignatureData(_ data: Data) throws -> Data { + + // EC algorithm does not work seamlessly when using iOS 10 SecKeyVerifySignature and related + // SecKeyAlgorithm. It is due to JWT contains raw R|S instead of encoded one. + // .ecdsaSignatureMessageX962 algorithm supports an ASN.1 DER x96.2 encoded signature + // instead of plain {r, s} signature (which is defined in ANSI X9.62, FIPS 186-4). + // So we need to convert the signature before use it to verify. + // + // See http://www.secg.org/sec1-v2.pdf + // https://crypto.stackexchange.com/questions/57731/ecdsa-signature-rs-to-asn1-der-encoding-question + // https://opensource.apple.com/source/Security/Security-57740.51.3/keychain/SecKey.h + // More discussion: + // https://forums.developer.apple.com/thread/83136 (ECDSA signature generated by OpenSSL) + // or https://forums.developer.apple.com/thread/89619 + // + return try DEREncodedSignatureData(data) } - - var curve: Curve { - switch self { - case .sha1, .sha224: Log.fatalError("Too simple SHA algorithm. Not supported.") - case .sha256: return .P256 - case .sha384: return .P384 - case .sha512: return .P521 + + func DEREncodedSignatureData(_ data: Data) throws -> Data { + + // The EC raw signature data is {r,s}. So it must be two int with equal length. + let count = data.count + guard count != 0 && count % 2 == 0 else { + throw CryptoError.algorithmsFailed(reason: .invalidSignature(data: data)) + } + + // Following RFC 5480 (https://www.ietf.org/rfc/rfc5480.txt) to encode {r, s} as: + // + // ECDSA-Sig-Value ::= SEQUENCE { + // r INTEGER, + // s INTEGER + // } + // https://tools.ietf.org/html/rfc3278#section-8.2 + // + var rBytes = [UInt8](data[..<(count / 2)]) + var sBytes = [UInt8](data[(count / 2)...]) + + // For byte >= 0x80 (first bit is 1), prefix 0x00 to make Security framework happy. + if rBytes.first! >= 0x80 { + rBytes.insert(0x00, at: 0) + } + + if sBytes.first! >= 0x80 { + sBytes.insert(0x00, at: 0) } + + let bytes = (rBytes.encode(as: .integer) + sBytes.encode(as: .integer)).encode(as: .sequence) + + #if swift(>=5.0) + return Data(bytes) + #else + return Data(bytes: bytes) + #endif + } } } diff --git a/LineSDK/LineSDK/Crypto/Algorithms/RSA.swift b/LineSDK/LineSDK/Crypto/Algorithms/RSA.swift index 22be706f..2f971eb9 100644 --- a/LineSDK/LineSDK/Crypto/Algorithms/RSA.swift +++ b/LineSDK/LineSDK/Crypto/Algorithms/RSA.swift @@ -1,13 +1,13 @@ // // RSA.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -30,16 +30,6 @@ extension RSA { enum Algorithm: CryptoAlgorithm { case sha1, sha224, sha256, sha384, sha512 - var length: CC_LONG { - switch self { - case .sha1: return CC_LONG(CC_SHA1_DIGEST_LENGTH) - case .sha224: return CC_LONG(CC_SHA224_DIGEST_LENGTH) - case .sha256: return CC_LONG(CC_SHA256_DIGEST_LENGTH) - case .sha384: return CC_LONG(CC_SHA384_DIGEST_LENGTH) - case .sha512: return CC_LONG(CC_SHA512_DIGEST_LENGTH) - } - } - var signatureAlgorithm: SecKeyAlgorithm { switch self { case .sha1: return .rsaSignatureMessagePKCS1v15SHA1 @@ -49,6 +39,16 @@ extension RSA { case .sha512: return .rsaSignatureMessagePKCS1v15SHA512 } } + + var digestAlgorithm: DigestAlgorithm { + switch self { + case .sha1: return .sha1 + case .sha224: return .sha224 + case .sha256: return .sha256 + case .sha384: return .sha384 + case .sha512: return .sha384 + } + } var encryptionAlgorithm: SecKeyAlgorithm { switch self { @@ -59,15 +59,5 @@ extension RSA { case .sha512: return .rsaEncryptionOAEPSHA512AESGCM } } - - var digest: CryptoDigest { - switch self { - case .sha1: return CC_SHA1 - case .sha224: return CC_SHA224 - case .sha256: return CC_SHA256 - case .sha384: return CC_SHA384 - case .sha512: return CC_SHA512 - } - } } } diff --git a/LineSDK/LineSDK/Crypto/CryptoAlgorithm.swift b/LineSDK/LineSDK/Crypto/CryptoAlgorithm.swift index ed419306..1d447a3b 100644 --- a/LineSDK/LineSDK/Crypto/CryptoAlgorithm.swift +++ b/LineSDK/LineSDK/Crypto/CryptoAlgorithm.swift @@ -1,13 +1,13 @@ // // CryptoAlgorithm.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -21,29 +21,85 @@ import Foundation import CommonCrypto +import Security typealias CryptoDigest = ( _ data: UnsafeRawPointer?, _ length: CC_LONG, _ md: UnsafeMutablePointer?) -> UnsafeMutablePointer? -/// Represents an algorithm used in crypto. +/// Represents an algorithm used in cryptography. protocol CryptoAlgorithm { + var digestAlgorithm: DigestAlgorithm { get } var length: CC_LONG { get } var signatureAlgorithm: SecKeyAlgorithm { get } var encryptionAlgorithm: SecKeyAlgorithm { get } var digest: CryptoDigest { get } + // Some algorithms require a different format for signature data. This is an injection point for it. + func convertSignatureData(_ data: Data) throws -> Data +} + +extension CryptoAlgorithm { + var length: CC_LONG { return digestAlgorithm.length } + var digest: CryptoDigest { return digestAlgorithm.digest } + func convertSignatureData(_ data: Data) throws -> Data { return data } } extension Data { /// Calculate the digest with a given algorithm. /// - /// - Parameter algorithm: The algorithm be used. It should provice a digest hash method at least. + /// - Parameter algorithm: The algorithm be used. It should provide a digest hash method at least. /// - Returns: The digest data. func digest(using algorithm: CryptoAlgorithm) -> Data { + return digest(using: algorithm.digestAlgorithm) + } + + func digest(using algorithm: DigestAlgorithm) -> Data { var hash = [UInt8](repeating: 0, count: Int(algorithm.length)) + #if swift(>=5.0) + withUnsafeBytes { _ = algorithm.digest($0.baseAddress, CC_LONG(count), &hash) } + return Data(hash) + #else withUnsafeBytes { _ = algorithm.digest($0, CC_LONG(count), &hash) } return Data(bytes: hash) + #endif + } + + static func randomData(bytesCount: Int) -> Data { + var bytes = [UInt8](repeating: 0, count: bytesCount) + let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) + if status != errSecSuccess { + bytes = bytes.map { _ in UInt8.random(in: UInt8.min...UInt8.max) } + } + #if swift(>=5.0) + return Data(bytes) + #else + return Data(bytes: bytes) + #endif + } +} + +enum DigestAlgorithm { + case sha1, sha224, sha256, sha384, sha512 + + var length: CC_LONG { + switch self { + case .sha1: return CC_LONG(CC_SHA1_DIGEST_LENGTH) + case .sha224: return CC_LONG(CC_SHA224_DIGEST_LENGTH) + case .sha256: return CC_LONG(CC_SHA256_DIGEST_LENGTH) + case .sha384: return CC_LONG(CC_SHA384_DIGEST_LENGTH) + case .sha512: return CC_LONG(CC_SHA512_DIGEST_LENGTH) + } + } + + var digest: CryptoDigest { + switch self { + case .sha1: return CC_SHA1 + case .sha224: return CC_SHA224 + case .sha256: return CC_SHA256 + case .sha384: return CC_SHA384 + case .sha512: return CC_SHA512 + } } } diff --git a/LineSDK/LineSDK/Crypto/CryptoData.swift b/LineSDK/LineSDK/Crypto/CryptoData.swift index 6046ef04..5c19c6b7 100644 --- a/LineSDK/LineSDK/Crypto/CryptoData.swift +++ b/LineSDK/LineSDK/Crypto/CryptoData.swift @@ -1,13 +1,13 @@ // // CryptoData.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -31,7 +31,7 @@ protocol CryptoData: Equatable { extension CryptoData { - /// Creates a data object in crypto domian for operation from a base64 string. + /// Creates a data object in crypto domain for operation from a base64 string. /// /// - Parameter string: The raw data in base64 encoding. /// - Throws: A `CryptoError` if something wrong happens. @@ -112,55 +112,20 @@ extension Crypto { /// - Returns: The digest algorithm used to verify data with `key`. /// - Throws: A `CryptoError` if something wrong happens. func verify(with key: CryptoPublicKey, signature: SignedData, algorithm: CryptoAlgorithm) throws -> Bool { - - // EC algorithm does not work when using iOS 10 SecKeyVerifySignature and related SecKeyAlgorithm. - // Maybe it is due to https://forums.developer.apple.com/thread/83136 (ECDSA signature generated by OpenSSL) - // or https://forums.developer.apple.com/thread/89619 - // - // .ecdsaSignatureMessageX962SHA256 requires DER x96.2 encoded signature. - // However, following RFC 5480 (https://www.ietf.org/rfc/rfc5480.txt) to encode {r, s} as: - // - // ECDSA-Sig-Value ::= SEQUENCE { - // r INTEGER, - // s INTEGER - // } - // - // causes verifying error when using `SecKeyVerifySignature` sometimes (even with or without prefixing 0x00 - // to the first byte if it is above 0x70). It might be an implemetation issue, so we decide to use - // `SecKeyRawVerify` now, which accepts raw {r, s} signature. - // - // Check this later if we have time to make all algorithms doing a `SecKeyVerifySignature` instead. - // See http://www.secg.org/sec1-v2.pdf - // https://crypto.stackexchange.com/questions/57731/ecdsa-signature-rs-to-asn1-der-encoding-question - // https://opensource.apple.com/source/Security/Security-57740.51.3/keychain/SecKey.h - // - if let rsaAlgorithm = algorithm as? RSA.Algorithm { - var error: Unmanaged? - let result = SecKeyVerifySignature( - key.key, rsaAlgorithm.signatureAlgorithm, raw as CFData, signature.raw as CFData, &error) - - guard error == nil else { - let err = error?.takeRetainedValue() - throw CryptoError.algorithmsFailed( - reason: .verifyingError(err, statusCode: (err as? CustomNSError)?.errorCode)) - } - - return result - } else if let ecAlgorithm = algorithm as? ECDSA.Algorithm { - let digestData = raw.digest(using: ecAlgorithm) - let digest = [UInt8](digestData) - let signatureBytes: [UInt8] = Array(signature.raw) - let status = SecKeyRawVerify( - key.key, .sigRaw, digest, digest.count, signatureBytes, ecAlgorithm.curve.signatureOctetLength) - if status != 0 { - let statusCode = Int(status) - let error = NSError(domain: CryptoError.errorDomain, code: statusCode, userInfo: nil) - throw CryptoError.algorithmsFailed(reason: .verifyingError(error, statusCode: statusCode)) - } - return true - } else { - Log.fatalError("Unsupported algorithm: \(algorithm)") + + let signatureRawData = try algorithm.convertSignatureData(signature.raw) + + var error: Unmanaged? + let result = SecKeyVerifySignature( + key.key, algorithm.signatureAlgorithm, raw as CFData, signatureRawData as CFData, &error) + + guard error == nil else { + let err = error?.takeRetainedValue() + throw CryptoError.algorithmsFailed( + reason: .verifyingError(err, statusCode: (err as? CustomNSError)?.errorCode)) } + + return result } } diff --git a/LineSDK/LineSDK/Crypto/CryptoError.swift b/LineSDK/LineSDK/Crypto/CryptoError.swift index a36f1921..9d02ffae 100644 --- a/LineSDK/LineSDK/Crypto/CryptoError.swift +++ b/LineSDK/LineSDK/Crypto/CryptoError.swift @@ -1,13 +1,13 @@ // // CryptoError.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -32,11 +32,11 @@ import Foundation /// to get a fixed error code to identify the error type quickly. All `CryptoError`s are under the /// "LineSDKError.CryptoError" error domain. /// -/// - algorithmsFailed: An error occurred while performing an algorithm related operation like creating -/// keys or verifying signed data. -/// - JWTFailed: An error occurred while performing a JWT related operation. -/// - JWKFailed: An error occurred while performing a JWK related operation. -/// - generalError: An error occurred while performing another crypto related operation. +/// - algorithmsFailed: An error occurred while performing an algorithm-related operation like +/// creating keys or verifying signed data. +/// - JWTFailed: An error occurred while performing a JWT-related operation. +/// - JWKFailed: An error occurred while performing a JWK-related operation. +/// - generalError: An error occurred while performing another cryptography-related operation. public enum CryptoError: Error { /// The possible underlying reasons an `.algorithmsFailed` error occurs. @@ -49,6 +49,8 @@ public enum CryptoError: Error { /// - decryptingError: An error occurred while decrypting encrypted data. Code 3016_1006. /// - signingError: An error occurred while signing plain data. Code 3016_1007. /// - verifyingError: An error occurred while verifying data. Code 3016_1008. + /// - invalidSignature: The algorithm does not accept the input data as a signature. + /// The data is corrupted. Code 3016_1009. public enum AlgorithmsErrorReason { /// The DER data does not contain a valid RSA key. Code 3016_1001. @@ -74,6 +76,9 @@ public enum CryptoError: Error { /// An error occurred while verifying data. Code 3016_1008. case verifyingError(Error?, statusCode: Int?) + + /// The algorithm does not accept the input data as a signature. The data is corrupted. Code 3016_1009. + case invalidSignature(data: Data) } /// The possible underlying reasons a `.JWTFailed` error occurs. @@ -130,9 +135,16 @@ public enum CryptoError: Error { case decodingFailed(string: String, type: Any.Type) } + /// An error occurred while performing an algorithm-related operation like creating keys or verifying signed data. case algorithmsFailed(reason: AlgorithmsErrorReason) + + /// An error occurred while performing a JWT-related operation. case JWTFailed(reason: JWTErrorReason) + + /// An error occurred while performing a JWK-related operation. case JWKFailed(reason: JWKErrorReason) + + /// An error occurred while performing another cryptography-related operation. case generalError(reason: GeneralErrorReason) } @@ -157,6 +169,8 @@ extension CryptoError.AlgorithmsErrorReason { case .verifyingError(let error, let code): return "Error happens while verifying some plain data. " + "Error: \(String(describing: error)), code: \(String(describing: code))" + case .invalidSignature(let data): + return "The algorithm does not accept the input data as a signature. Data: \([UInt8](data))" } } @@ -170,6 +184,7 @@ extension CryptoError.AlgorithmsErrorReason { case .decryptingError: return 3016_1006 case .signingError: return 3016_1007 case .verifyingError: return 3016_1008 + case .invalidSignature: return 3016_1009 } } @@ -196,6 +211,8 @@ extension CryptoError.AlgorithmsErrorReason { if let code = code { userInfo[.statusCode] = code } + case .invalidSignature(let data): + userInfo[.data] = data } return .init(uniqueKeysWithValues: userInfo.map { ($0.rawValue, $1) }) } diff --git a/LineSDK/LineSDK/Crypto/CryptoHelpers.swift b/LineSDK/LineSDK/Crypto/CryptoHelpers.swift index e6ebaca2..9870c637 100644 --- a/LineSDK/LineSDK/Crypto/CryptoHelpers.swift +++ b/LineSDK/LineSDK/Crypto/CryptoHelpers.swift @@ -1,13 +1,13 @@ // // RSAHelpers.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -96,7 +96,7 @@ extension Data { index += 1 let strippedKeyBytes = [UInt8](self[index...self.count - 1]) - let data = Data(bytes: UnsafePointer(strippedKeyBytes), count: self.count - index) + let data = Data(bytes: strippedKeyBytes, count: self.count - index) return data } @@ -104,7 +104,7 @@ extension Data { /// Data with x509 stripped from a provided ASN.1 DER EC public key. /// The DER data will be returned as is, if no header contained. func x509HeaderStrippedForEC() throws -> Data { - return try x509HeaderStripped(earlyTerminator: ASN1Type.uncompressIndicator.byte) + return try x509HeaderStripped(earlyTerminator: ASN1Type.uncompressedIndicator.byte) } /// Data with x509 stripped from a provided ASN.1 DER RSA public key. @@ -168,18 +168,14 @@ extension SecKey { } // Get public key from certData - if #available(iOS 10.3, *) { - guard let key = SecCertificateCopyPublicKey(certData) else { - throw CryptoError.algorithmsFailed( - reason: .createKeyFailed(data: data, reason: "Cannot copy public key from certificate")) - } - return key - } else { - throw CryptoError.generalError( - reason: .operationNotSupported( - reason: "Loading public key from certificate not supported below iOS 10.3.") - ) + let copyKey: (SecCertificate) -> SecKey? + copyKey = SecCertificateCopyKey + + guard let key = copyKey(certData) else { + throw CryptoError.algorithmsFailed( + reason: .createKeyFailed(data: data, reason: "Cannot copy public key from certificate")) } + return key } } @@ -191,7 +187,9 @@ extension String { } guard lines.count != 0 else { - throw CryptoError.algorithmsFailed(reason: .invalidPEMKey(string: self, reason: "Empty PEM key after stripping.")) + throw CryptoError.algorithmsFailed( + reason: .invalidPEMKey(string: self, reason: "Empty PEM key after stripping.") + ) } // Strip off carriage returns in case. @@ -214,14 +212,14 @@ enum ASN1Type { case sequence case integer case bitString - case uncompressIndicator + case uncompressedIndicator var byte: UInt8 { switch self { case .sequence: return 0x30 case .integer: return 0x02 case .bitString: return 0x03 - case .uncompressIndicator: return 0x04 + case .uncompressedIndicator: return 0x04 } } } diff --git a/LineSDK/LineSDK/Crypto/CryptoKey.swift b/LineSDK/LineSDK/Crypto/CryptoKey.swift index ee66f980..753569fb 100644 --- a/LineSDK/LineSDK/Crypto/CryptoKey.swift +++ b/LineSDK/LineSDK/Crypto/CryptoKey.swift @@ -1,13 +1,13 @@ // // CryptoKey.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Crypto/GetDiscoveryDocumentRequest.swift b/LineSDK/LineSDK/Crypto/GetDiscoveryDocumentRequest.swift index a2e25734..2030f385 100644 --- a/LineSDK/LineSDK/Crypto/GetDiscoveryDocumentRequest.swift +++ b/LineSDK/LineSDK/Crypto/GetDiscoveryDocumentRequest.swift @@ -1,13 +1,13 @@ // // GetDiscoveryDocumentRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -45,4 +45,9 @@ struct DiscoveryDocument: Decodable { case issuer case jwksURI = "jwks_uri" } + + struct ResolvedProviderMetadata { + let issuer: String + let jwk: JWK + } } diff --git a/LineSDK/LineSDK/Crypto/GetJWKSetRequest.swift b/LineSDK/LineSDK/Crypto/GetJWKSetRequest.swift index 707d6bc1..6c0971ce 100644 --- a/LineSDK/LineSDK/Crypto/GetJWKSetRequest.swift +++ b/LineSDK/LineSDK/Crypto/GetJWKSetRequest.swift @@ -1,13 +1,13 @@ // // GetJWKSetRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Crypto/JWK/JWA.swift b/LineSDK/LineSDK/Crypto/JWK/JWA.swift index dbceb92f..105e1bf9 100644 --- a/LineSDK/LineSDK/Crypto/JWK/JWA.swift +++ b/LineSDK/LineSDK/Crypto/JWK/JWA.swift @@ -1,13 +1,13 @@ // // JWA.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -21,8 +21,8 @@ import Foundation -// A partitial implementation for JSON Web Algorithms (JWA) RFC 7518 -// Only RSA related algorithms are required for LineSDK, ref: https://tools.ietf.org/html/rfc7518 +// A partial implementation for JSON Web Algorithms (JWA) RFC 7518 +// Ref: https://tools.ietf.org/html/rfc7518 struct JWA { enum Algorithm: String, Decodable { @@ -117,8 +117,8 @@ extension JWA { var modulusBytes = [UInt8](decodedModulusData) - // Make sure the modulusBytes starts with 0x00. If not, append it. - if let firstByte = modulusBytes.first, firstByte != 0x00 { + // Make sure the modulusBytes starts with 0x00 if the first bit is 1. + if let firstByte = modulusBytes.first, firstByte >= 0x80 { modulusBytes.insert(0x00, at: 0) } @@ -128,8 +128,12 @@ extension JWA { let exponentEncoded = exponentBytes.encode(as: .integer) let sequenceEncoded = (modulusEncoded + exponentEncoded).encode(as: .sequence) - + + #if swift(>=5.0) + return Data(sequenceEncoded) + #else return Data(bytes: sequenceEncoded) + #endif } } } @@ -173,9 +177,13 @@ extension JWA { yBytes = [UInt8](decodedYData).dropFirst { $0 == 0x00 } } - let uncompressedIndicator: [UInt8] = [ASN1Type.uncompressIndicator.byte] - - return Data(bytes: uncompressedIndicator + xBytes + yBytes) + let uncompressedIndicator: [UInt8] = [ASN1Type.uncompressedIndicator.byte] + let bytes = uncompressedIndicator + xBytes + yBytes + #if swift(>=5.0) + return Data(bytes) + #else + return Data(bytes: bytes) + #endif } } } diff --git a/LineSDK/LineSDK/Crypto/JWK/JWK.swift b/LineSDK/LineSDK/Crypto/JWK/JWK.swift index 25581f04..4c7b1af0 100644 --- a/LineSDK/LineSDK/Crypto/JWK/JWK.swift +++ b/LineSDK/LineSDK/Crypto/JWK/JWK.swift @@ -1,13 +1,13 @@ // // JWK.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -21,8 +21,8 @@ import Foundation -// A partitial implementation for JSON Web Key (JWK) RFC7517. -// Only RSA is required for LineSDK, ref: https://tools.ietf.org/html/rfc7517 +// A partial implementation for JSON Web Key (JWK) RFC7517. +// Ref: https://tools.ietf.org/html/rfc7517 struct JWK: Decodable { typealias Parameters = JWA.KeyParameters diff --git a/LineSDK/LineSDK/Crypto/JWK/JWKSet.swift b/LineSDK/LineSDK/Crypto/JWK/JWKSet.swift index ae093490..b31b1cc1 100644 --- a/LineSDK/LineSDK/Crypto/JWK/JWKSet.swift +++ b/LineSDK/LineSDK/Crypto/JWK/JWKSet.swift @@ -1,13 +1,13 @@ // // JWKSet.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Crypto/JWT/JWT.swift b/LineSDK/LineSDK/Crypto/JWT/JWT.swift index 3d7d72b0..1458dc45 100644 --- a/LineSDK/LineSDK/Crypto/JWT/JWT.swift +++ b/LineSDK/LineSDK/Crypto/JWT/JWT.swift @@ -1,13 +1,13 @@ // // JWT.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -187,6 +187,9 @@ extension JWT.Payload { } return Date(timeIntervalSince1970: timeInterval) } + + /// The authentication methods references of the ID token. + public var amr: [String]? { return self["amr", [String].self] } } // MARK: - LINE-specific claims diff --git a/LineSDK/LineSDK/Crypto/JWT/JWTCoder.swift b/LineSDK/LineSDK/Crypto/JWT/JWTCoder.swift index 50d5a65c..c82d7fff 100644 --- a/LineSDK/LineSDK/Crypto/JWT/JWTCoder.swift +++ b/LineSDK/LineSDK/Crypto/JWT/JWTCoder.swift @@ -1,13 +1,13 @@ // // JWTCoder.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -22,7 +22,7 @@ import Foundation // A customize JSON decoder to decode from base64URL strings. -class Base64JSONDecoder: JSONDecoder { +class Base64JSONDecoder: JSONDecoder, @unchecked Sendable { override func decode(_ type: T.Type, from data: Data) throws -> T where T : Decodable { guard let string = String(data: data, encoding: .ascii) else { throw CryptoError.generalError(reason: .dataConversionFailed(data: data, encoding: .ascii)) diff --git a/LineSDK/LineSDK/Crypto/JWT/JWTHelpers.swift b/LineSDK/LineSDK/Crypto/JWT/JWTHelpers.swift index b6101b88..1bf1774e 100644 --- a/LineSDK/LineSDK/Crypto/JWT/JWTHelpers.swift +++ b/LineSDK/LineSDK/Crypto/JWT/JWTHelpers.swift @@ -1,13 +1,13 @@ // // JWTHelpers.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Friendship/Request/GetBotFriendshipStatus.swift b/LineSDK/LineSDK/Friendship/Request/GetBotFriendshipStatus.swift index 4e99ec6e..f1a3b732 100644 --- a/LineSDK/LineSDK/Friendship/Request/GetBotFriendshipStatus.swift +++ b/LineSDK/LineSDK/Friendship/Request/GetBotFriendshipStatus.swift @@ -1,13 +1,13 @@ // // GetBotFriendshipStatus.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -21,24 +21,25 @@ import Foundation -/// Represents a request for getting the friendship status of the user and the bot linked to your LINE Login -/// channel. +/// Represents a request for getting the friendship status of the user and the LINE Official Account linked to your +/// LINE Login channel. public struct GetBotFriendshipStatusRequest: Request { /// :nodoc: public let method: HTTPMethod = .get /// :nodoc: - public let path: String = "/friendship/v1/status" + public let path = "/friendship/v1/status" /// :nodoc: public let authentication: AuthenticateMethod = .token - + /// :nodoc: public init() {} - /// Represents a response to a request for getting the friendship status of the user and the bot linked to - /// your LINE Login channel. - public struct Response: Decodable { - /// Indicates the friendship status. `true` if the bot is a friend of the user and the user has not - /// blocked the bot. `false` if the bot is not a friend of the user or the user has blocked the bot. + /// Represents a response to a request for getting the friendship status of the user and the LINE Official Account + /// linked to your LINE Login channel. + public struct Response: Codable { + /// Indicates the friendship status. `true` if the LINE Official Account is a friend of the user and the user + /// has not blocked the LINE Official Account. `false` if the LINE Official Account is not a friend of the user + /// or the user has blocked the LINE Official Account. public let friendFlag: Bool } } diff --git a/LineSDK/LineSDK/General/LineSDKError.swift b/LineSDK/LineSDK/General/LineSDKError.swift index 6a8d4edf..431761cb 100644 --- a/LineSDK/LineSDK/General/LineSDKError.swift +++ b/LineSDK/LineSDK/General/LineSDKError.swift @@ -1,13 +1,13 @@ // // LineSDKError.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -33,9 +33,9 @@ import Foundation /// - responseFailed: An error occurred while handling a response. /// - authorizeFailed: An error occurred while authorizing a user. /// - generalError: An error occurred while performing another process in the LINE SDK. -/// - untypedError: An error not defined in the LINE SDK. +/// - untypedError: An error not defined in the LINE SDK occurred. public enum LineSDKError: Error { - + /// The possible underlying reasons a `.requestFailed` error occurs. /// /// - missingURL: The `URL` object is missing while encoding a request. Code 1001. @@ -45,14 +45,18 @@ public enum LineSDKError: Error { public enum RequestErrorReason { /// The `URL` object is missing while encoding a request. Code 1001. case missingURL - + /// The request requires an access token but it is unavailable. Code 1002. case lackOfAccessToken - + /// The request requires a JSON body but the provided data cannot be encoded to valid JSON. Code 1003. case jsonEncodingFailed(Error) + + /// The request cannot be created due to the parameter does not match the precondition. Check the associated + /// values for detail information. Code 1004. + case invalidParameter([ParameterItem]) } - + /// The possible underlying reasons a `.responseFailed` error occurs. /// /// - URLSessionError: An error occurred in the underlying `URLSession` object. Code 2001. @@ -62,33 +66,40 @@ public enum LineSDKError: Error { /// - invalidHTTPStatusAPIError: The received response contains an invalid HTTP status code. Code 2004. public enum ResponseErrorReason { + /// Error details for `invalidHTTPStatusAPIError`. When `ResponseErrorReason` is `invalidHTTPStatusAPIError`, + /// `APIErrorDetail` is the associated value. It contains the HTTP status code and the error + /// messages returned by the LINE server. public struct APIErrorDetail { - let code: Int - let error: APIError? - let raw: HTTPURLResponse - let rawString: String? + + /// Error code received from server. This is usually the HTTP status code. + public let code: Int + + /// An `APIError` object, if the response data can be converted to it. If not: `nil`. + public let error: APIError? + + /// The raw response when this `invalidHTTPStatusAPIError` happens. + public let raw: HTTPURLResponse + + /// The plain error text converted from the response body data, if existing. + public let rawString: String? } - + /// An error occurred in the underlying `URLSession` object. Code 2001. case URLSessionError(Error) - + /// The response is not a valid `HTTPURLResponse` object. Code 2002. case nonHTTPURLResponse - + /// The received data cannot be parsed to an instance of the target type. Code 2003. /// - Associated values: Parsing destination type, original data, and system underlying error. - case dataParsingFailed(Any.Type, Data, Error) - + case dataParsingFailed(Any.Type, Data, Error?) + /// The received response contains an invalid HTTP status code. Code 2004. /// - /// Associated `APIErrorDetail` - /// contains information about the error detail. If the response data can be converted to an `APIError` object, - /// it will be associated with `APIErrorDetail`. The `error` property in `APIErrorDetail` indicates the cause of an error. - /// Otherwise, the `detail.error` will be `nil`. In both cases, `detail.raw` and `detail.rawString` will - /// contain the plain response and error text respectively. + /// The associated value `APIErrorDetail` contains error details. case invalidHTTPStatusAPIError(detail: APIErrorDetail) } - + /// The possible underlying reasons an `.authorizeFailed` error occurs. /// /// - exhaustedLoginFlow: There is no other login method left. The login process cannot be completed. @@ -100,16 +111,16 @@ public enum LineSDKError: Error { /// - callbackURLSchemeNotMatching: The received `URL` object while opening the app does not match the /// defined URL scheme. Code 3005. /// - invalidSourceApplication: The source application is invalid and cannot finish the authorization - /// process. Code 3006. + /// process. Not in use anymore from LINE SDK 5.2.4. Code 3006. /// - malformedRedirectURL: The received `URL` object while opening the app is invalid or does not /// contain necessary information. Code 3007. /// - invalidLineURLResultCode: The received `URL` object while opening the app has an unknown result /// code. Code 3008. - /// - lineClientError: An error occurs in the LINE app during the authorization process. Code 3009. + /// - lineClientError: An error occurs in LINE during the authorization process. Code 3009. /// - responseStateValueNotMatching: Failed to verify the `state` value. The received URL response is /// not from the original authorization request. Code 3010. - /// - webLoginError: An error occurrs in the web login flow during the authorization process. Code 3011. - /// - keychainOperation: An error occurrs while accessing the keychain. It prevents the LINE SDK from + /// - webLoginError: An error occurs in the web login flow during the authorization process. Code 3011. + /// - keychainOperation: An error occurs while accessing the keychain. It prevents the LINE SDK from /// loading data from or writing data to the keychain. Code 3012. /// - invalidDataInKeychain: The retrieved authorization information from the keychain cannot be /// converted to valid data. Code 3013. @@ -121,92 +132,114 @@ public enum LineSDKError: Error { /// certificate or a key, or an unsupported algorithm is used. For more information, see /// `CryptoError`. Code 3016. public enum AuthorizeErrorReason { - + /// There is no other login method left. The login process cannot be completed. Code 3001. case exhaustedLoginFlow - + /// The view hierarchy or view controller hierarchy is malformed and the LINE /// SDK cannot show its login view controller. Code 3002. case malformedHierarchy - + /// The user cancelled or interrupted the login process. Code 3003. case userCancelled - + /// The `stop` method is called during the login process. Code 3004. case forceStopped - + /// The received `URL` object while opening the app does not match the defined URL scheme. Code 3005. case callbackURLSchemeNotMatching - /// The source application is invalid and cannot finish the authorization process. Code 3006. + /// The source application is invalid and cannot finish the authorization process. + /// Not in use anymore from LINE SDK 5.2.4. Code 3006. case invalidSourceApplication - + /// The received `URL` object while opening the app is invalid or does not /// contain necessary information. Code 3007. /// - url: The url which is used to open current app. /// - message: A human readable message to describe the error reason. case malformedRedirectURL(url: URL, message: String?) - + /// The received `URL` object while opening the app has an unknown result code. Code 3008. /// - Associated value: The `resultCode` in the response url. case invalidLineURLResultCode(String) - - /// An error occurs in the LINE app during the authorization process. Code 3009. - /// - code: The code returned by LINE client during the authorization. + + /// An error occurs in LINE during the authorization process. Code 3009. + /// - code: The code returned by LINE during the authorization. /// - message: The message describes the error. case lineClientError(code: String, message: String?) - + /// Failed to verify the `state` value. The received URL response /// is not from the original authorization request. Code 3010. /// - expected: Expected state value. /// - got: The state value actually got from URL response. case responseStateValueNotMatching(expected: String, got: String?) - - /// An error occurrs in the web login flow during the authorization process. Code 3011. + + /// An error occurs in the web login flow during the authorization process. Code 3011. /// - error: Error reason when login with web flow. /// - description: A human readable message to describe the error reason. case webLoginError(error: String, description: String?) - - /// An error occurrs while accessing the keychain. It prevents the LINE SDK from + + /// An error occurs while accessing the keychain. It prevents the LINE SDK from /// loading data from or writing data to the keychain. Code 3012. /// - status: The `OSStatus` number system gives. case keychainOperation(status: OSStatus) - + /// The retrieved authorization information from the keychain cannot be converted to valid data. Code 3013. case invalidDataInKeychain - + /// The authorization request contains the OpenID scope, but any ID token is not found /// in or parsed from the server response. Code 3014. /// - raw: Raw value of the ID Token, which cannot be parsed correctly. case lackOfIDToken(raw: String?) - + /// The public key is not found for a give key ID or the key ID does not exist. Code 3015. /// - keyID: The key ID specified in ID Token header which should be used. case JWTPublicKeyNotFound(keyID: String?) - + /// An error occurred at the LINE SDK crypto part. Usually this indicates a malformed /// certificate or a key, or an unsupported algorithm is used. For more information, see /// `CryptoError`. Code 3016. /// - error: Underlying `CryptoError` value. case cryptoError(error: CryptoError) } - + /// The possible underlying reasons `.generalError` occurs. /// /// - conversionError: Cannot convert `string` to valid data with `encoding`. Code 4001. /// - parameterError: The method is invoked with an invalid parameter. Code 4002. + /// - notOriginalTask: The image download task finished but it is not the original task issued. Code 4003. + /// - processDiscarded: The process is discarded when a new login process is created. This only + /// happens when `allowRecreatingLoginProcess` in `LoginManager.Parameters` is `true` + /// and users are trying to create another login process. Code 4004. public enum GeneralErrorReason { /// Cannot convert `string` to valid data with `encoding`. Code 4001. case conversionError(string: String, encoding: String.Encoding) - + /// The method is invoked with an invalid parameter. Code 4002. case parameterError(parameterName: String, description: String) + + /// The image download task finished but it is not the original task issued. Code 4003. + case notOriginalTask(token: UInt) + + /// The process is discarded when a new login process is created. This only + /// happens when `allowRecreatingLoginProcess` in `LoginManager.Parameters` is `true` + /// and users are trying to create another login process. Code 4004. + case processDiscarded(LoginProcess) } - + + /// An error occurred while constructing a request. case requestFailed(reason: RequestErrorReason) + + /// An error occurred while handling a response. case responseFailed(reason: ResponseErrorReason) + + /// An error occurred while authorizing a user. case authorizeFailed(reason: AuthorizeErrorReason) + + /// An error occurred while performing another process in the LINE SDK. case generalError(reason: GeneralErrorReason) + + /// An error not defined in the LINE SDK occurred. case untypedError(error: Error) } @@ -225,7 +258,7 @@ extension LineSDKError { } return false } - + /// Checks and returns whether the `LineSDKError` is a response error. public var isResponseError: Bool { if case .responseFailed = self { @@ -233,7 +266,7 @@ extension LineSDKError { } return false } - + /// Checks and returns whether the `LineSDKError` is an authorization error. public var isAuthorizeError: Bool { if case .authorizeFailed = self { @@ -241,7 +274,7 @@ extension LineSDKError { } return false } - + /// Checks and returns whether the `LineSDKError` is a general error. public var isGeneralError: Bool { if case .generalError = self { @@ -261,29 +294,29 @@ extension LineSDKError { } return false } - + /// Checks and returns whether the `LineSDKError` is a bad request error. public var isBadRequest: Bool { return isResponseError(statusCode: 400) } - + /// Checks and returns whether the `LineSDKError` is a refresh token error. Typically, when the user uses - /// an expired access token to send an API request, an automatic token refresh operation with the + /// an expired access token to send a public API request, an automatic token refresh operation with the /// current refresh token will be triggered. This error typically occurs when the refresh token also /// expires or is invalid. If this error occurs, let the user log in again before you can continue to /// access the LINE Platform. public var isRefreshTokenError: Bool { let refreshTokenRequest = PostRefreshTokenRequest(channelID: "", refreshToken: "") - let url = refreshTokenRequest.baseURL.appendingPathComponentIfNotEmpty(refreshTokenRequest.path) + let url = refreshTokenRequest.baseURL.appendingPathComponentIfNotEmpty(refreshTokenRequest) return isResponseError(statusCode: 400, url: url) } - + /// Checks and returns whether the `LineSDKError` is a permission error. This typically means you /// do not have sufficient permissions to access an endpoint of the LINE Platform. public var isPermissionError: Bool { return isResponseError(statusCode: 403) } - + /// Checks and returns whether the `LineSDKError` is an access token error. Usually, it means the user's /// access token is expired or malformed. public var isTokenError: Bool { @@ -321,13 +354,13 @@ extension LineSDKError { guard url == detail.raw.url else { return false } return true } - + /// Checks and returns whether the `LineSDKError` is a timeout error caused by the underlying URL /// session error. public var isURLSessionTimeOut: Bool { return isURLSessionErrorCode(sessionErrorCode: NSURLErrorTimedOut) } - + /// Checks whether the `LineSDKError` is a URL session error with a specified error code. /// /// - Parameter code: The underlying URL session error code. See `URLError` in the documentation for the @@ -367,7 +400,7 @@ extension LineSDKError: CustomNSError { case .untypedError: return -1 } } - + /// :nodoc: public var errorUserInfo: [String : Any] { switch self { @@ -378,13 +411,35 @@ extension LineSDKError: CustomNSError { case .untypedError(error: let error): return [LineSDKErrorUserInfoKey.underlyingError.rawValue: error] } } - + /// :nodoc: public static var errorDomain: String { return "LineSDKError" } } +extension LineSDKError.RequestErrorReason { + public struct ParameterItem { + public let name: String + public let value: String + public let description: String + } +} + +extension LineSDKError.RequestErrorReason.ParameterItem { + static func invalidEntityID( + _ parameterName: String, + value: EntityID + ) -> LineSDKError.RequestErrorReason.ParameterItem + { + return .init( + name: parameterName, + value: value, + description: "The value of `\(parameterName)` is not valid. It should be in the range of [^a-zA-Z0-9], but now \(value)." + ) + } +} + // MARK: - Private Definition extension LineSDKError.RequestErrorReason { @@ -396,17 +451,21 @@ extension LineSDKError.RequestErrorReason { return "The request requires an access token, but there is no one." case .jsonEncodingFailed(let error): return "The request requires a JSON body, but provided data cannot be encoded to valid JSON. \(error)" + case .invalidParameter(let parameters): + return "Cannot create the request. Invalid parameters:" + + (parameters.map { $0.description }).joined(separator: "\n") } } - + var errorCode: Int { switch self { case .missingURL: return 1001 case .lackOfAccessToken: return 1002 case .jsonEncodingFailed: return 1003 + case .invalidParameter: return 1004 } } - + var errorUserInfo: [String: Any] { var userInfo: [LineSDKErrorUserInfoKey: Any] = [:] switch self { @@ -414,6 +473,7 @@ extension LineSDKError.RequestErrorReason { case .lackOfAccessToken: break case .jsonEncodingFailed(let error): userInfo[.underlyingError] = error + case .invalidParameter: break } return .init(uniqueKeysWithValues: userInfo.map { ($0.rawValue, $1) }) } @@ -427,7 +487,8 @@ extension LineSDKError.ResponseErrorReason { case .nonHTTPURLResponse: return "The response is not a valid `HTTPURLResponse`." case .dataParsingFailed(let type, let data, let error): - let result = "Parsing response data to \(type) failed: \(error)." + let errorMessage = error != nil ? "\(error!)" : "" + let result = "Parsing response data to \(type) failed: \(errorMessage)." if let text = String(data: data, encoding: .utf8) { return result + "\nOriginal: \(text)" } else { @@ -444,7 +505,7 @@ extension LineSDKError.ResponseErrorReason { } } } - + var errorCode: Int { switch self { case .URLSessionError: return 2001 @@ -453,7 +514,7 @@ extension LineSDKError.ResponseErrorReason { case .invalidHTTPStatusAPIError: return 2004 } } - + var errorUserInfo: [String: Any] { var userInfo: [LineSDKErrorUserInfoKey: Any] = [:] switch self { @@ -515,7 +576,7 @@ extension LineSDKError.AuthorizeErrorReason { return "CryptoError: \(error.errorDescription ?? "nil")" } } - + var errorCode: Int { switch self { case .exhaustedLoginFlow: return 3001 @@ -536,7 +597,7 @@ extension LineSDKError.AuthorizeErrorReason { case .cryptoError: return 3016 } } - + var errorUserInfo: [String: Any] { var userInfo: [LineSDKErrorUserInfoKey: Any] = [:] switch self { @@ -580,16 +641,22 @@ extension LineSDKError.GeneralErrorReason { return "Cannot convert target \"\(text)\" to valid data under \(encoding) encoding." case .parameterError(let parameterName, let reason): return "Method invoked with an invalid parameter \"\(parameterName)\". Reason: \(reason)" + case .notOriginalTask(let token): + return "Image downloading finished but it is not the original one. Token \"\(token)\"." + case .processDiscarded(let process): + return "Current process is discarded. \(process)" } } - + var errorCode: Int { switch self { case .conversionError(_, _): return 4001 case .parameterError(_, _): return 4002 + case .notOriginalTask(_): return 4003 + case .processDiscarded: return 4004 } } - + var errorUserInfo: [String: Any] { var userInfo: [LineSDKErrorUserInfoKey: Any] = [:] switch self { @@ -599,22 +666,25 @@ extension LineSDKError.GeneralErrorReason { case .parameterError(let parameterName, let reason): userInfo[.parameterName] = parameterName userInfo[.reason] = reason + case .notOriginalTask: break + case .processDiscarded(let process): + userInfo[.process] = process } return .init(uniqueKeysWithValues: userInfo.map { ($0.rawValue, $1) }) } } -extension Result where Error == LineSDKError { - init(_ throwing: () throws -> Value) { +extension Result where Failure == LineSDKError { + init(catching body: () throws -> Success) { do { - let value = try throwing() - self = .success(value) + self = .success(try body()) } catch { self = .failure(error.sdkError) } } } +/// :nodoc: public enum LineSDKErrorUserInfoKey: String { case underlyingError case statusCode @@ -633,5 +703,5 @@ public enum LineSDKErrorUserInfoKey: String { case index case key case got + case process } - diff --git a/LineSDK/LineSDK/Graph/Model/Group.swift b/LineSDK/LineSDK/Graph/Model/Group.swift index 39915d34..b89f2c54 100644 --- a/LineSDK/LineSDK/Graph/Model/Group.swift +++ b/LineSDK/LineSDK/Graph/Model/Group.swift @@ -1,13 +1,13 @@ // // Group.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -22,7 +22,7 @@ import Foundation /// LINE internal use only. -/// Represents an `Group` object which LineSDK used in `group list` or `approvers in given group`. +/// Represents a `Group` object which LineSDK used in `group list` or `approvers in given group`. public struct Group: Decodable { /// Identifier of the group @@ -31,9 +31,19 @@ public struct Group: Decodable { /// The group's display name public let groupName: String - /// The URL of the group picture + /// URL of group picture public let pictureURL: URL? + /// URL of group's large profile image. `nil` if no profile image is set. + public var pictureURLLarge: URL? { + return pictureURL?.appendingPathComponent("/large") + } + + /// URL of group's small profile image. `nil` if no profile image is set. + public var pictureURLSmall: URL? { + return pictureURL?.appendingPathComponent("/small") + } + enum CodingKeys: String, CodingKey { case groupID = "groupId" case groupName diff --git a/LineSDK/LineSDK/Graph/Model/User.swift b/LineSDK/LineSDK/Graph/Model/User.swift index 294c1d3a..60f59a83 100644 --- a/LineSDK/LineSDK/Graph/Model/User.swift +++ b/LineSDK/LineSDK/Graph/Model/User.swift @@ -1,13 +1,13 @@ // // User.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -22,21 +22,40 @@ import Foundation /// LINE internal use only. -/// Represents an `User` object which LineSDK used in `friend list` or `approvers in friend list`. +/// Represents a `User` object which LineSDK used in `friend list` or `approvers in friend list`. public struct User: Decodable { - /// Identifier of the user + /// Identifier of the user. public let userID: String - /// User's display name - public let displayName: String + /// User's display name. This is the preferred username to be displayed in UI. When + /// `displayNameOverridden` is not `nil`, this value is identical to it. Otherwise, it is `displayNameOriginal`. + public var displayName: String { return displayNameOverridden ?? displayNameOriginal } + + /// User's original display name. It is the friend's user display name set by himself/herself. + public let displayNameOriginal: String + + /// User's overridden display name. It is the friend’s nickname which changed by the current user. + /// It is `nil` if the current user didn't set a nickname for this user. + public let displayNameOverridden: String? /// Profile image URL. Not included in the response if the user doesn't have a profile image. public let pictureURL: URL? + /// URL of user's large profile image. `nil` if no profile image is set. + public var pictureURLLarge: URL? { + return pictureURL?.appendingPathComponent("/large") + } + + /// URL of user's small profile image. `nil` if no profile image is set. + public var pictureURLSmall: URL? { + return pictureURL?.appendingPathComponent("/small") + } + enum CodingKeys: String, CodingKey { case userID = "userId" - case displayName + case displayNameOriginal = "displayName" + case displayNameOverridden case pictureURL = "pictureUrl" } } diff --git a/LineSDK/LineSDK/Graph/Request/GetApproversInFriendsRequest.swift b/LineSDK/LineSDK/Graph/Request/GetApproversInFriendsRequest.swift index 9873d562..244292b8 100644 --- a/LineSDK/LineSDK/Graph/Request/GetApproversInFriendsRequest.swift +++ b/LineSDK/LineSDK/Graph/Request/GetApproversInFriendsRequest.swift @@ -1,13 +1,13 @@ // // GetApproversInFriendsRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -55,3 +55,7 @@ public struct GetApproversInFriendsRequest: Request { public let pageToken: String? } } + +extension GetApproversInFriendsRequest.Response: PaginatedResponse { + var paginatedValues: [User] { return friends } +} diff --git a/LineSDK/LineSDK/Graph/Request/GetApproversInGroupRequest.swift b/LineSDK/LineSDK/Graph/Request/GetApproversInGroupRequest.swift index da789bb1..bdfe5dfc 100644 --- a/LineSDK/LineSDK/Graph/Request/GetApproversInGroupRequest.swift +++ b/LineSDK/LineSDK/Graph/Request/GetApproversInGroupRequest.swift @@ -1,13 +1,13 @@ // // GetApproversInGroupRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -26,7 +26,12 @@ import Foundation /// Note that this API does not take friendship status into account. public struct GetApproversInGroupRequest: Request { - public init(groupID: String, pageToken: String? = nil) { + public init(groupID: EntityID, pageToken: String? = nil) throws { + guard groupID.isValid else { + throw LineSDKError.requestFailed(reason: + .invalidParameter([.invalidEntityID("groupID", value: groupID)]) + ) + } self.groupID = groupID self.pageToken = pageToken } @@ -66,3 +71,7 @@ public struct GetApproversInGroupRequest: Request { } } } + +extension GetApproversInGroupRequest.Response: PaginatedResponse { + var paginatedValues: [User] { return users } +} diff --git a/LineSDK/LineSDK/Graph/Request/GetFriendsRequest.swift b/LineSDK/LineSDK/Graph/Request/GetFriendsRequest.swift index 806d9603..c921c254 100644 --- a/LineSDK/LineSDK/Graph/Request/GetFriendsRequest.swift +++ b/LineSDK/LineSDK/Graph/Request/GetFriendsRequest.swift @@ -1,13 +1,13 @@ // // GetFriendsRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -22,17 +22,26 @@ import Foundation /// LINE internal use only. -/// Represents the request of getting friends list, returns a friend list of current user. -/// Unless already having granted the channel, users who've configured the privacy filter are excluded from the list. +/// Represents a request for a user's friends list. Returns a list of current user's friends. +/// The list will not include users who blocked external apps from getting their profile +/// information, unless they specifically authorized the app. public struct GetFriendsRequest: Request { + + let sort: Sort? + let pageToken: String? - /// Sorting method for the returned freind list. - /// Only a value of `name` is supported. + /// Sorting method for the returned friend list. + /// Only supports `name` currently. /// /// - name: Sort by `displayName` + /// - relation: Sort by relationship. public enum Sort: String { /// Sort by `displayName` case name + + /// Sort by relationship between current user and friend. Usually, the more messages + /// the user sent to a friend recently, the higher that friend is sorted. + case relation } public init(sort: Sort? = nil, pageToken: String? = nil) { @@ -40,9 +49,6 @@ public struct GetFriendsRequest: Request { self.sort = sort } - let sort: Sort? - let pageToken: String? - public let method: HTTPMethod = .get public let path = "/graph/v2/friends" public let authentication: AuthenticateMethod = .token @@ -68,3 +74,11 @@ public struct GetFriendsRequest: Request { public let pageToken: String? } } + +extension GetFriendsRequest: SortParameterRequest { + var sortParameter: String? { return sort?.rawValue } +} + +extension GetFriendsRequest.Response: PaginatedResponse { + var paginatedValues: [User] { return friends } +} diff --git a/LineSDK/LineSDK/Graph/Request/GetGroupsRequest.swift b/LineSDK/LineSDK/Graph/Request/GetGroupsRequest.swift index 20f6f982..8ef900ad 100644 --- a/LineSDK/LineSDK/Graph/Request/GetGroupsRequest.swift +++ b/LineSDK/LineSDK/Graph/Request/GetGroupsRequest.swift @@ -1,13 +1,13 @@ // // GetGroupsRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -54,3 +54,7 @@ public struct GetGroupsRequest: Request { public let pageToken: String? } } + +extension GetGroupsRequest.Response: PaginatedResponse { + var paginatedValues: [Group] { return groups } +} diff --git a/LineSDK/LineSDK/Graph/Request/GetShareFriendsRequest.swift b/LineSDK/LineSDK/Graph/Request/GetShareFriendsRequest.swift new file mode 100644 index 00000000..874c0547 --- /dev/null +++ b/LineSDK/LineSDK/Graph/Request/GetShareFriendsRequest.swift @@ -0,0 +1,82 @@ +// +// GetShareFriendsRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// LINE internal use only. +/// Represents a request for a user's friends list for sharing. Returns a list of current user's friends. +/// The list will not include users who blocked external apps from getting their profile +/// information, unless they specifically authorized the app. +/// +/// `LoginPermission.oneTimeShare` is required. +/// +public struct GetShareFriendsRequest: Request { + + let sort: Sort? + let pageToken: String? + + public enum Sort: String { + /// Sort by `displayName` + case name + + /// Sort by relationship between current user and friend. Usually, the more messages + /// the user sent to a friend recently, the higher that friend is sorted. + case relation + } + + public init(sort: Sort? = nil, pageToken: String? = nil) { + self.pageToken = pageToken + self.sort = sort + } + + public let method: HTTPMethod = .get + public let path = "/graph/v2/ots/friends" + public let authentication: AuthenticateMethod = .token + + public var parameters: [String : Any]? { + var param: [String : Any] = [:] + if let sort = sort { + param["sort"] = sort + } + if let pageToken = pageToken { + param["pageToken"] = pageToken + } + return param + } + + public struct Response: Decodable { + + /// An array of `User` of current user's friends. + public let friends: [User] + + /// If there are more objects in the subsequent pages, use this value as the index in the next page request. + /// This field is omitted when there is no more objects in subsequent pages. + public let pageToken: String? + } +} + +extension GetShareFriendsRequest: SortParameterRequest { + var sortParameter: String? { return sort?.rawValue } +} + +extension GetShareFriendsRequest.Response: PaginatedResponse { + var paginatedValues: [User] { return friends } +} diff --git a/LineSDK/LineSDK/Graph/Request/GetShareGroupsRequest.swift b/LineSDK/LineSDK/Graph/Request/GetShareGroupsRequest.swift new file mode 100644 index 00000000..e4d35180 --- /dev/null +++ b/LineSDK/LineSDK/Graph/Request/GetShareGroupsRequest.swift @@ -0,0 +1,62 @@ +// +// GetShareGroupsRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// LINE internal use only. +/// Represents the request of returning a list of groups that the user belongs to. +/// +/// `LoginPermission.oneTimeShare` is required. +/// +public struct GetShareGroupsRequest: Request { + + public init(pageToken: String? = nil) { + self.pageToken = pageToken + } + + let pageToken: String? + + public let method: HTTPMethod = .get + public let path = "/graph/v2/ots/groups" + public let authentication: AuthenticateMethod = .token + + public var parameters: [String : Any]? { + var param: [String : Any] = [:] + if let pageToken = pageToken { + param["pageToken"] = pageToken + } + return param + } + + public struct Response: Decodable { + + /// An array of `Group` that the user belongs to. + public let groups: [Group] + + /// If there are more objects in the subsequent pages, use this value as the index in the next page request. + /// This field is omitted when there is no more objects in subsequent pages. + public let pageToken: String? + } +} + +extension GetShareGroupsRequest.Response: PaginatedResponse { + var paginatedValues: [Group] { return groups } +} diff --git a/LineSDK/LineSDK/Info.plist b/LineSDK/LineSDK/Info.plist index dd517f6b..b98597ca 100644 --- a/LineSDK/LineSDK/Info.plist +++ b/LineSDK/LineSDK/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.0.0 + 5.11.2 CFBundleVersion - 402 + 1208 NSPrincipalClass diff --git a/LineSDK/LineSDK/LineSDKUI/AuthorizationStatus.swift b/LineSDK/LineSDK/LineSDKUI/AuthorizationStatus.swift new file mode 100644 index 00000000..922d4b3a --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/AuthorizationStatus.swift @@ -0,0 +1,48 @@ +// +// AuthorizationStatus.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// Represents the authorization status for a certain action. +/// Before creating and presenting a UI in LINE SDK, we strongly recommend checking whether your app +/// has a valid token and the necessary permissions. +/// +/// A local authorization status checking API returns an `AuthorizationStatus` value to indicate the +/// current authorization status for giving action. +/// +/// - lackOfToken: There is no valid token in the local token store. The user hasn't logged in and authorized +/// your app yet. +/// - lackOfPermissions: There is a valid token, but it doesn't contain the necessary permissions. +/// The associated value is an array of `LoginPermission`, containing all lacking permissions. +/// - authorized: The token exists locally and contains the necessary permissions. +/// +public enum AuthorizationStatus { + + /// There is no valid token in the local token store. The user hasn't logged in and authorized your app yet. + case lackOfToken + + /// There is a valid token, but it doesn't contain the necessary permissions for sharing a message. + /// The associated value is an array of `LoginPermission`, containing all lacking permissions. + case lackOfPermissions(Set) + + /// The token exists locally and contains the necessary permissions to share messages. + case authorized +} diff --git a/LineSDK/LineSDK/LineSDKUI/Base/StyleNavigationController.swift b/LineSDK/LineSDK/LineSDKUI/Base/StyleNavigationController.swift new file mode 100644 index 00000000..4454f7fd --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/Base/StyleNavigationController.swift @@ -0,0 +1,64 @@ +// +// LineSDKNavigationController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +open class StyleNavigationController: UINavigationController { + enum Design { + static var navigationBarTintColor: UIColor { + return .compatibleColor(light: 0x283145, dark: 0x161B26) + } + static var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } + static var navigationBarTextColor: UIColor { return .white } + } + + /// The bar tint color of the navigation bar. + public var navigationBarTintColor = Design.navigationBarTintColor { + didSet { updateNavigationStyles() } + } + + /// The color of text, including navigation bar title and bar button text, on the navigation bar. + public var navigationBarTextColor = Design.navigationBarTextColor { + didSet { updateNavigationStyles() } + } + + /// The preferred status bar style of this navigation controller. + public var statusBarStyle = Design.preferredStatusBarStyle { + didSet { updateNavigationStyles() } + } + + open override func viewDidLoad() { + super.viewDidLoad() + updateNavigationStyles() + } + + private func updateNavigationStyles() { + navigationBar.shadowImage = UIImage() + navigationBar.barTintColor = navigationBarTintColor + navigationBar.tintColor = navigationBarTextColor + navigationBar.titleTextAttributes = [.foregroundColor: navigationBarTextColor] + } + + /// :nodoc: + open override var preferredStatusBarStyle: UIStatusBarStyle { + return statusBarStyle + } +} diff --git a/LineSDK/LineSDK/Login/LoginButton.swift b/LineSDK/LineSDK/LineSDKUI/LoginButton.swift similarity index 72% rename from LineSDK/LineSDK/Login/LoginButton.swift rename to LineSDK/LineSDK/LineSDKUI/LoginButton.swift index 52b2ab51..5e404f1a 100644 --- a/LineSDK/LineSDK/Login/LoginButton.swift +++ b/LineSDK/LineSDK/LineSDKUI/LoginButton.swift @@ -1,13 +1,13 @@ // // LoginButton.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -23,27 +23,47 @@ import UIKit /// Defines methods that allow you to handle different login statuses if you use the predefined LINE Login /// button by using the `LoginButton` class. -public protocol LoginButtonDelegate: class { +public protocol LoginButtonDelegate: AnyObject { /// Called after the login action is started. Since LINE Login is an asynchronous operation, you might /// want to show an indicator or another visual effect to prevent the user from taking other actions. func loginButtonDidStartLogin(_ button: LoginButton) - /// Called if the login action succeeded. + /// Called if the login action succeeds. /// /// - Parameters: - /// - button: The button which is used to start the login action. + /// - button: The button used to start the login action. /// - loginResult: The successful login result. func loginButton(_ button: LoginButton, didSucceedLogin loginResult: LoginResult) - /// Called if the login action failed. + /// Called if the login action fails. /// /// - Parameters: - /// - button: The button which is used to start the login action. + /// - button: The button used to start the login action. + /// - error: The strong typed `LineSDKError` of the failed login. + func loginButton(_ button: LoginButton, didFailLogin error: LineSDKError) + + /// - Warning: Deprecated. Use the same delegate method which receives `LineSDKError` instead. + /// It provides a strongly typed and consistent error for the login failure. + /// + /// Called if the login action fails. + /// + /// - Parameters: + /// - button: The button used to start the login action. /// - error: The error of the failed login. func loginButton(_ button: LoginButton, didFailLogin error: Error) } +/// :nodoc: +public extension LoginButtonDelegate { + func loginButtonDidStartLogin(_ button: LoginButton) { } + func loginButton(_ button: LoginButton, didSucceedLogin loginResult: LoginResult) { } + func loginButton(_ button: LoginButton, didFailLogin error: LineSDKError) { + loginButton(button, didFailLogin: error as Error) + } + func loginButton(_ button: LoginButton, didFailLogin error: Error) { } +} + /// Represents a login button which executes the login function when the user taps the button. /// /// - Note: @@ -113,9 +133,9 @@ open class LoginButton: UIButton { /// The default value is `[.profile]`. public var permissions: Set = [.profile] - /// Represents a set of options. - /// The default value is empty. - public var options: LoginManagerOptions = [] + /// Represents the parameters used during login. + /// The default value is `nil`. + public var parameters: LoginManager.Parameters = .init() /// The size of the login button. The default value is `normal`. public var buttonSize: ButtonSize = .normal { @@ -141,6 +161,7 @@ open class LoginButton: UIButton { setup() } + /// Creates a predefined LINE Login button. public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() @@ -151,13 +172,13 @@ open class LoginButton: UIButton { // set accessibility label for sample UI test accessibilityLabel = "login.button" - titleLabel?.font = UIFont(name: "Helvetica-Bold", size: 11) + titleLabel?.font = .boldSystemFont(ofSize: 11) titleLabel?.textAlignment = .center setTitleColor(.white, for: .normal) // Without animation to prevent blinking on first setup. UIView.performWithoutAnimation { - buttonText = NSLocalizedString("linesdk.login.button.login", bundle: .frameworkBundle, comment: "") + buttonText = Localization.string("linesdk.login.button.login") buttonSize = .normal } @@ -167,30 +188,31 @@ open class LoginButton: UIButton { // This method is called when the style of `LoginButton` is changed. // It will update the appearance of button to new style you set. func updateButtonStyle() { - let bundle = Bundle(for: LoginButton.self) let imagesPairs: [(String, UIControl.State)] switch buttonSize { case .small: imagesPairs = [ - ("small_btn_login_base", .normal), - ("small_btn_login_press", .highlighted), + ("small_btn_login_base", .normal), + ("small_btn_login_press", .highlighted), ("small_btn_login_disable", .disabled)] case .normal: imagesPairs = [ - ("normal_btn_login_base", .normal), - ("normal_btn_login_press", .highlighted), + ("normal_btn_login_base", .normal), + ("normal_btn_login_press", .highlighted), ("normal_btn_login_disable", .disabled)] } imagesPairs.forEach { (imageName, state) in - setBackgroundImage(UIImage(named: imageName, in: bundle, compatibleWith: nil), for: state) + setBackgroundImage(UIImage(bundleNamed: imageName), for: state) } - + titleEdgeInsets = UIEdgeInsets( - top: CGFloat(buttonSize.constant.bubbleWidth / 2), - left: CGFloat(buttonSize.constant.iconWidth + buttonSize.constant.separatorWidth + buttonSize.constant.leftPadding), + top: CGFloat(buttonSize.constant.bubbleWidth / 2), + left: CGFloat(buttonSize.constant.iconWidth + + buttonSize.constant.separatorWidth + + buttonSize.constant.leftPadding), bottom: CGFloat(buttonSize.constant.bubbleWidth / 2), - right: CGFloat(buttonSize.constant.rightPadding) + right: CGFloat(buttonSize.constant.rightPadding) ) setTitle(buttonText, for: .normal) @@ -206,17 +228,12 @@ open class LoginButton: UIButton { return buttonSize.sizeForTitleSize(titleSize) } - // Executes the login action when the user taps the login button. + /// Executes the login action when the user taps the login button. @objc open func login() { - if LoginManager.shared.isAuthorizing { - // Authorizing process is ongoing so not to call login again - return - } - isUserInteractionEnabled = false LoginManager.shared.login( permissions: permissions, in: presentingViewController, - options: options + parameters: parameters ) { result in switch result { @@ -225,9 +242,17 @@ open class LoginButton: UIButton { case .failure(let error): self.delegate?.loginButton(self, didFailLogin: error) } - self.isUserInteractionEnabled = true } delegate?.loginButtonDidStartLogin(self) } - + + // MARK: - Deprecated + + /// - Warning: Deprecated. Use `LoginManager.Parameters`. + /// + /// Represents a set of options. The default value is empty. + @available( + *, deprecated, + message: "Convert this value into a `LoginManager.Parameters` and use `parameters` instead.") + public var options: LoginManagerOptions = [] } diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Model/FormEntry.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Model/FormEntry.swift new file mode 100644 index 00000000..49b47cd6 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Model/FormEntry.swift @@ -0,0 +1,166 @@ +// +// FormEntry.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +// Render a certain model to compatible static table view cell. +protocol FormEntry { + var cell: UITableViewCell { get } +} + +class RoomNameText: FormEntry { + + let maximumCount = 50 + let onTextUpdated = Delegate() + + lazy var cell = render() + + private func render() -> UITableViewCell { + let cell = OpenChatRoomNameTableViewCell(style: .default, reuseIdentifier: nil) + cell.textView.maximumCount = maximumCount + cell.textView.placeholderText = Localization.string("openchat.create.room.name.placeholder") + cell.textView.onTextUpdated.delegate(on: self) { (self, result) in + self.onTextUpdated.call(result) + } + return cell + } +} + +class RoomDescriptionText: FormEntry { + + let maximumCount = 200 + + let onTextUpdated = Delegate() + let onTextHeightUpdated = Delegate() + + lazy var cell = render() + + private func render() -> UITableViewCell { + let cell = OpenChatRoomDescriptionTableViewCell(style: .default, reuseIdentifier: nil) + cell.textView.maximumCount = maximumCount + cell.textView.placeholderText = Localization.string("openchat.create.room.description.placeholder") + + cell.textView.onTextUpdated.delegate(on: self) { (self, result) in + self.onTextUpdated.call(result) + } + cell.textView.onTextViewChangeContentSize.delegate(on: self) { [unowned cell] (self, size) in + cell.updateContentHeightConstraint(size.height) + self.onTextHeightUpdated.call(size.height) + } + cell.textView.onShouldReplaceText.delegate(on: self) { (self, value) in + let (_, text) = value + return !text.containsNewline + } + return cell + } +} + +class Option: FormEntry { + var selectedOption: T { + didSet { + cell.detailTextLabel?.text = selectedOption.description + onValueChange.call(selectedOption) + } + } + let options: [T] + let title: String? + + let onValueChange = Delegate() + let onPresenting = Delegate<(), UIViewController>() + + lazy var cell = render() + + init(title: String?, options: [T], selectedOption: T? = nil) { + self.title = title + self.options = options + guard !options.isEmpty else { + Log.fatalError("No selectable options provided. Check your data source.") + } + self.selectedOption = selectedOption ?? options[0] + } + + private func render() -> UITableViewCell { + let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) + cell.textLabel?.font = .systemFont(ofSize: 15.0) + cell.textLabel?.textColor = .label + cell.textLabel?.text = title + cell.detailTextLabel?.font = .systemFont(ofSize: 15.0) + cell.detailTextLabel?.textColor = .secondaryLabel + cell.detailTextLabel?.text = selectedOption.description + cell.accessoryType = .disclosureIndicator + + cell.selectionStyle = .none + + let tap = UITapGestureRecognizer(target: self, action: #selector(tapCell)) + cell.addGestureRecognizer(tap) + + return cell + } + + @objc func tapCell() { + guard let presentingViewController = onPresenting.call() else { + return + } + let (navigation, optionsViewController) = + OptionSelectingViewController.createViewController(data: options, selected: selectedOption) + optionsViewController.onSelected.delegate(on: self) { (self, selected) in + self.selectedOption = selected + } + presentingViewController.present(navigation, animated: true) + } +} + +class Toggle: FormEntry { + + let title: String? + let initialValue: Bool + + let onValueChange = Delegate() + + lazy var cell = render() + + init(title: String?, initialValue: Bool = false) { + self.title = title + self.initialValue = initialValue + } + + private func render() -> UITableViewCell { + let cell = UITableViewCell(style: .default, reuseIdentifier: nil) + cell.selectionStyle = .none + cell.textLabel?.font = .systemFont(ofSize: 15.0) + cell.textLabel?.textColor = .label + cell.textLabel?.text = title + cell.accessoryView = searchOptionSwitch + return cell + } + + private lazy var searchOptionSwitch: UISwitch = { + let searchSwitch = UISwitch() + searchSwitch.isOn = initialValue + searchSwitch.addTarget(self, action: #selector(switchValueDidChange(_:)), for: .valueChanged) + return searchSwitch + }() + + @objc + func switchValueDidChange(_ sender: UISwitch) { + onValueChange.call(sender.isOn) + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Model/FormSection.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Model/FormSection.swift new file mode 100644 index 00000000..f7738af2 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Model/FormSection.swift @@ -0,0 +1,87 @@ +// +// FormSection.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +struct FormSection { + let formEntries: [FormEntry] + let footerText: String? + + var renderer: FormSectionRenderer! + + init(entries: [FormEntry], footerText: String?) { + self.formEntries = entries + self.footerText = footerText + + self.renderer = FormSectionRenderer(section: self) + } +} + +class FormSectionRenderer { + + let section: FormSection + var footerContentInsets = UIEdgeInsets(top: 7, left: 15, bottom: 24, right: 15) + lazy var footerView = renderFooterView() + + private lazy var footerLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 13) + label.textColor = .secondaryLabel + label.numberOfLines = 0 + label.text = self.section.footerText + return label + }() + + init(section: FormSection) { + self.section = section + } + + private func renderFooterView() -> UIView { + let footerView = UIView() + footerLabel.translatesAutoresizingMaskIntoConstraints = false + footerView.addSubview(footerLabel) + NSLayoutConstraint.activate([ + footerLabel.topAnchor + .constraint(equalTo: footerView.topAnchor, constant: footerContentInsets.top), + footerLabel.bottomAnchor + .constraint(equalTo: footerView.bottomAnchor, constant: -footerContentInsets.bottom), + footerLabel.leadingAnchor + .constraint(equalTo: footerView.safeLeadingAnchor, constant: footerContentInsets.left), + footerLabel.trailingAnchor + .constraint(equalTo: footerView.safeTrailingAnchor,constant: -footerContentInsets.right) + ]) + + return footerView + } + + func heightOfFooterView(in width: CGFloat) -> CGFloat { + + let heightMargin = footerContentInsets.top + footerContentInsets.bottom + if section.footerText == nil { + return heightMargin + } + + let widthMargin = footerContentInsets.left + footerContentInsets.right + + let size = footerLabel.sizeThatFits(.init(width: width - widthMargin, height: .greatestFiniteMagnitude)) + return size.height + heightMargin + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Model/OpenChatCategoryExtensions.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Model/OpenChatCategoryExtensions.swift new file mode 100644 index 00000000..7473d14a --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Model/OpenChatCategoryExtensions.swift @@ -0,0 +1,58 @@ +// +// OpenChatCategoryExtensions.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +extension OpenChatCategory: CustomStringConvertible { + /// :nodoc: + public var description: String { + let key: String + switch self { + case .notSelected: key = "square.create.category.notselected" + case .school: key = "square.create.category.school" + case .friend: key = "square.create.category.friend" + case .company: key = "square.create.category.company" + case .organization: key = "square.create.category.org" + case .region: key = "square.create.category.region" + case .baby: key = "square.create.category.baby" + case .sports: key = "square.create.category.sports" + case .game: key = "square.create.category.game" + case .book: key = "square.create.category.book" + case .movies: key = "square.create.category.movies" + case .photo: key = "square.create.category.photo" + case .art: key = "square.create.category.art" + case .animation: key = "square.create.category.ani" + case .music: key = "square.create.category.music" + case .tv: key = "square.create.category.tv" + case .celebrity: key = "square.create.category.celebrity" + case .food: key = "square.create.category.food" + case .travel: key = "square.create.category.travel" + case .pet: key = "square.create.category.pet" + case .car: key = "square.create.category.car" + case .fashion: key = "square.create.category.fashion" + case .health: key = "square.create.category.health" + case .finance: key = "square.create.category.finance" + case .study: key = "square.create.category.study" + case .etc: key = "square.create.category.etc" + } + return Localization.string(key) + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Public/OpenChatCreatingController.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Public/OpenChatCreatingController.swift new file mode 100644 index 00000000..8af18ce1 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Public/OpenChatCreatingController.swift @@ -0,0 +1,245 @@ +// +// OpenChatCreatingController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +/// A controller which manages open chat creating operations. +/// +/// It checks whether the Open Chat user term has been accepted by current user. If accepted, LINE SDK shows a standard +/// open chat creating interface to collect information from user input, then try to create the open chat room based on +/// them. Otherwise, the user is prompted to agree the term first before an open chat room can be created. +/// +/// It is encouraged to call `OpenChatCreatingController.localAuthorizationStatusForCreatingOpenChat()` first and check +/// the authorization status to make sure your user has authorized you to create an open chat. Then call +/// `loadAndPresent(in:presentedHandler:)` to show the built-in UI to collect user information and create the open chat +/// room. +/// +/// To get the result of the creating controller or have more control of the behaviors, implement methods in +/// `OpenChatCreatingControllerDelegate` and set `delegate` property of an `OpenChatCreatingController` instance. +/// +/// - Note: +/// The class is intended to be used as-is and to provide a default open chat creating experience across all LINE and +/// LINE SDK integrations. Users expect a consistent UI and interaction across different apps when using the Open Chat +/// features. But if it's so important for you to provide a fully customized sharing interaction, you can still use the +/// related APIs to create your own UIs. +/// +public class OpenChatCreatingController { + + /// The delegate object of this open chat creating controller. + /// + /// The delegate receives events when user encounters an error during open chat creating, user cancels the creating, + /// or the creating finishes successfully. You can choose to implement one or more methods to offer your users a + /// better experience when an event happens. + /// + /// For information about the methods you can implement for your delegate object, + /// see `OpenChatCreatingControllerDelegate`. + /// + public weak var delegate: OpenChatCreatingControllerDelegate? + + /// The suggested category shows as the default category when user creates a new open chat room. + /// + /// - Note: + /// Users can select a category in UI from a predefined list of `OpenChatCategory`s. It determines which category + /// the created room should belong to. The `suggestedCategory` value will be show as the selected state when the + /// user opens open chat creating UI. + /// + /// It does not prevent users from selecting another category from the list. + public var suggestedCategory: OpenChatCategory = .notSelected + + /// Creates a `OpenChatCreatingController` with default behavior. Always use this initializer to create an + /// `OpenChatCreatingController` instance. + public init() { } + + /// Loads the user term agreement status and shows the open chat room UI if possible. + /// - Parameters: + /// - viewController: A presenting view controller from which the open chat creating view controller should be + /// presented from. Normally, it should be your current top-most view controller which takes + /// responsibility of user interaction. + /// - handler: A block called when the open chat creating view controller presenting action is done with a result. + /// + /// - Note: + /// If the `handler` is called with a `.failure` case, it means there is no view controller from LINE SDK + /// shown. A few reasons can cause it, such as term agreement status cannot be retrieved due to network error. + /// On the other hand, a `.success` case and its associated value means a view controller from LINE SDK is + /// presented. But it does not mean that the open chat room is created. To handle either the creating failure or + /// success case for the whole process, you need to use the methods in `OpenChatCreatingControllerDelegate`. + /// + /// For either result, it is a chance for you to remove any blocking UI you may add to your view controller, like + /// this in your view controller: + /// + /// ``` + /// self.showLoadingIndicator() + /// OpenChatCreatingController.loadAndPresent(in: self) { _ in + /// self.hideLoadingIndicator() + /// } + /// ``` + public func loadAndPresent( + in viewController: UIViewController, + presentedHandler handler: ((Result) -> Void)? = nil + ) + { + let checkTermRequest = GetOpenChatTermAgreementStatusRequest() + Session.shared.send(checkTermRequest) { result in + switch result { + case .success(let response): + if response.agreed { + self.presentCreatingViewController(in: viewController, handler: handler) + } else { + let shouldPreventAlert = self.delegate?.openChatCreatingController( + self, shouldPreventUserTermAlertFrom: viewController) + ?? false + if !shouldPreventAlert { + self.presentTermAgreementAlert(in: viewController, handler: handler) + } + } + + case .failure(let error): + handler?(.failure(error)) + } + } + } + + func presentTermAgreementAlert( + in viewController: UIViewController, + handler: ((Result) -> Void)? = nil + ) + { + let alert = UIAlertController( + title: nil, + message: Localization.string("openchat.not.agree.with.terms"), + preferredStyle: .alert + ) + + if Constant.isLINEInstalled { + alert.addAction( + .init(title: Localization.string("common.cancel"), style: .cancel) + ) + alert.addAction( + .init(title: Localization.string("open.line"), style: .default) { _ in + UIApplication.shared.openLINEApp() + } + ) + } else { + alert.addAction( + .init(title: Localization.string("common.ok"), style: .cancel) + ) + } + viewController.present(alert, animated: true) { handler?(.success(alert)) } + } + + func presentCreatingViewController( + in viewController: UIViewController, + handler: ((Result) -> Void)? + ) + { + let (navigation, roomInfoFormViewController) = OpenChatRoomInfoViewController.createViewController(self) + roomInfoFormViewController.suggestedCategory = suggestedCategory + + roomInfoFormViewController.onClose.delegate(on: self) { (self, vc) in + vc.dismiss(animated: true) { + self.delegate?.openChatCreatingControllerDidCancelCreating(self) + } + } + roomInfoFormViewController.onNext.delegate(on: self) { [unowned navigation] (self, item) in + let userInfoFormViewController = OpenChatUserProfileViewController() + + var itemCopy = item + if let cachedName = UserDefaultsValue.cachedOpenChatUserProfileName { + itemCopy.userName = cachedName + } + userInfoFormViewController.formItem = itemCopy + + userInfoFormViewController.onProfileDone.delegate(on: self) { [unowned navigation] (self, item) in + + let room = OpenChatRoomCreatingItem(form: item) + let createRoomRequest = PostOpenChatCreateRequest(room: room) + + let indicator = LoadingIndicator.add(to: navigation.view) + Session.shared.send(createRoomRequest) { result in + indicator.remove() + switch result { + case .success(let response): + UserDefaultsValue.cachedOpenChatUserProfileName = room.creatorDisplayName + navigation.dismiss(animated: true) { + self.delegate?.openChatCreatingController( + self, didCreateChatRoom: response, withCreatingItem: room + ) + } + case .failure(let error): + self.delegate?.openChatCreatingController( + self, + didFailWithError: error, + withCreatingItem: room, + presentingViewController: navigation + ) + } + } + } + + navigation.pushViewController(userInfoFormViewController, animated: true) + } + + navigation.modalPresentationStyle = .fullScreen + + delegate?.openChatCreatingController(self, willPresentCreatingNavigationController: navigation) + viewController.present(navigation, animated: true) { handler?(.success(navigation)) } + } +} + +extension OpenChatCreatingController { + + /// Gets the local authorization status for creating an open chat room. + /// + /// - Returns: The local authorization status based on the currently stored token and the permissions specified + /// in that token. + /// + /// - Note: + /// If the return value is `.authorized`, you can present a `OpenChatCreatingController` instance for creating a + /// room. But `.authorized` status doesn't necessarily mean the creating would succeed; there may be problems with + /// the token or permissions. + /// + /// The token status is stored locally and may not have been synchronized with the server-side status. + /// The token may have expired or been revoked by the server or via another client. + /// + /// To get the correct result about creating behavior, specify `OpenChatCreatingController.delegate` and implement + /// the methods in `OpenChatCreatingControllerDelegate`. + /// + public static func localAuthorizationStatusForCreatingOpenChat() -> AuthorizationStatus { + guard let token = AccessTokenStore.shared.current else { + return .lackOfToken + } + + return localAuthorizationStatusForOpenChat(permissions: token.permissions) + } + + static func localAuthorizationStatusForOpenChat(permissions: [LoginPermission]) + -> AuthorizationStatus + { + let lackPermissions = Set([.openChatTermStatus, .openChatRoomCreateAndJoin]).filter { + !permissions.contains($0) + } + + guard lackPermissions.isEmpty else { + return .lackOfPermissions(lackPermissions) + } + return .authorized + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Public/OpenChatCreatingControllerDelegate.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Public/OpenChatCreatingControllerDelegate.swift new file mode 100644 index 00000000..ab30e2c3 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/Public/OpenChatCreatingControllerDelegate.swift @@ -0,0 +1,144 @@ +// +// OpenChatCreatingControllerDelegate.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +/** + A set of methods that your delegate object could implement to receive open chat controller events when user interacts + with the interface. + + The methods of this protocol notify your delegate when an event happens in the owner `OpenChatCreatingController`. + Although specifying a delegate object for `OpenChatCreatingController` isn't strictly required, we strongly recommend + that you do so. + + Without implementing the delegate methods, you can't receive information about events like network failure, user + cancellation or open chat creating done. + */ +public protocol OpenChatCreatingControllerDelegate: AnyObject { + + /// Tells the delegate that the new open chat room is created successfully. + /// - Parameters: + /// - controller: The controller object for this event. + /// - room: Information of the created chat room. + /// - item: The basic setting of the room when creating. + func openChatCreatingController( + _ controller: OpenChatCreatingController, + didCreateChatRoom room: OpenChatRoomInfo, + withCreatingItem item: OpenChatRoomCreatingItem + ) + + /// Tells the delegate that an error happens during the room creation request. + /// - Parameters: + /// - controller: The controller object for this event. + /// - error: A value containing the details of the error. + /// - item: The basic setting of the room when creating. + /// - presentingViewController: The view controller which presents the current room creating view controller. + /// Present your error handling UI with this view controller, if needed. + /// + /// - Note: + /// This delegate method will only be called during the creation operation, after the `OpenChatCreatingController` + /// collected all necessary open chat room information from user input. The collected information is in the + /// `item` parameter. + /// + /// If a user term related error happens before the LINE SDK has a chance to collect user input, another delegate + /// method, `openChatCreatingController(_:didEncounterUserAgreementError:presentingViewController:)` will be called. + /// + func openChatCreatingController( + _ controller: OpenChatCreatingController, + didFailWithError error: LineSDKError, + withCreatingItem item: OpenChatRoomCreatingItem, + presentingViewController: UIViewController + ) + + /// Tells the delegate that an error happens when checking the user agreement status for open chat. + /// - Parameters: + /// - controller: The controller object for this event. + /// - error: A value containing the details of the error. + /// - presentingViewController: The view controller which presents the current room creating view controller. + /// Present your error handling UI with this view controller, if needed. + /// - Returns: + /// A flag indicates whether LINE SDK should prevent displaying a default alert when the user agreement is not + /// accepted yet. + /// + /// - Note: + /// To create an open chat room, the user must accept the user agreement term of Open Chat. An + /// `OpenChatCreatingController` will check the agreement status and determine whether the user already agreed with + /// it. + /// + /// This delegate method will only be called during checking user agreement status before the user can input the + /// room information or create the room actually. + /// + /// If not implemented, a default alert will be shown to ask the user to check their agreement status, or open the + /// LINE app if it is installed, to agree the term. You can override this behavior and UI by providing your own + /// implementation of this delegate method, and return a `true` to tell LINE SDK you have handled the case. + /// + func openChatCreatingController( + _ controller: OpenChatCreatingController, + shouldPreventUserTermAlertFrom presentingViewController: UIViewController + ) -> Bool + + /// Tells the delegate that the user cancelled the open chat creating action. + /// - Parameter controller: The controller object for this event. + func openChatCreatingControllerDidCancelCreating(_ controller: OpenChatCreatingController) + + /// Tells the delegate that the open chat creating view controller is about to be presented. It is a chance that + /// you can do some customization for style of the presented view controller to match your app's UI better. + /// - Parameters: + /// - controller: The controller object for this event. + /// - navigationController: The `OpenChatCreatingNavigationController` which is about to show. + func openChatCreatingController( + _ controller: OpenChatCreatingController, + willPresentCreatingNavigationController navigationController: OpenChatCreatingNavigationController + ) +} + +public extension OpenChatCreatingControllerDelegate { + func openChatCreatingController( + _ controller: OpenChatCreatingController, + didCreateChatRoom room: OpenChatRoomInfo, + withCreatingItem item: OpenChatRoomCreatingItem + ) + {} + + func openChatCreatingController( + _ controller: OpenChatCreatingController, + didFailWithError error: LineSDKError, + withCreatingItem item: OpenChatRoomCreatingItem, + presentingViewController: UIViewController + ) + {} + + func openChatCreatingController( + _ controller: OpenChatCreatingController, + shouldPreventUserTermAlertFrom presentingViewController: UIViewController + ) -> Bool + { + return false + } + + func openChatCreatingControllerDidCancelCreating(_ controller: OpenChatCreatingController) {} + + func openChatCreatingController( + _ controller: OpenChatCreatingController, + willPresentCreatingNavigationController navigationController: OpenChatCreatingNavigationController + ) + {} +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/CountLimitedTextView.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/CountLimitedTextView.swift new file mode 100644 index 00000000..235e76de --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/CountLimitedTextView.swift @@ -0,0 +1,245 @@ +// +// CountLimitedTextView.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +protocol CountLimitedTextViewStyle { + var font: UIFont { get } + var textColor: UIColor { get } + var placeholderFont: UIFont { get } + var placeholderColor: UIColor { get } + var textCountLabelFont: UIFont { get } + var textCountLabelColor: UIColor { get } + + var showCountLimitLabel: Bool { get } + var showUnderBorderLine: Bool { get } +} + +extension CountLimitedTextViewStyle { + var showCountLimitLabel: Bool { return true } + var showUnderBorderLine: Bool { return false } +} + +class CountLimitedTextView: UIView { + + var style: CountLimitedTextViewStyle { + didSet { + textView.font = style.font + textView.textColor = style.textColor + + placeholderLabel.font = style.placeholderFont + placeholderLabel.textColor = style.placeholderColor + + textCountLabel.font = style.textCountLabelFont + textCountLabel.textColor = style.textCountLabelColor + + layoutIfNeeded() + } + } + + let onTextUpdated = Delegate() + let onTextViewChangeContentSize = Delegate() + let onTextCountLimitReached = Delegate<(), Void>() + + let onShouldReplaceText = Delegate<(NSRange, String), Bool>() + + var maximumTextContentHeight: CGFloat? + + var placeholderText: String? { + didSet { placeholderLabel.text = placeholderText } + } + + var maximumCount: Int? = nil { + didSet { textViewDidChange(textView) } + } + + var text: String { + get { return textView.text } + set { + textView.text = newValue + textViewDidChange(textView) + } + } + + private(set) lazy var textView: VerticallyCenteredTextView = { + let textView = VerticallyCenteredTextView() + textView.backgroundColor = .clear + textView.alwaysBounceVertical = false + textView.alwaysBounceHorizontal = false + textView.textColor = style.textColor + textView.font = style.font + textView.layer.borderWidth = 0 + textView.textContainerInset = .zero + textView.delegate = self + return textView + }() + + private(set) lazy var placeholderLabel: UILabel = { + let label = UILabel() + label.font = style.placeholderFont + label.textColor = style.placeholderColor + return label + }() + + private(set) lazy var clearButton: UIButton = { + let button = UIButton(type: .custom) + button.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) + button.setImage(UIImage(bundleNamed: "setting_icon_delete_normal"), for: .normal) + button.isHidden = true + button.addTarget(self, action: #selector(clearText), for: .touchUpInside) + return button + }() + + private(set) lazy var textCountLabel: UILabel = { + let label = UILabel() + label.font = style.textCountLabelFont + label.textColor = style.textCountLabelColor + return label + }() + + private(set) lazy var underline: UIView? = { + if self.style.showUnderBorderLine { + let line = UIView() + line.backgroundColor = style.placeholderColor + return line + } else { + return nil + } + }() + + init(style: CountLimitedTextViewStyle) { + self.style = style + super.init(frame: .zero) + setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setup() { + setupSubviews() + setupLayouts() + + } + + private func setupSubviews() { + addSubview(textView) + addSubview(placeholderLabel) + addSubview(clearButton) + addSubview(textCountLabel) + + if let underline = underline { + addSubview(underline) + } + } + + private func setupLayouts() { + // Text View + textView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + textView.leadingAnchor.constraint(equalTo: leadingAnchor), + textView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -39), + textView.topAnchor.constraint(equalTo: topAnchor, constant: 20), + textView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20), + ]) + + // Count Label + textCountLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + textCountLabel.trailingAnchor.constraint(equalTo: trailingAnchor), + textCountLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8) + ]) + + // Clear Button + clearButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + clearButton.centerYAnchor.constraint(equalTo: centerYAnchor), + clearButton.trailingAnchor + .constraint(equalTo: trailingAnchor, constant: -13 + clearButton.contentEdgeInsets.right) + ]) + + // Placeholder + placeholderLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + placeholderLabel.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: 5), + placeholderLabel.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: 0), + placeholderLabel.centerYAnchor.constraint(equalTo: centerYAnchor) + ]) + + if let underline = underline { + underline.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + underline.leadingAnchor.constraint(equalTo: placeholderLabel.leadingAnchor), + underline.trailingAnchor.constraint(equalTo: clearButton.trailingAnchor), + underline.topAnchor.constraint(equalTo: textView.bottomAnchor), + underline.heightAnchor.constraint(equalToConstant: 1) + ]) + } + } + + @objc private func clearText() { + text = "" + textView.sizeToFit() + } + + private func validateString(_ text: String) { + guard let maximumCount = maximumCount else { + textCountLabel.isHidden = true + return + } + textCountLabel.isHidden = !style.showCountLimitLabel || false + + let trimmed = text.prefixNormalized.trimming(upper: maximumCount) + let textCount = text.count + if trimmed.count == text.count { + // Nothing is trimmed + textCountLabel.text = "\(textCount)/\(maximumCount)" + } else { + textView.text = trimmed + textCountLabel.text = "\(trimmed.count)/\(maximumCount)" + + onTextCountLimitReached.call() + } + } +} + +extension CountLimitedTextView: UITextViewDelegate { + func textViewDidChange(_ textView: UITextView) { + + placeholderLabel.isHidden = !textView.text.isEmpty + clearButton.isHidden = textView.text.isEmpty + + guard textView.markedTextRange == nil else { return } + validateString(textView.text) + + onTextUpdated.call(textView.text) + + if maximumTextContentHeight == nil || textView.contentSize.height <= maximumTextContentHeight! { + textView.layoutIfNeeded() + self.onTextViewChangeContentSize.call(textView.contentSize) + } + } + + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + return onShouldReplaceText.call((range, text)) ?? true + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/OpenChatRoomDescriptionTableViewCell.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/OpenChatRoomDescriptionTableViewCell.swift new file mode 100644 index 00000000..24b6f504 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/OpenChatRoomDescriptionTableViewCell.swift @@ -0,0 +1,94 @@ +// +// OpenChatRoomDescriptionTableViewCell.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class OpenChatRoomDescriptionTableViewCell: UITableViewCell { + + struct TextViewStyle: CountLimitedTextViewStyle { + let font: UIFont = .boldSystemFont(ofSize: 15) + let textColor: UIColor = .label + let placeholderFont: UIFont = .boldSystemFont(ofSize: 15) + let placeholderColor: UIColor = .secondaryLabel + let textCountLabelFont: UIFont = .systemFont(ofSize: 12) + let textCountLabelColor: UIColor = .secondaryLabel + } + + lazy var textView: CountLimitedTextView = { + let textView = CountLimitedTextView(style: TextViewStyle()) + textView.maximumTextContentHeight = 100 + return textView + }() + + private var contentHeightConstraint: NSLayoutConstraint! + private let textViewVerticalSpacing: CGFloat = 20 + private let textViewInitialContentHeight: CGFloat = 34 + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + selectionStyle = .none + + let tap = UITapGestureRecognizer(target: self, action: #selector(tapped)) + addGestureRecognizer(tap) + + setupSubviews() + setupLayouts() + } + + private func setupSubviews() { + contentView.addSubview(textView) + } + + private func setupLayouts() { + let contentHeight = contentView.heightAnchor.constraint( + equalToConstant: textViewVerticalSpacing * 2 + textViewInitialContentHeight + ) + contentHeight.priority = .init(999) + contentHeight.isActive = true + + contentHeightConstraint = contentHeight + + textView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + textView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), + textView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -13), + textView.topAnchor.constraint(equalTo: contentView.topAnchor), + textView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ]) + } + + func updateContentHeightConstraint(_ contentHeight: CGFloat) { + let height = textViewVerticalSpacing * 2 + max(textViewInitialContentHeight, contentHeight) + contentHeightConstraint.constant = height + } + + @objc private func tapped() { + let textView = self.textView.textView + if !textView.isFirstResponder, textView.canBecomeFirstResponder { + textView.becomeFirstResponder() + } + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/OpenChatRoomNameTableViewCell.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/OpenChatRoomNameTableViewCell.swift new file mode 100644 index 00000000..772c0126 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/OpenChatRoomNameTableViewCell.swift @@ -0,0 +1,78 @@ +// +// OpenChatRoomNameTableViewCell.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class OpenChatRoomNameTableViewCell: UITableViewCell { + + struct TextViewStyle: CountLimitedTextViewStyle { + let font: UIFont = .boldSystemFont(ofSize: 18) + let textColor: UIColor = .label + let placeholderFont: UIFont = .boldSystemFont(ofSize: 18) + let placeholderColor: UIColor = .secondaryLabel + let textCountLabelFont: UIFont = .systemFont(ofSize: 12) + let textCountLabelColor: UIColor = .secondaryLabel + } + + let textView = CountLimitedTextView(style: TextViewStyle()) + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + selectionStyle = .none + + let tap = UITapGestureRecognizer(target: self, action: #selector(tapped)) + addGestureRecognizer(tap) + + setupSubviews() + setupLayouts() + } + + private func setupSubviews() { + contentView.addSubview(textView) + } + + private func setupLayouts() { + let contentHeight = contentView.heightAnchor.constraint(equalToConstant: 123) + contentHeight.priority = .init(999) + contentHeight.isActive = true + + textView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + textView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + textView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -13), + textView.topAnchor.constraint(equalTo: contentView.topAnchor), + textView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ]) + } + + @objc private func tapped() { + let textView = self.textView.textView + if !textView.isFirstResponder, textView.canBecomeFirstResponder { + textView.becomeFirstResponder() + } + } + +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/ToastView.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/ToastView.swift new file mode 100644 index 00000000..48464447 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/ToastView.swift @@ -0,0 +1,133 @@ +// +// ToastView.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class ToastView: UIView { + + let containerView: UIStackView + let padding: UIEdgeInsets + + let onDisappeared = Delegate<(), Void>() + + init(views: [UIView], padding: UIEdgeInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)) { + self.containerView = UIStackView(arrangedSubviews: views) + self.padding = padding + super.init(frame: .zero) + setup() + } + + func setup() { + containerView.axis = .vertical + containerView.distribution = .fill + containerView.alignment = .center + + containerView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(containerView) + NSLayoutConstraint.activate([ + containerView.topAnchor.constraint(equalTo: topAnchor, constant: padding.top), + containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding.left), + containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding.bottom), + containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding.right) + ]) + + layer.cornerRadius = 5 + clipsToBounds = true + } + + func dismiss(fadeOut: Bool) { + if fadeOut { + UIView.animate( + withDuration: 0.25, + animations: { + self.alpha = 0 + }, + completion: { _ in + self.removeFromSuperview() + self.onDisappeared.call() + } + ) + } else { + removeFromSuperview() + onDisappeared.call() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class ToastLabelView: ToastView { + init(label: UILabel, padding: UIEdgeInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)) { + super.init(views: [label], padding: padding) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension ToastView { + @discardableResult + static func show( + text: String, + in view: UIView, + fadeIn: Bool = true, + fadeOut: Bool = true, + duration: TimeInterval = 2.0 + ) -> ToastLabelView { + + let label = UILabel() + label.font = .systemFont(ofSize: 14) + label.textColor = UIColor.white.withAlphaComponent(0.85) + label.text = text + label.numberOfLines = 0 + NSLayoutConstraint.activate([ + label.widthAnchor.constraint(lessThanOrEqualToConstant: view.frame.width * 0.5) + ]) + + let toast = ToastLabelView(label: label) + toast.backgroundColor = UIColor.black.withAlphaComponent(0.85) + + view.addSubview(toast) + + toast.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + toast.centerXAnchor.constraint(equalTo: view.centerXAnchor), + toast.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + + if fadeIn { + toast.alpha = 0 + UIView.animate(withDuration: 0.25) { + toast.alpha = 1 + } + } + + let showTime = max(duration, 0) + DispatchQueue.main.asyncAfter(deadline: .now() + showTime) { + toast.dismiss(fadeOut: fadeOut) + } + + return toast + } +} diff --git a/LineSDK/LineSDKObjC/LineSDKObjC.h b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/VerticallyCenteredTextView.swift similarity index 57% rename from LineSDK/LineSDKObjC/LineSDKObjC.h rename to LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/VerticallyCenteredTextView.swift index 7968d9ac..203d659a 100644 --- a/LineSDK/LineSDKObjC/LineSDKObjC.h +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/View/VerticallyCenteredTextView.swift @@ -1,13 +1,13 @@ // -// LineSDKObjC.h +// VerticallyCenteredTextView.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,14 +19,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#import - -//! Project version number for LineSDKObjC. -FOUNDATION_EXPORT double LineSDKObjCVersionNumber; - -//! Project version string for LineSDKObjC. -FOUNDATION_EXPORT const unsigned char LineSDKObjCVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import +import UIKit +class VerticallyCenteredTextView: UITextView { + override var contentSize: CGSize { + didSet { + let topCorrection = (bounds.size.height - contentSize.height * zoomScale) / 2.0 + contentInset = UIEdgeInsets(top: max(0, topCorrection), left: 0, bottom: 0, right: 0) + } + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OpenChatCreatingNavigationController.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OpenChatCreatingNavigationController.swift new file mode 100644 index 00000000..00e194e4 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OpenChatCreatingNavigationController.swift @@ -0,0 +1,27 @@ +// +// OpenChatCreatingNavigationController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +public class OpenChatCreatingNavigationController: StyleNavigationController { + // Hold `OpenChatCreatingController` to prevent unexpected release. + var controller: OpenChatCreatingController? +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OpenChatRoomInfoViewController.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OpenChatRoomInfoViewController.swift new file mode 100644 index 00000000..b0f6ee8a --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OpenChatRoomInfoViewController.swift @@ -0,0 +1,199 @@ +// +// OpenChatRoomInfoViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class OpenChatRoomInfoViewController: UITableViewController { + + enum Design { + static var backgroundColor: UIColor { return .systemGroupedBackground } + } + + let onClose = Delegate() + let onNext = Delegate() + + /// The pre-selected category in the list. + var suggestedCategory: OpenChatCategory = .notSelected + + var formItem = OpenChatCreatingFormItem() { + didSet { + updateViews() + } + } + + // MARK: - Setting entries + private lazy var roomName: RoomNameText = { + let entry = RoomNameText() + entry.onTextUpdated.delegate(on: self) { (self, text) in + self.formItem.roomName = text + } + return entry + }() + + private lazy var roomDescription: RoomDescriptionText = { + let entry = RoomDescriptionText() + entry.onTextUpdated.delegate(on: self) { (self, text) in + self.formItem.roomDescription = text + } + entry.onTextHeightUpdated.delegate(on: self) { (self, _) in + // This updates the cell height in the table view. + self.tableView.beginUpdates() + self.tableView.endUpdates() + } + return entry + }() + + private lazy var category: Option = { + let entry = Option( + title: Localization.string("openchat.create.room.category"), + options: OpenChatCategory.allCases, + selectedOption: suggestedCategory + ) + entry.onValueChange.delegate(on: self) { (self, selected) in + self.formItem.category = selected + } + entry.onPresenting.delegate(on: self) { (self, _) in + return self + } + return entry + }() + + private lazy var enableSearch: Toggle = { + let entry = Toggle( + title: Localization.string("openchat.create.room.search"), + initialValue: formItem.allowSearch + ) + entry.onValueChange.delegate(on: self) { (self, allowSearch) in + self.formItem.allowSearch = allowSearch + } + return entry + }() + + private lazy var sections: [FormSection] = [ + FormSection( + entries: [roomName], + footerText: nil), + FormSection( + entries: [roomDescription], + footerText: Localization.string("openchat.create.room.description.guide")), + FormSection( + entries: [category], + footerText: Localization.string("openchat.create.room.category.guide")), + FormSection( + entries: [enableSearch], + footerText: Localization.string("openchat.create.room.search.guide")), + ] + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = Design.backgroundColor + setupNavigationBar() + setupTableView() + + updateViews() + } + + private func setupTableView() { + tableView.keyboardDismissMode = .interactive + } + + private func setupNavigationBar() { + + title = Localization.string("openchat.create.room.title") + + navigationItem.backBarButtonItem = UIBarButtonItem( + title: "", + style: .plain, + target: nil, + action: nil) + navigationItem.leftBarButtonItem = UIBarButtonItem( + image: UIImage(bundleNamed: "navi_icon_close"), + style: .plain, + target: self, + action: #selector(closeForm) + ) + navigationItem.rightBarButtonItem = UIBarButtonItem( + title: Localization.string("common.next"), + style: .plain, + target: self, + action: #selector(nextPage) + ) + } + + @objc private func closeForm() { + view.endEditing(true) + onClose.call(self) + } + + @objc private func nextPage() { + view.endEditing(true) + + formItem.normalize() + onNext.call(formItem) + } + + private func updateViews() { + navigationItem.rightBarButtonItem?.isEnabled = !formItem.roomName.isEmpty + } +} + +// MARK: - Table view related methods +extension OpenChatRoomInfoViewController { + override func numberOfSections(in tableView: UITableView) -> Int { + return sections.count + } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return sections[section].formEntries.count + } + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return sections[indexPath.section].formEntries[indexPath.row].cell + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return nil + } + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return CGFloat.leastNormalMagnitude + } + + override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + return sections[section].renderer.footerView + } + + override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return sections[section].renderer.heightOfFooterView(in: tableView.frame.width) + } +} + +// MARK: - Factory +extension OpenChatRoomInfoViewController { + static func createViewController( + _ controller: OpenChatCreatingController + ) -> (OpenChatCreatingNavigationController, OpenChatRoomInfoViewController) + { + let viewController = OpenChatRoomInfoViewController(style: .grouped) + let navigation = OpenChatCreatingNavigationController(rootViewController: viewController) + navigation.controller = controller + return (navigation, viewController) + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OpenChatUserProfileViewController.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OpenChatUserProfileViewController.swift new file mode 100644 index 00000000..2e76eccf --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OpenChatUserProfileViewController.swift @@ -0,0 +1,221 @@ +// +// OpenChatUserProfileViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class OpenChatUserProfileViewController: UIViewController { + + enum Design { + static var backgroundColor: UIColor { return .systemGroupedBackground } + } + + struct TextViewStyle: CountLimitedTextViewStyle { + let font: UIFont = .systemFont(ofSize: 22, weight: .semibold) + let textColor: UIColor = .label + let placeholderFont: UIFont = .systemFont(ofSize: 22, weight: .semibold) + let placeholderColor = UIColor.secondaryLabel.withAlphaComponent(0.7) + let textCountLabelFont: UIFont = .systemFont(ofSize: 12) + let textCountLabelColor: UIColor = .secondaryLabel + let showCountLimitLabel = false + let showUnderBorderLine = true + } + + var formItem: OpenChatCreatingFormItem! { + didSet { + updateViews() + } + } + + let onProfileDone = Delegate() + + private var containerBottomConstraint: NSLayoutConstraint? + private var textViewHeightConstraint: NSLayoutConstraint? + + private let textViewVerticalSpacing: CGFloat = 20 + private let textViewInitialContentHeight: CGFloat = 43 + + // MARK: - Subviews + private lazy var scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.alwaysBounceVertical = true + scrollView.keyboardDismissMode = .interactive + return scrollView + }() + + private lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [nameTextView, nickNameTipLabel]) + + stackView.alignment = .center + stackView.axis = .vertical + stackView.distribution = .equalSpacing + stackView.spacing = 5 + stackView.backgroundColor = UIColor.yellow.withAlphaComponent(0.4) + return stackView + }() + + private lazy var nameTextView: CountLimitedTextView = { + let textView = CountLimitedTextView(style: TextViewStyle()) + textView.placeholderText = Localization.string("openchat.create.profile.input.placeholder") + textView.maximumCount = 20 + textView.text = self.formItem.userName + + textView.onTextUpdated.delegate(on: self) { (self, name) in + self.formItem.userName = name + } + textView.onTextViewChangeContentSize.delegate(on: self) { (self, size) in + self.textViewHeightConstraint?.constant = + self.textViewVerticalSpacing * 2 + max(size.height, self.textViewInitialContentHeight) + } + textView.onTextCountLimitReached.delegate(on: self) { (self, _) in + let alreadyShown = self.textCountLimitationToast != nil + self.textCountLimitationToast?.dismiss(fadeOut: false) + self.textCountLimitationToast = ToastView.show( + text: Localization.string("openchat.create.profile.input.max.count"), + in: self.view, + fadeIn: !alreadyShown + ) + } + textView.onShouldReplaceText.delegate(on: self) { (self, value) in + let (_, text) = value + if text.containsNewline { + self.view.endEditing(false) + return false + } + return true + } + return textView + }() + + private lazy var nickNameTipLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 13) + label.textColor = UIColor.secondaryLabel.withAlphaComponent(0.7) + label.textAlignment = .center + label.numberOfLines = 0 + label.text = String(format: Localization.string("openchat.create.profile.input.guide"), formItem.roomName) + return label + }() + + // Conforming to `KeyboardObservable` + var keyboardObservers: [NotificationToken] = [] + + var contentViewBottomConstraint: NSLayoutConstraint? + + private weak var textCountLimitationToast: ToastView? + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = Design.backgroundColor + + setupSubviews() + setupLayouts() + setupNavigationBar() + + updateViews() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + addKeyboardObserver() + + nameTextView.textView.becomeFirstResponder() + } + + override func viewDidDisappear(_ animated: Bool) { + removeKeyboardObserver() + super.viewDidDisappear(animated) + } + + private func setupSubviews() { + view.addSubview(scrollView) + scrollView.addSubview(stackView) + } + + private func setupLayouts() { + + contentViewBottomConstraint = scrollView.bottomAnchor.constraint(equalTo: safeBottomAnchor) + + scrollView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + scrollView.topAnchor.constraint(equalTo: safeTopAnchor), + contentViewBottomConstraint! + ]) + + stackView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + stackView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor), + stackView.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor, constant: -12), + ]) + + nameTextView.translatesAutoresizingMaskIntoConstraints = false + textViewHeightConstraint = nameTextView.heightAnchor.constraint( + equalToConstant: textViewVerticalSpacing * 2 + textViewInitialContentHeight + ) + NSLayoutConstraint.activate([ + nameTextView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 32), + nameTextView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -32), + textViewHeightConstraint! + ]) + + nickNameTipLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + nickNameTipLabel.widthAnchor.constraint(equalTo: nameTextView.widthAnchor, multiplier: 0.75) + ]) + } + + private func setupNavigationBar() { + title = Localization.string("openchat.create.profile.title") + navigationItem.rightBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .done, target: self, action: #selector(profileDone) + ) + } + + private func handleKeyboardChange(_ keyboardInfo: KeyboardInfo) { + if keyboardInfo.isVisible, let endFrame = keyboardInfo.endFrame { + contentViewBottomConstraint?.constant = -endFrame.height + } else { + contentViewBottomConstraint?.constant = 0 + } + UIView.animate(withDuration: keyboardInfo.duration) { + self.view.layoutIfNeeded() + } + } + + private func updateViews() { + navigationItem.rightBarButtonItem?.isEnabled = !formItem.userName.isEmpty + } + + @objc private func profileDone() { + view.endEditing(false) + + formItem.normalize() + onProfileDone.call(formItem) + } +} + +extension OpenChatUserProfileViewController: KeyboardObservable { + func keyboardInfoWillChange(keyboardInfo: KeyboardInfo) { + handleKeyboardChange(keyboardInfo) + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OptionSelectingViewController.swift b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OptionSelectingViewController.swift new file mode 100644 index 00000000..75915e85 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/OpenChatUI/ViewControllers/OptionSelectingViewController.swift @@ -0,0 +1,94 @@ +// +// OptionSelectingViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class OptionSelectingViewController: UITableViewController { + + let onSelected = Delegate() + + private var data: [T] = [] + private var selected: T? + + private var cellResultIdentifier: String { return String(describing: OptionSelectingViewController.self) } + + override func viewDidLoad() { + super.viewDidLoad() + setupNavigationBar() + tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellResultIdentifier) + } + + private func setupNavigationBar() { + title = Localization.string("openchat.create.room.category") + navigationItem.leftBarButtonItem = UIBarButtonItem( + image: UIImage(bundleNamed: "navi_icon_close"), + style: .plain, + target: self, + action: #selector(closeCategory) + ) + } + + @objc private func closeCategory() { + dismiss(animated: true) + } + + // MARK: - Table view related methods + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return data.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: cellResultIdentifier, for: indexPath) + cell.selectionStyle = .none + cell.textLabel?.text = data[indexPath.row].description + if data[indexPath.row] == selected { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // For visual effect that the target option was selected + selected = data[indexPath.row] + tableView.reloadData() + + closeCategory() + onSelected.call(data[indexPath.row]) + } +} + +// MARK: - Factory +extension OptionSelectingViewController { + static func createViewController( + data: [T], + selected: T? + ) -> (UINavigationController, OptionSelectingViewController) + { + let optionSelecting = OptionSelectingViewController(style: .grouped) + optionSelecting.data = data + optionSelecting.selected = selected + + let navigation = StyleNavigationController(rootViewController: optionSelecting) + return (navigation, optionSelecting) + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/ResourceLoading.swift b/LineSDK/LineSDK/LineSDKUI/ResourceLoading.swift new file mode 100644 index 00000000..e00512e8 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/ResourceLoading.swift @@ -0,0 +1,73 @@ +// +// ResourceLoading.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +enum Localization { + static func string(_ key: String) -> String { + return NSLocalizedString(key, bundle: .frameworkResourceBundle, comment: "") + } +} + +extension UIImage { + + /// Creates a `UIImage` object in current framework bundle. + /// + /// - Parameters: + /// - name: The image name. + /// - trait: The traits associated with the intended environment for the image. + convenience init?(bundleNamed name: String, compatibleWith trait: UITraitCollection? = nil) { + self.init(named: name, in: .sdkBundle, compatibleWith: trait) + } +} + +extension Bundle { + static let frameworkResourceBundle: Bundle = { + guard let path = sdkBundle.path(forResource: "Resource", ofType: "bundle"), + let bundle = Bundle(path: path) else + { + Log.fatalError("SDK resource bundle cannot be found, " + + "please verify your installation is not corrupted and try to reinstall LineSDK.") + } + return bundle + }() + + #if LineSDKCocoaPods + // SDK Bundle is for CocoaPods: ( sp.resource_bundles = { 'LineSDK' => [ ... ] } ) + static let sdkBundle: Bundle = { + guard let path = Bundle.frameworkBundle.path(forResource: "LineSDK", ofType: "bundle"), + let bundle = Bundle(path: path) else + { + Log.fatalError("LineSDK.bundle cannot be found, " + + "please verify your installation is not corrupted and try to reinstall LineSDK.") + } + return bundle + }() + #elseif LineSDKXCProj + static let sdkBundle: Bundle = .frameworkBundle + #else // Swift Package Manager + static let sdkBundle: Bundle = .module + #endif + + static let frameworkBundle: Bundle = { + return Bundle(for: LoginManager.self) + }() +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/Model/ColumnDataStore.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/Model/ColumnDataStore.swift new file mode 100644 index 00000000..2d00ba25 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/Model/ColumnDataStore.swift @@ -0,0 +1,140 @@ +// +// ColumnDataStore.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +extension Notification.Name { + static let columnDataStoreDidAppendData = Notification.Name("com.linecorp.linesdk.columnDataStoreDidAppendData") + static let columnDataStoreDidSelect = Notification.Name("com.linecorp.linesdk.columnDataStoreDidSelect") + static let columnDataStoreDidDeselect = Notification.Name("com.linecorp.linesdk.columnDataStoreDidDeselect") +} + +extension LineSDKNotificationKey { + static let appendDataIndexRange = "appendDataIndexRange" + static let selectingIndex = "selectingIndex" + static let positionInSelected = "positionInSelected" +} + +// A column-based data structure. It makes it easier to store and interact with data in a 2D array. +class ColumnDataStore { + + struct ColumnIndex: Equatable { + let column: Int + let row: Int + } + + struct AppendingIndexRange { + let startIndex: Int + let endIndex: Int + let column: Int + } + + var selectedData: [T] { + return selectedIndexes.map { data(at: $0) } + } + + var maximumSelectedCount = 10 + + private var data: [[T]] + private(set) var selectedIndexes: [ColumnIndex] = [] + private var columnCount: Int { return data.count } + + init(columnCount: Int) { + data = .init(repeating: [], count: columnCount) + } + + func append(data appendingData: [T], to columnIndex: Int) { + var column = data(atColumn: columnIndex) + + let startIndex = column.count + + column.append(contentsOf: appendingData) + data[columnIndex] = column + + let endIndex = column.count + let indexRange = AppendingIndexRange(startIndex: startIndex, endIndex: endIndex, column: columnIndex) + + // Make sure the notification is delivered on the main thread, because it's often used to update the UI. + CallbackQueue.currentMainOrAsync.execute { + NotificationCenter.default.post( + name: .columnDataStoreDidAppendData, + object: self, + userInfo: [LineSDKNotificationKey.appendDataIndexRange: indexRange] + ) + } + } + + func data(atColumn column: Int) -> [T] { + precondition(column < columnCount, "Input index `column` is out of range. Data range: 0..<\(data.count)") + return data[column] + } + + func data(atColumn column: Int, row: Int) -> T { + return data[column][row] + } + + func data(at index: ColumnIndex) -> T { + return data(atColumn: index.column, row: index.row) + } + + func isSelected(at index: ColumnIndex) -> Bool { + return selectedIndexes.contains(index) + } + + // Return `false` if the toggle failed due to reaching `maximumSelectedCount`. + @discardableResult + func toggleSelect(atColumn columnIndex: Int, row rowIndex: Int) -> Bool { + + func notifySelectingChange(selected: Bool, targetIndex: ColumnIndex, positionInSelected: Int) { + NotificationCenter.default.post( + name: selected ? .columnDataStoreDidSelect : .columnDataStoreDidDeselect, + object: self, + userInfo: [LineSDKNotificationKey.selectingIndex: targetIndex, + LineSDKNotificationKey.positionInSelected: positionInSelected] + ) + } + + let targetIndex = ColumnIndex(column: columnIndex, row: rowIndex) + if let index = selectedIndexes.firstIndex(of: targetIndex) { + selectedIndexes.remove(at: index) + notifySelectingChange(selected: false, targetIndex: targetIndex, positionInSelected: index) + } else { + guard selectedIndexes.count < maximumSelectedCount else { + return false + } + selectedIndexes.append(targetIndex) + notifySelectingChange(selected: true, targetIndex: targetIndex, positionInSelected: selectedIndexes.count - 1) + } + + return true + } + + func indexes(atColumn column: Int, where filtered: ((T) -> Bool)) -> [ColumnIndex] { + return data(atColumn: column) + .enumerated() + .filter { _, elem in filtered(elem) } + .map { ColumnIndex(column: column, row: $0.offset) } + } + + func indexes(where filtered: ((T) -> Bool)) -> [[ColumnIndex]] { + return (0 ..< data.count).map { indexes(atColumn: $0, where: filtered) } + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/PageViewController/PageTabView.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/PageViewController/PageTabView.swift new file mode 100644 index 00000000..031987a2 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/PageViewController/PageTabView.swift @@ -0,0 +1,268 @@ +// +// PageTabView.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +protocol PageTabViewDelegate: AnyObject { + func pageTabView(_ pageTabView: PageTabView, didSelectIndex index: Int) +} + +class PageTabView: UIView { + + class TabView: UIControl { + enum Design { + static var titleColor: UIColor { return .systemGray } + static var selectedTitleColor: UIColor { return .label } + + static var titleFont: UIFont { return .systemFont(ofSize: 15) } + static var selectedTitleFont: UIFont { return .systemFont(ofSize: 15, weight: .semibold) } + static var height: CGFloat { return 45.0 } + } + + let index: Int + + let textLabel: UILabel + + init(title: String, index: Int) { + self.index = index + self.textLabel = { + let label = UILabel(frame: .zero) + label.text = title + label.textAlignment = .center + return label + }() + + super.init(frame: .zero) + isSelected = false + setupViews() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupViews() { + addSubview(textLabel) + textLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + textLabel.centerXAnchor.constraint(equalTo: centerXAnchor), + textLabel.centerYAnchor.constraint(equalTo: centerYAnchor), + textLabel.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, multiplier: 0.8) + ]) + } + + override var isSelected: Bool { + didSet { + textLabel.font = isSelected ? Design.selectedTitleFont : Design.titleFont + textLabel.textColor = isSelected ? Design.selectedTitleColor : Design.titleColor + } + } + } + + class Underline: UIView { + + enum Design { + static var height: CGFloat { return 3 } + static var widthMargin: CGFloat { return 4 } + static var color: UIColor { return .label } + } + + private let underline: UIView = { + let underline = UIView() + underline.backgroundColor = Design.color + return underline + }() + + init() { + super.init(frame: .zero) + backgroundColor = .clear + addSubview(underline) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup(centerX: CGFloat, width: CGFloat) { + underline.bounds.size = CGSize(width: width + Design.widthMargin, + height: Design.height) + underline.center = CGPoint(x: centerX, y: bounds.midY) + } + + static func preferredWidth(progress: CGFloat, titleWidths: [CGFloat]) -> CGFloat { + precondition(!titleWidths.isEmpty, "PageTabView does not accept empty titles.") + switch progress { + case _ where progress <= 0: + return titleWidths[0] + case _ where progress >= CGFloat(titleWidths.count - 1): + return titleWidths.last! + default: + return titleWidths.enumerated().reduce(0) { (res, arg) in + let (index, w) = arg + return res + w * (1 - min(1, abs(progress - CGFloat(index)))) + } + } + } + + static func preferredCenterX(progress: CGFloat, tabWidth: CGFloat, countOfTabs: CGFloat) -> CGFloat { + switch progress { + case _ where progress <= 0: + return 0.5 * tabWidth + case _ where progress >= (countOfTabs - 1): + return (countOfTabs - 0.5) * tabWidth + default: + return (0.5 + progress) * tabWidth + } + } + } + + lazy var underline = Underline() + + weak var delegate: PageTabViewDelegate? + + private(set) var selectedIndex: Int = 0 + + private let countOfTabs: Int + + private var tabCenterSpacing: CGFloat { + return bounds.width / CGFloat(countOfTabs) + } + + // Used when select index for multiple tabs. + private var nextSpacingFactor: CGFloat = 1.0 + + private var tabs: [TabView] = [] + + init(titles: [String]) { + + precondition(!titles.isEmpty, "PageTabView does not accept empty titles.") + + countOfTabs = titles.count + + super.init(frame: .zero) + + var leading = leadingAnchor + + for (i, title) in titles.enumerated() { + let tabView = TabView(title: title, index: i) + addSubview(tabView) + tabView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + tabView.leadingAnchor.constraint(equalTo: leading), + tabView.topAnchor.constraint(equalTo: topAnchor), + tabView.bottomAnchor.constraint(equalTo: bottomAnchor), + tabView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0 / CGFloat(titles.count)) + ]) + + tabView.addTarget(self, action: #selector(tabViewTouchUpInside), for: .touchUpInside) + tabs.append(tabView) + + leading = tabView.trailingAnchor + } + + addSubview(underline) + underline.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + underline.leadingAnchor.constraint(equalTo: leadingAnchor), + underline.trailingAnchor.constraint(equalTo: trailingAnchor), + underline.heightAnchor.constraint(equalToConstant: Underline.Design.height), + underline.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + + updateSelectedIndex(selectedIndex) + } + + // Select a certain index. + func selectIndex(_ index: Int) { + if selectedIndex == index { return } + nextSpacingFactor = abs(CGFloat(index) - CGFloat(selectedIndex)) + updateSelectedIndex(index) + + delegate?.pageTabView(self, didSelectIndex: index) + } + + func tabIndex(from progress: CGFloat) -> Int { + return min(Int(progress * CGFloat(tabs.count)), tabs.count - 1) + } + + func updateSelectedIndexForCurrentProgress() { + updateSelectedIndex(tabIndex(from: currentProgress)) + } + + // This only update the `selectedIndex` property and update style when necessary. + func updateSelectedIndex(_ index: Int) { + selectedIndex = index + + // update tabs style + tabs.enumerated().forEach { (i, tabView) in + tabView.isSelected = (i == selectedIndex) + } + } + + func updateScrollingProgress(_ progress: CGFloat) { + normalizeProgress(progress) + + let centerX = Underline.preferredCenterX(progress: currentProgress, + tabWidth: tabs.first!.bounds.width, + countOfTabs: CGFloat(countOfTabs)) + let width = Underline.preferredWidth(progress: currentProgress, + titleWidths: tabs.map { $0.textLabel.bounds.width }) + underline.setup(centerX: centerX, width: width) + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + reset() + updateScrollingProgress(0) + layoutIfNeeded() + } + + func normalizeProgress(_ progress: CGFloat) { + // UIPageViewController resets the content offset when new page displayed. + let diff = currentProgress - progress * nextSpacingFactor - currentDiff + if abs(diff) > 0.5 { // process normally continuous + currentDiff += diff.rounded() + } + currentProgress = progress * nextSpacingFactor + currentDiff + } + + private var currentProgress: CGFloat = 0 + private var currentDiff: CGFloat = 0 + + @objc func tabViewTouchUpInside(_ sender: TabView) { + self.isUserInteractionEnabled = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.isUserInteractionEnabled = true + } + + selectIndex(sender.index) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func reset() { + nextSpacingFactor = 1.0 + currentDiff = 0 + currentProgress = CGFloat(selectedIndex) + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/PageViewController/PageViewController.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/PageViewController/PageViewController.swift new file mode 100644 index 00000000..559cde3c --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/PageViewController/PageViewController.swift @@ -0,0 +1,222 @@ +// +// PageViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class PageViewController: UIViewController { + + enum Design { + static var backgroundColor: UIColor { return .systemBackground } + } + + struct Page { + let viewController: UIViewController + let title: String + } + + let pages: [Page] + + private var pageScrollViewObserver: NSKeyValueObservation? + private var pageTabHeightConstraint: NSLayoutConstraint? + + private let pageContainerLayout = UILayoutGuide() + + private lazy var pageTabView: PageTabView = { + let pageTabView = PageTabView(titles: pages.map { $0.title }) + pageTabView.clipsToBounds = true + pageTabView.delegate = self + + return pageTabView + }() + + private lazy var pageViewController: UIPageViewController = { + return UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal) + }() + + private lazy var pageScrollView: UIScrollView? = { + let scrollView = (pageViewController.view.subviews.first { $0 is UIScrollView }) as? UIScrollView + scrollView?.delegate = self + return scrollView + }() + + init(pages: [Page]) { + self.pages = pages + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = Design.backgroundColor + + setupSubviews() + setupPageViewController() + setupLayouts() + + pageScrollViewObserver = pageScrollView?.observe(\.contentOffset, options: [.new]) { + [weak self] scrollView, change in + + guard let self = self else { return } + self.pageTabView.updateScrollingProgress(self.tabProgress) + } + } + + private var tabProgress: CGFloat { + guard let pageScrollView = pageScrollView else { + return 0 + } + let width = pageViewController.view.bounds.width + return (pageScrollView.contentOffset.x - width) / width + } + + private func setupPageViewController() { + let initial: [UIViewController]? = { + guard let firstPage = pages.first?.viewController else { + return nil + } + return [firstPage] + }() + + pageViewController.setViewControllers(initial, direction: .forward, animated: false) + pageViewController.dataSource = self + + addChild(pageViewController, to: pageContainerLayout) + } + + private func setupSubviews() { + view.addLayoutGuide(pageContainerLayout) + view.addSubview(pageTabView) + } + + private func setupLayouts() { + + NSLayoutConstraint.activate([ + pageContainerLayout.topAnchor .constraint(equalTo: pageTabView.bottomAnchor), + pageContainerLayout.leadingAnchor .constraint(equalTo: view.leadingAnchor), + pageContainerLayout.trailingAnchor.constraint(equalTo: view.trailingAnchor), + pageContainerLayout.bottomAnchor .constraint(equalTo: view.bottomAnchor) + ]) + + pageTabView.translatesAutoresizingMaskIntoConstraints = false + pageTabHeightConstraint = pageTabView.heightAnchor.constraint( + equalToConstant: PageTabView.TabView.Design.height) + NSLayoutConstraint.activate([ + pageTabHeightConstraint!, + pageTabView.leadingAnchor .constraint(equalTo: view.leadingAnchor), + pageTabView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + pageTabView.topAnchor .constraint(equalTo: safeTopAnchor) + ]) + } + + func setPageTabViewHidden(_ hidden: Bool) { + pageTabHeightConstraint?.constant = hidden ? 0 : PageTabView.TabView.Design.height + view.layoutIfNeeded() + } +} + +extension PageViewController { + var currentViewControllerIndex: Int? { + if let viewControllers = pageViewController.viewControllers, + let currentViewController = viewControllers.first + { + return indexForViewController(currentViewController) + } + return nil + } + + func indexForViewController(_ viewController: UIViewController) -> Int? { + return pages.firstIndex { $0.viewController === viewController } + } +} + +extension PageViewController: UIPageViewControllerDataSource { + func pageViewController( + _ pageViewController: UIPageViewController, + viewControllerBefore viewController: UIViewController) -> UIViewController? + { + guard let index = indexForViewController(viewController) else { return nil } + guard index != 0 else { return nil } + return pages[index - 1].viewController + } + + func pageViewController( + _ pageViewController: UIPageViewController, + viewControllerAfter viewController: UIViewController) -> UIViewController? + { + guard let index = indexForViewController(viewController) else { return nil } + guard index != pages.count - 1 else { return nil } + return pages[index + 1].viewController + } +} + +extension PageViewController: PageTabViewDelegate { + func pageTabView(_ pageTabView: PageTabView, didSelectIndex index: Int) { + let direction: UIPageViewController.NavigationDirection + if let currentIndex = currentViewControllerIndex { + direction = index >= currentIndex ? .forward : .reverse + } else { + assertionFailure("Cannot get current index for page view controller. It should not happen.") + direction = .forward + } + // Prevents any interaction while the page is scrolling when triggered by page tab selecting. + setUserInteractionEnabled(false) + pageViewController.setViewControllers([pages[index].viewController], direction: direction, animated: true) + } +} + +extension PageViewController: UIScrollViewDelegate { + // triggered when programmatically set the index of PageViewController and its animation ended + func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + pageTabView.reset() + setUserInteractionEnabled(true) + } + + // In some cases, `pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)` is not + // called correctly by UIKit. To prevent that case, use `updateSelectedIndexForCurrentProgress` from + // this delegate method. + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + pageTabView.updateSelectedIndexForCurrentProgress() + setUserInteractionEnabled(true) + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + setUserInteractionEnabled(false) + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + // In some cases, dragging will not trigger a deceleration. + // So we need to set interaction enabled immediately. + // If `willDecelerate`, it is handled in `scrollViewDidEndDecelerating` + if !decelerate { + setUserInteractionEnabled(true) + } + } + + private func setUserInteractionEnabled(_ enabled: Bool) { + pages.forEach { $0.viewController.view.isUserInteractionEnabled = enabled } + view.isUserInteractionEnabled = enabled + pageTabView.isUserInteractionEnabled = enabled + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/MessageShareTargetType.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/MessageShareTargetType.swift new file mode 100644 index 00000000..46c5535f --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/MessageShareTargetType.swift @@ -0,0 +1,37 @@ +// +// MessageShareTargetType.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +/// Represents the share target type. +public enum MessageShareTargetType: Int, CaseIterable { + + /// Share to friends of current user. + case friends + + /// Share to groups of which current user is a member. + case groups + + var title: String { + switch self { + case .friends: return Localization.string("shareRecipient.section.friends.title") + case .groups: return Localization.string("shareRecipient.section.groups.title") + } + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/ShareTarget.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/ShareTarget.swift new file mode 100644 index 00000000..f27ec239 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/ShareTarget.swift @@ -0,0 +1,54 @@ +// +// ShareTarget.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +/// Represents the share target in a share action. +/// A target can be either a friend of current user, or a group of which the current user is a member. +public protocol ShareTarget { + + /// The ID of this share target. + var targetID: String { get } + + /// The display name of this share target. + var displayName: String { get } + + /// URL for the profile image of this share target. + var avatarURL: URL? { get } +} + +extension User: ShareTarget { + public var targetID: String { return userID } + public var avatarURL: URL? { return pictureURL } +} + +extension Group: ShareTarget { + public var targetID: String { return groupID } + public var displayName: String { return groupName } + public var avatarURL: URL? { return pictureURL } +} + +extension ShareTarget { + var placeholderImage: UIImage? { + let value = displayName.count % 4 + 1 + return UIImage(bundleNamed: "unknown_user_small_0\(value)") + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/ShareViewController.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/ShareViewController.swift new file mode 100644 index 00000000..979f67c7 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/ShareViewController.swift @@ -0,0 +1,246 @@ +// +// ShareViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +/** + A view controller that provides a default UI for selecting friends and groups, + then share some `Message`s to the selected targets. + + ## Overview + + A `ShareViewController` allows users to share a message to LINE via a default UI. + An authorized user can browse, search, and select up to 10 users or groups in a tab-based table view UI. + After choosing their share targets, the user taps "Send" to share a preset `Message` to the targets. + The message appears to the target recipients as having been sent by the user themselves. + + `ShareViewController` is a subclass of `UINavigationController`, so you need to create and present it modally. + To use `ShareViewController`, follow these steps: + + 1. Verify that the user has granted your app the necessary permissions. `ShareViewController` will show both + Friends and Groups tabs. To get the friend list and group list, and send a message, you need + `LoginPermission.oneTimeShare`. Use `ShareViewController.localAuthorizationStatusForSendingMessage()` to check + whether you have a valid token with the necessary permissions. If you don't have them, don't create and + show the `ShareViewController`, but instead prompt your user to grant your app the needed permissions. + + 2. Create a `ShareViewController` instance. `ShareViewController` can't be initialized from Storyboard or + XIB. Use the provided initializer `init()`. + + 3. Specify `messages` to tell the `ShareViewController` the `Message` values you want to share. + + 4. Present the created `ShareViewController` modally by calling `present(_:animated:completion:)`. + + You can customize the `ShareViewController` navigation bar style and status bar content style to match your app. + Use `navigationBarTintColor`, `navigationBarTextColor`, and `statusBarStyle` to do so. + + ## Share Delegate + + `ShareViewController` will deliver results of user interaction to a delegate object. To get these related events, + you must provide a delegate that conforms to the `ShareViewControllerDelegate` protocol, and set it to `shareDelegate` + property. + + See `ShareViewControllerDelegate` for more information. + + - Warning: + Although `ShareViewController` is marked as `open`, we recommend against creating a subclass for it. The class is + intended to be used as-is, to ensure a consistent sharing experience across all LINE and LINE SDK integrations. Users + expect sharing messages to friends and groups in LINE to work the same across different apps. Nevertheless, if you + absolutely need a custom sharing interaction, you can create it using the related APIs. + */ +open class ShareViewController: StyleNavigationController { + + /// The delegate object of this share view controller. + /// + /// The delegate receives events when the friends/groups list loading fails, user cancels the sharing view + /// controller or the sharing finishes successfully. You can choose to implement one or more methods to offer + /// your users a better experience when an event happens. + /// + /// For information about the methods you can implement for your delegate object, + /// see `ShareViewControllerDelegate`. + /// + public weak var shareDelegate: ShareViewControllerDelegate? + + /// The `Message` objects about to be sent. + /// + /// - Note: + /// If you didn't specify the `shareDelegate` for `ShareViewController` or you didn't implement the + /// `shareViewController(_:messagesForSendingToTargets:)` method in the delegate object, the value from + /// this property will be used as the messages to be sent. + /// + /// You must either set this property to a non-nil value, or implement the + /// `shareViewController(_:messagesForSendingToTargets:)` delegate method and return a valid message array. If you + /// don't, a trap will be triggered. If you implemented both, the returned value from delegate method will overwrite + /// the value in this property. + /// + public var messages: [MessageConvertible]? { + set { rootViewController.messages = newValue } + get { return rootViewController.messages } + } + + private let rootViewController = ShareRootViewController() + + // MARK: - Initializers + + /// Creates a `ShareViewController` with default behavior. Always use this initializer to create a + /// `ShareViewController` instance. + public init() { + super.init(nibName: nil, bundle: nil) + setupRootDelegates() + setupPresentationDelegate() + self.viewControllers = [rootViewController] + } + + /// `ShareViewController` can't be created from Storyboard or XIB file. This method merely throws a + /// fatal error. + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup & Style + + /// :nodoc: + open override var preferredStatusBarStyle: UIStatusBarStyle { + return statusBarStyle + } + + private func setupRootDelegates() { + rootViewController.onCancelled.delegate(on: self) { (self, _) in + if let shareDelegate = self.shareDelegate { + self.dismiss(animated: true) { + shareDelegate.shareViewControllerDidCancelSharing(self) + } + } else { + self.dismiss(animated: true) + } + } + rootViewController.onLoadingFailed.delegate(on: self) { (self, value) in + let (type, error) = value + self.shareDelegate?.shareViewController(self, didFailLoadingListType: type, withError: error) + } + rootViewController.onSendingMessage.delegate(on: self) { (self, targets) in + + if let messages = self.shareDelegate?.shareViewController(self, messagesForSendingToTargets: targets) { + return messages + } + + guard let messages = self.messages else { + Log.fatalError( + """ + You need at least set the `ShareViewController.message` or implement + `shareViewController(:messageForSendingToTargets:)` before sharing a message.) + """ + ) + } + + return messages + } + rootViewController.onSendingSuccess.delegate(on: self) { (self, success) in + self.shareDelegate?.shareViewController( + self, + didSendMessages: success.messages, + toTargets: success.targets) + } + rootViewController.onSendingFailure.delegate(on: self) { (self, failure) in + self.shareDelegate?.shareViewController( + self, + didFailSendingMessages: failure.messages, + toTargets: failure.targets, + withError: failure.error) + } + rootViewController.onShouldDismiss.delegate(on: self) { (self, _) in + return self.shareDelegate?.shareViewControllerShouldDismissAfterSending(self) ?? true + } + } + + private func setupPresentationDelegate() { + presentationController?.delegate = self + } +} + +/// Represents the authorization status for sharing messages. +/// Before creating and presenting a message sharing UI, we strongly recommend checking whether your app +/// has a valid token and the necessary permissions to share messages. +/// +/// `ShareViewController.localAuthorizationStatusForSendingMessage()` returns a `MessageShareAuthorizationStatus` value +/// to indicate the current authorization status for sharing messages. +/// +/// - lackOfToken: There is no valid token in the local token store. The user hasn't logged in and authorized +/// your app yet. +/// - lackOfPermissions: There is a valid token, but it doesn't contain the necessary permissions for sharing a message. +/// The associated value is an array of `LoginPermission`, containing all lacking permissions. +/// - authorized: The token exists locally and contains the necessary permissions to share messages. +/// +public typealias MessageShareAuthorizationStatus = AuthorizationStatus + +// MARK: - Authorization Helpers +extension ShareViewController { + + /// Gets the local authorization status for sending messages to friends and groups. + /// + /// - Returns: The local authorization status based on the currently stored token and the permissions specified + /// in that token. + /// + /// - Note: + /// If the return value is `.authorized`, you can present a `ShareViewController` instance for message sharing. + /// But `.authorized` status doesn't necessarily mean sharing would succeed; there may be problems with the + /// token or permissions. + /// The token status is stored locally and may not have been synchronized with the server-side status. + /// The token may have expired or been revoked by the server or via another client. + /// + /// To get the correct result about sharing behavior, specify `ShareViewController.shareDelegate` and implement + /// the methods in `ShareViewControllerDelegate`. + /// + public static func localAuthorizationStatusForSendingMessage() + -> AuthorizationStatus + { + guard let token = AccessTokenStore.shared.current else { + return .lackOfToken + } + + return localAuthorizationStatusForSendingMessage(permissions: token.permissions) + } + + static func localAuthorizationStatusForSendingMessage(permissions: [LoginPermission]) + -> AuthorizationStatus + { + let lackPermissions = Set([.oneTimeShare]).filter { + !permissions.contains($0) + } + + guard lackPermissions.isEmpty else { + return .lackOfPermissions(lackPermissions) + } + return .authorized + } +} + +/// :nodoc: +extension ShareViewController: UIAdaptivePresentationControllerDelegate { + /// :nodoc: + public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + shareDelegate?.shareViewControllerDidCancelSharing(self) + } + + /// :nodoc: + public func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { + return rootViewController.selectedCount == 0 + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/ShareViewControllerDelegate.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/ShareViewControllerDelegate.swift new file mode 100644 index 00000000..6b44dd60 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/Public/ShareViewControllerDelegate.swift @@ -0,0 +1,197 @@ +// +// ShareViewControllerDelegate.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +/** + A set of methods that your delegate object could implement to receive share view controller events when user interacts + with the interface. + + ## Overview + + The methods of this protocol notify your delegate when an event happens in the owner `ShareViewController`. + Although specifying a delegate object for `ShareViewController` isn't strictly required, we strongly recommend + that you do so. Without implementing the delegate methods, you can't retrieve information about events like + loading failure, user cancellation, or message sending success. +*/ +public protocol ShareViewControllerDelegate: AnyObject { + + /// Tells the delegate that the loading process fails for a specified type with an error. + /// + /// - Parameters: + /// - controller: The controller object managing the share interface. + /// - shareType: The type which fails to load. + /// - error: A value containing the details of the error. + /// + /// The `ShareViewController` will automatically load the user's friends and groups list, and use them to populate + /// the respective table views. If an error happens during the loading process, this delegate method is called. + /// + /// You can check the `error` parameter to decide what to do next. The loading failure could be caused by a bad + /// network connection, invalid server responses, or an API error. `ShareViewController` doesn't offer a way to + /// retry loading, so you may need to dismiss the current share view controller instance and prompt your user choose + /// to start a new sharing action or cancel altogether. + /// + /// This method can be called multiple times, since there are multiple lists loading in the `ShareViewController`. + /// Each invocation contains a `shareType` parameter. For a single `shareType`, this method will be called no more + /// than once. + /// + /// See `LineSDKError` for more about error handling. + /// + func shareViewController( + _ controller: ShareViewController, + didFailLoadingListType shareType: MessageShareTargetType, + withError error: LineSDKError) + + /// Tells the delegate that the user cancelled the sharing action. + /// + /// - Parameter controller: The controller object managing the share interface. + /// + /// When the user cancels sharing action by tapping the "Close" button in the sharing UI, the `ShareViewController` + /// will be dismissed with an animation and this delegate method is called after the dismissing animation finished. + /// + func shareViewControllerDidCancelSharing(_ controller: ShareViewController) + + /// Tells the delegate that the message sending fails due to an error. + /// + /// - Parameters: + /// - controller: The controller object managing the share interface. + /// - messages: An array of `Message` values which should be sent. + /// - targets: An array of `ShareTarget` values to which the `messages` should be sent to. + /// - error: A value contains information about the detail of the error. + /// + /// This method is called when the user taps "Send" but an error happens during the network request for sending the + /// messages. + /// + /// It means there is a problem while connecting to the server, or the server refused the request. No message is + /// sent to selected users or groups. + /// + /// Check the `error` parameter for error details. You may also want to dismiss the current share view controller + /// and show an error message to the user. See `LineSDKError` for more about error handling. + /// + /// - Note: + /// By default, after the message is sent and the response received, the share view controller is dismissed + /// automatically. You can prevent this by implementing the `shareViewControllerShouldDismissAfterSending(_:)` + /// method in the delegate object and returning `false` there. You can then dismiss the share view controller + /// yourself if necessary. + /// + func shareViewController( + _ controller: ShareViewController, + didFailSendingMessages messages: [MessageConvertible], + toTargets targets: [ShareTarget], + withError error: LineSDKError) + + /// Tells the delegate that the message sending succeeded and the messages are delivered to server. + /// + /// - Parameters: + /// - controller: The controller object managing the share interface. + /// - messages: An array of `Message` values which were sent. + /// - targets: An array of `ShareTarget` values to which the `messages` should be sent to. + /// + /// This method is called when the user taps "Send" and the server accepts the messages. Delivering the `messages` + /// to the server doesn't mean they'll be actually delivered to the `targets`. The target friends or groups may + /// have blocked your channel or the current user from sending them messages. They can also choose to block + /// messages from unauthorized channels. + /// + /// - Note: + /// By default, after the message is sent and the response received, the share view controller is dismissed + /// automatically. You can prevent this by implementing the `shareViewControllerShouldDismissAfterSending(_:)` + /// method in the delegate object and returning `false` there. You can then dismiss the share view controller + /// yourself if necessary. + /// + func shareViewController( + _ controller: ShareViewController, + didSendMessages messages: [MessageConvertible], + toTargets targets: [ShareTarget]) + + /// Controls whether the share view controller should dismiss itself after sending messages. + /// + /// - Parameter controller: The controller object managing the share interface. + /// - Returns: Whether the share view controller should dismiss itself after sending messages. + /// + /// By default, after the message is sent and the response received, the share view controller is dismissed + /// automatically. You can prevent this by implementing the `shareViewControllerShouldDismissAfterSending(_:)` + /// method in the delegate object and returning `false` there. You can then dismiss the share view controller + /// yourself if necessary. + /// + /// - Note: + /// Use this method to control dismissal of the share view controller. In the completion handler of your own dismiss + /// call, you can choose to display an alert in UI to notify users the result of sharing, for example. + /// + func shareViewControllerShouldDismissAfterSending(_ controller: ShareViewController) -> Bool + + /// Returns an array of `Message` or `MessageConvertible` for a set of given selected targets to share. + /// + /// - Parameters: + /// - controller: The controller object managing the share interface. + /// - targets: The selected `ShareTarget` values to which user want to send messages. + /// - Returns: An array of messages to be sent. + /// + /// This method is called when the user taps "Send" and the message sending request is about to be sent to the + /// server. If implemented, the messages in the returned `MessageConvertible` array are sent to the selected targets. + /// This step provides a final chance to modify and prepare the messages to send. + /// + /// If you didn't set a delegate object for `ShareViewController` or you didn't implement this method in the + /// delegate object, the value from `ShareViewController.messages` property will be used as the messages to be sent. + /// You must either set the `ShareViewController.messages` to a non-nil value, or implement this method and return a + /// valid message array. If you don't, a trap will be triggered. If you implemented both, the returned value from + /// this method will overwrite the `messages` in `ShareViewController`. + /// + func shareViewController( + _ controller: ShareViewController, + messagesForSendingToTargets targets: [ShareTarget]) -> [MessageConvertible] + +} + +extension ShareViewControllerDelegate { + public func shareViewController( + _ controller: ShareViewController, + didFailLoadingListType shareType: MessageShareTargetType, + withError error: LineSDKError) { } + + public func shareViewControllerDidCancelSharing(_ controller: ShareViewController) {} + + public func shareViewController( + _ controller: ShareViewController, + didFailSendingMessages messages: [MessageConvertible], + toTargets targets: [ShareTarget], + withError error: LineSDKError) { } + + public func shareViewController( + _ controller: ShareViewController, + didSendMessages messages: [MessageConvertible], + toTargets targets: [ShareTarget]) { } + + public func shareViewController( + _ controller: ShareViewController, + messagesForSendingToTargets targets: [ShareTarget]) -> [MessageConvertible] + { + guard let messages = controller.messages else { + Log.fatalError( + """ + You need at least set the `ShareViewController.message` or implement + `shareViewController(:messageForSendingToTargets:)` before sharing a message.) + """ + ) + } + return messages + } + public func shareViewControllerShouldDismissAfterSending(_ controller: ShareViewController) -> Bool { + return true + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/SelectedPanel/SelectedTargetPanelCell.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/SelectedPanel/SelectedTargetPanelCell.swift new file mode 100644 index 00000000..380451e0 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/SelectedPanel/SelectedTargetPanelCell.swift @@ -0,0 +1,99 @@ +// +// SelectedTargetViewCell.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class SelectedTargetPanelCell: UICollectionViewCell { + + enum Design { + static let size = CGSize(width: 50, height: 66) + static let avatarFrame = CGRect(x: 0, y: 4, width: 45, height: 45) + static let nameLabelTopSpacing: CGFloat = 1 + static let deleteSize = CGSize(width: 21, height: 21) + + static var textColor: UIColor { return .compatibleColor(light: 0x596478, dark: 0xEBEBF5) } + static var font: UIFont { return .systemFont(ofSize: 12) } + } + + static let reuseIdentifier = String(describing: SelectedTargetPanelCell.self) + + private let avatarImageView: DownloadableImageView = { + let imageView = DownloadableImageView(frame: Design.avatarFrame) + imageView.layer.cornerRadius = Design.avatarFrame.width / 2; + imageView.clipsToBounds = true + return imageView + }() + + private let nameLabel: UILabel = { + let label = UILabel(frame: .zero) + label.font = Design.font + label.textColor = Design.textColor + label.numberOfLines = 1 + label.textAlignment = .center + return label + }() + + private let deleteIconImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(bundleNamed: "list_icon_delete_normal") + return imageView + }() + + func setShareTarget(_ target: ShareTarget) { + nameLabel.text = target.displayName + avatarImageView.setImage(target.avatarURL, placeholder: target.placeholderImage) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + setupLayouts() + } + + private func setupSubviews() { + contentView.addSubview(avatarImageView) + contentView.addSubview(nameLabel) + contentView.addSubview(deleteIconImageView) + } + + private func setupLayouts() { + nameLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + nameLabel.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: Design.nameLabelTopSpacing), + nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + nameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, + constant: Design.avatarFrame.maxX - Design.size.width), + nameLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor), + ]) + + deleteIconImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + deleteIconImageView.topAnchor.constraint(equalTo: contentView.topAnchor), + deleteIconImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + deleteIconImageView.widthAnchor.constraint(equalToConstant: Design.deleteSize.width), + deleteIconImageView.heightAnchor.constraint(equalToConstant: Design.deleteSize.height), + ]) + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/SelectedPanel/SelectedTargetPanelViewController.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/SelectedPanel/SelectedTargetPanelViewController.swift new file mode 100644 index 00000000..99ade485 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/SelectedPanel/SelectedTargetPanelViewController.swift @@ -0,0 +1,244 @@ +// +// ShareTargetDisplayViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class SelectedTargetPanelViewController: UIViewController { + + typealias ColumnIndex = ColumnDataStore.ColumnIndex + + enum Design { + static var height: CGFloat { return 79 } + static var backgroundColor: UIColor { return .LineSDKPanelBackground } + static var borderColor: UIColor { return .LineSDKPanelBorder } + static var borderWidth: CGFloat { return 0.5 } + + // CollectionView + static var minimumLineSpacing: CGFloat { return 10 } + } + + var collectionViewContentOffset: CGPoint { + get { return collectionView.contentOffset } + set { collectionView.setContentOffset(newValue, animated: false) } + } + + private var slideAnimationViewTopConstraint: NSLayoutConstraint! + + private let slideAnimationView: UIView = { + let view = UIView(frame: .zero) + view.backgroundColor = Design.backgroundColor + view.layer.borderWidth = Design.borderWidth + view.layer.borderColor = Design.borderColor.cgColor + return view + }() + + private func updateColorAppearance() { + slideAnimationView.layer.borderColor = Design.borderColor.cgColor + } + + private let collectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.minimumLineSpacing = Design.minimumLineSpacing + layout.scrollDirection = .horizontal + layout.itemSize = SelectedTargetPanelCell.Design.size + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.showsHorizontalScrollIndicator = false + collectionView.backgroundColor = Design.backgroundColor + collectionView.alwaysBounceHorizontal = true + collectionView.scrollsToTop = false + collectionView.contentInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15) + collectionView.register( + SelectedTargetPanelCell.self, + forCellWithReuseIdentifier: SelectedTargetPanelCell.reuseIdentifier + ) + return collectionView + }() + + // Observers + private var selectingObserver: NotificationToken! + private var deselectingObserver: NotificationToken! + + private let store: ColumnDataStore + + init(store: ColumnDataStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .clear + setupSubviews() + setupLayouts() + setupObservers() + + setMode(modeFromSelection, animated: false) + } + + private func setupSubviews() { + collectionView.dataSource = self + collectionView.delegate = self + slideAnimationView.addSubview(collectionView) + view.addSubview(slideAnimationView) + + updateColorAppearance() + } + + private func setupLayouts() { + slideAnimationView.translatesAutoresizingMaskIntoConstraints = false + slideAnimationViewTopConstraint = slideAnimationView.topAnchor.constraint(equalTo: view.topAnchor) + slideAnimationViewTopConstraint.isActive = true + NSLayoutConstraint.activate([ + slideAnimationView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + slideAnimationView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + slideAnimationView.heightAnchor.constraint(equalTo: view.heightAnchor) + ]) + + collectionView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + collectionView.topAnchor.constraint(equalTo: slideAnimationView.topAnchor), + collectionView.leadingAnchor.constraint(equalTo: slideAnimationView.leadingAnchor), + collectionView.trailingAnchor.constraint(equalTo: slideAnimationView.trailingAnchor), + collectionView.heightAnchor.constraint(equalToConstant: Design.height) + ]) + } + + private func setupObservers() { + selectingObserver = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidSelect, object: store, queue: nil) + { + [unowned self] noti in + self.handleSelectingChange(noti, isSelecting: true) + } + + deselectingObserver = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidDeselect, object: store, queue: nil) + { + [unowned self] noti in + self.handleSelectingChange(noti, isSelecting: false) + } + } + + private func handleSelectingChange(_ notification: Notification, isSelecting: Bool) { + setMode(modeFromSelection, animated: true) + + guard let positionInSelected = notification.userInfo?[LineSDKNotificationKey.positionInSelected] as? Int else { + return + } + + let indexPath = IndexPath(row: positionInSelected, section: 0) + collectionView.performBatchUpdates({ + if isSelecting { + collectionView.insertItems(at: [indexPath]) + } else { + collectionView.deleteItems(at: [indexPath]) + } + }, completion: { _ in + if isSelecting { self.scrollToLast() } + }) + } + + private func scrollToLast() { + guard !store.selectedIndexes.isEmpty else { return } + let indexPath = IndexPath(item: store.selectedIndexes.count - 1, section: 0) + collectionView.scrollToItem(at: indexPath, at: .left, animated: true) + } + + private enum Mode { + case show + case hide + } + + private var modeFromSelection: Mode { + return store.selectedIndexes.isEmpty ? .hide : .show + } + + private func setMode(_ mode: Mode, animated: Bool) { + self.mode = mode + updateLayout(animated: animated) + } + + private var mode = Mode.hide + + private func updateLayout(animated: Bool) { + slideAnimationViewTopConstraint.isActive = false + let anchor: NSLayoutYAxisAnchor + let alpha: CGFloat + switch mode { + case .show: + anchor = view.topAnchor + view.isUserInteractionEnabled = true + alpha = 1 + case .hide: + anchor = view.bottomAnchor + view.isUserInteractionEnabled = false + alpha = 0 + } + slideAnimationViewTopConstraint = slideAnimationView.topAnchor.constraint(equalTo: anchor) + slideAnimationViewTopConstraint.isActive = true + + func applyChange() { + view.alpha = alpha + view.layoutIfNeeded() + } + + if animated { + UIView.animate(withDuration: 0.2, animations: applyChange) + } else { + applyChange() + } + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + self.updateColorAppearance() + } + } +} + +extension SelectedTargetPanelViewController: UICollectionViewDataSource, UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return store.selectedIndexes.count + } + + func collectionView( + _ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath) -> UICollectionViewCell + { + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: SelectedTargetPanelCell.reuseIdentifier, + for: indexPath) as! SelectedTargetPanelCell + let target = store.data(at: store.selectedIndexes[indexPath.item]) + cell.setShareTarget(target) + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + collectionView.deselectItem(at: indexPath, animated: true) + let columnIndex = store.selectedIndexes[indexPath.item] + store.toggleSelect(atColumn: columnIndex.column, row: columnIndex.row) + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSearch/ShareTargetSearchResultTableViewController.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSearch/ShareTargetSearchResultTableViewController.swift new file mode 100644 index 00000000..a4bb2a18 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSearch/ShareTargetSearchResultTableViewController.swift @@ -0,0 +1,179 @@ +// +// ShareTargetSearchResultTableViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +final class ShareTargetSearchResultTableViewController: UITableViewController, ShareTargetTableViewStyling { + + // The order of search result section. + var sectionOrder: [MessageShareTargetType] = [.friends, .groups] + + @objc dynamic var hasSearchResult: Bool = true + + var searchText: String = "" { + didSet { + guard searchText != oldValue else { return } + filteredIndexes = store.indexes { + $0.displayName.localizedCaseInsensitiveContains(searchText) + } + hasSearchResult = searchText.isEmpty || filteredIndexes.contains { !$0.isEmpty } + } + } + + private typealias ColumnIndex = ColumnDataStore.ColumnIndex + private let store: ColumnDataStore + + private var selectingObserver: NotificationToken! + private var deselectingObserver: NotificationToken! + + init(store: ColumnDataStore) { + self.store = store + super.init(style: .plain) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupTableView() + } + + private var filteredIndexes = [[ColumnIndex]]( + repeating: [], count: MessageShareTargetType.allCases.count) + { + didSet { tableView.reloadData() } + } + + func startObserving() { + setupObservers() + } + + func stopObserving() { + stopObservers() + searchText = "" + } + + private func setupObservers() { + selectingObserver = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidSelect, object: store, queue: nil) + { + [unowned self] notification in + self.handleSelectingChange(notification) + } + + deselectingObserver = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidDeselect, object: store, queue: nil) + { + [unowned self] notification in + self.handleSelectingChange(notification) + } + } + + private func stopObservers() { + selectingObserver = nil + deselectingObserver = nil + } + + private func handleSelectingChange(_ notification: Notification) { + guard let index = notification.userInfo?[LineSDKNotificationKey.selectingIndex] as? ColumnIndex else { + assertionFailure("The `columnDataStoreSelected` notification should contain " + + "`selectingIndex` in `userInfo`. But got `userInfo`: \(String(describing: notification.userInfo))") + return + } + + guard let row = filteredIndexes[index.column].firstIndex(of: index) else { + return + } + + let section = actualSection(index.column) + let indexPath = IndexPath(row: row, section: section) + + if let cell = tableView.cellForRow(at: indexPath) as? ShareTargetSelectingTableCell { + let target = store.data(at: index) + let selected = store.isSelected(at: index) + cell.setShareTarget(target, selected: selected, highlightText: searchText) + } + } + + private func setupTableView() { + setupTableViewStyle() + tableView.contentInsetAdjustmentBehavior = .never + tableView.contentInset = UIEdgeInsets(top: expectedSearchBarHeight, left: 0, bottom: 0, right: 0) + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return filteredIndexes.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return filteredIndexes[actualSection(section)].count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell( + withIdentifier: ShareTargetSelectingTableCell.reuseIdentifier, + for: indexPath + ) as! ShareTargetSelectingTableCell + + let section = actualSection(indexPath.section) + let dataIndex = filteredIndexes[section][indexPath.row] + let target = store.data(at: dataIndex) + let selected = store.isSelected(at: dataIndex) + cell.setShareTarget(target, selected: selected, highlightText: searchText) + return cell + } + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + let section = actualSection(section) + if filteredIndexes[section].isEmpty { + return 0 + } + return ShareTargetSelectingSectionHeaderView.Design.height + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let section = actualSection(section) + if filteredIndexes[section].isEmpty { + return nil + } + let view = ShareTargetSelectingSectionHeaderView(frame: .zero) + view.titleLabel.text = MessageShareTargetType(rawValue: section)?.title + return view + } + + private func actualSection(_ section: Int) -> Int { + return sectionOrder[section].rawValue + } +} + +extension ShareTargetSearchResultTableViewController { + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let section = actualSection(indexPath.section) + let selectedIndex = filteredIndexes[section][indexPath.row] + let toggled = store.toggleSelect(atColumn: selectedIndex.column, row: selectedIndex.row) + if !toggled { + popSelectingLimitAlert(max: store.maximumSelectedCount) + } + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSearch/ShareTargetSearchResultViewController.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSearch/ShareTargetSearchResultViewController.swift new file mode 100644 index 00000000..68a42fba --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSearch/ShareTargetSearchResultViewController.swift @@ -0,0 +1,180 @@ +// +// ShareTargetSearchResultViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class ShareTargetSearchResultViewController: UIViewController { + + enum Design { + static var emptyResultLabelColor: UIColor { return .secondaryLabel } + } + + var searchText: String { + get { return tableViewController.searchText } + set { tableViewController.searchText = newValue } + } + + var sectionOrder: [MessageShareTargetType] { + get { return tableViewController.sectionOrder } + set { tableViewController.sectionOrder = newValue } + } + + // Conforming to `KeyboardObservable` + var keyboardObservers: [NotificationToken] = [] + + private let store: ColumnDataStore + private let tableViewController: ShareTargetSearchResultTableViewController + + private(set) lazy var panelViewController = SelectedTargetPanelViewController(store: store) + private let panelContainer = UILayoutGuide() + + private let emptyResultLabel: UILabel + private var hasSearchResultObserver: NSKeyValueObservation? + + private var panelBottomConstraint: NSLayoutConstraint? + private var panelHeightConstraint: NSLayoutConstraint? + + private var temporaryKeyboardInfo: KeyboardInfo? + + init(store: ColumnDataStore) { + self.store = store + self.tableViewController = ShareTargetSearchResultTableViewController(store: store) + self.emptyResultLabel = UILabel(frame: .zero) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + setupSubviews() + setupLayouts() + } + + private func setupSubviews() { + addChild(tableViewController, to: view) + + view.addLayoutGuide(panelContainer) + addChild(panelViewController, to: panelContainer) + + emptyResultLabel.text = Localization.string("search.no.result") + emptyResultLabel.textColor = Design.emptyResultLabelColor + view.addSubview(emptyResultLabel) + hasSearchResultObserver = tableViewController + .observe(\.hasSearchResult, options: [.initial, .new]) { + [weak self] _, change in + guard let self = self else { return } + if let hasSearchResult = change.newValue { + self.emptyResultLabel.isHidden = hasSearchResult + } + } + } + + private func setupLayouts() { + emptyResultLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + emptyResultLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + emptyResultLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: view.bounds.height / 3) + ]) + + NSLayoutConstraint.activate([ + panelContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor), + panelContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor) + ]) + + panelBottomConstraint = panelContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor) + panelBottomConstraint!.isActive = true + + let panelHeight = SelectedTargetPanelViewController.Design.height + safeAreaInsets.bottom + panelHeightConstraint = panelContainer.heightAnchor.constraint(equalToConstant: panelHeight) + panelHeightConstraint!.isActive = true + } + + func start() { + tableViewController.startObserving() + } + + func clear() { + tableViewController.stopObserving() + } +} + +// MARK: - SelectedTargetPanelViewController +extension ShareTargetSearchResultViewController { + + private func updatePanelBottomConstraint(keyboardInfo: KeyboardInfo) { + + panelBottomConstraint?.isActive = false + panelHeightConstraint?.isActive = false + + let keyboardOverlayHeight: CGFloat + let panelHeight: CGFloat + if keyboardInfo.isVisible, let keyboardOrigin = keyboardInfo.endFrame?.origin + { + let viewFrameInWindow = view.convert(view.bounds, to: nil) + keyboardOverlayHeight = max(0, viewFrameInWindow.maxY - keyboardOrigin.y) + panelHeight = SelectedTargetPanelViewController.Design.height + } else { + keyboardOverlayHeight = 0 + panelHeight = SelectedTargetPanelViewController.Design.height + safeAreaInsets.bottom + } + + let bottomConstraint = panelContainer.bottomAnchor.constraint( + equalTo: view.bottomAnchor, + constant: -keyboardOverlayHeight) + bottomConstraint.isActive = true + panelBottomConstraint = bottomConstraint + + let heightConstraint = panelContainer.heightAnchor.constraint(equalToConstant: panelHeight) + heightConstraint.isActive = true + panelHeightConstraint = heightConstraint + } + + private func handleKeyboardChange(_ keyboardInfo: KeyboardInfo) { + updatePanelBottomConstraint(keyboardInfo: keyboardInfo) + UIView.animate(withDuration: keyboardInfo.duration) { + self.view.layoutIfNeeded() + } + } + + override func viewDidLayoutSubviews() { + if let keyboardInfo = temporaryKeyboardInfo { + handleKeyboardChange(keyboardInfo) + temporaryKeyboardInfo = nil + } + } +} + +extension ShareTargetSearchResultViewController: KeyboardObservable { + func keyboardInfoWillChange(keyboardInfo: KeyboardInfo) { + // `self.view` is not yet added to current view hierarchy. + if view.window == nil { + // Wait for iOS to layout the current view. Otherwise, a wrong initial layout happens when presenting `self` + // with a `.formSheet` style. + temporaryKeyboardInfo = keyboardInfo + } else { + handleKeyboardChange(keyboardInfo) + } + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSearch/ShareTargetSelectingSectionHeaderView.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSearch/ShareTargetSelectingSectionHeaderView.swift new file mode 100644 index 00000000..b925e4b4 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSearch/ShareTargetSelectingSectionHeaderView.swift @@ -0,0 +1,57 @@ +// +// ShareTargetSelectingSectionHeaderView.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class ShareTargetSelectingSectionHeaderView: UIView { + + enum Design { + static var height: CGFloat { return 25 } + static var fontSize: CGFloat { return 12 } + static var fontColor: UIColor { return .compatibleColor(light: 0x797F8C, dark: 0xF5F5F5) } + static var backgroundColor: UIColor { return .compatibleColor(light: 0xF9F9F9, dark: 0x000000) } + static var borderColor: UIColor { return .compatibleColor(light: 0xEDEDF1, dark: 0x171717) } + static var borderWidth: CGFloat { return 0.5 } + } + + let titleLabel = UILabel(frame: .zero) + + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = Design.backgroundColor + layer.borderColor = Design.borderColor.cgColor + layer.borderWidth = Design.borderWidth + + titleLabel.font = UIFont.systemFont(ofSize: Design.fontSize) + titleLabel.textColor = Design.fontColor + + titleLabel.translatesAutoresizingMaskIntoConstraints = false + addSubview(titleLabel) + NSLayoutConstraint.activate([ + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8), + titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 8), + titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor) + ]) + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareRootViewController.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareRootViewController.swift new file mode 100644 index 00000000..de2dad0f --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareRootViewController.swift @@ -0,0 +1,279 @@ +// +// ShareRootViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class ShareRootViewController: UIViewController { + + struct OnSendingSuccessData { + let messages: [MessageConvertible] + let targets: [ShareTarget] + } + + struct OnSendingFailureData { + let messages: [MessageConvertible] + let targets: [ShareTarget] + let error: LineSDKError + } + + typealias ColumnIndex = ColumnDataStore.ColumnIndex + + private let store = ColumnDataStore(columnCount: MessageShareTargetType.allCases.count) + + // States + @objc dynamic private var allLoaded: Bool = false + + // Observers + private var selectingObserver: NotificationToken! + private var deselectingObserver: NotificationToken! + + private var loadedObserver: NSKeyValueObservation? + + let onCancelled = Delegate<(), Void>() + let onLoadingFailed = Delegate<(MessageShareTargetType, LineSDKError), Void>() + let onSendingMessage = Delegate<[ShareTarget], [MessageConvertible]>() + + let onSendingSuccess = Delegate() + let onSendingFailure = Delegate() + let onShouldDismiss = Delegate<(), Bool>() + + private lazy var panelContainer = UILayoutGuide() + private lazy var panelViewController = SelectedTargetPanelViewController(store: store) + + var messages: [MessageConvertible]? + + var selectedCount: Int { + return store.selectedIndexes.count + } + + private lazy var pageViewController: PageViewController = { + let controllers = MessageShareTargetType.allCases + .map { index -> ShareTargetSelectingViewController in + let controller = ShareTargetSelectingViewController(store: store, columnIndex: index.rawValue) + // Force load view for pages to setup table view initial state. + _ = controller.view + return controller + } + + controllers.forEach { $0.delegate = self } + + let pages = zip(MessageShareTargetType.allCases, controllers).map { + index, controller -> PageViewController.Page in + return .init(viewController: controller, title: index.title) + } + + return PageViewController(pages: pages) + }() + + deinit { + ImageManager.shared.purgeCache() + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = "LINE" + navigationItem.leftBarButtonItem = + UIBarButtonItem( + title: Localization.string("common.action.close"), + style: .plain, + target: self, + action: #selector(cancelSharing)) + + + setupSubviews() + setupLayouts() + + loadGraphList() + setupObservers() + } + + private func setupSubviews() { + addChild(pageViewController, to: view) + + view.addLayoutGuide(panelContainer) + addChild(panelViewController, to: panelContainer) + } + + private func setupLayouts() { + NSLayoutConstraint.activate([ + panelContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor), + panelContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor), + panelContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor), + panelContainer.topAnchor.constraint( + equalTo: safeBottomAnchor, + constant: -SelectedTargetPanelViewController.Design.height) + ]) + } + + private func setupObservers() { + selectingObserver = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidSelect, object: store, queue: nil) + { + [unowned self] noti in + self.handleSelectingChange(noti) + } + + deselectingObserver = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidDeselect, object: store, queue: nil) + { + [unowned self] noti in + self.handleSelectingChange(noti) + } + } +} + +// MARK: - Controller Actions +extension ShareRootViewController { + @objc private func cancelSharing() { + onCancelled.call() + } +} + + +// MARK: - Controller Actions +extension ShareRootViewController { + + private func loadGraphList() { + + let friendsRequest = GetShareFriendsRequest(sort: .relation) + let chainedFriendsRequest = ChainedPaginatedRequest(originalRequest: friendsRequest) + chainedFriendsRequest.onPageLoaded.delegate(on: self) { (self, response) in + self.store.append(data: response.friends, to: MessageShareTargetType.friends.rawValue) + } + + let groupsRequest = GetShareGroupsRequest() + let chainedGroupsRequest = ChainedPaginatedRequest(originalRequest: groupsRequest) + chainedGroupsRequest.onPageLoaded.delegate(on: self) { (self, response) in + self.store.append(data: response.groups, to: MessageShareTargetType.groups.rawValue) + } + + let sendingDispatchGroup = DispatchGroup() + + sendingDispatchGroup.enter() + Session.shared.send(chainedFriendsRequest) { result in + sendingDispatchGroup.leave() + switch result { + case .success: + break + case .failure(let error): + self.onLoadingFailed.call((.friends, error)) + } + } + + sendingDispatchGroup.enter() + Session.shared.send(chainedGroupsRequest) { result in + sendingDispatchGroup.leave() + switch result { + case .success: + break + case .failure(let error): + self.onLoadingFailed.call((.groups, error)) + } + } + + sendingDispatchGroup.notify(queue: .main) { + self.allLoaded = true + } + } + + private func handleSelectingChange(_ notification: Notification) { + if selectedCount == 0 { + navigationItem.rightBarButtonItem = nil + } else { + let title = Localization.string("common.action.send") + navigationItem.rightBarButtonItem = UIBarButtonItem( + title: "\(title) (\(selectedCount))", style: .plain, target: self, action: #selector(sendMessage)) + } + } + + @objc private func sendMessage() { + let selected = store.selectedData + + // `onSendingMessage` is expected to be always delegated. + let messages = onSendingMessage.call(selected)! + + func callbackFailure(_ error: LineSDKError) { + let failureData = OnSendingFailureData(messages: messages, targets: selected, error: error) + self.onSendingFailure.call(failureData) + } + + func callbackSuccess(_ response: Unit) { + let successData = OnSendingSuccessData(messages: messages, targets: selected) + self.onSendingSuccess.call(successData) + } + + let indicator = LoadingIndicator.add(to: view) + API.getMessageSendingOneTimeToken(userIDs: selected.map { $0.targetID }) { result in + switch result { + case .success(let token): + API.multiSendMessages(messages, withMessageToken: token) { result in + indicator.remove() + switch result { + case .success(let response): callbackSuccess(response) + case .failure(let error): callbackFailure(error) + } + + let shouldDismiss = self.onShouldDismiss.call() ?? true + if shouldDismiss { + self.dismiss(animated: true) + } + } + case .failure(let error): + indicator.remove() + callbackFailure(error) + } + } + } +} + +// MARK: - Selecting view controller delegate +extension ShareRootViewController: ShareTargetSelectingViewControllerDelegate { + func shouldSearchStart(_ viewController: ShareTargetSelectingViewController) -> Bool { + if allLoaded { return true } + + let indicator = LoadingIndicator.add(to: view) + loadedObserver = observe(\.allLoaded, options: .new) { [weak self] controller, change in + guard let self = self else { return } + if let loaded = change.newValue, loaded { + indicator.remove() + self.loadedObserver = nil + viewController.continueSearch() + } + } + + return false + } + + func correspondingSelectedPanelViewController( + for viewController: ShareTargetSelectingViewController + ) -> SelectedTargetPanelViewController + { + return panelViewController + } + + func pageViewController( + for viewController: ShareTargetSelectingViewController + ) -> PageViewController + { + return pageViewController + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetSearchController.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetSearchController.swift new file mode 100644 index 00000000..54b16de8 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetSearchController.swift @@ -0,0 +1,65 @@ +// +// ShareTargetSearchController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class ShareTargetSearchController: UISearchController { + + enum Design { + static var searchBarTintColor: UIColor { + return .compatibleColor(light: 0x283145, dark: 0xffffff) + } + static var searchBarBackgroundColor: UIColor { + return .compatibleColor(light: .init(hex6: 0xEAEAEE), dark: .systemBackground) + } + } + + override init(searchResultsController: UIViewController?) { + super.init(searchResultsController: searchResultsController) + setupSearchBar() + } + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupSearchBar() { + updateColorAppearance() + searchBar.autocapitalizationType = .none + searchBar.autocorrectionType = .no + searchBar.spellCheckingType = .no + searchBar.returnKeyType = .done + searchBar.placeholder = Localization.string("friends.share.search") + } + + func updateColorAppearance() { + let searchBarBackgroundImage = Design.searchBarBackgroundColor.image() + [UIBarPosition.top, .topAttached] .forEach { position in + [UIBarMetrics.default, .defaultPrompt].forEach { metrics in + searchBar.setBackgroundImage(searchBarBackgroundImage, for: position, barMetrics: metrics) + }} + searchBar.tintColor = Design.searchBarTintColor + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetSelectingTableCell.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetSelectingTableCell.swift new file mode 100644 index 00000000..a3f60c61 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetSelectingTableCell.swift @@ -0,0 +1,155 @@ +// +// ShareTargetSelectingTableCell.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +final class ShareTargetSelectingTableCell: UITableViewCell { + + enum Design { + static var height: CGFloat { return 54.0 } + static var tickLeading: CGFloat { return 10.0 } + static var tickWidth: CGFloat { return 22.0 } + + static var avatarLeading: CGFloat { return 10.0 } + static var avatarWidth: CGFloat { return 44.0 } + + static var displayNameLeading: CGFloat { return 10.0 } + static var displayNameTrailing: CGFloat { return 10.0 } + + static var displayNameTextColor: UIColor { return .label } + static var displayNameFont: UIFont { return .systemFont(ofSize: 16) } + static var displayNameHighlightedNameColor: UIColor { + return .compatibleColor(light: 0x13C84D, dark: 0x14DE54) + } + + static var separatorInset: UIEdgeInsets { return .init(top: 0, left: 96, bottom: 0, right: 0) } + static var backgroundColor: UIColor { return .systemBackground } + static var selectionTickColor: UIColor { + return .compatibleColor(light: .init(hex6: 0xEFF1F6), dark: .tertiarySystemBackground) + } + } + + let tickImageView = UIImageView(frame: .zero) + let avatarImageView = DownloadableImageView(frame: .zero) + let displayNameLabel = UILabel(frame: .zero) + + static let reuseIdentifier = String(describing: ShareTargetSelectingTableCell.self) + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: .default, reuseIdentifier: reuseIdentifier) + + backgroundColor = Design.backgroundColor + selectionStyle = .gray + separatorInset = Design.separatorInset + + setupSubviews() + setupLayouts() + } + + private func setupSubviews() { + contentView.addSubview(tickImageView) + contentView.addSubview(avatarImageView) + contentView.addSubview(displayNameLabel) + + avatarImageView.layer.cornerRadius = Design.avatarWidth / 2; + avatarImageView.clipsToBounds = true + + displayNameLabel.lineBreakMode = .byTruncatingMiddle + } + + private func setupLayouts() { + tickImageView.translatesAutoresizingMaskIntoConstraints = false + avatarImageView.translatesAutoresizingMaskIntoConstraints = false + displayNameLabel.translatesAutoresizingMaskIntoConstraints = false + + // tickImageView + NSLayoutConstraint.activate([ + tickImageView.leadingAnchor + .constraint(equalTo: contentView.leadingAnchor, constant: Design.tickLeading), + tickImageView.centerYAnchor + .constraint(equalTo: contentView.centerYAnchor), + tickImageView.widthAnchor + .constraint(equalToConstant: Design.tickWidth), + tickImageView.heightAnchor + .constraint(equalTo: tickImageView.widthAnchor) + ]) + + // avatarImageView + NSLayoutConstraint.activate([ + avatarImageView.leadingAnchor + .constraint(equalTo: tickImageView.trailingAnchor, constant: Design.avatarLeading), + avatarImageView.centerYAnchor + .constraint(equalTo: contentView.centerYAnchor), + avatarImageView.widthAnchor + .constraint(equalToConstant: Design.avatarWidth), + avatarImageView.heightAnchor + .constraint(equalTo: avatarImageView.widthAnchor) + ]) + + // displayNameLabel + NSLayoutConstraint.activate([ + displayNameLabel.leadingAnchor + .constraint(equalTo: avatarImageView.trailingAnchor, constant: Design.displayNameLeading), + displayNameLabel.centerYAnchor + .constraint(equalTo: contentView.centerYAnchor), + displayNameLabel.trailingAnchor + .constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -Design.displayNameTrailing) + ]) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension ShareTargetSelectingTableCell { + + private func displayNameAttributedString(_ name: String, highlightText: String? = nil) -> NSAttributedString { + let displayNameAttributedString = NSMutableAttributedString( + string: name, + attributes: [ + .font: Design.displayNameFont, + .foregroundColor: Design.displayNameTextColor + ]) + if let highlightText = highlightText { + let range = NSString(string: name).range(of: highlightText, options: .caseInsensitive) + if range.location != NSNotFound { + displayNameAttributedString.addAttribute( + .foregroundColor, value: Design.displayNameHighlightedNameColor, range: range) + } + } + return displayNameAttributedString + } + + func setShareTarget(_ target: ShareTarget, selected: Bool, highlightText: String? = nil) { + + displayNameLabel.attributedText = + displayNameAttributedString(target.displayName, highlightText: highlightText) + + avatarImageView.setImage(target.avatarURL, placeholder: target.placeholderImage) + + let selectedImage = selected ? + UIImage(bundleNamed: "friend_check_on") : + UIImage(bundleNamed: "friend_check_off") + tickImageView.tintColor = Design.selectionTickColor + tickImageView.image = selectedImage + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetSelectingViewController.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetSelectingViewController.swift new file mode 100644 index 00000000..f326fbca --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetSelectingViewController.swift @@ -0,0 +1,260 @@ +// +// ShareTargetSelectingViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +protocol ShareTargetSelectingViewControllerDelegate: AnyObject { + func shouldSearchStart(_ viewController: ShareTargetSelectingViewController) -> Bool + + func correspondingSelectedPanelViewController( + for viewController: ShareTargetSelectingViewController + ) -> SelectedTargetPanelViewController + + func pageViewController( + for viewController: ShareTargetSelectingViewController + ) -> PageViewController +} + +final class ShareTargetSelectingViewController: UITableViewController, ShareTargetTableViewStyling { + + private typealias AppendingIndexRange = ColumnDataStore.AppendingIndexRange + private typealias ColumnIndex = ColumnDataStore.ColumnIndex + + weak var delegate: ShareTargetSelectingViewControllerDelegate? + + // Model + private var store: ColumnDataStore! + private let columnIndex: Int + + // Observers + private var dataAppendingObserver: NotificationToken! + private var selectingObserver: NotificationToken! + private var deselectingObserver: NotificationToken! + + // Search + private let searchController: ShareTargetSearchController + private let resultViewController: ShareTargetSearchResultViewController + + init(store: ColumnDataStore, columnIndex: Int) { + self.store = store + self.columnIndex = columnIndex + + let resultViewController = ShareTargetSearchResultViewController(store: store) + resultViewController.addKeyboardObserver() + + switch MessageShareTargetType(rawValue: columnIndex) { + case .friends?: + resultViewController.sectionOrder = [.friends, .groups] + case .groups?: + resultViewController.sectionOrder = [.groups, .friends] + case .none: + fatalError("The input column index should match a message share target type.") + } + + self.resultViewController = resultViewController + + let searchController = ShareTargetSearchController(searchResultsController: resultViewController) + self.searchController = searchController + + super.init(style: .plain) + + searchController.searchResultsUpdater = self + searchController.searchBar.delegate = self + searchController.delegate = self + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + setupTableView() + setupObservers() + } + + private func setupTableView() { + setupTableViewStyle() + tableView.tableHeaderView = searchController.searchBar + tableView.prefetchDataSource = self + } + + private func setupObservers() { + dataAppendingObserver = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidAppendData, object: store, queue: nil) + { + [unowned self] notification in + self.handleDataAppended(notification) + } + + selectingObserver = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidSelect, object: store, queue: nil) + { + [unowned self] notification in + self.handleSelectingChange(notification) + } + + deselectingObserver = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidDeselect, object: store, queue: nil) + { + [unowned self] notification in + self.handleSelectingChange(notification) + } + } + + private func handleSelectingChange(_ notification: Notification) { + guard let index = notification.userInfo?[LineSDKNotificationKey.selectingIndex] as? ColumnIndex else { + assertionFailure("The `columnDataStoreSelected` notification should contain " + + "`selectingIndex` in `userInfo`. But got `userInfo`: \(String(describing: notification.userInfo))") + return + } + guard index.column == columnIndex else { + return + } + let indexPath = IndexPath(row: index.row, section: 0) + + if let cell = tableView.cellForRow(at: indexPath) as? ShareTargetSelectingTableCell { + let target = store.data(at: index) + let selected = store.isSelected(at: index) + cell.setShareTarget(target, selected: selected) + } + } + + private func handleDataAppended(_ notification: Notification) { + guard let range = + notification.userInfo?[LineSDKNotificationKey.appendDataIndexRange] as? AppendingIndexRange else + { + assertionFailure("The `columnDataStoreDidAppendData` notification should contain " + + "`appendDataIndexRange` in `userInfo`. But got `userInfo`: \(String(describing: notification.userInfo))") + return + } + guard range.column == columnIndex else { + return + } + tableView.reloadData() + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + self.searchController.updateColorAppearance() + } + } +} + +// MARK: - UITableViewDataSource +extension ShareTargetSelectingViewController { + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return store.data(atColumn: columnIndex).count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell( + withIdentifier: ShareTargetSelectingTableCell.reuseIdentifier, + for: indexPath) as! ShareTargetSelectingTableCell + + let dataIndex = ColumnIndex(column: columnIndex, row: indexPath.row) + + let target = store.data(at: dataIndex) + let selected = store.isSelected(at: dataIndex) + cell.setShareTarget(target, selected: selected) + return cell + } +} + +// MARK: - UITableViewDelegate +extension ShareTargetSelectingViewController { + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let toggled = store.toggleSelect(atColumn: columnIndex, row: indexPath.row) + if !toggled { + popSelectingLimitAlert(max: store.maximumSelectedCount) + } + } +} + +// MARK: - UITableViewPrefetching +extension ShareTargetSelectingViewController: UITableViewDataSourcePrefetching { + func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { + indexPaths.forEach { indexPath in + let index = ColumnIndex(column: columnIndex, row: indexPath.row) + guard let url = store.data(at: index).avatarURL else { return } + ImageManager.shared.getImage(url) + } + } +} + +// MARK: - Search Controller +extension ShareTargetSelectingViewController: UISearchResultsUpdating { + public func updateSearchResults(for searchController: UISearchController) { + resultViewController.view.isHidden = false + guard let text = searchController.searchBar.text?.trimmingCharacters(in: .whitespaces) else { + return + } + resultViewController.searchText = text + } +} + +extension ShareTargetSelectingViewController: UISearchBarDelegate { + func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool { + return delegate?.shouldSearchStart(self) ?? true + } + + func continueSearch() { + searchController.searchBar.becomeFirstResponder() + } +} + +extension ShareTargetSelectingViewController: UISearchControllerDelegate { + + func willPresentSearchController(_ searchController: UISearchController) { + if let selectingPanel = delegate?.correspondingSelectedPanelViewController(for: self) { + // Sync selected panel collection view content offset from selecting vc to search result vc. + syncContentOffset(from: selectingPanel, to: resultViewController.panelViewController) + } + + if let pageViewController = delegate?.pageViewController(for: self) { + pageViewController.setPageTabViewHidden(true) + } + } + + func didPresentSearchController(_ searchController: UISearchController) { + resultViewController.start() + } + + func willDismissSearchController(_ searchController: UISearchController) { + if let selectingPanel = delegate?.correspondingSelectedPanelViewController(for: self) { + // Sync selected panel collection view content offset from search result vc to selecting vc. + syncContentOffset(from: resultViewController.panelViewController, to: selectingPanel) + } + resultViewController.clear() + } + + func didDismissSearchController(_ searchController: UISearchController) { + if let pageViewController = delegate?.pageViewController(for: self) { + pageViewController.setPageTabViewHidden(false) + } + } + + private func syncContentOffset(from: SelectedTargetPanelViewController, to: SelectedTargetPanelViewController) { + to.collectionViewContentOffset = from.collectionViewContentOffset + } +} diff --git a/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetTableViewStyling.swift b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetTableViewStyling.swift new file mode 100644 index 00000000..aa003184 --- /dev/null +++ b/LineSDK/LineSDK/LineSDKUI/SharingUI/TargetSelecting/ShareTargetTableViewStyling.swift @@ -0,0 +1,61 @@ +// +// ShareTargetTableViewStyling.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +enum ShareTargetTableViewDesign { + static var separatorColor: UIColor { + return .compatibleColor(light: .init(hex6: 0xE6E7EA), dark: .init(hex8: 0x54545899)) } + static var backgroundViewColor: UIColor { return .systemBackground } +} + +protocol ShareTargetTableViewStyling { + var tableView: UITableView! { get } + func setupTableViewStyle() +} + +extension ShareTargetTableViewStyling { + func setupTableViewStyle() { + tableView.register( + ShareTargetSelectingTableCell.self, + forCellReuseIdentifier: ShareTargetSelectingTableCell.reuseIdentifier) + tableView.rowHeight = ShareTargetSelectingTableCell.Design.height + let selectedPanelHeight = SelectedTargetPanelViewController.Design.height + tableView.tableFooterView = UIView(frame: + .init(x: 0, y: 0, width: tableView.frame.width, height: selectedPanelHeight) + ) + tableView.showsVerticalScrollIndicator = false + tableView.separatorColor = ShareTargetTableViewDesign.separatorColor + + let backgroundView = UIView() + backgroundView.backgroundColor = ShareTargetTableViewDesign.backgroundViewColor + tableView.backgroundView = backgroundView + } +} + +extension ShareTargetTableViewStyling where Self: UIViewController { + func popSelectingLimitAlert(max: Int) { + let message = String(format: Localization.string("chat.multi.fwd.confirm"), max) + let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert) + alert.addAction(.init(title: Localization.string("common.ok"), style: .default)) + present(alert, animated: true) + } +} diff --git a/LineSDK/LineSDK/Login/LoginConfiguration.swift b/LineSDK/LineSDK/Login/LoginConfiguration.swift index 390132b3..cc938959 100644 --- a/LineSDK/LineSDK/Login/LoginConfiguration.swift +++ b/LineSDK/LineSDK/Login/LoginConfiguration.swift @@ -1,13 +1,13 @@ // // LoginConfiguration.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -22,28 +22,28 @@ import Foundation struct LoginConfiguration { - + static var _shared: LoginConfiguration? static var shared: LoginConfiguration { return guardSharedProperty(_shared) } - + let channelID: String let universalLinkURL: URL? - + init(channelID: String, universalLinkURL: URL?) { self.channelID = channelID - + if let url = universalLinkURL, url.scheme?.lowercased() != "https" { Log.assertionFailure("Universal link is required to start with https scheme.") } - + self.universalLinkURL = universalLinkURL } - - /// Whether a `url` is a valid customize URL scheme of current app. + + /// Whether a `url` is a valid customize URL scheme of current app. /// - /// - Parameter url: The input URL from LINE client or web login flow. + /// - Parameter url: The input URL from LINE or web login flow. /// - Returns: `true` if the `url` is a valid app URL scheme for current app. Otherwise, `false`. func isValidCustomizeURL(url: URL) -> Bool { guard let scheme = url.scheme else { @@ -56,45 +56,31 @@ struct LoginConfiguration { return false } return true - + } /// Compares `url` with current set `universalLinkURL`, to check whether `url` is a valid universal URL or not. /// - /// - Parameter url: The input URL from LINE client or web login flow. + /// - Parameter url: The input URL from LINE or web login flow. /// - Returns: `true` if the `url` is a valid app universal link for current app. Otherwise, `false`. func isValidUniversalLinkURL(url: URL) -> Bool { - + guard let setURL = universalLinkURL else { return false } - + guard setURL.scheme?.lowercased() == url.scheme?.lowercased() else { return false } - + guard setURL.host?.lowercased() == url.host?.lowercased() else { return false } - + guard setURL.path.lowercased() == url.path.lowercased() else { return false } - + return true } - - /// Checks whether the `appID` is on the white list of calling back source app. - /// - /// - Parameter appID: The app ID of the source app which opens current app by `open(:url:)`. - /// - Returns: `true` if `appID` is from a valid auth application. - func isValidSourceApplication(appID: String) -> Bool { - var validPrefixes = ["jp.naver", "com.apple"] - if let currentAppID = Bundle.main.bundleIdentifier { - validPrefixes.append(currentAppID) - } - - let valid = validPrefixes.contains { appID.hasPrefix($0) } - return valid - } } diff --git a/LineSDK/LineSDK/Login/LoginManager.swift b/LineSDK/LineSDK/Login/LoginManager.swift index 5bcfff51..264209b1 100644 --- a/LineSDK/LineSDK/Login/LoginManager.swift +++ b/LineSDK/LineSDK/Login/LoginManager.swift @@ -1,13 +1,13 @@ // // LoginConfiguration.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,21 +19,22 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import Foundation +import UIKit /// Represents a login manager. You can set up the LINE SDK configuration, log in and log out the user with the /// LINE authorization flow, and check the authorization status. public class LoginManager { - + let lock = NSLock() - - /// The shared instance of the login manager. Always use this instance to interact with the login process of the LINE SDK. + + /// The shared instance of the login manager. Always use this instance to interact with the login process of + /// the LINE SDK. public static let shared = LoginManager() - + /// The current login process. A non-`nil` value indicates that there is an ongoing process and the LINE SDK /// is waiting for the login result; `nil` otherwise. public private(set) var currentProcess: LoginProcess? - + /// Checks and returns whether the current `LoginManager` instance is ready to use. Call the `setup` /// method to set up the LINE SDK with basic information before you call any other methods or properties /// in the LINE SDK. @@ -42,14 +43,14 @@ public class LoginManager { defer { lock.unlock() } return setup } - + /// Checks and returns whether the user was authorized and an access token exists locally. This method /// does not check whether the access token has been expired. To verify an access token, use the - /// `API.verifyAccessToken` method. + /// `API.Auth.verifyAccessToken` method. public var isAuthorized: Bool { return AccessTokenStore.shared.current != nil } - + /// Checks and returns whether the authorizing process is currently ongoing. public var isAuthorizing: Bool { return currentProcess != nil @@ -57,36 +58,36 @@ public class LoginManager { /// A flag to prevent setup multiple times var setup = false - + private init() { } - + /// Sets up the current `LoginManager` instance. /// /// - Parameters: /// - channelID: The channel ID for your app. - /// - universalLinkURL: The universal link used to navigate back to your app from the LINE app. + /// - universalLinkURL: The universal link used to navigate back to your app from LINE. /// - Note: /// Call this method before you access any other methods or properties in the LINE SDK. Call this method /// only once because the login manager cannot be set up multiple times. /// /// We strongly suggest that you specify a valid universal link URL. Set up your own universal link /// callback for your channel by following the guide on the LINE Developers site. When the callback is set - /// properly, the LINE app will try to bring up your app with the universal link first, which improves the + /// properly, LINE will try to bring up your app with the universal link first, which improves the /// security of the authorization flow and protects your data. If the `universalLinkURL` parameter is - /// `nil`, only a custom URL scheme will be used to open your app after the authorization in the LINE app + /// `nil`, only a custom URL scheme will be used to open your app after the authorization in LINE /// is complete. /// public func setup(channelID: String, universalLinkURL: URL?) { - + lock.lock() defer { lock.unlock() } - + guard !setup else { Log.assertionFailure("Trying to set configuration multiple times is not permitted.") return } defer { setup = true } - + let config = LoginConfiguration(channelID: channelID, universalLinkURL: universalLinkURL) LoginConfiguration._shared = config AccessTokenStore._shared = AccessTokenStore(configuration: config) @@ -100,7 +101,8 @@ public class LoginManager { /// `[.profile]`. /// - viewController: The the view controller that presents the login view controller. If `nil`, the topmost /// view controller in the current view controller hierarchy will be used. - /// - options: The options used during the login process. For more information, see `LoginManagerOptions`. + /// - parameters: The parameters used during the login process. For more information, + /// see `LoginManager.Parameters`. /// - completion: The completion closure to be invoked when the login action is finished. /// - Returns: The `LoginProcess` object which indicates that this method has started the login process. /// @@ -108,39 +110,55 @@ public class LoginManager { /// Only one process can be started at a time. Do not call this method again to start a new login process /// before `completion` is invoked. /// - /// If the value of `permissions` is `.profile`, the user profile will be retrieved during the login + /// If the value of `permissions` contains `.profile`, the user profile will be retrieved during the login /// process and contained in the `userProfile` property of the `LoginResult` object in `completion`. /// Otherwise, the `userProfile` property will be `nil`. Use this profile to identify your user. For /// more information, see `UserProfile`. /// - /// An access token will be issued if the user authorizes your app. This token and a refresh token + /// An access token will be issued if the user authorizes your app. This token and a refresh token /// will be automatically stored in the keychain of your app for later use. You do not need to /// refresh the access token manually because any API call will attempt to refresh the access token if - /// necessary. However, if you would like to refresh the access token manually, use the - /// `API.refreshAccessToken(with:)` method. + /// necessary. However, if you need to refresh the access token manually, use the + /// `API.Auth.refreshAccessToken(with:)` method. /// @discardableResult public func login( permissions: Set = [.profile], in viewController: UIViewController? = nil, - options: LoginManagerOptions = [], - completionHandler completion: @escaping (Result) -> Void) -> LoginProcess? + parameters: LoginManager.Parameters = .init(), + completionHandler completion: @escaping (Result) -> Void + ) -> LoginProcess? { lock.lock() defer { lock.unlock() } - - guard currentProcess == nil else { - Log.assertionFailure("Trying to start another login process " + - "while the previous one still valid is not permitted.") + + if !parameters.allowRecreatingLoginProcess && isAuthorizing { + Log.print("Trying to start another login process while previous process is still valid. " + + "New login process is ignored. Set `allowRecreatingLoginProcess` in login parameter" + + "if you want to allow this action.") return nil } + if parameters.allowRecreatingLoginProcess && isAuthorizing { + if let process = currentProcess { + self.currentProcess?.onFail.call( + LineSDKError.generalError(reason: .processDiscarded(process)) + ) + } else { + Log.assertionFailure("Current process couldn't be asserted. This shouldn't happen." + + "Please report an issue here: https://github.com/line/line-sdk-ios-swift/issues") + } + } + + // Reset cached values for current user + UserDefaultsValue.clear() + + // Start login with a process let process = LoginProcess( configuration: LoginConfiguration.shared, scopes: permissions, - options: options, + parameters: parameters, viewController: viewController) - process.start() process.onSucceed.delegate(on: self) { [unowned process] (self, result) in self.currentProcess = nil self.postLogin( @@ -153,11 +171,13 @@ public class LoginManager { self.currentProcess = nil completion(.failure(error.sdkError)) } + + process.start() self.currentProcess = process return currentProcess } - + /// Actions after auth process finishes. We do something like storing token, getting user profile and ID /// token verification before we can inform framework users every thing is done. /// @@ -175,34 +195,42 @@ public class LoginManager { let group = DispatchGroup() var profile: UserProfile? - var webToken: JWK? + + var providerMetadata: DiscoveryDocument.ResolvedProviderMetadata? + // Any possible errors will be held here. var errors: [Error] = [] - + if token.permissions.contains(.profile) { // We need to pass token since it is not stored in keychain yet. getUserProfile(with: token, in: group) { result in - profile = result.value - result.error.map { errors.append($0) } + do { profile = try result.get() } + catch { errors.append(error) } } } - + if token.permissions.contains(.openID) { - getJWK(for: token, in: group) { result in - webToken = result.value - result.error.map { errors.append($0) } + getProviderMetadata(for: token, in: group) { result in + do { providerMetadata = try result.get() } + catch { errors.append(error) } } } group.notify(queue: .main) { guard errors.isEmpty else { - completion(.failure(errors[0].sdkError)) + let error = errors[0] + completion(.failure(error.sdkError)) return } - if let key = webToken { + if let providerMetadata = providerMetadata { do { - try self.verifyIDToken(token.IDToken!, key: key, process: process, userID: profile?.userID) + try self.verifyIDToken( + token.IDToken!, + providerMetadata: providerMetadata, + process: process, + userID: profile?.userID + ) } catch { if let cryptoError = error as? CryptoError { completion(.failure(.authorizeFailed(reason: .cryptoError(error: cryptoError)))) @@ -212,7 +240,7 @@ public class LoginManager { return } } - + // Everything goes fine now. Store token. let result = Result { try AccessTokenStore.shared.setCurrentToken(token) @@ -221,20 +249,22 @@ public class LoginManager { accessToken: token, permissions: Set(token.permissions), userProfile: profile, - friendshipStatusChanged: response.friendshipStatusChanged) + friendshipStatusChanged: response.friendshipStatusChanged, + IDTokenNonce: process.IDTokenNonce + ) } completion(result) } } - /// Logs out the current user by revoking the access token. + /// Logs out the current user by revoking the refresh token and all its corresponding access tokens. /// /// - Parameter completion: The completion closure to be invoked when the logout action is finished. public func logout(completionHandler completion: @escaping (Result<(), LineSDKError>) -> Void) { - API.revokeAccessToken(completionHandler: completion) + API.Auth.revokeRefreshToken(completionHandler: completion) } - - /// Asks this `LoginManager` object to handle a URL callback from either the LINE app or the web login flow. + + /// Asks this `LoginManager` object to handle a URL callback from either LINE or the web login flow. /// /// - Parameters: /// - app: The singleton app object. @@ -252,9 +282,74 @@ public class LoginManager { { guard let url = url else { return false } guard let currentProcess = currentProcess else { return false } - - let sourceApplication = options[.sourceApplication] as? String - return currentProcess.resumeOpenURL(url: url, sourceApplication: sourceApplication) + + return currentProcess.resumeOpenURL(url: url) + } + + // MARK: - Deprecated + + /// Sets the preferred language used when logging in with the web authorization flow. + /// + /// If not set, the web authentication flow shows the login page in the user's device language, or falls + /// back to English. Once set, the web page will be displayed in the preferred language. + /// + /// - Note: + /// This property does not affect the preferred language when LINE is used for authorization. + /// LINE and the login screen are always displayed in the user's device language. + @available( + *, deprecated, + message: """ + Set the preferred language in a `LoginManager.Parameters` value and use + `login(permissions:in:parameters:completionHandler:)` instead.") + """) + public var preferredWebPageLanguage: WebPageLanguage? = nil + + /// Logs in to the LINE Platform. + /// + /// - Parameters: + /// - permissions: The set of permissions requested by your app. The default value is + /// `[.profile]`. + /// - viewController: The the view controller that presents the login view controller. If `nil`, the topmost + /// view controller in the current view controller hierarchy will be used. + /// - options: The options used during the login process. For more information, see `LoginManagerOptions`. + /// - completion: The completion closure to be invoked when the login action is finished. + /// - Returns: The `LoginProcess` object which indicates that this method has started the login process. + /// + /// - Note: + /// Only one process can be started at a time. Do not call this method again to start a new login process + /// before `completion` is invoked. + /// + /// If the value of `permissions` is `.profile`, the user profile will be retrieved during the login + /// process and contained in the `userProfile` property of the `LoginResult` object in `completion`. + /// Otherwise, the `userProfile` property will be `nil`. Use this profile to identify your user. For + /// more information, see `UserProfile`. + /// + /// An access token will be issued if the user authorizes your app. This token and a refresh token + /// will be automatically stored in the keychain of your app for later use. You do not need to + /// refresh the access token manually because any API call will attempt to refresh the access token if + /// necessary. However, if you need to refresh the access token manually, use the + /// `API.Auth.refreshAccessToken(with:)` method. + /// + @available( + *, deprecated, + message: """ + Convert the `options` to a `LoginManager.Parameters` value and + use `login(permissions:in:parameters:completionHandler:)` instead.") + """) + @discardableResult + public func login( + permissions: Set = [.profile], + in viewController: UIViewController? = nil, + options: LoginManagerOptions, + completionHandler completion: @escaping (Result) -> Void) -> LoginProcess? + { + let parameters = Parameters(options: options, language: preferredWebPageLanguage) + return login( + permissions: permissions, + in: viewController, + parameters: parameters, + completionHandler: completion + ) } } @@ -265,17 +360,17 @@ extension LoginManager { handler: @escaping (Result) -> Void) { group.enter() - + Session.shared.send(GetUserProfileRequestInjectedToken(token: token.value)) { profileResult in handler(profileResult) group.leave() } } - func getJWK( + func getProviderMetadata( for token: AccessToken, in group: DispatchGroup, - handler: @escaping (Result) -> Void) + handler: @escaping (Result) -> Void) { group.enter() // We need a valid ID Token existing to continue. @@ -292,8 +387,8 @@ extension LoginManager { group.leave() return } - - // Use Discovery Document to find JWKs URI. How about introducing some promise mechanism + + // Use Discovery Document to find JWKs URI. How about introducing some promise mechanism Session.shared.send(GetDiscoveryDocumentRequest()) { documentResult in switch documentResult { case .success(let document): @@ -307,7 +402,9 @@ extension LoginManager { group.leave() return } - handler(.success(key)) + handler( + .success(DiscoveryDocument.ResolvedProviderMetadata(issuer: document.issuer, jwk: key)) + ) group.leave() case .failure(let err): handler(.failure(err)) @@ -320,23 +417,27 @@ extension LoginManager { } } } - - func verifyIDToken(_ token: JWT, key: JWK, process: LoginProcess, userID: String?) throws { - - try token.verify(with: key) + + func verifyIDToken( + _ token: JWT, + providerMetadata: DiscoveryDocument.ResolvedProviderMetadata, + process: LoginProcess, userID: String?) throws + { + + try token.verify(with: providerMetadata.jwk) let payload = token.payload - try payload.verify(keyPath: \.issuer, expected: "https://access.line.me") + try payload.verify(keyPath: \.issuer, expected: providerMetadata.issuer) if let userID = userID { try payload.verify(keyPath: \.subject, expected: userID) } try payload.verify(keyPath: \.audience, expected: process.configuration.channelID) - + let now = Date() let allowedClockSkew: TimeInterval = 5 * 60 try payload.verify(keyPath: \.expiration, laterThan: now.addingTimeInterval(-allowedClockSkew)) try payload.verify(keyPath: \.issueAt, earlierThan: now.addingTimeInterval(allowedClockSkew)) - try payload.verify(keyPath: \.nonce, expected: process.tokenIDNonce!) + try payload.verify(keyPath: \.nonce, expected: process.IDTokenNonce!) } } diff --git a/LineSDK/LineSDK/Login/LoginManagerOptions.swift b/LineSDK/LineSDK/Login/LoginManagerOptions.swift index bef4e457..3d61bf4b 100644 --- a/LineSDK/LineSDK/Login/LoginManagerOptions.swift +++ b/LineSDK/LineSDK/Login/LoginManagerOptions.swift @@ -1,13 +1,13 @@ // // LoginManagerOptions.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -21,7 +21,10 @@ import Foundation +/// - Warning: Deprecated. Use `LoginManager.Parameters` type instead. +/// /// Represents options for logging in to the LINE Platform using the `LoginManager` class. +@available(*, deprecated, message: "Use `LoginManager.Parameters` type instead.") public struct LoginManagerOptions: OptionSet { /// The raw value of an option. @@ -34,19 +37,28 @@ public struct LoginManagerOptions: OptionSet { self.rawValue = rawValue } + /// - Warning: Deprecated. Use `LoginManager.Parameters.onlyWebLogin` instead. + /// /// Uses the web authentication flow instead of the LINE app-to-app authentication flow. + @available(*, deprecated, message: "Use `LoginManager.Parameters.onlyWebLogin` instead.") public static let onlyWebLogin = LoginManagerOptions(rawValue: 1 << 0) - /// Includes an option to add a bot as friend on the consent screen. If `.botPromptNormal` and + /// - Warning: Deprecated. Use `LoginManager.Parameters.botPromptStyle` instead. + /// + /// Includes an option to add a LINE Official Account as friend on the consent screen. If `.botPromptNormal` and /// `.botPromptAggressive` are set at the same time, `.botPromptAggressive` will be used. + @available(*, deprecated, message: "Use `LoginManager.Parameters.botPromptStyle` instead.") public static let botPromptNormal = LoginManagerOptions(rawValue: 1 << 1) - /// Opens a new screen to add a bot as a friend after the user agrees to the permissions on the consent - /// screen. If `.botPromptNormal` and `.botPromptAggressive` is set at the same time, + /// - Warning: Deprecated. Use `LoginManager.Parameters.botPromptStyle` instead. + /// + /// Opens a new screen to add a LINE Official Account as a friend after the user agrees to the permissions on the + /// consent screen. If `.botPromptNormal` and `.botPromptAggressive` is set at the same time, /// `.botPromptAggressive` will be used. + @available(*, deprecated, message: "Use `LoginManager.Parameters.botPromptStyle` instead.") public static let botPromptAggressive = LoginManagerOptions(rawValue: 1 << 2) - var botPrompt: LoginProcess.BotPrompt? { + var botPrompt: LoginManager.BotPrompt? { if contains(.botPromptAggressive) { return .aggressive } if contains(.botPromptNormal) { return .normal } return nil diff --git a/LineSDK/LineSDK/Login/LoginManagerParameters.swift b/LineSDK/LineSDK/Login/LoginManagerParameters.swift new file mode 100644 index 00000000..199e6a70 --- /dev/null +++ b/LineSDK/LineSDK/Login/LoginManagerParameters.swift @@ -0,0 +1,153 @@ +// +// LoginManagerParameters.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +extension LoginManager { + + /// Parameters used during login. + public struct Parameters { + + /// Forces the use of web authentication flow instead of LINE app-to-app authentication flow. + public var onlyWebLogin: Bool = false + + /// The style for showing the "Add LINE Official Account as friend" prompt on the consent screen. + public var botPromptStyle: BotPrompt? = nil + + /// Sets the preferred language used when logging in with the web authorization flow. + /// + /// If not set, the web authentication flow shows the login page in the user's device language, or falls + /// back to English. Once set, the web page is displayed in the preferred language. + /// + /// - Note: + /// This property does not affect the preferred language when LINE is used for authorization. + /// LINE and the login screen are always displayed in the user's device language. + /// + public var preferredWebPageLanguage: WebPageLanguage? = nil + + /// :nodoc: + // Not yet public available. + public var promptBotID: String? = nil + + /// Sets the nonce value for ID token verification. This value is used when requesting user authorization + /// with `.openID` permission to prevent replay attacks to your backend server. If not set, LINE SDK will + /// generate a random value as the token nonce. Whether set or not, LINE SDK verifies against the nonce value + /// in received ID token locally. + public var IDTokenNonce: String? = nil + + /// Determines whether it's possible to create another login process while the original one is still valid. + /// If `true`, when a new login action is started, any existing one ends with a + /// `GeneralErrorReason.processDiscarded` error. If `false`, the new login action is ignored, and the + /// existing one continues to wait for a result. + /// When the deploy target is **macCatalyst**, the default value is `true`. In other cases, it's `false`. + #if targetEnvironment(macCatalyst) + public var allowRecreatingLoginProcess = true + #else + public var allowRecreatingLoginProcess = false + #endif + + /// Specifies the initial web authentication method to be used when starting the login process. + /// + /// By default, `.email` is used to provide input boxes for email and password. + public var initialWebAuthenticationMethod: WebAuthenticationMethod = .email + + /// Creates a default `LoginManager.Parameters` value. + public init() {} + + // MARK: - Deprecated + /// :nodoc: + @available(*, deprecated, + message: "Internally deprecated to suppress warning. Set properties in `Parameters` instead.") + public init(options: LoginManagerOptions, language: WebPageLanguage?) { + self.onlyWebLogin = options.contains(.onlyWebLogin) + self.botPromptStyle = options.botPrompt + self.preferredWebPageLanguage = language + } + } +} + +extension LoginManager { + + /// The style for showing the "Add LINE Official Account as friend" prompt on the consent screen. + public enum BotPrompt: String { + /// Includes an option to add a LINE Official Account as friend on the consent screen. + case normal + /// Opens a new screen to add a LINE Official Account as a friend after the user agrees to the permissions on the + /// consent screen. + case aggressive + } + + /// The method used for the authentication when using the web authentication flow. + public enum WebAuthenticationMethod: String { + case email + case qrCode + } + + /// Represents the language used in the web page. + public struct WebPageLanguage { + /// :nodoc: + public let rawValue: String + + /// Creates a web page language with a given raw string language code value. + /// + /// - Parameter rawValue: The value represents the language code. + public init(rawValue: String) { + self.rawValue = rawValue + } + + /// The Arabic language. + public static let arabic = WebPageLanguage(rawValue: "ar") + /// The German language. + public static let german = WebPageLanguage(rawValue: "de") + /// The English language. + public static let english = WebPageLanguage(rawValue: "en") + /// The Spanish language. + public static let spanish = WebPageLanguage(rawValue: "es") + /// The French language. + public static let french = WebPageLanguage(rawValue: "fr") + /// The Indonesian language. + public static let indonesian = WebPageLanguage(rawValue: "id") + /// The Italian language. + public static let italian = WebPageLanguage(rawValue: "it") + /// The Japanese language. + public static let japanese = WebPageLanguage(rawValue: "ja") + /// The Korean language. + public static let korean = WebPageLanguage(rawValue: "ko") + /// The Malay language. + public static let malay = WebPageLanguage(rawValue: "ms") + /// The Brazilian Portuguese language. + public static let portugueseBrazilian = WebPageLanguage(rawValue: "pt-BR") + /// The European Portuguese language. + public static let portugueseEuropean = WebPageLanguage(rawValue: "pt-PT") + /// The Russian language. + public static let russian = WebPageLanguage(rawValue: "ru") + /// The Thai language. + public static let thai = WebPageLanguage(rawValue: "th") + /// The Turkish language. + public static let turkish = WebPageLanguage(rawValue: "tr") + /// The Vietnamese language. + public static let vietnamese = WebPageLanguage(rawValue: "vi") + /// The Simplified Chinese language. + public static let chineseSimplified = WebPageLanguage(rawValue: "zh-Hans") + /// The Traditional Chinese language. + public static let chineseTraditional = WebPageLanguage(rawValue: "zh-Hant") + } +} diff --git a/LineSDK/LineSDK/Login/LoginPermission.swift b/LineSDK/LineSDK/Login/LoginPermission.swift index 3d033a2a..70150847 100644 --- a/LineSDK/LineSDK/Login/LoginPermission.swift +++ b/LineSDK/LineSDK/Login/LoginPermission.swift @@ -1,13 +1,13 @@ // // LoginPermission.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -25,36 +25,49 @@ import Foundation public struct LoginPermission: Hashable { /// The raw value of the permission. A `LoginPermission` object is composed of a plain raw string. public let rawValue: String - + /// Initializes a `LoginPermission` value with a plain string. Use this method to set permissions that /// are not defined in the framework. public init(rawValue: String) { self.rawValue = rawValue } - + /// The permission to get an ID token in the login response. public static let openID = LoginPermission(rawValue: "openid") - + /// The permission to get the user's profile including the user ID, display name, and the profile image /// URL in the login response. public static let profile = LoginPermission(rawValue: "profile") + /// The permission to select friends or groups and share content with them. + public static let oneTimeShare = LoginPermission(rawValue: "onetime.share") + /// :nodoc: /// LINE internal use only. The permission to get friends information of current user. public static let friends = LoginPermission(rawValue: "friends") - + /// :nodoc: /// LINE internal use only. The permission to get groups information of current user. public static let groups = LoginPermission(rawValue: "groups") - + /// :nodoc: /// LINE internal use only. The permission to write a message as current user. public static let messageWrite = LoginPermission(rawValue: "message.write") + + /// The permission to check Open Chat use term agreement status. This is necessary if you want to create or join an + /// open chat room. + public static let openChatTermStatus = LoginPermission(rawValue: "openchat.term.agreement.status") + + /// The permission to create or join to an Open Chat room. + public static let openChatRoomCreateAndJoin = LoginPermission(rawValue: "openchat.create.join") + + /// The permission to check subscription information of an Open Chat room. + public static let openChatInfo = LoginPermission(rawValue: "openchat.info") } -/// Subpermissions of .openID. Permissions in this extension will not be included in the `permissions` property of +/// Sub-permissions of .openID. Permissions in this extension will not be included in the `permissions` property of /// issued access token. -public extension LoginPermission { +extension LoginPermission { /// The permission to get the user's email from an ID Token in the login response. This permission /// requires the `.openID` permission to be granted at the same time. The channel of your app must have /// the email permission that can be configured in the LINE Developers console. @@ -62,28 +75,38 @@ public extension LoginPermission { } /// :nodoc: -/// LINE internal use only. Subpermissions of .openID. Permissions in this extension will not be included in +/// LINE internal use only. Sub-permissions of .openID. Permissions in this extension will not be included in /// the `permissions` property of issued access token. -public extension LoginPermission { - /// Whether could access user's phone inside ID Token. Requires `.openID` set. +extension LoginPermission { + /// Whether you can access user's phone inside ID Token. Requires `.openID` set. /// Only available to LINE internal partners. public static let phone = LoginPermission(rawValue: "phone") - - /// Whether could access user's gender inside ID Token. Requires `.openID` set. + + /// Whether you can access user's gender inside ID Token. Requires `.openID` set. /// Only available to LINE internal partners. public static let gender = LoginPermission(rawValue: "gender") - - /// Whether could access user's birthdate inside ID Token. Requires `.openID` set. + + /// Whether you can access user's date of birth inside ID Token. Requires `.openID` set. /// Only available to LINE internal partners. public static let birthdate = LoginPermission(rawValue: "birthdate") - - /// Whether could access user's address inside ID Token. Requires `.openID` set. + + /// Whether you can access user's address inside ID Token. Requires `.openID` set. /// Only available to LINE internal partners. public static let address = LoginPermission(rawValue: "address") - - /// Whether could access user's real name inside ID Token. Requires `.openID` set. + + /// Whether you can access user's real name inside ID Token. Requires `.openID` set. /// Only available to LINE internal partners. - public static let realName = LoginPermission(rawValue: "real_name") + public static let realName = LoginPermission(rawValue: "real_name") +} + +/// :nodoc: +/// LINE internal use only. Sub-permissions of Open Chat Plug. +extension LoginPermission { + public static let openChatPlugManagement = LoginPermission(rawValue: "openchatplug.managament") + public static let openChatPlugInfo = LoginPermission(rawValue: "openchatplug.info") + public static let openChatPlugProfile = LoginPermission(rawValue: "openchatplug.profile") + public static let openChatPlugSendMessage = LoginPermission(rawValue: "openchatplug.send.message") + public static let openChatPlugReceiveMessageEvent = LoginPermission(rawValue: "openchatplug.receive.message.and.event") } /// :nodoc: diff --git a/LineSDK/LineSDK/Login/LoginProcess.swift b/LineSDK/LineSDK/Login/LoginProcess.swift index a9355bd3..e8578d8e 100644 --- a/LineSDK/LineSDK/Login/LoginProcess.swift +++ b/LineSDK/LineSDK/Login/LoginProcess.swift @@ -1,13 +1,13 @@ // // LoginProcess.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -27,28 +27,48 @@ import SafariServices /// login flows will run serially. If a flow logs in the user successfully, subsequent flows will not be /// executed. public class LoginProcess { - - enum BotPrompt: String { - case normal - case aggressive + + /// Represents a login route for how the auth flow is initiated. + public enum LoginRoute: String { + /// The auth flow starts with a LINE app universal link. + case appUniversalLink + /// The auth flow starts with a LINE customize URL scheme. + case appAuthScheme + /// The auth flow starts in a web page inside LINE SDK. + case webLogin } - + struct FlowParameters { let channelID: String let universalLinkURL: URL? let scopes: Set - let otp: OneTimePassword + let pkce: PKCE let processID: String let nonce: String? - let botPrompt: BotPrompt? + + let loginParameter: LoginManager.Parameters + + var botPrompt: LoginManager.BotPrompt? { + loginParameter.botPromptStyle + } + var preferredWebPageLanguage: LoginManager.WebPageLanguage? { + loginParameter.preferredWebPageLanguage + } + var onlyWebLogin: Bool { + loginParameter.onlyWebLogin + } + var promptBotID: String? { + loginParameter.promptBotID + } } /// Observes application switching to foreground. + /// /// - Note: /// If the app switching happens during login process, we want to /// inspect the event of switched back from another app (Safari or LINE or any other) /// If the framework container app has not been started up by an `open(url:)`, we think current - /// login process fails and we need to call the completion closuer with a `.userCancelled` error. + /// login process fails and we need to call the completion closure with a `.userCancelled` error. class AppSwitchingObserver { // A token holds current observing. It will be released and trigger remove observer // when this `AppSwitchingObserver` gets released. @@ -76,15 +96,28 @@ public class LoginProcess { let configuration: LoginConfiguration let scopes: Set - let options: LoginManagerOptions - + let parameters: LoginManager.Parameters + // Flows of login process. A flow will be `nil` until it is running, so we could tell which one should take // responsibility to handle a url callback response. // LINE Client app auth flow captured by LINE universal link. - var appUniversalLinkFlow: AppUniversalLinkFlow? + var appUniversalLinkFlow: AppUniversalLinkFlow? { + didSet { + if appUniversalLinkFlow != nil && loginRoute == nil { + loginRoute = .appUniversalLink + } + } + } // LINE Client app auth flow by LINE customize URL scheme. - var appAuthSchemeFlow: AppAuthSchemeFlow? + var appAuthSchemeFlow: AppAuthSchemeFlow? { + didSet { + if appAuthSchemeFlow != nil && loginRoute == nil { + loginRoute = .appAuthScheme + } + } + } + // Web login flow with Safari View Controller or Mobile Safari var webLoginFlow: WebLoginFlow? { didSet { @@ -92,68 +125,81 @@ public class LoginProcess { if webLoginFlow == nil { oldValue?.dismiss() } + + if webLoginFlow != nil && loginRoute == nil { + loginRoute = .webLogin + } } } - + + /// Describes how the authentication flow is initiated for this login result. + /// + /// If the LINE app was launched to obtain this result, the value will be either `.appUniversalLink` or + /// `.appAuthScheme`, depending on how the LINE app was opened. If authentication occurred via a web page within + /// the LINE SDK, the value will be `.webLogin`. If the authentication flow is never or not yet initiated, the value + /// will be `nil`. + /// + /// This value is `nil` until the process starts the auth flow actually. You can access this value safely when an + /// auth result is retrieved. + public private(set) var loginRoute: LoginRoute? + // When we leave current app, we need to set the switching observer // to intercept cancel event (switching back but without a token url response) var appSwitchingObserver: AppSwitchingObserver? weak var presentingViewController: UIViewController? - /// A UUID string of current process. Used to verify with server `state` response. + /// A random piece of data for current process. Used to verify with server `state` response. let processID: String - /// A string used to prevent replay attacks. This value is returned in an ID token. - let tokenIDNonce: String? - - var otp: OneTimePassword! + /// A string used to prevent replay attacks. This value will be returned in an ID token. + let IDTokenNonce: String? + let pkce: PKCE + let onSucceed = Delegate<(token: AccessToken, response: LoginProcessURLResponse), Void>() let onFail = Delegate() init( configuration: LoginConfiguration, scopes: Set, - options: LoginManagerOptions, + parameters: LoginManager.Parameters, viewController: UIViewController?) { self.configuration = configuration - self.processID = UUID().uuidString + self.processID = Data.randomData(bytesCount: 32).base64URLEncoded + self.pkce = PKCE() self.scopes = scopes - self.options = options + self.parameters = parameters self.presentingViewController = viewController if scopes.contains(.openID) { - tokenIDNonce = UUID().uuidString + IDTokenNonce = self.parameters.IDTokenNonce ?? Data.randomData(bytesCount: 32).base64URLEncoded } else { - tokenIDNonce = nil + IDTokenNonce = nil } } func start() { - let otpRequest = PostOTPRequest(channelID: configuration.channelID) - Session.shared.send(otpRequest) { result in - switch result { - case .success(let otp): - self.otp = otp - let parameters = FlowParameters( - channelID: self.configuration.channelID, - universalLinkURL: self.configuration.universalLinkURL, - scopes: self.scopes, - otp: otp, - processID: self.processID, - nonce: self.tokenIDNonce, - botPrompt: self.options.botPrompt) - if self.options.contains(.onlyWebLogin) { - self.startWebLoginFlow(parameters) - } else { - self.startAppUniversalLinkFlow(parameters) - } - case .failure(let error): - self.invokeFailure(error: error) - } + let parameters = FlowParameters( + channelID: configuration.channelID, + universalLinkURL: configuration.universalLinkURL, + scopes: scopes, + pkce: pkce, + processID: processID, + nonce: IDTokenNonce, + loginParameter: parameters + ) + #if targetEnvironment(macCatalyst) + // On macCatalyst, we only support web login + startWebLoginFlow(parameters) + #else + if parameters.onlyWebLogin { + startWebLoginFlow(parameters) + } else { + startAppUniversalLinkFlow(parameters) } + #endif } /// Stops the login process. The login process will fail with a `.forceStopped` error. @@ -218,9 +264,6 @@ public class LoginProcess { switch result { case .safariViewController: self.webLoginFlow = webLoginFlow - case .externalSafari: - self.setupAppSwitchingObserver() - self.webLoginFlow = webLoginFlow case .error(let error): // Starting login flow failed. There is no more // fallback methods or cannot find correct view controller. @@ -235,8 +278,8 @@ public class LoginProcess { webLoginFlow.start(in: presentingViewController) } - func resumeOpenURL(url: URL, sourceApplication: String?) -> Bool { - + func resumeOpenURL(url: URL) -> Bool { + let isValidUniversalLinkURL = configuration.isValidUniversalLinkURL(url: url) let isValidCustomizeURL = configuration.isValidCustomizeURL(url: url) @@ -246,51 +289,63 @@ public class LoginProcess { return false } - // For universal link callback, we can skip source application checking. - // Just do it for customize URL scheme. - if isValidCustomizeURL { - guard let sourceApp = sourceApplication, configuration.isValidSourceApplication(appID: sourceApp) else { - invokeFailure(error: LineSDKError.authorizeFailed(reason: .invalidSourceApplication)) - return false - } - } - // It is the callback url we could handle, so the app switching observer should be invalidated. appSwitchingObserver?.valid = false // Wait for a while before request access token. // // When switching back to SDK container app from another app, with url scheme or universal link, - // the URL Session is not avaliable yet (sending a request causes "53: Software caused connection abort" or + // the URL Session is not available yet (sending a request causes "53: Software caused connection abort" or // "-1005 The network connection was lost.", seems only happening on some iOS 12 devices). // So as a workaround, we need wait for a while before continuing. // // ref: https://github.com/AFNetworking/AFNetworking/issues/4279 + // + // https://github.com/AFNetworking/AFNetworking/issues/4279#issuecomment-447108981 + // It seems that plan A in the comment above also works great (even when the background execution time + // expired). But I cannot explain why the `URLSession` can retry the request even when background task ends. + // Maybe it is some internal implementation. Delay the request now works fine so we choose it as a workaround. + // + // In some edge cases, the network would be still lost after 0.3 sec of delay. But it should be very rare. + // So an auto retry for NSURLErrorNetworkConnectionLost (-1005) is applied to make sure the error not happen. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { do { let response = try LoginProcessURLResponse(from: url, validatingWith: self.processID) - let tokenExchangeRequest = PostExchangeTokenRequest( - channelID: self.configuration.channelID, - code: response.requestToken, - otpValue: self.otp.otp, - redirectURI: Constant.thirdPartyAppReturnURL, - optionalRedirectURI: self.configuration.universalLinkURL?.absoluteString) - Session.shared.send(tokenExchangeRequest) { tokenResult in - switch tokenResult { - case .success(let token): self.invokeSuccess(result: token, response: response) - case .failure(let error): self.invokeFailure(error: error) - } - } + self.exchangeToken(response: response, canRetryOnNetworkLost: true) } catch { self.invokeFailure(error: error) } } - + return true } + + private func exchangeToken(response: LoginProcessURLResponse, canRetryOnNetworkLost: Bool) { + + let tokenExchangeRequest = PostExchangeTokenRequest( + channelID: self.configuration.channelID, + code: response.requestToken, + codeVerifier: self.pkce.codeVerifier, + redirectURI: Constant.thirdPartyAppReturnURL, + optionalRedirectURI: self.configuration.universalLinkURL?.absoluteString) + Session.shared.send(tokenExchangeRequest) { tokenResult in + switch tokenResult { + case .success(let token): self.invokeSuccess(result: token, response: response) + case .failure(let error): + if error.isURLSessionErrorCode(sessionErrorCode: NSURLErrorNetworkConnectionLost) && canRetryOnNetworkLost { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.exchangeToken(response: response, canRetryOnNetworkLost: false) + } + } else { + self.invokeFailure(error: error) + } + } + } + + } private var canUseLineAuthV2: Bool { - return UIApplication.shared.canOpenURL(Constant.lineAppAuthURLv2) + return Constant.isLINEInstalled } private func resetFlows() { @@ -349,7 +404,6 @@ class WebLoginFlow: NSObject { enum Next { case safariViewController - case externalSafari case error(Error) } @@ -360,8 +414,15 @@ class WebLoginFlow: NSObject { weak var safariViewController: UIViewController? init(parameter: LoginProcess.FlowParameters) { - let webLoginURLBase = URL(string: Constant.lineWebAuthURL)! - url = webLoginURLBase.appendedLoginQuery(parameter) + var component = URLComponents(string: Constant.lineWebAuthURL)! + if parameter.loginParameter.initialWebAuthenticationMethod == .qrCode { + if let _ = component.fragment { + assertionFailure("Multiple fragment is not yet supported. Require review or report to developer.") + } + component.fragment = "/qr" + } + let baseURL = component.url! + url = baseURL.appendedLoginQuery(parameter) } func start(in viewController: UIViewController?) { @@ -369,7 +430,8 @@ class WebLoginFlow: NSObject { safariViewController.modalPresentationStyle = .overFullScreen safariViewController.modalTransitionStyle = .coverVertical safariViewController.delegate = self - + safariViewController.dismissButtonStyle = .cancel + self.safariViewController = safariViewController guard let presenting = viewController ?? .topMost else { @@ -388,8 +450,12 @@ class WebLoginFlow: NSObject { extension WebLoginFlow: SFSafariViewControllerDelegate { func safariViewControllerDidFinish(_ controller: SFSafariViewController) { + // macCatalyst calls `didFinish` immediately when open page in Safari. + // It should not be a cancellation. + #if !targetEnvironment(macCatalyst) // This happens when user tap "Cancel" in the SFSafariViewController. onCancel.call() + #endif } } @@ -403,7 +469,8 @@ extension String { "sdk_ver": Constant.SDKVersion, "client_id": parameter.channelID, "scope": (parameter.scopes.map { $0.rawValue }).joined(separator: " "), - "otpId": parameter.otp.otpId, + "code_challenge": parameter.pkce.codeChallenge, + "code_challenge_method": parameter.pkce.codeChallengeMethod, "state": parameter.processID, "redirect_uri": Constant.thirdPartyAppReturnURL, ] @@ -417,6 +484,9 @@ extension String { if let botPrompt = parameter.botPrompt { parameters["bot_prompt"] = botPrompt.rawValue } + if let promptBotID = parameter.promptBotID { + parameters["prompt_bot_id"] = promptBotID + } let base = URL(string: "/oauth2/v2.1/authorize/consent")! let encoder = URLQueryEncoder(parameters: parameters) return encoder.encoded(for: base).absoluteString @@ -426,10 +496,17 @@ extension String { extension URL { func appendedLoginQuery(_ flowParameters: LoginProcess.FlowParameters) -> URL { let returnUri = String.returnUri(flowParameters) - let parameters: [String: Any] = [ + var parameters: [String: Any] = [ "returnUri": returnUri, "loginChannelId": flowParameters.channelID ] + if let lang = flowParameters.preferredWebPageLanguage { + parameters["ui_locales"] = lang.rawValue + } + if flowParameters.onlyWebLogin { + parameters["disable_ios_auto_login"] = true + } + let encoder = URLQueryEncoder(parameters: parameters) return encoder.encoded(for: self) } @@ -447,7 +524,7 @@ extension URL { extension UIWindow { static func findKeyWindow() -> UIWindow? { - if let window = UIApplication.shared.keyWindow, window.windowLevel == .normal { + if let window = (UIApplication.shared.windows.filter {$0.isKeyWindow}.first), window.windowLevel == .normal { // A key window of main app exists, go ahead and use it return window } diff --git a/LineSDK/LineSDK/Login/LoginProcessURLResponse.swift b/LineSDK/LineSDK/Login/LoginProcessURLResponse.swift index 876f5417..b2629e2b 100644 --- a/LineSDK/LineSDK/Login/LoginProcessURLResponse.swift +++ b/LineSDK/LineSDK/Login/LoginProcessURLResponse.swift @@ -1,13 +1,13 @@ // // LoginProcessURLResponse.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Login/LoginResult.swift b/LineSDK/LineSDK/Login/LoginResult.swift index fe64f401..abee7e71 100644 --- a/LineSDK/LineSDK/Login/LoginResult.swift +++ b/LineSDK/LineSDK/Login/LoginResult.swift @@ -1,13 +1,13 @@ // // LoginResult.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -23,6 +23,7 @@ import Foundation /// Represents a successful login. public struct LoginResult { + /// The access token obtained by the login process. public let accessToken: AccessToken /// The permissions bound to the `accessToken` object by the authorization process. @@ -30,9 +31,34 @@ public struct LoginResult { /// Contains the user profile including the user ID, display name, and so on. The value exists only when the /// `.profile` permission is set in the authorization request. public let userProfile: UserProfile? - /// Indicates that the friendship status between the user and the bot changed during the login. This value is - /// non-`nil` only if the `.botPromptNormal` or `.botPromptAggressive` are specified as part of the - /// `LoginManagerOption` object when the user logs in. For more information, see Linking a bot with your LINE - /// Login channel at https://developers.line.me/en/docs/line-login/web/link-a-bot/. + /// Indicates that the friendship status between the user and the LINE Official Account changed during the login. + /// This value is non-`nil` only if the `.botPromptNormal` or `.botPromptAggressive` are specified as part of the + /// `LoginManagerOption` object when the user logs in. For more information, see "Add a LINE Official Account as + /// a friend when logged in (bot link)" at https://developers.line.biz/en/docs/line-login/web/link-a-bot/ public let friendshipStatusChanged: Bool? + /// The `nonce` value when requesting ID Token during login process. Use this value as a parameter when you + /// verify the ID Token against the LINE server. This value is `nil` if `.openID` permission is not requested. + public let IDTokenNonce: String? +} + +extension LoginResult: Encodable { + + enum CodingKeys: String, CodingKey { + case accessToken + case scope + case userProfile + case friendshipStatusChanged + case IDTokenNonce + case loginRoute + } + + /// :nodoc: + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(accessToken, forKey: .accessToken) + try container.encodeLoginPermissions(Array(permissions), forKey: .scope) + try container.encodeIfPresent(userProfile, forKey: .userProfile) + try container.encodeIfPresent(friendshipStatusChanged, forKey: .friendshipStatusChanged) + try container.encodeIfPresent(IDTokenNonce, forKey: .IDTokenNonce) + } } diff --git a/LineSDK/LineSDK/Login/Model/AccessToken.swift b/LineSDK/LineSDK/Login/Model/AccessToken.swift index 16f27be6..718d4653 100644 --- a/LineSDK/LineSDK/Login/Model/AccessToken.swift +++ b/LineSDK/LineSDK/Login/Model/AccessToken.swift @@ -1,13 +1,13 @@ // // AccessToken.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -40,35 +40,42 @@ protocol AccessTokenType {} /// `current` property of an `AccessTokenStore` object. /// public struct AccessToken: Codable, AccessTokenType, Equatable { - + /// The value of the access token. public let value: String - + let expiresIn: TimeInterval - + /// The creation time of the access token. It is the system time of the device that receives the current /// access token. public let createdAt: Date - + /// The ID token bound to the access token. The value exists only if the access token is obtained with /// the `.openID` permission. public let IDToken: JWT? - let IDTokenRaw: String? - + + /// The raw string value of the ID token bound to the access token. The value exists only if the access token + /// is obtained with the `.openID` permission. + public let IDTokenRaw: String? + /// The refresh token bound to the access token. - public let refreshToken: String - + @available(*, unavailable, + message: "`refreshToken` is not publicly provided anymore. You should not access or store it yourself.") + public var refreshToken: String { Log.fatalError("`refreshToken` is not publicly provided anymore.") } + + let _refreshToken: String + /// Permissions of the access token. public let permissions: [LoginPermission] let tokenType: String - + /// The expiration time of the access token. It is calculated using `createdAt` and the validity period /// of the access token. This value might not be the actual expiration time because this value depends /// on the system time of the device when `createdAt` is determined. public var expiresAt: Date { return createdAt.addingTimeInterval(expiresIn) } - + enum CodingKeys: String, CodingKey { case value = "access_token" case expiresIn = "expires_in" @@ -78,30 +85,47 @@ public struct AccessToken: Codable, AccessTokenType, Equatable { case tokenType = "token_type" case createdAt } - + /// :nodoc: public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) value = try container.decode(String.self, forKey: .value) expiresIn = try container.decode(TimeInterval.self, forKey: .expiresIn) - + // Try to decode createdAt. If there is no such value, it means we are receiving it // from server and we should create a reference Date for it. // Otherwise, it is the case that loaded from keychain. createdAt = try container.decodeIfPresent(Date.self, forKey: .createdAt) ?? Date() - + IDTokenRaw = try container.decodeIfPresent(String.self, forKey: .IDTokenRaw) if let tokenRaw = IDTokenRaw { IDToken = try JWT(text: tokenRaw) } else { IDToken = nil } - - refreshToken = try container.decode(String.self, forKey: .refreshToken) + + _refreshToken = try container.decode(String.self, forKey: .refreshToken) permissions = try container.decodeLoginPermissions(forKey: .scope) tokenType = try container.decode(String.self, forKey: .tokenType) } - + + // Internal helper for creating a new token object while retaining current ID Token when refreshing. + init(token: AccessToken, currentIDTokenRaw: String?) throws { + self.value = token.value + self.expiresIn = token.expiresIn + self.createdAt = token.createdAt + self._refreshToken = token._refreshToken + self.permissions = token.permissions + self.tokenType = token.tokenType + + self.IDTokenRaw = currentIDTokenRaw + if let tokenRaw = IDTokenRaw { + IDToken = try JWT(text: tokenRaw) + } else { + IDToken = nil + } + } + /// :nodoc: public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -109,12 +133,8 @@ public struct AccessToken: Codable, AccessTokenType, Equatable { try container.encode(expiresIn, forKey: .expiresIn) try container.encode(createdAt, forKey: .createdAt) try container.encodeIfPresent(IDTokenRaw, forKey: .IDTokenRaw) - try container.encode(refreshToken, forKey: .refreshToken) - - let scope = permissions.map { $0.rawValue }.joined(separator: " ") - try container.encode(scope, forKey: .scope) - + try container.encode(_refreshToken, forKey: .refreshToken) + try container.encodeLoginPermissions(permissions, forKey: .scope) try container.encode(tokenType, forKey: .tokenType) } } - diff --git a/LineSDK/LineSDK/Login/Model/AccessTokenStore.swift b/LineSDK/LineSDK/Login/Model/AccessTokenStore.swift index ddc319a5..fb5d041c 100644 --- a/LineSDK/LineSDK/Login/Model/AccessTokenStore.swift +++ b/LineSDK/LineSDK/Login/Model/AccessTokenStore.swift @@ -1,13 +1,13 @@ // // AccessTokenStore.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -108,6 +108,9 @@ public class AccessTokenStore { } static var _shared: AccessTokenStore? + + /// The shared instance of `AccessTokenStore`. Use this instance to access values in the token store of LINE SDK. + /// Access this value after you setup the LINE SDK. Otherwise, your app will be trapped. public static var shared: AccessTokenStore { return guardSharedProperty(_shared) } diff --git a/LineSDK/LineSDK/Login/Model/AccessTokenVerifyResult.swift b/LineSDK/LineSDK/Login/Model/AccessTokenVerifyResult.swift index 4a922b80..d60e9e2a 100644 --- a/LineSDK/LineSDK/Login/Model/AccessTokenVerifyResult.swift +++ b/LineSDK/LineSDK/Login/Model/AccessTokenVerifyResult.swift @@ -1,13 +1,13 @@ // // AccessTokenVerifyResult.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -22,7 +22,7 @@ import Foundation /// Represents a response to the `GetVerifyTokenRequest` method. -public struct AccessTokenVerifyResult: Decodable { +public struct AccessTokenVerifyResult: Codable { /// The channel ID bound to the access token. public let channelID: String @@ -46,4 +46,12 @@ public struct AccessTokenVerifyResult: Decodable { permissions = try container.decodeLoginPermissions(forKey: .scope) expiresIn = try container.decode(TimeInterval.self, forKey: .expiresIn) } + + /// :nodoc: + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(channelID, forKey: .clientID) + try container.encode(expiresIn, forKey: .expiresIn) + try container.encodeLoginPermissions(permissions, forKey: .scope) + } } diff --git a/LineSDK/LineSDK/Login/Model/UserProfile.swift b/LineSDK/LineSDK/Login/Model/UserProfile.swift index a18e19b6..163a4849 100644 --- a/LineSDK/LineSDK/Login/Model/UserProfile.swift +++ b/LineSDK/LineSDK/Login/Model/UserProfile.swift @@ -1,13 +1,13 @@ // // UserProfile.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -22,7 +22,7 @@ import Foundation /// Represents a response to the `GetUserProfileRequest` method. -public struct UserProfile: Decodable { +public struct UserProfile: Codable { /// The user ID of the current authorized user. public let userID: String diff --git a/LineSDK/LineSDK/Login/PKCE/PKCE.swift b/LineSDK/LineSDK/Login/PKCE/PKCE.swift new file mode 100644 index 00000000..b14d2a2c --- /dev/null +++ b/LineSDK/LineSDK/Login/PKCE/PKCE.swift @@ -0,0 +1,61 @@ +// +// PKCE.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +struct PKCE { + + /// If the client is capable of using "S256", it MUST use "S256", as + /// "S256" is Mandatory To Implement (MTI) on the server. + /// Ref: https://tools.ietf.org/html/rfc7636#section-4.2 + let codeChallengeMethod = "S256" + + /// Code Verifier + /// The code verifier SHOULD have enough entropy to make it + /// impractical to guess the value. It is RECOMMENDED that the output of + /// a suitable random number generator be used to create a 32-octet + /// sequence. The octet sequence is then base64url-encoded to produce a + /// 43-octet URL safe string to use as the code verifier. + /// + /// Ref: https://tools.ietf.org/html/rfc7636#section-4.1 + let codeVerifier: String + + /// Code Challenge + /// The client creates a code challenge derived from the code verifier by using S256 transformations + /// code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) + /// + /// Ref: https://tools.ietf.org/html/rfc7636#section-4.2 + /// + var codeChallenge: String { + return PKCE.generateCodeChallenge(codeVerifier: codeVerifier) + } + + init() { + codeVerifier = Data.randomData(bytesCount: 32).base64URLEncoded + } + + static func generateCodeChallenge(codeVerifier: String) -> String { + guard let codeVerifierData = codeVerifier.data(using: .ascii) else { + preconditionFailure("Invalid codeVerifier parameter") + } + return codeVerifierData.digest(using: .sha256).base64URLEncoded + } +} diff --git a/LineSDK/LineSDK/Login/Request/GetUserProfileRequest.swift b/LineSDK/LineSDK/Login/Request/GetUserProfileRequest.swift index bd580588..738c3467 100644 --- a/LineSDK/LineSDK/Login/Request/GetUserProfileRequest.swift +++ b/LineSDK/LineSDK/Login/Request/GetUserProfileRequest.swift @@ -1,13 +1,13 @@ // // GetUserProfileRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -23,6 +23,7 @@ import Foundation /// Represents a request for getting the user's profile. public struct GetUserProfileRequest: Request { + /// :nodoc: public init() {} /// :nodoc: public let method: HTTPMethod = .get diff --git a/LineSDK/LineSDK/Login/Request/GetVerifyTokenRequest.swift b/LineSDK/LineSDK/Login/Request/GetVerifyTokenRequest.swift index 201f9a03..3a7513d2 100644 --- a/LineSDK/LineSDK/Login/Request/GetVerifyTokenRequest.swift +++ b/LineSDK/LineSDK/Login/Request/GetVerifyTokenRequest.swift @@ -1,13 +1,13 @@ // // GetVerifyTokenRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -24,8 +24,10 @@ import Foundation /// Represents a request for verifying an access token. public struct GetVerifyTokenRequest: Request { + /// The access token to be verified. public let accessToken: String + /// Creates a token verification request. public init(accessToken: String) { self.accessToken = accessToken } diff --git a/LineSDK/LineSDK/Login/Request/PostExchangeTokenRequest.swift b/LineSDK/LineSDK/Login/Request/PostExchangeTokenRequest.swift index cb0ad678..1d04d81c 100644 --- a/LineSDK/LineSDK/Login/Request/PostExchangeTokenRequest.swift +++ b/LineSDK/LineSDK/Login/Request/PostExchangeTokenRequest.swift @@ -1,13 +1,13 @@ // // PostExchangeTokenRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -24,7 +24,7 @@ import Foundation struct PostExchangeTokenRequest: Request { let channelID: String let code: String - let otpValue: String + let codeVerifier: String let redirectURI: String let optionalRedirectURI: String? @@ -38,7 +38,7 @@ struct PostExchangeTokenRequest: Request { "client_id": channelID, "grant_type": "authorization_code", "code": code, - "otp": otpValue, + "code_verifier": codeVerifier, "redirect_uri": redirectURI, "client_version": Constant.SDKVersionString, "id_token_key_type": "JWK" diff --git a/LineSDK/LineSDK/Login/Request/PostRefreshTokenRequest.swift b/LineSDK/LineSDK/Login/Request/PostRefreshTokenRequest.swift index 12d1a007..2b147309 100644 --- a/LineSDK/LineSDK/Login/Request/PostRefreshTokenRequest.swift +++ b/LineSDK/LineSDK/Login/Request/PostRefreshTokenRequest.swift @@ -1,13 +1,13 @@ // // PostRefreshTokenRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Login/Request/PostRevokeTokenRequest.swift b/LineSDK/LineSDK/Login/Request/PostRevokeTokenRequest.swift index 0a832c8a..8ede8f72 100644 --- a/LineSDK/LineSDK/Login/Request/PostRevokeTokenRequest.swift +++ b/LineSDK/LineSDK/Login/Request/PostRevokeTokenRequest.swift @@ -1,13 +1,13 @@ // // PostRevokeTokenRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -21,15 +21,24 @@ import Foundation -struct PostRevokeTokenRequest: Request { +protocol RevokeTokenRequest: Request { + var channelID: String { get } +} + +extension RevokeTokenRequest { + var method: HTTPMethod { return .post } + var path: String { return "/oauth2/v2.1/revoke" } + var contentType: ContentType { return .formUrlEncoded } + var authentication: AuthenticateMethod { return .none } + var prefixPipelines: [ResponsePipeline]? { + return [emptyDataTransformer] + } +} + +struct PostRevokeTokenRequest: RevokeTokenRequest { let channelID: String let accessToken: String - let method: HTTPMethod = .post - let path = "/oauth2/v2.1/revoke" - let contentType: ContentType = .formUrlEncoded - let authentication: AuthenticateMethod = .none - var parameters: [String : Any]? { return [ "client_id": channelID, @@ -38,16 +47,18 @@ struct PostRevokeTokenRequest: Request { } typealias Response = Unit - - var prefixPipelines: [ResponsePipeline]? { - - // Convert empty data to an empty JSON `{}` - let isDataEmpty: ((Data) -> Bool) = { $0.isEmpty } - let dataTransformer = DataTransformRedirector(condition: isDataEmpty) { _ in - return "{}".data(using: .utf8)! - } +} + +struct PostRevokeRefreshTokenRequest: RevokeTokenRequest { + let channelID: String + let refreshToken: String + + var parameters: [String : Any]? { return [ - .redirector(dataTransformer) + "client_id": channelID, + "refresh_token": refreshToken ] } + + typealias Response = Unit } diff --git a/LineSDK/LineSDK/Messaging/Model/Actions/MessageAction.swift b/LineSDK/LineSDK/Messaging/Model/Actions/MessageAction.swift index aa28f5ee..9eaaa20a 100644 --- a/LineSDK/LineSDK/Messaging/Model/Actions/MessageAction.swift +++ b/LineSDK/LineSDK/Messaging/Model/Actions/MessageAction.swift @@ -1,13 +1,13 @@ // // MessageAction.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -27,7 +27,7 @@ enum MessageActionType: String, Codable { case URI = "uri" } -/// Represents an action in the LINE SDK Message types. Users can interact with the actions in the LINE app. +/// Represents an action in the LINE SDK Message types. Users can interact with the actions in LINE. /// /// - URI: Represents an action navigates users to a URI resource. /// - unknown: An action type is not defined in the LINE SDK yet. @@ -36,11 +36,11 @@ public enum MessageAction: Codable, MessageActionConvertible { case URI(MessageURIAction) /// An action type is not defined in the LINE SDK yet. case unknown - + enum CodingKeys: String, CodingKey { case type } - + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let type = try? container.decode(MessageActionType.self, forKey: .type) @@ -52,7 +52,7 @@ public enum MessageAction: Codable, MessageActionConvertible { self = .unknown } } - + public func encode(to encoder: Encoder) throws { switch self { case .URI(let action): @@ -61,24 +61,24 @@ public enum MessageAction: Codable, MessageActionConvertible { Log.assertionFailure("Cannot encode unknown message type.") } } - + public var asURIAction: MessageURIAction? { if case .URI(let action) = self { return action } return nil } - + public var action: MessageAction { return self } } public struct MessageURIAction: Codable, TemplateMessageActionTypeCompatible, MessageActionConvertible { - let type = MessageActionType.URI - public let label: String + private(set) var type = MessageActionType.URI + public let label: String? public let uri: URL - - public init(label: String, uri: URL) { + + public init(label: String? = nil, uri: URL) { self.label = label self.uri = uri } - + public var action: MessageAction { return .URI(self) } } diff --git a/LineSDK/LineSDK/Messaging/Model/AudioMessage.swift b/LineSDK/LineSDK/Messaging/Model/AudioMessage.swift index d7da431c..05774ea9 100644 --- a/LineSDK/LineSDK/Messaging/Model/AudioMessage.swift +++ b/LineSDK/LineSDK/Messaging/Model/AudioMessage.swift @@ -1,13 +1,13 @@ // // AudioMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,6 +19,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import Foundation + /// LINE internal use only. /// Represents a message containing an audio URL. public struct AudioMessage: Codable, MessageTypeCompatible { diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexBoxComponent.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexBoxComponent.swift index 882e256d..3e893a7d 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexBoxComponent.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexBoxComponent.swift @@ -1,13 +1,13 @@ // // FlexBoxComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -24,7 +24,7 @@ /// A box component behave as a container of other components. It defines the layout of its child components. /// You can also include a nested box in a box. public struct FlexBoxComponent: Codable, FlexMessageComponentTypeCompatible, MessageActionContainer { - let type: FlexMessageComponentType = .box + private(set) var type: FlexMessageComponentType = .box /// The placement style of components in this box. public let layout: FlexMessageComponent.Layout @@ -100,6 +100,23 @@ public struct FlexBoxComponent: Codable, FlexMessageComponentTypeCompatible, Mes self.layout = layout self.contents = contents.map { $0.component } } + + public init( + layout: FlexMessageComponent.Layout, + flex: FlexMessageComponent.Ratio? = nil, + spacing: FlexMessageComponent.Spacing? = nil, + margin: FlexMessageComponent.Margin? = nil, + action: MessageAction? = nil, + contents: (() -> [FlexMessageComponentConvertible]) + ) + { + self.layout = layout + self.flex = flex + self.spacing = spacing + self.margin = margin + self.action = action + self.contents = contents().map { $0.component } + } /// Appends a component to current `contents`. /// @@ -117,9 +134,15 @@ public struct FlexBoxComponent: Codable, FlexMessageComponentTypeCompatible, Mes public mutating func removeFisrtComponent( where condition: (FlexMessageComponent) throws -> Bool) rethrows -> FlexMessageComponent? { + #if swift(>=5.0) + guard let index = try contents.firstIndex(where: condition) else { + return nil + } + #else guard let index = try contents.index(where: condition) else { return nil } + #endif return contents.remove(at: index) } diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexButtonComponent.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexButtonComponent.swift index d8ccf826..52943ae4 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexButtonComponent.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexButtonComponent.swift @@ -1,13 +1,13 @@ // // FlexButtonComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -45,7 +45,7 @@ public struct FlexButtonComponent: Codable, FlexMessageComponentTypeCompatible { public static let defaultCase: FlexButtonComponent.Style = .link } - let type = FlexMessageComponentType.button + private(set) var type = FlexMessageComponentType.button /// An action to perform when the button tapped. /// Use `setAction` method if you want to set a `MessageActionConvertible` as the action of current component. @@ -73,12 +73,35 @@ public struct FlexButtonComponent: Codable, FlexMessageComponentTypeCompatible { /// Vertical alignment style. If not specified, `.top` will be used. /// If the `layout` property of the parent box is `.baseline`, the `gravity` property will be ignored. public var gravity: FlexMessageComponent.Gravity? - + /// Creates a button component with given information. - /// /// - Parameter action: An action to perform when the button tapped. - public init(action: MessageActionConvertible) { + /// - Parameter flex: The ratio of the width or height of this box within the parent box. The default value for + /// the horizontal parent box is 1, and the default value for the vertical parent box is 0. + /// - Parameter margin: Minimum space between this component and the previous component in the parent box. + /// - Parameter height: Height of the button. + /// - Parameter style: Styles of the button. + /// - Parameter color: Character color when the `style` property is `.link`. Background color when the `style` + /// property is `.primary` or `.secondary`. + /// - Parameter gravity: Vertical alignment style. If not specified, `.top` will be used. If the `layout` property + /// of the parent box is `.baseline`, the `gravity` property will be ignored. + public init( + action: MessageActionConvertible, + flex: FlexMessageComponent.Ratio? = nil, + margin: FlexMessageComponent.Margin? = nil, + height: FlexMessageComponent.Height? = nil, + style: Style? = nil, + color: HexColor? = nil, + gravity: FlexMessageComponent.Gravity? = nil + ) + { self.action = action.action + self.flex = flex + self.margin = margin + self.height = height + self.style = style + self.color = color + self.gravity = gravity } } diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexFillerComponent.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexFillerComponent.swift index ca7ef5b5..40469ccd 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexFillerComponent.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexFillerComponent.swift @@ -1,13 +1,13 @@ // // FlexFillerComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -23,7 +23,7 @@ /// Represents an invisible component to fill extra space between components. /// The filler's `flex` is fixed to 1. The `spacing` property of the parent box will be ignored for fillers. public struct FlexFillerComponent: Codable, FlexMessageComponentTypeCompatible { - let type: FlexMessageComponentType = .filler + private(set) var type: FlexMessageComponentType = .filler /// Creates a filler component. public init() {} diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexIconComponent.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexIconComponent.swift index c573f283..3399ca2c 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexIconComponent.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexIconComponent.swift @@ -1,13 +1,13 @@ // // FlexIconComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import Foundation + /// LINE internal use only. /// Represents an icon component. It is used to embed into a baseline layout and its flex is fixed to 0. public struct FlexIconComponent: Codable, FlexMessageComponentTypeCompatible { - let type: FlexMessageComponentType = .icon + private(set) var type: FlexMessageComponentType = .icon /// Icon URL. It should start with "https". public let url: URL @@ -48,6 +50,19 @@ public struct FlexIconComponent: Codable, FlexMessageComponentTypeCompatible { try assertHTTPSScheme(url: url, parameterName: "url") self.url = url } + + public init( + url: URL, + margin: FlexMessageComponent.Margin? = nil, + size: FlexMessageComponent.Size? = nil, + aspectRatio: FlexMessageComponent.AspectRatio? = nil) throws + { + try assertHTTPSScheme(url: url, parameterName: "url") + self.url = url + self.margin = margin + self.size = size + self.aspectRatio = aspectRatio + } } extension FlexIconComponent: FlexMessageComponentConvertible { diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexImageComponent.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexImageComponent.swift index edac3669..a1123b16 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexImageComponent.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexImageComponent.swift @@ -1,13 +1,13 @@ // // FlexImageComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,6 +19,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import Foundation + /// LINE internal use only. /// Represents an image component in a flex message. public struct FlexImageComponent: Codable, FlexMessageComponentTypeCompatible, MessageActionContainer { @@ -56,22 +58,53 @@ public struct FlexImageComponent: Codable, FlexMessageComponentTypeCompatible, M public var backgroundColor: HexColor? /// An action to perform when the box tapped. - /// Use `setAction` method if you want to set a `MessageActionConvertible` as the action of current component. public var action: MessageAction? enum CodingKeys: String, CodingKey { case type, url, flex, margin, gravity, size, aspectRatio, aspectMode, backgroundColor, action case alignment = "align" } - + /// Creates an image component with given information. - /// /// - Parameter url: Image URL. It should start with "https". + /// - Parameter flex: The ratio of the width or height of this box within the parent box. The default value for + /// the horizontal parent box is 1, and the default value for the vertical parent box is 0. + /// - Parameter margin: Minimum space between this component and the previous component in the parent box. + /// - Parameter alignment: Horizontal alignment style. If not specified, `.start` will be used. + /// - Parameter gravity: Vertical alignment style. If not specified, `.top` will be used. If the `layout` property + /// of the parent box is `.baseline`, the `gravity` property will be ignored. + /// - Parameter size: Maximum size of the image width. If not specified, `.md` will be used. + /// - Parameter aspectRatio: Aspect ratio for the image. Width versus height. If not specified, `.ratio_1x1` will be used. + /// - Parameter aspectMode: Aspect scaling mode for the image. If not specified, `.fit` will be used. + /// - Parameter backgroundColor: Background color of the image. + /// - Parameter action: An action to perform when the box tapped. + /// /// - Throws: An error if something wrong during creating the message. It's usually due to you provided invalid /// parameter. - public init(url: URL) throws { + public init( + url: URL, + flex: FlexMessageComponent.Ratio? = nil, + margin: FlexMessageComponent.Margin? = nil, + alignment: FlexMessageComponent.Alignment? = nil, + gravity: FlexMessageComponent.Gravity? = nil, + size: FlexMessageComponent.Size? = nil, + aspectRatio: FlexMessageComponent.AspectRatio? = nil, + aspectMode: FlexMessageComponent.AspectMode? = nil, + backgroundColor: HexColor? = nil, + action: MessageActionConvertible? = nil + ) throws + { try assertHTTPSScheme(url: url, parameterName: "url") self.url = url + self.flex = flex + self.margin = margin + self.alignment = alignment + self.gravity = gravity + self.size = size + self.aspectRatio = aspectRatio + self.aspectMode = aspectMode + self.backgroundColor = backgroundColor + self.action = action?.action } } diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexMessageComponent.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexMessageComponent.swift index f5b7d0d7..9a75951e 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexMessageComponent.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexMessageComponent.swift @@ -1,13 +1,13 @@ // // FlexMessageComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -46,7 +46,7 @@ enum FlexMessageComponentType: String, Codable { /// - spacer: Represents the type of spacer component. A `FlexSpacerComponent` value is associated. /// - unknown: A component type is not defined in the LINE SDK yet. /// -/// For more information, see https://developers.line.me/en/reference/messaging-api/#component +/// For more information, see https://developers.line.biz/en/reference/messaging-api/#component public enum FlexMessageComponent: Codable { /// Represents the type of box component. A `FlexBoxComponent` value is associated. diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexMessageProperties.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexMessageProperties.swift index 720d4911..4a993f59 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexMessageProperties.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexMessageProperties.swift @@ -1,13 +1,13 @@ // // FlexMessageProperties.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -27,7 +27,7 @@ extension FlexMessageComponent { /// The ratio (or flex value) of a flex component should take. /// For more information, see - /// https://developers.line.me/en/docs/messaging-api/flex-message-layout/#component-width-and-height . + /// https://developers.line.biz/en/docs/messaging-api/flex-message-layout/#component-width-and-height . public typealias Ratio = UInt /// Represents the placement style of components in a box. @@ -39,7 +39,7 @@ extension FlexMessageComponent { /// components are aligned. /// /// For more information, see - /// https://developers.line.me/en/docs/messaging-api/flex-message-layout/#box-layout-types . + /// https://developers.line.biz/en/docs/messaging-api/flex-message-layout/#box-layout-types . public enum Layout: String, DefaultEnumCodable { /// Components are placed horizontally. @@ -121,7 +121,7 @@ extension FlexMessageComponent { public static let defaultCase: Gravity = .top } - /// Represents the font weight used in compoment. + /// Represents the font weight used in component. /// /// - regular: Normal font weight. /// - bold: Bold font weight. diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexSeparatorComponent.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexSeparatorComponent.swift index 833f7154..ed3a0b0e 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexSeparatorComponent.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexSeparatorComponent.swift @@ -1,13 +1,13 @@ // // FlexSeparatorComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -24,7 +24,7 @@ /// Different from the `separator` property of `FlexBlockStyle`, the `FlexSeparatorComponent` allows you to add a /// separator between components instead of container block, as well as full control on separator `margin`. public struct FlexSeparatorComponent: Codable, FlexMessageComponentTypeCompatible { - let type: FlexMessageComponentType = .separator + private(set) var type: FlexMessageComponentType = .separator /// Minimum space between this component and the previous component in the parent box. /// If not specified, the `spacing` of parent box will be used. diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexSpacerComponent.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexSpacerComponent.swift index a17fd661..bff144d4 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexSpacerComponent.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexSpacerComponent.swift @@ -1,13 +1,13 @@ // // FlexSpacerComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -23,7 +23,7 @@ /// Represents some spacing in a box component. /// This is an invisible component that places a fixed-size space at the beginning or end of the box. public struct FlexSpacerComponent: Codable, FlexMessageComponentTypeCompatible { - let type: FlexMessageComponentType = .spacer + private(set) var type: FlexMessageComponentType = .spacer /// Size of the space. /// You can specify one from: `[.xs, .sm, .md, .lg, .xl, .xxl]`. @@ -33,7 +33,7 @@ public struct FlexSpacerComponent: Codable, FlexMessageComponentTypeCompatible { /// /// - Parameter size: Size of the space. /// You can specify one from: `[.xs, .sm, .md, .lg, .xl, .xxl]`. - public init(size: FlexMessageComponent.Size?) { + public init(size: FlexMessageComponent.Size? = nil) { self.size = size } } diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexTextComponent.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexTextComponent.swift index 9f58a1d9..fda404e5 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexTextComponent.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Component/FlexTextComponent.swift @@ -1,13 +1,13 @@ // // FlexTextComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -76,12 +76,51 @@ public struct FlexTextComponent: Codable, FlexMessageComponentTypeCompatible, Me case wrapping = "wrap" case alignment = "align" } - + /// Creates a text component with given information. - /// /// - Parameter text: Content text of this component. - public init(text: String) { + /// - Parameter flex: The ratio of the width or height of this box within the parent box. The default value for + /// the horizontal parent box is 1, and the default value for the vertical parent box is 0. + /// - Parameter margin: Minimum space between this component and the previous component in the parent box. + /// If not specified, the `spacing` of parent box will be used. If this component is the first + /// component in the parent box, this margin property will be ignored. + /// - Parameter size: Font size used for the text. You cannot specify a `.full` size. If not specified, `.md` + /// will be used. + /// - Parameter alignment: Horizontal alignment style. If not specified, `.start` will be used. + /// - Parameter gravity: Vertical alignment style. If not specified, `.top` will be used. + /// If the `layout` property of the parent box is `.baseline`, the `gravity` property will be + /// ignored. + /// - Parameter wrapping: Whether the text should be wrapped. If not specified, `false` will be used. If set to + /// true, you can use a new line character ("\n") to begin on a new line. + /// - Parameter maxLines: Max number of lines. + /// - Parameter weight: Weight of font used in this component. + /// - Parameter color: Color of font used in this component. + /// - Parameter action: An action to perform when the box tapped. + public init( + text: String, + flex: FlexMessageComponent.Ratio? = nil, + margin: FlexMessageComponent.Margin? = nil, + size: FlexMessageComponent.Size? = nil, + alignment: FlexMessageComponent.Alignment? = nil, + gravity: FlexMessageComponent.Gravity? = nil, + wrapping: Bool? = nil, + maxLines: UInt? = nil, + weight: FlexMessageComponent.Weight? = nil, + color: HexColor? = nil, + action: MessageAction? = nil + ) + { self.text = text + self.flex = flex + self.margin = margin + self.size = size + self.alignment = alignment + self.gravity = gravity + self.wrapping = wrapping + self.maxLines = maxLines + self.weight = weight + self.color = color + self.action = action } } diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexBubbleContainer.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexBubbleContainer.swift index 75a0833a..03a14dbc 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexBubbleContainer.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexBubbleContainer.swift @@ -1,13 +1,13 @@ // // FlexBubbleContainer.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -45,7 +45,7 @@ public struct FlexBubbleContainer: Codable, FlexMessageContainerTypeCompatible { public init() {} } - /// Represents the text direction inside a buddle. + /// Represents the text direction inside a bubble. /// /// - leftToRight: The text should be from left to right. /// - rightToLeft: The text should be from right to left. @@ -61,7 +61,7 @@ public struct FlexBubbleContainer: Codable, FlexMessageContainerTypeCompatible { public static let defaultCase: Direction = .leftToRight } - let type = FlexMessageContainerType.bubble + private(set) var type = FlexMessageContainerType.bubble /// The header block. Header section of the bubble. This block is a `FlexBoxComponent` and could contains /// arbitrary nested components. @@ -70,7 +70,7 @@ public struct FlexBubbleContainer: Codable, FlexMessageContainerTypeCompatible { /// The hero block. Hero block is a `FlexImageComponent` which show an image inside the bubble. public var hero: FlexImageComponent? - /// The body block. Main content of the buddle. This block is a `FlexBoxComponent` and could contains + /// The body block. Main content of the bubble. This block is a `FlexBoxComponent` and could contains /// arbitrary nested components. public var body: FlexBoxComponent? @@ -91,7 +91,7 @@ public struct FlexBubbleContainer: Codable, FlexMessageContainerTypeCompatible { /// - header: The header block. Header section of the bubble. This block is a `FlexBoxComponent` and could /// contains arbitrary nested components. /// - hero: The hero block. Hero block is a `FlexImageComponent` which show an image inside the bubble. - /// - body: The body block. Main content of the buddle. This block is a `FlexBoxComponent` and could contains + /// - body: The body block. Main content of the bubble. This block is a `FlexBoxComponent` and could contains /// arbitrary nested components. /// - footer: The footer block. Footer section of the bubble. This block is a `FlexBoxComponent` and could contains /// arbitrary nested components. diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexCarouselContainer.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexCarouselContainer.swift index 81c1e9b8..f69dec3d 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexCarouselContainer.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexCarouselContainer.swift @@ -1,13 +1,13 @@ // // FlexCarouselContainer.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -24,7 +24,7 @@ /// scrolling horizontally. /// public struct FlexCarouselContainer: Codable, FlexMessageContainerTypeCompatible { - let type = FlexMessageContainerType.carousel + private(set) var type = FlexMessageContainerType.carousel /// Array of `FlexBubbleContainer`s. You could set at most 10 bubble container in this carousel container. /// Line SDK does not check the elements count in a container. However, it would cause an API response error @@ -54,9 +54,16 @@ public struct FlexCarouselContainer: Codable, FlexMessageContainerTypeCompatible public mutating func removeFirstBubble( where condition: (FlexBubbleContainer) throws -> Bool) rethrows -> FlexBubbleContainer? { + #if swift(>=5.0) + guard let index = try contents.firstIndex(where: condition) else { + return nil + } + #else guard let index = try contents.index(where: condition) else { return nil } + #endif + return contents.remove(at: index) } diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexMessageContainer.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexMessageContainer.swift index 52ece300..6c8596d4 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexMessageContainer.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Container/FlexMessageContainer.swift @@ -1,13 +1,13 @@ // // FlexMessageContainer.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Messaging/Model/Flex/Style/FlexBlockStyle.swift b/LineSDK/LineSDK/Messaging/Model/Flex/Style/FlexBlockStyle.swift index f03576ab..aec97afd 100644 --- a/LineSDK/LineSDK/Messaging/Model/Flex/Style/FlexBlockStyle.swift +++ b/LineSDK/LineSDK/Messaging/Model/Flex/Style/FlexBlockStyle.swift @@ -1,13 +1,13 @@ // // FlexBlockStyle.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -39,7 +39,7 @@ public struct FlexBlockStyle: Codable { /// - Parameters: /// - backgroundColor: Background color of the block. White color will be used if not specified. /// - separator: Whether a separator should be placed above the block. - /// - separatorColor: olor of the separator. White color will be used if not specified. + /// - separatorColor: Color of the separator. White color will be used if not specified. public init(backgroundColor: HexColor? = nil, separator: Bool? = nil, separatorColor: HexColor? = nil) { self.backgroundColor = backgroundColor self.separator = separator diff --git a/LineSDK/LineSDK/Messaging/Model/FlexMessage.swift b/LineSDK/LineSDK/Messaging/Model/FlexMessage.swift index 8a700eb8..02e7ca5a 100644 --- a/LineSDK/LineSDK/Messaging/Model/FlexMessage.swift +++ b/LineSDK/LineSDK/Messaging/Model/FlexMessage.swift @@ -1,13 +1,13 @@ // // FlexMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -27,9 +27,9 @@ /// To create a `FlexMessage`, firstly you need to create a certain container, and then pass it with an `altText` /// to initializer. /// -/// For more information, see https://developers.line.me/en/docs/messaging-api/message-types/#flex-messages . +/// For more information, see https://developers.line.biz/en/docs/messaging-api/message-types/#flex-messages . public struct FlexMessage: Codable, MessageTypeCompatible { - let type = MessageType.flex + private(set) var type = MessageType.flex /// An alternate text to show in LINE push notification or chat preview. public var altText: String diff --git a/LineSDK/LineSDK/Messaging/Model/ImageMessage.swift b/LineSDK/LineSDK/Messaging/Model/ImageMessage.swift index 2e301fe7..4b223acc 100644 --- a/LineSDK/LineSDK/Messaging/Model/ImageMessage.swift +++ b/LineSDK/LineSDK/Messaging/Model/ImageMessage.swift @@ -1,13 +1,13 @@ // // ImageMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,6 +19,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import Foundation + /// LINE internal use only. /// Represents a message containing an image URL and a preview image URL. public struct ImageMessage: Codable, MessageTypeCompatible { diff --git a/LineSDK/LineSDK/Messaging/Model/LocationMessage.swift b/LineSDK/LineSDK/Messaging/Model/LocationMessage.swift index dcbf5fe5..fb96ba7a 100644 --- a/LineSDK/LineSDK/Messaging/Model/LocationMessage.swift +++ b/LineSDK/LineSDK/Messaging/Model/LocationMessage.swift @@ -1,13 +1,13 @@ // // LocationMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -26,7 +26,7 @@ public struct LocationMessage: Codable, MessageTypeCompatible { /// Represents a location latitude or longitude degrees. public typealias LocationDegrees = Double - let type = MessageType.location + private(set) var type = MessageType.location /// Title name of the location. public var title: String diff --git a/LineSDK/LineSDK/Messaging/Model/Message.swift b/LineSDK/LineSDK/Messaging/Model/Message.swift index 83af3c52..4aaa357b 100644 --- a/LineSDK/LineSDK/Messaging/Model/Message.swift +++ b/LineSDK/LineSDK/Messaging/Model/Message.swift @@ -1,13 +1,13 @@ // // Message.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -43,7 +43,7 @@ enum MessageType: String, Codable { /// - audio: Represents the type of audio message. A `AudioMessage` value is associated. /// - location: Represents the type of location message. A `LocationMessage` value is associated. /// - template: Represents the type of template message. A `TemplateMessage` value is associated. LINE APIs supports a -/// few types of template messagess. `TemplateMessage` is also a representing wrapper for underlying +/// few types of template messages. `TemplateMessage` is also a representing wrapper for underlying /// `TemplateMessagePayload`. /// - flex: Represents the type of flexible message. A `FlexMessage` value is associated. LINE APIs allows you to send /// a flex message by constructing with blocks and components. It gives you freedom to control the message diff --git a/LineSDK/LineSDK/Messaging/Model/MessageProtocols.swift b/LineSDK/LineSDK/Messaging/Model/MessageProtocols.swift index f43e4c40..bcdf6fa5 100644 --- a/LineSDK/LineSDK/Messaging/Model/MessageProtocols.swift +++ b/LineSDK/LineSDK/Messaging/Model/MessageProtocols.swift @@ -1,13 +1,13 @@ // // MessageProtocols.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -63,7 +63,7 @@ public protocol FlexMessageConvertible: AltTextMessageConvertible { var container: FlexMessageContainer { get } } -public extension FlexMessageConvertible { +extension FlexMessageConvertible { /// Returns a converted `Message` which wraps `Self` with an alternate text. /// diff --git a/LineSDK/LineSDK/Messaging/Model/MessageSender.swift b/LineSDK/LineSDK/Messaging/Model/MessageSender.swift index dbdf9373..4cfc947d 100644 --- a/LineSDK/LineSDK/Messaging/Model/MessageSender.swift +++ b/LineSDK/LineSDK/Messaging/Model/MessageSender.swift @@ -1,13 +1,13 @@ // // MessageSender.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -23,9 +23,9 @@ import Foundation /// LINE internal use only. /// Represents a message sender of a certain type of `Message`. -/// - Note: Some message types have footer area which shows the agent information who sends the message on behalf of -/// the sender user. The information includes an icon image, a text label and a link URL to move page when -/// tapped. +/// - Note: +/// Some message types have footer area which shows the agent information who sends the message on behalf of the sender +/// user. The information includes an icon image, a text label and a link URL to move page when tapped. public struct MessageSender: Codable { /// A text label to present some text. diff --git a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateButtonsPayload.swift b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateButtonsPayload.swift index 667b63e3..35fb7091 100644 --- a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateButtonsPayload.swift +++ b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateButtonsPayload.swift @@ -1,13 +1,13 @@ // // TemplateButtonsPayload.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,6 +19,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import Foundation + /// LINE internal use only. /// Represents a template payload with an image, title, text, and multiple action buttons. /// The message bubble size is the same with a regular message. It may include an image at most. @@ -53,21 +55,39 @@ public struct TemplateButtonsPayload: Codable, TemplateMessagePayloadTypeCompati /// Message agent who sends this message on behalf of the sender. public var sender: MessageSender? - + /// Creates a buttons payload with given information. - /// - /// - Parameters: - /// - title: Message title of chat bubble. - /// - text: Message text in the chat bubble. - /// - actions: Actions to perform when tapped. Default is empty. + /// - Parameter title: Message title of chat bubble. + /// - Parameter text: Message text in the chat bubble. + /// - Parameter defaultAction: Action when image is tapped. + /// - Parameter thumbnailImageURL: An image to display in the chat bubble. It should start with "https". + /// - Parameter imageAspectRatio: Aspect ratio of the image. Specify to `.rectangle` or `.square`. + /// If not specified, `.rectangle` will be used. + /// - Parameter imageContentMode: Size of the image. Specify to `.aspectFill` or `.aspectFit`. + /// If not specified, `.aspectFill` will be used. + /// - Parameter imageBackgroundColor: Background color of image. If not specified, white color will be used. + /// - Parameter sender: Message agent who sends this message on behalf of the sender. + /// - Parameter actions: Actions to perform when tapped. Default is empty. public init( title: String? = nil, text: String, - actions: [MessageActionConvertible] = []) + defaultAction: MessageAction? = nil, + thumbnailImageURL: URL? = nil, + imageAspectRatio: TemplateMessagePayload.ImageAspectRatio? = nil, + imageContentMode: TemplateMessagePayload.ImageContentMode? = nil, + imageBackgroundColor: HexColor? = nil, + sender: MessageSender? = nil, + actions: [MessageActionConvertible] = [] + ) { self.text = text self.title = title - + self.defaultAction = defaultAction + self.thumbnailImageURL = thumbnailImageURL + self.imageAspectRatio = imageAspectRatio + self.imageContentMode = imageContentMode + self.imageBackgroundColor = imageBackgroundColor + self.sender = sender self.actions = actions.map { $0.action } } diff --git a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateCarouselPayload.swift b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateCarouselPayload.swift index 811fa149..4140a655 100644 --- a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateCarouselPayload.swift +++ b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateCarouselPayload.swift @@ -1,13 +1,13 @@ // // TemplateCarouselPayload.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,6 +19,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import Foundation + /// LINE internal use only. /// Represents a template payload with multiple `Column`s which can be cycled like a carousel. /// The columns will be shown in order by scrolling horizontally. @@ -38,25 +40,37 @@ public struct TemplateCarouselPayload: Codable, TemplateMessagePayloadTypeCompat /// Action when image is tapped. Set for the entire image, title, and text area of the column. /// To set the `defaultAction` with any `MessageActionConvertible` type, use `setDefaultAction` method. - public var defaultAction: MessageAction? = nil + public var defaultAction: MessageAction? /// An image to display in the chat bubble. It should start with "https". - public var thumbnailImageURL: URL? = nil + public var thumbnailImageURL: URL? /// Background color of image. If not specified, white color will be used. - public var imageBackgroundColor: HexColor? = nil - + public var imageBackgroundColor: HexColor? + /// Creates a column with given information. - /// - /// - Parameters: - /// - title: Message title of the column. - /// - text: Message text of the column. - /// - actions: Actions to perform when tapped. - /// - public init(title: String? = nil, text: String, actions: [MessageActionConvertible] = []) { + /// - Parameter title: Message title of the column. + /// - Parameter text: Message text of the column. + /// - Parameter actions: Actions to perform when tapped. + /// - Parameter defaultAction: Action when image is tapped. Set for the entire image, title, and text area of + /// the column. To set the `defaultAction` with any `MessageActionConvertible` type, + /// use `setDefaultAction` method. + /// - Parameter thumbnailImageURL: An image to display in the chat bubble. It should start with "https". + /// - Parameter imageBackgroundColor: Background color of image. If not specified, white color will be used. + public init( + title: String? = nil, + text: String, + actions: [MessageActionConvertible] = [], + defaultAction: MessageActionConvertible? = nil, + thumbnailImageURL: URL? = nil, + imageBackgroundColor: HexColor? = nil + ) { self.text = text self.title = title self.actions = actions.map { $0.action } + self.defaultAction = defaultAction?.action + self.thumbnailImageURL = thumbnailImageURL + self.imageBackgroundColor = imageBackgroundColor } /// Appends an action to current `actions` list. diff --git a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateConfirmPayload.swift b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateConfirmPayload.swift index da20acb8..5ef92709 100644 --- a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateConfirmPayload.swift +++ b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateConfirmPayload.swift @@ -1,13 +1,13 @@ // // TemplateConfirmPayload.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -23,7 +23,7 @@ /// Represents a template payload with a text and two action button. /// Use this template if you want users to answer between "Yes" or "No". public struct TemplateConfirmPayload: Codable, TemplateMessagePayloadTypeCompatible { - let type = TemplateMessagePayloadType.confirm + private(set) var type = TemplateMessagePayloadType.confirm /// Message text in the chat bubble. public var text: String diff --git a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateImageCarouselPayload.swift b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateImageCarouselPayload.swift index 5e2d045d..240d18bf 100644 --- a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateImageCarouselPayload.swift +++ b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateImageCarouselPayload.swift @@ -1,13 +1,13 @@ // // TemplateImageCarouselPayload.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,6 +19,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import Foundation + /// LINE internal use only. /// Represents a template payload with multiple `Column`s with image which can be cycled like a carousel. /// The columns with image will be shown in order by scrolling horizontally. @@ -53,7 +55,7 @@ public struct TemplateImageCarouselPayload: Codable, TemplateMessagePayloadTypeC } } - let type = TemplateMessagePayloadType.imageCarousel + private(set) var type = TemplateMessagePayloadType.imageCarousel /// Array of columns. You could set at most 10 columns in the payload. Line SDK does not check the elements count /// in a payload. However, it would cause an API response error if more columns contained in the payload. diff --git a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateMessagePayload.swift b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateMessagePayload.swift index cf60ebbf..fb096846 100644 --- a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateMessagePayload.swift +++ b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateMessagePayload.swift @@ -1,13 +1,13 @@ // // TemplateMessagePayload.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateMessageProperties.swift b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateMessageProperties.swift index 5504ade4..b075f564 100644 --- a/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateMessageProperties.swift +++ b/LineSDK/LineSDK/Messaging/Model/Template/Payloads/TemplateMessageProperties.swift @@ -1,13 +1,13 @@ // // TemplateMessageProperties.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Messaging/Model/TemplateMessage.swift b/LineSDK/LineSDK/Messaging/Model/TemplateMessage.swift index 146705ed..1b0f2bc8 100644 --- a/LineSDK/LineSDK/Messaging/Model/TemplateMessage.swift +++ b/LineSDK/LineSDK/Messaging/Model/TemplateMessage.swift @@ -1,13 +1,13 @@ // // TemplateMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -26,7 +26,7 @@ /// To create a `TemplateMessage`, firstly you need to create a certain payload, and then pass it with an `altText` /// to initializer. /// -/// For more information, see https://developers.line.me/en/docs/messaging-api/message-types/#template-messages . +/// For more information, see https://developers.line.biz/en/docs/messaging-api/message-types/#template-messages . public struct TemplateMessage: Codable, MessageTypeCompatible { let type = MessageType.template diff --git a/LineSDK/LineSDK/Messaging/Model/TextMessage.swift b/LineSDK/LineSDK/Messaging/Model/TextMessage.swift index 933c5e77..7f623792 100644 --- a/LineSDK/LineSDK/Messaging/Model/TextMessage.swift +++ b/LineSDK/LineSDK/Messaging/Model/TextMessage.swift @@ -1,13 +1,13 @@ // // TextMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Messaging/Model/VideoMessage.swift b/LineSDK/LineSDK/Messaging/Model/VideoMessage.swift index a593f961..b1547e7a 100644 --- a/LineSDK/LineSDK/Messaging/Model/VideoMessage.swift +++ b/LineSDK/LineSDK/Messaging/Model/VideoMessage.swift @@ -1,13 +1,13 @@ // // VideoMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,6 +19,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import Foundation + /// LINE internal use only. /// Represents a message containing an video URL and a preview image URL. public struct VideoMessage: Codable, MessageTypeCompatible { diff --git a/LineSDK/LineSDK/Messaging/Request/PostMessageSendingTokenIssueRequest.swift b/LineSDK/LineSDK/Messaging/Request/PostMessageSendingTokenIssueRequest.swift new file mode 100644 index 00000000..8759eeb1 --- /dev/null +++ b/LineSDK/LineSDK/Messaging/Request/PostMessageSendingTokenIssueRequest.swift @@ -0,0 +1,57 @@ +// +// PostMessageSendingTokenIssueRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// LINE internal use only. +/// Represents a request for issuing a one time token for sharing messages. +/// +/// `LoginPermission.oneTimeShare` is required. +/// +public struct PostMessageSendingTokenIssueRequest: Request { + + public typealias Response = MessageSendingToken + + public let userIDs: [String] + + public init(userIDs: [String]) { + self.userIDs = userIDs + } + + public let method: HTTPMethod = .post + public let path = "/message/v3/ott/issue" + public let authentication: AuthenticateMethod = .token + + public var parameters: [String: Any]? { + return [ + "userIds": userIDs + ] + } +} + +public struct MessageSendingToken: Codable { + + public let token: String + + public init(value: String) { + self.token = value + } +} diff --git a/LineSDK/LineSDK/Messaging/Request/PostMultisendMessagesRequest.swift b/LineSDK/LineSDK/Messaging/Request/PostMultisendMessagesRequest.swift index 50e1490b..51e05b9e 100644 --- a/LineSDK/LineSDK/Messaging/Request/PostMultisendMessagesRequest.swift +++ b/LineSDK/LineSDK/Messaging/Request/PostMultisendMessagesRequest.swift @@ -1,13 +1,13 @@ // // PostMultisendMessagesRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -26,13 +26,13 @@ import Foundation /// This request requires you have the `.messageWrite` permission, otherwise, you would get a 403 permission /// grant error. public struct PostMultisendMessagesRequest: Request { - + /// An array of user IDs to where messages will be sent. Up to 10 elements. public let userIDs: [String] - + /// `Messages`s will be sent. Up to 5 elements. public let messages: [Message] - + /// Creates a request consisted of given `chatID` and `messages`. /// /// - Parameters: @@ -53,20 +53,20 @@ public struct PostMultisendMessagesRequest: Request { "messages": try! messages.toJSON() ] } - + /// Server response of `PostMultisendMessagesRequest`. public struct Response: Decodable { - /// Represents a result pair of message sending behavior + /// Represents a result pair of message sending behavior. public struct SendingResult: Decodable { - /// The destination user ID of this result. + /// The destination user or group ID of this result. public let to: String /// Represents the sending status. public let status: MessageSendingStatus } /// Represents sending results of this request. Each `SendingResult` in this array represents a result for a - /// specified user in `userIDs` of request. If a server error + /// specified user in `userIDs` of request. public let results: [SendingResult] } } diff --git a/LineSDK/LineSDK/Messaging/Request/PostMultisendMessagesWithTokenRequest.swift b/LineSDK/LineSDK/Messaging/Request/PostMultisendMessagesWithTokenRequest.swift new file mode 100644 index 00000000..15d3a350 --- /dev/null +++ b/LineSDK/LineSDK/Messaging/Request/PostMultisendMessagesWithTokenRequest.swift @@ -0,0 +1,55 @@ +// +// PostMultisendMessagesWithTokenRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// LINE internal use only. +/// Represents the request of sending some messages to multiple users on behalf of the current authorized user, +/// with an issued one time token. This is used to share messages. +/// +/// `LoginPermission.oneTimeShare` is required. +/// +public struct PostMultisendMessagesWithTokenRequest: Request { + + public typealias Response = Unit + + /// An array of user IDs to where messages will be sent. Up to 10 elements. + public let messageToken: MessageSendingToken + + /// `Messages`s will be sent. Up to 5 elements. + public let messages: [Message] + + public init(token: MessageSendingToken, messages: [MessageConvertible]) { + self.messageToken = token + self.messages = messages.map { $0.message } + } + + public let method: HTTPMethod = .post + public let path = "/message/v3/ott/share" + public let authentication: AuthenticateMethod = .token + + public var parameters: [String: Any]? { + return [ + "token": messageToken.token, + "messages": try! messages.toJSON() + ] + } +} diff --git a/LineSDK/LineSDK/Messaging/Request/PostSendMessagesRequest.swift b/LineSDK/LineSDK/Messaging/Request/PostSendMessagesRequest.swift index 25ab26bd..3cb8ce79 100644 --- a/LineSDK/LineSDK/Messaging/Request/PostSendMessagesRequest.swift +++ b/LineSDK/LineSDK/Messaging/Request/PostSendMessagesRequest.swift @@ -1,13 +1,13 @@ // // PostSendMessagesRequest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Networking/API.swift b/LineSDK/LineSDK/Networking/API.swift deleted file mode 100644 index 2aa51b8c..00000000 --- a/LineSDK/LineSDK/Networking/API.swift +++ /dev/null @@ -1,178 +0,0 @@ -// -// API.swift -// -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. -// -// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, -// copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. -// -// As with any software that integrates with the LINE Corporation platform, your use of this software -// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. -// This copyright notice shall be included in all copies or substantial portions of the software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import Foundation - -/// Represents a utility structure for calling the LINE Platform. -/// -/// - Note: -/// For most API calls, using the interfaces in the `API` structure is equivalent to using and sending an -/// underlying `Request` object with a `Session` object. However, some methods in the `API` provides -/// additional useful features such as working with the keychain and redirecting the final result in a more -/// reasonable way. -/// -/// Using the `API` structure to interact with the LINE Platform is highly recommended unless you are familiar -/// with and want to extend the LINE SDK to send unimplemented API requests to the LINE Platform. -/// -public struct API { - /// Refreshes the access token with `refreshToken`. - /// - /// - Parameters: - /// - refreshToken: A refresh token. Optional. If not specified, the current refresh token is used. - /// - queue: The callback queue that is used for `completion`. The default value is - /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. - /// - completion: The completion closure to be invoked when the access token is refreshed. - /// - Note: - /// If the token refresh process finishes successfully, the refreshed access token will be - /// automatically stored in the keychain for later use and you will get a - /// `.LineSDKAccessTokenDidUpdate` notification. Normally, you do not need to refresh the access token - /// manually because any API call will attempt to refresh the access token if necessary. - /// - public static func refreshAccessToken( - _ refreshToken: String? = nil, - callbackQueue queue: CallbackQueue = .currentMainOrAsync, - completionHandler completion: @escaping (Result) -> Void) - { - guard let token = refreshToken ?? AccessTokenStore.shared.current?.refreshToken else { - queue.execute { completion(.failure(LineSDKError.requestFailed(reason: .lackOfAccessToken))) } - return - } - let request = PostRefreshTokenRequest(channelID: LoginConfiguration.shared.channelID, refreshToken: token) - Session.shared.send(request, callbackQueue: queue) { result in - switch result { - case .success(let token): - let reuslt = Result { - try AccessTokenStore.shared.setCurrentToken(token) - }.map { token } - completion(reuslt) - case .failure(let error): - completion(.failure(error)) - } - } - } - - /// Revokes the access token. - /// - /// - Parameters: - /// - token: The access token to be revoked. Optional. If not specified, the current access token will - /// be revoked. - /// - queue: The callback queue that is used for `completion`. The default value is - /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. - /// - completion: The completion closure to be invoked when the access token is revoked. - /// - Note: - /// - /// The revoked token will be automatically removed from the keychain. If `token` has a `nil` value - /// and the current access token does not exist, `completion` will be called with `.success`. The - /// same applies when `token` has an invalid access token. - /// - /// After the access token is revoked, you cannot use it again to access the LINE Platform. You - /// need to have the user authorize your app again to issue a new access token before accessing the - /// LINE Platform. - /// - /// The `LineSDKAccessTokenDidRemove` notification will be sent when the access token removed from the device. - public static func revokeAccessToken( - _ token: String? = nil, - callbackQueue queue: CallbackQueue = .currentMainOrAsync, - completionHandler completion: @escaping (Result<(), LineSDKError>) -> Void) - { - func handleSuccessResult() { - let result = Result { try AccessTokenStore.shared.removeCurrentAccessToken() } - completion(result) - } - - guard let token = token ?? AccessTokenStore.shared.current?.value else { - // No token input or found in store, just recognize it as success. - queue.execute { completion(.success(())) } - return - } - let request = PostRevokeTokenRequest(channelID: LoginConfiguration.shared.channelID, accessToken: token) - Session.shared.send(request, callbackQueue: queue) { result in - switch result { - case .success(_): - handleSuccessResult() - case .failure(let error): - guard case .responseFailed(reason: .invalidHTTPStatusAPIError(let detail)) = error else { - completion(.failure(error)) - return - } - // We recognize response 400 as a success for revoking (since the token itself is invalid). - if detail.code == 400 { - Log.print(error.localizedDescription) - handleSuccessResult() - } - } - } - } - - /// Verifies the access token. - /// - /// - Parameters: - /// - token: The access token to be verified. Optional. If not specified, the current access token - /// will be verified. - /// - queue: The callback queue that is used for `completion`. The default value is - /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. - /// - completion: The completion closure to be invoked when the access token is verified. - /// - public static func verifyAccessToken( - _ token: String? = nil, - callbackQueue queue: CallbackQueue = .currentMainOrAsync, - completionHandler completion: @escaping (Result) -> Void) - { - guard let token = token ?? AccessTokenStore.shared.current?.value else { - queue.execute { completion(.failure(LineSDKError.requestFailed(reason: .lackOfAccessToken))) } - return - } - let request = GetVerifyTokenRequest(accessToken: token) - Session.shared.send(request, callbackQueue: queue, completionHandler: completion) - } - - /// Gets the user's profile. - /// - /// - Parameters: - /// - queue: The callback queue that is used for `completion`. The default value is - /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. - /// - completion: The completion closure to be invoked when the user's profile is returned. - /// - Note: The `.profile` permission is required. - /// - public static func getProfile( - callbackQueue queue: CallbackQueue = .currentMainOrAsync, - completionHandler completion: @escaping (Result) -> Void) - { - let request = GetUserProfileRequest() - Session.shared.send(request, callbackQueue: queue, completionHandler: completion) - } - - /// Gets the friendship status of the user and the bot linked to your LINE Login channel. - /// - /// - Parameters: - /// - queue: The callback queue that is used for `completion`. The default value is - /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. - /// - completion: The completion closure to be invoked when the friendship status is returned. - /// - Note: The `.profile` permission is required. - /// - public static func getBotFriendshipStatus( - callbackQueue queue: CallbackQueue = .currentMainOrAsync, - completionHandler completion: @escaping (Result) -> Void) - { - let request = GetBotFriendshipStatusRequest() - Session.shared.send(request, callbackQueue: queue, completionHandler: completion) - } -} diff --git a/LineSDK/LineSDK/Networking/API/API+Auth.swift b/LineSDK/LineSDK/Networking/API/API+Auth.swift new file mode 100644 index 00000000..a9294763 --- /dev/null +++ b/LineSDK/LineSDK/Networking/API/API+Auth.swift @@ -0,0 +1,212 @@ +// +// API+Auth.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +// MARK: - Auth API +extension API { + + /// Authentication-related APIs. Unlike other public APIs, methods in this type don't refresh + /// the access token automatically. Don't use these methods as a means of refreshing current + /// access tokens. + /// + public enum Auth { + /// Refreshes the access token with `refreshToken`. + /// + /// - Parameters: + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the access token is refreshed. + /// - Note: + /// If the token refresh process finishes successfully, the refreshed access token will be + /// automatically stored in the keychain for later use and you will get a + /// `.LineSDKAccessTokenDidUpdate` notification. Normally, you don't need to refresh the access token + /// manually because any API call will attempt to refresh the access token if necessary. + /// + public static func refreshAccessToken( + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void) + { + guard let token = AccessTokenStore.shared.current else + { + queue.execute { completion(.failure(LineSDKError.requestFailed(reason: .lackOfAccessToken))) } + return + } + let request = PostRefreshTokenRequest( + channelID: LoginConfiguration.shared.channelID, + refreshToken: token._refreshToken) + Session.shared.send(request, callbackQueue: queue) { result in + switch result { + case .success(let newToken): + do { + let combinedToken = try AccessToken(token: newToken, currentIDTokenRaw: token.IDTokenRaw) + try AccessTokenStore.shared.setCurrentToken(combinedToken) + completion(.success(combinedToken)) + } catch { + completion(.failure(error.sdkError)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } + + /// Revokes the access token. + /// + /// - Parameters: + /// - token: The access token to be revoked. Optional. If not specified, the current access token will + /// be revoked. + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the access token is revoked. + /// + /// - Note: + /// The revoked token will be automatically removed from the keychain. If `token` has a `nil` value + /// and the current access token does not exist, `completion` will be called with `.success`. The + /// same applies when `token` has an invalid access token. + /// + /// After the access token is revoked, you cannot use it again to access the LINE Platform. You + /// need to have the user authorize your app again to issue a new access token before accessing the + /// LINE Platform. + /// + /// The `LineSDKAccessTokenDidRemove` notification is sent when the access token is removed from the device. + public static func revokeAccessToken( + _ token: String? = nil, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result<(), LineSDKError>) -> Void) + { + func handleSuccessResult() { + UserDefaultsValue.clear() + let result = Result { try AccessTokenStore.shared.removeCurrentAccessToken() } + completion(result) + } + + guard let token = token ?? AccessTokenStore.shared.current?.value else { + // No token input or found in store, just recognize it as success. + queue.execute { completion(.success(())) } + return + } + let request = PostRevokeTokenRequest(channelID: LoginConfiguration.shared.channelID, accessToken: token) + Session.shared.send(request, callbackQueue: queue) { result in + switch result { + case .success(_): + handleSuccessResult() + case .failure(let error): + if case .responseFailed(reason: .invalidHTTPStatusAPIError(let detail)) = error, + detail.code != 400 + { + // We recognize response 400 as a success for revoking (since the token itself is invalid). + // For other status code, finish with the error. + completion(.failure(error)) + } else { + Log.print(error.localizedDescription) + handleSuccessResult() + } + } + } + } + + /// Revokes the refresh token and all its corresponding access tokens. + /// + /// - Parameters: + /// - refreshToken: The refresh token to be revoked. Optional. If not specified, the current refresh token will + /// be revoked. + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the access token is revoked. + /// + /// - Note: + /// Do not pass an access token to the `refreshToken` parameter. To revoke an access token, use + /// `revokeAccessToken(_:callbackQueue:completionHandler:)` instead. + /// + /// The revoked token will be automatically removed from the keychain. If `refreshToken` has a `nil` value + /// and the current refresh token does not exist, `completion` will be called with `.success`. The + /// same applies when `refreshToken` has an invalid refresh token. + /// + /// This API will revoke the given refresh token and all its corresponding access tokens. Once these tokens are + /// revoked, you can neither call an API protected by an access token or refresh the access token with the refresh + /// token. To access the resource owner's content, you need to ask your users to authorize your app again. + /// + /// The `LineSDKAccessTokenDidRemove` notification is sent when the access token is removed from the device. + public static func revokeRefreshToken( + _ refreshToken: String? = nil, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result<(), LineSDKError>) -> Void) + { + func handleSuccessResult() { + UserDefaultsValue.clear() + let result = Result { try AccessTokenStore.shared.removeCurrentAccessToken() } + completion(result) + } + + guard let refreshToken = refreshToken ?? AccessTokenStore.shared.current?._refreshToken else { + // No token input or found in store, just recognize it as success. + queue.execute { completion(.success(())) } + return + } + let request = PostRevokeRefreshTokenRequest( + channelID: LoginConfiguration.shared.channelID, + refreshToken: refreshToken) + Session.shared.send(request, callbackQueue: queue) { result in + switch result { + case .success(_): + handleSuccessResult() + case .failure(let error): + if case .responseFailed(reason: .invalidHTTPStatusAPIError(let detail)) = error, + detail.code != 400 + { + // We recognize response 400 as a success for revoking (since the token itself is invalid). + // For other status code, finish with the error. + completion(.failure(error)) + } else { + Log.print(error.localizedDescription) + handleSuccessResult() + } + } + } + } + + /// Verifies the access token. + /// + /// - Parameters: + /// - token: The access token to be verified. Optional. If not specified, the current access token + /// will be verified. + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the access token is verified. + /// + /// - Note: + /// This method does not try to refresh the current access token when it is invalid or expired. + /// Instead, if verification fails, it just returns the server response as an error to you. + public static func verifyAccessToken( + _ token: String? = nil, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void) + { + guard let token = token ?? AccessTokenStore.shared.current?.value else { + queue.execute { completion(.failure(LineSDKError.requestFailed(reason: .lackOfAccessToken))) } + return + } + let request = GetVerifyTokenRequest(accessToken: token) + Session.shared.send(request, callbackQueue: queue, completionHandler: completion) + } + } +} diff --git a/LineSDK/LineSDK/Networking/API/API+Deprecated.swift b/LineSDK/LineSDK/Networking/API/API+Deprecated.swift new file mode 100644 index 00000000..fb5d5f8e --- /dev/null +++ b/LineSDK/LineSDK/Networking/API/API+Deprecated.swift @@ -0,0 +1,155 @@ +// +// API+Deprecated.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +extension API { + + /// - Warning: Deprecated. Use `API.Auth.refreshAccessToken(callbackQueue:completionHandler:)`. + /// + /// Refreshes the access token with `refreshToken`. + /// + /// - Parameters: + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the access token is refreshed. + /// + /// - Note: + /// If the token refresh process finishes successfully, the refreshed access token will be + /// automatically stored in the keychain for later use and you will get a + /// `.LineSDKAccessTokenDidUpdate` notification. Normally, you do not need to refresh the access token + /// manually because any API call will attempt to refresh the access token if necessary. + /// + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access token automatically. + Make sure you don't need token refreshing as a side effect, then use methods from `API.Auth` instead. + """, + renamed: "Auth.refreshAccessToken" + ) + public static func refreshAccessToken( + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void) + { + Auth.refreshAccessToken(callbackQueue: queue, completionHandler: completion) + } + + /// - Warning: Deprecated. Use `API.Auth.revokeAccessToken(_:callbackQueue:completionHandler:)`. + /// + /// Revokes the access token. + /// + /// - Parameters: + /// - token: The access token to be revoked. Optional. If not specified, the current access token will + /// be revoked. + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the access token is revoked. + /// + /// - Note: + /// The revoked token will be automatically removed from the keychain. If `token` has a `nil` value + /// and the current access token does not exist, `completion` will be called with `.success`. The + /// same applies when `token` has an invalid access token. + /// + /// After the access token is revoked, you cannot use it again to access the LINE Platform. You + /// need to have the user authorize your app again to issue a new access token before accessing the + /// LINE Platform. + /// + /// The `LineSDKAccessTokenDidRemove` notification is sent when the access token is removed from the device. + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods from `API.Auth` instead. + """, + renamed: "Auth.revokeAccessToken" + ) + public static func revokeAccessToken( + _ token: String? = nil, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result<(), LineSDKError>) -> Void) + { + Auth.revokeAccessToken(token, callbackQueue: queue, completionHandler: completion) + } + + /// - Warning: Deprecated. Use `API.Auth.revokeRefreshToken(_:callbackQueue:completionHandler:)`. + /// + /// Revokes the refresh token and all its corresponding access tokens. + /// + /// - Parameters: + /// - refreshToken: The refresh token to be revoked. Optional. If not specified, the current refresh token will + /// be revoked. + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the access token is revoked. + /// + /// - Note: + /// Do not pass an access token to the `refreshToken` parameter. To revoke an access token, use + /// `revokeAccessToken(_:callbackQueue:completionHandler:)` instead. + /// + /// The revoked token will be automatically removed from the keychain. If `refreshToken` has a `nil` value + /// and the current refresh token does not exist, `completion` will be called with `.success`. The + /// same applies when `refreshToken` has an invalid refresh token. + /// + /// This API will revoke the given refresh token and all its corresponding access tokens. Once these tokens are + /// revoked, you can neither call an API protected by an access token or refresh the access token with the refresh + /// token. To access the resource owner's content, you need to ask your users to authorize your app again. + /// + /// The `LineSDKAccessTokenDidRemove` notification will be sent when the access token is removed from the device. + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods from `API.Auth` instead. + """, + renamed: "Auth.revokeRefreshToken" + ) + public static func revokeRefreshToken( + _ refreshToken: String? = nil, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result<(), LineSDKError>) -> Void) + { + Auth.revokeRefreshToken(refreshToken, callbackQueue: queue, completionHandler: completion) + } + + /// - Warning: Deprecated. Use `API.Auth.verifyAccessToken(_:callbackQueue:completionHandler:)`. + /// + /// Verifies the access token. + /// + /// - Parameters: + /// - token: The access token to be verified. Optional. If not specified, the current access token + /// will be verified. + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the access token is verified. + /// + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods from `API.Auth` instead. + """, + renamed: "Auth.verifyAccessToken" + ) + public static func verifyAccessToken( + _ token: String? = nil, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void) + { + Auth.verifyAccessToken(token, callbackQueue: queue, completionHandler: completion) + } +} diff --git a/LineSDK/LineSDK/Networking/API+Internal.swift b/LineSDK/LineSDK/Networking/API/API+Internal.swift similarity index 62% rename from LineSDK/LineSDK/Networking/API+Internal.swift rename to LineSDK/LineSDK/Networking/API/API+Internal.swift index 8f08c4d9..2632927c 100644 --- a/LineSDK/LineSDK/Networking/API+Internal.swift +++ b/LineSDK/LineSDK/Networking/API/API+Internal.swift @@ -1,13 +1,13 @@ // // API+Internal.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -29,7 +29,7 @@ extension API { /// This API returns a maximum of 200 users per request. /// /// - Parameters: - /// - sort: Sorting method for the returned freind list. + /// - sort: Sorting method for the returned friend list. /// Only a value of `name` is supported. If not specified, the sort will be determined by server. /// - pageToken: If a `pageToken` value is included in the previous API call's completion closure, /// pass it here to get the following page of the user's friend list. Otherwise, pass `nil` to get @@ -116,8 +116,12 @@ extension API { callbackQueue queue: CallbackQueue = .currentMainOrAsync, completionHandler completion: @escaping (Result) -> Void) { - let request = GetApproversInGroupRequest(groupID: groupID, pageToken: pageToken) - Session.shared.send(request, callbackQueue: queue, completionHandler:completion) + do { + let request = try GetApproversInGroupRequest(groupID: groupID, pageToken: pageToken) + Session.shared.send(request, callbackQueue: queue, completionHandler:completion) + } catch { + queue.execute { completion(.failure(error.sdkError)) } + } } } @@ -191,3 +195,121 @@ extension API { Session.shared.send(request, callbackQueue: queue, completionHandler: completion) } } + +// MARK: - Sharing Related API +extension API { + public static func getMessageSendingOneTimeToken( + userIDs: [String], + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHander completion: @escaping (Result) -> Void) + { + let request = PostMessageSendingTokenIssueRequest(userIDs: userIDs) + Session.shared.send(request, callbackQueue: queue, completionHandler: completion) + } + + public static func multiSendMessages( + _ messages: [MessageConvertible], + withMessageToken token: MessageSendingToken, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void) + { + let request = PostMultisendMessagesWithTokenRequest(token: token, messages: messages) + Session.shared.send(request, callbackQueue: queue, completionHandler: completion) + } +} + +// MARK: - OpenChat Related API +extension API { + /// Gets the availability of a given Open Chat room. + /// - Parameters: + /// - openChatId: The Open Chat room Id. + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the room availability status is returned. + /// - Note: The `.openChatSubscriptionInfo` permission is required. + /// + public static func getOpenChatRoomStatus( + openChatId: EntityID, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void + ) + { + do { + let request = try GetOpenChatRoomStatusRequest(openChatId: openChatId) + Session.shared.send(request, callbackQueue: queue, completionHandler: completion) + } catch { + queue.execute { completion(.failure(error.sdkError)) } + } + + } + + /// Gets the membership state of the current user for a given Open Chat room. + /// - Parameters: + /// - openChatId: The Open Chat room Id. + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the membership state is returned. + /// - Note: The `.openChatSubscriptionInfo` permission is required. + /// + public static func getOpenChatRoomMembershipState( + openChatId: EntityID, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void + ) + { + do { + let request = try GetOpenChatRoomMembershipStateRequest(openChatId: openChatId) + Session.shared.send(request, callbackQueue: queue, completionHandler: completion) + } catch { + queue.execute { completion(.failure(error.sdkError)) } + } + } + + /// Gets the joining type of an Open Chat room. + /// - Parameters: + /// - openChatId: The identifier of the joining Open Chat room. + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the membership state is returned. + /// - Note: The `.openChatSubscriptionInfo` permission is required. + /// + public static func getOpenChatRoomJoinType( + openChatId: EntityID, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void + ) + { + do { + let request = try GetOpenChatRoomJoinTypeRequest(openChatId: openChatId) + Session.shared.send(request, callbackQueue: queue, completionHandler: completion) + } catch { + queue.execute { completion(.failure(error.sdkError)) } + } + } + + /// Joins a specified Open Chat room with the desired display name. + /// - Parameters: + /// - openChatId: The identifier of the joining Open Chat room. + /// - displayName: The desired display name of current user in the Open Chat room. + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the membership state is returned. + /// - Note: The `.openChatRoomCreateAndJoin` permission is required. + /// + public static func postOpenChatRoomJoin( + openChatId: EntityID, + displayName: String, + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void + ) + { + do { + let request = try PostOpenChatRoomJoinRequest(openChatId: openChatId, displayName: displayName) + Session.shared.send(request, callbackQueue: queue, completionHandler: completion) + } catch { + queue.execute { completion(.failure(error.sdkError)) } + } + + } + +} diff --git a/LineSDK/LineSDK/Networking/API/API.swift b/LineSDK/LineSDK/Networking/API/API.swift new file mode 100644 index 00000000..c9d95641 --- /dev/null +++ b/LineSDK/LineSDK/Networking/API/API.swift @@ -0,0 +1,69 @@ +// +// API.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// A utility type for calling APIs on the LINE Platform. +/// +/// - Note: +/// For most API calls, using the methods in the `API` is equivalent to using and sending an +/// underlying `Request` object with a `Session` object. However, some methods in `API` provide +/// additional useful features such as working with the keychain and redirecting the final result +/// in a more reasonable way. +/// +/// Using methods in `API` to interact with the LINE Platform is highly recommended unless you are familiar +/// with and want to extend the LINE SDK to send unimplemented API requests to the LINE Platform. +/// +public enum API { + /// Gets the user's profile. + /// + /// - Parameters: + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the user's profile is returned. + /// - Note: The `.profile` permission is required. + /// + public static func getProfile( + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void + ) + { + let request = GetUserProfileRequest() + Session.shared.send(request, callbackQueue: queue, completionHandler: completion) + } + + /// Gets the friendship status of the user and the LINE Official Account linked to your LINE Login channel. + /// + /// - Parameters: + /// - queue: The callback queue that is used for `completion`. The default value is + /// `.currentMainOrAsync`. For more information, see `CallbackQueue`. + /// - completion: The completion closure to be invoked when the friendship status is returned. + /// - Note: The `.profile` permission is required. + /// + public static func getBotFriendshipStatus( + callbackQueue queue: CallbackQueue = .currentMainOrAsync, + completionHandler completion: @escaping (Result) -> Void + ) + { + let request = GetBotFriendshipStatusRequest() + Session.shared.send(request, callbackQueue: queue, completionHandler: completion) + } +} diff --git a/LineSDK/LineSDK/Networking/Client/ChainedPaginatedRequest.swift b/LineSDK/LineSDK/Networking/Client/ChainedPaginatedRequest.swift new file mode 100644 index 00000000..03268d2b --- /dev/null +++ b/LineSDK/LineSDK/Networking/Client/ChainedPaginatedRequest.swift @@ -0,0 +1,132 @@ +// +// GetAllFriendsRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +protocol SortParameterRequest { + var sortParameter: String? { get } +} + +protocol PaginatedResponse: Decodable { + associatedtype Item: Decodable + var paginatedValues: [Item] { get } + var pageToken: String? { get } +} + +class ChainedPaginatedRequest : Request where T.Response: PaginatedResponse { + + var method: HTTPMethod { return originalRequest.method } + var path: String { return originalRequest.path } + var authentication: AuthenticateMethod { return originalRequest.authentication } + + typealias Response = [T.Response.Item] + + init(originalRequest: T) { + self.originalRequest = originalRequest + } + + /// Called when every time a page is loaded and parsed. This would be invoked in the session's delegate queue. + let onPageLoaded = Delegate() + + let originalRequest: T + + var items: Response = [] + var currentPageToken: String? + + var parameters: Parameters? { + var param: [String : Any] = [:] + if let s = originalRequest as? SortParameterRequest, let sort = s.sortParameter { + param["sort"] = sort + } + if let pageToken = currentPageToken { + param["pageToken"] = pageToken + } + return param + } + + var pipelines: [ResponsePipeline] { + var pipelines: [ResponsePipeline] = [] + if let refreshTokenPipeline = authentication.refreshTokenPipeline { + pipelines.append(refreshTokenPipeline) + } + + pipelines.append(.redirector(BadHTTPStatusRedirector(valid: 200..<300))) + + let intermediateParser = PaginatedParseRedirector(request: self, parser: defaultJSONParser) + intermediateParser.onParsed.delegate(on: self) { (self, response) in + self.onPageLoaded.call(response) + } + + pipelines.append(.redirector(intermediateParser)) + pipelines.append(.terminator(PaginatedResultTerminator(request: self))) + + return pipelines + } +} + +// Parse an intermediate response (single response) of a `ChainedPaginatedRequest`. +class PaginatedParseRedirector: ResponsePipelineRedirector where Wrapped.Response: PaginatedResponse { + + let parser: JSONDecoder + let chainedPaginatedRequest: ChainedPaginatedRequest + + let onParsed = Delegate() + + init(request: ChainedPaginatedRequest, parser: JSONDecoder) { + self.chainedPaginatedRequest = request + self.parser = parser + } + + func shouldApply(request: T, data: Data, response: HTTPURLResponse) -> Bool { + return true + } + + func redirect( + request: T, + data: Data, + response: HTTPURLResponse, + done closure: @escaping (ResponsePipelineRedirectorAction) throws -> Void) throws + { + let paginatedValue = try parser.decode(Wrapped.Response.self, from: data) + onParsed.call(paginatedValue) + + chainedPaginatedRequest.items.append(contentsOf: paginatedValue.paginatedValues) + + if let nextPageToken = paginatedValue.pageToken { + chainedPaginatedRequest.currentPageToken = nextPageToken + try closure(.restart) + } else { + try closure(.continue) + } + } +} + +class PaginatedResultTerminator: ResponsePipelineTerminator where Wrapped.Response: PaginatedResponse { + let chainedPaginatedRequest: ChainedPaginatedRequest + + init(request: ChainedPaginatedRequest) { + self.chainedPaginatedRequest = request + } + + func parse(request: T, data: Data) throws -> T.Response { + return chainedPaginatedRequest.items as! T.Response + } +} diff --git a/LineSDK/LineSDK/Networking/Client/ParametersAdapter.swift b/LineSDK/LineSDK/Networking/Client/ParametersAdapter.swift index a660d031..c61e4ff2 100644 --- a/LineSDK/LineSDK/Networking/Client/ParametersAdapter.swift +++ b/LineSDK/LineSDK/Networking/Client/ParametersAdapter.swift @@ -1,13 +1,13 @@ // // ParametersAdapter.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -47,7 +47,8 @@ struct URLQueryEncoder: RequestAdapter { var allowedCharacterSet = CharacterSet.urlQueryAllowed allowedCharacterSet.remove(charactersIn: "!*'();:@&=+$,/?%#[]") - let percentEncodedQuery = (components.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters, allowed: allowedCharacterSet) + let percentEncodedQuery = (components.percentEncodedQuery.map { $0 + "&" } ?? "") + + query(parameters, allowed: allowedCharacterSet) components.percentEncodedQuery = percentEncodedQuery return components.url ?? url } @@ -91,7 +92,11 @@ private func query(_ parameters: Parameters, allowed: CharacterSet = .urlQueryAl .joined(separator: "&") } -private func queryComponents(fromKey key: String, value: Any, allowed: CharacterSet = .urlQueryAllowed) -> [(String, String)] { +private func queryComponents( + fromKey key: String, + value: Any, + allowed: CharacterSet = .urlQueryAllowed) -> [(String, String)] +{ var components: [(String, String)] = [] if let dictionary = value as? [String: Any] { diff --git a/LineSDK/LineSDK/Networking/Client/Request.swift b/LineSDK/LineSDK/Networking/Client/Request.swift index 1eba693d..1ec0bffb 100644 --- a/LineSDK/LineSDK/Networking/Client/Request.swift +++ b/LineSDK/LineSDK/Networking/Client/Request.swift @@ -1,13 +1,13 @@ // // Request.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -131,6 +131,10 @@ public protocol Request { /// The API entry path for the request. var path: String { get } + + /// The query items which should be appended to the path. Only applies to POST requests. + /// For GET request, use `parameters` to specify the path queries. + var pathQueries: [URLQueryItem]? { get } /// Parameters to be encoded and sent. The default value is `nil`. var parameters: Parameters? { get } @@ -195,7 +199,10 @@ public extension Request { var adapters: [RequestAdapter] { // Default header, UA etc - var adapters: [RequestAdapter] = [HeaderAdapter.default, method.adapter] + var adapters: [RequestAdapter] = [ + HeaderAdapter.default, + method.adapter + ] // Parameter adapters if let parameters = parameters { @@ -232,7 +239,8 @@ public extension Request { ]) return pipelines } - + + var pathQueries: [URLQueryItem]? { return nil } var suffixAdapters: [RequestAdapter]? { return nil } var prefixPipelines: [ResponsePipeline]? { return nil } var dataParser: ResponsePipelineTerminator { diff --git a/LineSDK/LineSDK/Networking/Client/RequestAdapter.swift b/LineSDK/LineSDK/Networking/Client/RequestAdapter.swift index f37c695d..0714c8b4 100644 --- a/LineSDK/LineSDK/Networking/Client/RequestAdapter.swift +++ b/LineSDK/LineSDK/Networking/Client/RequestAdapter.swift @@ -1,13 +1,13 @@ // // RequestAdapter.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,7 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import Foundation +import UIKit /// Adapts a request to another request. Adapters modify an input `URLRequest` object. public protocol RequestAdapter { @@ -64,7 +64,8 @@ struct HeaderAdapter: RequestAdapter { let systemVersion = device.systemVersion.replacingOccurrences(of: ".", with: "_") let model = device.model - userAgent = "\(appID)/\(appVersion) ChannelSDK/\(Constant.SDKVersion) (\(model); CPU iPhone OS \(systemVersion) like Mac OS X)" + userAgent = "\(appID)/\(appVersion) ChannelSDK/\(Constant.SDKVersion) " + + "(\(model); CPU iPhone OS \(systemVersion) like Mac OS X)" } func adapted(_ request: URLRequest) throws -> URLRequest { diff --git a/LineSDK/LineSDK/Networking/Client/ResponsePipeline.swift b/LineSDK/LineSDK/Networking/Client/ResponsePipeline.swift index 4fee8aee..f07b1efb 100644 --- a/LineSDK/LineSDK/Networking/Client/ResponsePipeline.swift +++ b/LineSDK/LineSDK/Networking/Client/ResponsePipeline.swift @@ -1,13 +1,13 @@ // // ResponsePipeline.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -24,7 +24,7 @@ import Foundation /// Represents the final pipeline of a series of response pipelines. Use the terminator to parse response /// data into a final `Response` object of a certain `Request` object. -public protocol ResponsePipelineTerminator: class { // Use class protocol for easier Equatable conforming +public protocol ResponsePipelineTerminator: AnyObject { // Use class protocol for easier Equatable conforming /// Parses `data` that holds input values to a `Response` object. /// /// - Parameters: @@ -38,9 +38,29 @@ public protocol ResponsePipelineTerminator: class { // Use class protocol for ea /// Represents a redirection stage of a series of response pipelines. Use redirectors to additionally /// perform data processing by invoking `closure` with a proper /// `ResponsePipelineRedirectorAction` enumeration member. -public protocol ResponsePipelineRedirector: class { // Use class protocol for easier Equatable conforming +public protocol ResponsePipelineRedirector: AnyObject { // Use class protocol for easier Equatable conforming + + /// Whether this redirector should be applied to execute and handle a received HTTP response. + /// - Parameters: + /// - request: The original `Request`. + /// - data: The received data contained in the HTTP response. + /// - response: The received HTTP response to the request. func shouldApply(request: T, data: Data, response: HTTPURLResponse) -> Bool - func redirect(request: T, data: Data, response: HTTPURLResponse, done closure: @escaping (ResponsePipelineRedirectorAction) throws -> Void) throws + + /// Performs the redirect action for current redirector. Define how to process the received response and data. + /// When the process is finished, call `closure` with the required action to make the response pipeline continue. + /// - Parameters: + /// - request: The original `Request`. + /// - data: The received data contained in the HTTP response. + /// - response: The received HTTP response to the request. + /// - closure: A block to be called when you have finished processing the received response and data. + /// The block takes a single parameter, which must be one of the member in the + /// `ResponsePipelineRedirectorAction` enumeration. + func redirect( + request: T, + data: Data, + response: HTTPURLResponse, + done closure: @escaping (ResponsePipelineRedirectorAction) throws -> Void) throws } /// Actions against the processing result from a `ResponsePipelineRedirector` object. A redirector needs to @@ -137,7 +157,7 @@ class RefreshTokenRedirector: ResponsePipelineRedirector { response: HTTPURLResponse, done closure: @escaping (ResponsePipelineRedirectorAction) throws -> Void) throws { - API.refreshAccessToken { result in + API.Auth.refreshAccessToken { result in switch result { case .success(_): try? closure(.restartWithout(.redirector(self))) @@ -170,7 +190,7 @@ class BadHTTPStatusRedirector: ResponsePipelineRedirector { decoder.keyDecodingStrategy = .convertFromSnakeCase let rawString = String(data: data, encoding: .utf8) do { - // There are two possible error format now. + // There are two possible error formats now. // First, try to parse the error into a auth related error let error = try decoder.decode(InternalAuthError.self, from: data) let detail = LineSDKError.ResponseErrorReason.APIErrorDetail( @@ -237,6 +257,13 @@ class DataTransformRedirector: ResponsePipelineRedirector { { try closure(.continueWith(transform(data), response)) } - - } + +// Convert empty data to an empty JSON `{}` +let emptyDataTransformer: ResponsePipeline = { + let isDataEmpty: ((Data) -> Bool) = { $0.isEmpty } + let dataTransformer = DataTransformRedirector(condition: isDataEmpty) { _ in + return "{}".data(using: .utf8)! + } + return .redirector(dataTransformer) +}() diff --git a/LineSDK/LineSDK/Networking/Client/Session.swift b/LineSDK/LineSDK/Networking/Client/Session.swift index 33c7383c..d66f1b71 100644 --- a/LineSDK/LineSDK/Networking/Client/Session.swift +++ b/LineSDK/LineSDK/Networking/Client/Session.swift @@ -1,13 +1,13 @@ // // Session.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -48,6 +48,9 @@ public class Session { } static var _shared: Session? + + /// The shared instance of `Session`. Access this value after you setup the LINE SDK. + /// Otherwise, your app will be trapped. public static var shared: Session { return guardSharedProperty(_shared) } @@ -169,10 +172,13 @@ public class Session { /// - Returns: Configured request. /// - Throws: Any error might happen during creating the request. func create(_ request: T) throws -> URLRequest { - let url = request.baseURL.appendingPathComponentIfNotEmpty(request.path) + let url = request.baseURL + .appendingPathComponentIfNotEmpty(request) + .appendingPathQueryItems(request) + let urlRequest = URLRequest( url: url, - cachePolicy: .reloadIgnoringLocalCacheData, + cachePolicy: request.cachePolicy, timeoutInterval: request.timeout) let adapters = request.adapters + (request.suffixAdapters ?? []) @@ -355,7 +361,22 @@ extension SessionTask { } extension URL { - func appendingPathComponentIfNotEmpty(_ path: String) -> URL { + func appendingPathComponentIfNotEmpty(_ request: R) -> URL { + let path = request.path return path.isEmpty ? self : appendingPathComponent(path) } + + func appendingPathQueryItems(_ request: R) -> URL { + guard request.method != .get else { + return self + } + guard let items = request.pathQueries else { + return self + } + guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { + return self + } + components.queryItems = items + return components.url ?? self + } } diff --git a/LineSDK/LineSDK/Networking/Images/DownloadableImageView.swift b/LineSDK/LineSDK/Networking/Images/DownloadableImageView.swift new file mode 100644 index 00000000..e850607b --- /dev/null +++ b/LineSDK/LineSDK/Networking/Images/DownloadableImageView.swift @@ -0,0 +1,57 @@ +// +// DownloadableImageView.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class DownloadableImageView: UIImageView { + + var taskToken: ImageManager.TaskToken? + + func setImage( + _ url: URL?, + placeholder: UIImage? = nil, + completion: ((Result) -> Void)? = nil) + { + taskToken = ImageManager.shared.nextToken() + image = placeholder + guard let url = url else { + completion?(.success(placeholder ?? UIImage())) + return + } + + + ImageManager.shared.getImage(url, taskToken: taskToken!) { result, token in + + guard token == self.taskToken else { + completion?(.failure(LineSDKError.generalError(reason: .notOriginalTask(token: token)))) + return + } + + guard let image = try? result.get() else { // Error case + completion?(result) + return + } + + self.image = image + completion?(result) + } + } +} diff --git a/LineSDK/LineSDK/Networking/Images/ImageDownloader.swift b/LineSDK/LineSDK/Networking/Images/ImageDownloader.swift new file mode 100644 index 00000000..0d28e989 --- /dev/null +++ b/LineSDK/LineSDK/Networking/Images/ImageDownloader.swift @@ -0,0 +1,67 @@ +// +// ImageDownloader.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class ImageDownloader { + let session: URLSession + + init() { + session = URLSession(configuration: .default) + } + + func download( + url: URL, + callbackQueue: CallbackQueue = .asyncMain, + completion: @escaping (ImageSettingResult) -> Void) + { + session.dataTask(with: url) { (data, response, error) in + + let callback: ((Result) -> Void) = { result in + callbackQueue.execute { completion(result) } + } + + guard let response = response as? HTTPURLResponse else { + callback(.failure(.responseFailed(reason: .nonHTTPURLResponse))) + return + } + + guard response.statusCode < 300 && response.statusCode >= 200 else { + callback(.failure(.responseFailed(reason: .nonHTTPURLResponse))) + return + } + + if let data = data { + if let image = UIImage(data: data) { + callback(.success(image)) + } else { + callback(.failure(.responseFailed(reason: .dataParsingFailed(UIImage.self, data, nil)))) + } + } else { + if let error = error { + callback(.failure(.responseFailed(reason: .URLSessionError(error)))) + } else { + callback(.failure(.responseFailed(reason: .nonHTTPURLResponse))) + } + } + }.resume() + } +} diff --git a/LineSDK/LineSDK/Networking/Images/ImageManager.swift b/LineSDK/LineSDK/Networking/Images/ImageManager.swift new file mode 100644 index 00000000..2707ce57 --- /dev/null +++ b/LineSDK/LineSDK/Networking/Images/ImageManager.swift @@ -0,0 +1,92 @@ +// +// ImageManager.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +typealias ImageSettingResult = Result + +class ImageManager { + + typealias TaskToken = UInt + private var currentToken: TaskToken = 0 + func nextToken() -> TaskToken { + if currentToken < TaskToken.max - 1 { + currentToken += 1 + } else { + currentToken = 1 + } + return currentToken + } + + static let shared = ImageManager() + + let downloader = ImageDownloader() + let cache: NSCache + + private init() { + cache = NSCache() + cache.countLimit = 500 + } + + func getImage( + _ url: URL, + taskToken: TaskToken, + callbackQueue: CallbackQueue = .currentMainOrAsync, + completion: @escaping (ImageSettingResult, TaskToken) -> Void) + { + getImage(url, callbackQueue: callbackQueue) { completion($0, taskToken) } + } + + func getImage( + _ url: URL, + callbackQueue: CallbackQueue = .currentMainOrAsync, + completion: ((ImageSettingResult) -> Void)? = nil) + { + let nsURL = url as NSURL + if let image = cache.object(forKey: nsURL) { + if let completion = completion { + callbackQueue.execute { completion(.success(image)) } + } + return + } + + downloader.download(url: url, callbackQueue: callbackQueue) { result in + + let callbackResult: ImageSettingResult + switch result { + case .success(let image): + self.cache.setObject(image, forKey: nsURL) + callbackResult = .success(image) + case .failure(let error): + callbackResult = .failure(error) + } + + if let completion = completion { + callbackQueue.execute { completion(callbackResult) } + } + } + } + + func purgeCache() { + cache.removeAllObjects() + } + +} diff --git a/LineSDK/LineSDK/Networking/Model/APIError.swift b/LineSDK/LineSDK/Networking/Model/APIError.swift index ec00bf81..c382a1af 100644 --- a/LineSDK/LineSDK/Networking/Model/APIError.swift +++ b/LineSDK/LineSDK/Networking/Model/APIError.swift @@ -1,13 +1,13 @@ // // APIError.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Networking/Model/CustomizeCoding/CodingExtension.swift b/LineSDK/LineSDK/Networking/Model/CustomizeCoding/CodingExtension.swift index d49f5f91..8a1774f2 100644 --- a/LineSDK/LineSDK/Networking/Model/CustomizeCoding/CodingExtension.swift +++ b/LineSDK/LineSDK/Networking/Model/CustomizeCoding/CodingExtension.swift @@ -1,13 +1,13 @@ // // CodingExtension.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,8 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import Foundation -import CoreGraphics +import UIKit extension KeyedDecodingContainer { func decodeLoginPermissions(forKey key: Key) throws -> [LoginPermission] { @@ -35,6 +34,13 @@ extension KeyedDecodingContainer { } } +extension KeyedEncodingContainer { + mutating func encodeLoginPermissions(_ permissions: [LoginPermission], forKey key: Key) throws { + let scopes = permissions.map { $0.rawValue }.joined(separator: " ") + try encode(scopes, forKey: key) + } +} + extension Encodable { func toJSON() throws -> Any { let data = try JSONEncoder().encode(self) @@ -42,92 +48,22 @@ extension Encodable { } } -extension UIColor { - - convenience init(hex3: UInt16, alpha: CGFloat = 1) { - let divisor = CGFloat(15) - let red = CGFloat((hex3 & 0xF00) >> 8) / divisor - let green = CGFloat((hex3 & 0x0F0) >> 4) / divisor - let blue = CGFloat( hex3 & 0x00F ) / divisor - self.init(red: red, green: green, blue: blue, alpha: alpha) - } - - convenience init(hex4: UInt16) { - let divisor = CGFloat(15) - let red = CGFloat((hex4 & 0xF000) >> 12) / divisor - let green = CGFloat((hex4 & 0x0F00) >> 8) / divisor - let blue = CGFloat((hex4 & 0x00F0) >> 4) / divisor - let alpha = CGFloat( hex4 & 0x000F ) / divisor - self.init(red: red, green: green, blue: blue, alpha: alpha) - } - - convenience init(hex6: UInt32, alpha: CGFloat = 1) { - let divisor = CGFloat(255) - let red = CGFloat((hex6 & 0xFF0000) >> 16) / divisor - let green = CGFloat((hex6 & 0x00FF00) >> 8) / divisor - let blue = CGFloat( hex6 & 0x0000FF ) / divisor - self.init(red: red, green: green, blue: blue, alpha: alpha) - } - - convenience init(hex8: UInt32) { - let divisor = CGFloat(255) - let red = CGFloat((hex8 & 0xFF000000) >> 24) / divisor - let green = CGFloat((hex8 & 0x00FF0000) >> 16) / divisor - let blue = CGFloat((hex8 & 0x0000FF00) >> 8) / divisor - let alpha = CGFloat( hex8 & 0x000000FF ) / divisor - self.init(red: red, green: green, blue: blue, alpha: alpha) - } - - convenience init(rgb: String, default color: UIColor = .white) { - guard rgb.hasPrefix("#") else { - self.init(cgColor: color.cgColor) - return - } - - let hexString = String(rgb[String.Index(encodedOffset: 1)...]) - var hexValue: UInt32 = 0 - - guard Scanner(string: hexString).scanHexInt32(&hexValue) else { - self.init(cgColor: color.cgColor) - return - } - switch (hexString.count) { - case 3: self.init(hex3: UInt16(hexValue)) - case 4: self.init(hex4: UInt16(hexValue)) - case 6: self.init(hex6: hexValue) - case 8: self.init(hex8: hexValue) - default: self.init(cgColor: color.cgColor) - } - } - - func hexString(_ includeAlpha: Bool = false) -> String { - var r: CGFloat = 0 - var g: CGFloat = 0 - var b: CGFloat = 0 - var a: CGFloat = 0 - - self.getRed(&r, green: &g, blue: &b, alpha: &a) - - guard r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1 else { return "#FFFFFF" } - - if (includeAlpha) { - return String(format: "#%02X%02X%02X%02X", Int(r * 255), Int(g * 255), Int(b * 255), Int(a * 255)) - } else { - return String(format: "#%02X%02X%02X", Int(r * 255), Int(g * 255), Int(b * 255)) - } - } -} - +/// A data structure that can be parsed to a `RawRepresentable` type, with a default case to be used if the received +/// data cannot be represented by any value in the type. public protocol DefaultEnumCodable: RawRepresentable, Codable { + /// The default value to use when the parsing fails due to the received data not being representable by any value + /// in the type. static var defaultCase: Self { get } } +/// The default implementation of `DefaultEnumCodable` when the `Self.RawValue` is decodable. It tries to parse a single +/// value in the decoder container and initialize `Self` with the value. If the decoded value is not convertible to +/// `Self`, it will be initialized as the `defaultCase`. public extension DefaultEnumCodable where Self.RawValue: Decodable { + /// :nodoc: init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let rawValue = try container.decode(RawValue.self) self = Self.init(rawValue: rawValue) ?? Self.defaultCase } } - - diff --git a/LineSDK/LineSDK/Networking/Model/CustomizeCoding/HexColor.swift b/LineSDK/LineSDK/Networking/Model/CustomizeCoding/HexColor.swift index 6d008f38..e4d55522 100644 --- a/LineSDK/LineSDK/Networking/Model/CustomizeCoding/HexColor.swift +++ b/LineSDK/LineSDK/Networking/Model/CustomizeCoding/HexColor.swift @@ -1,13 +1,13 @@ // // HexColor.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,7 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import Foundation +import UIKit /// Represents a color in hexadecimal notation. This type provides compatibility with the `Codable` protocol /// for color objects. diff --git a/LineSDK/LineSDK/Networking/Model/Unit.swift b/LineSDK/LineSDK/Networking/Model/Unit.swift index 840f2c3d..dc2a5b36 100644 --- a/LineSDK/LineSDK/Networking/Model/Unit.swift +++ b/LineSDK/LineSDK/Networking/Model/Unit.swift @@ -1,13 +1,13 @@ // // Unit.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -21,4 +21,5 @@ import Foundation -struct Unit: Decodable {} +/// Represents a signal of some event. It is a decodable version of `Void`. +public struct Unit: Decodable {} diff --git a/LineSDK/LineSDK/OpenChat/Model/OpenChatCategory.swift b/LineSDK/LineSDK/OpenChat/Model/OpenChatCategory.swift new file mode 100644 index 00000000..ab13f53c --- /dev/null +++ b/LineSDK/LineSDK/OpenChat/Model/OpenChatCategory.swift @@ -0,0 +1,79 @@ +// +// OpenChatCategory.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// Represents the category of an Open Chat room. +public enum OpenChatCategory: Int, CaseIterable { + // The order is important. It is the order which displayed when using `OpenChatCategory.allCases`. + /// Not selected. + case notSelected = 1 + /// Category "Schools". + case school = 2 + /// Category "Friends". + case friend = 7 + /// Category "Company". + case company = 5 + /// Category "Organizations". + case organization = 6 + /// Category "Local". + case region = 8 + /// Category "Kids". + case baby = 28 + /// Category "Sports". + case sports = 16 + /// Category "Games". + case game = 17 + /// Category "Books". + case book = 29 + /// Category "Movies". + case movies = 30 + /// Category "Photos". + case photo = 37 + /// Category "Art". + case art = 41 + /// Category "Animation & comics". + case animation = 22 + /// Category "Music". + case music = 33 + /// Category "TV shows". + case tv = 24 + /// Category "Famous people". + case celebrity = 26 + /// Category "Food". + case food = 12 + /// Category "Travel". + case travel = 18 + /// Category "Pets". + case pet = 27 + /// Category "Automotive". + case car = 19 + /// Category "Fashion & beauty". + case fashion = 20 + /// Category "Health". + case health = 23 + /// Category "Finance & business". + case finance = 40 + /// Category "Study". + case study = 11 + /// Category "Other". + case etc = 35 +} diff --git a/LineSDK/LineSDK/OpenChat/Model/OpenChatCreatingFormItem.swift b/LineSDK/LineSDK/OpenChat/Model/OpenChatCreatingFormItem.swift new file mode 100644 index 00000000..23700bcf --- /dev/null +++ b/LineSDK/LineSDK/OpenChat/Model/OpenChatCreatingFormItem.swift @@ -0,0 +1,37 @@ +// +// OpenChatCreatingFormItem.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +struct OpenChatCreatingFormItem { + var roomName: String = "" + var roomDescription: String = "" + var category: OpenChatCategory = .notSelected + var allowSearch = true + + var userName: String = "" + + mutating func normalize() { + roomName = roomName.normalized + roomDescription = roomDescription.normalized + userName = userName.normalized + } +} diff --git a/LineSDK/LineSDK/OpenChat/Model/OpenChatRoomCreatingItem.swift b/LineSDK/LineSDK/OpenChat/Model/OpenChatRoomCreatingItem.swift new file mode 100644 index 00000000..9cfd0387 --- /dev/null +++ b/LineSDK/LineSDK/OpenChat/Model/OpenChatRoomCreatingItem.swift @@ -0,0 +1,99 @@ +// +// OpenChatRoomCreatingItem.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// Represents the information used when creating the Open Chat room. +public struct OpenChatRoomCreatingItem { + + /// The name the room. + public let name: String + + /// The room description. + public let roomDescription: String + + /// The creator name of the room. + public let creatorDisplayName: String + + /// The category index that the room belongs to. + public let category: Int + + /// Whether this room allows to be searched. + public let allowSearch: Bool + + init(form: OpenChatCreatingFormItem) { + var normalizedForm = form + normalizedForm.normalize() + self.init( + name: normalizedForm.roomName, + roomDescription: normalizedForm.roomDescription, + creatorDisplayName: normalizedForm.userName, + category: normalizedForm.category.rawValue, + allowSearch: normalizedForm.allowSearch + ) + } + + /// Creates an item holds the information used when creating the Open Chat room. + /// - Parameters: + /// - name: The name the room. + /// - roomDescription: The room description. + /// - creatorDisplayName: The creator name of the room. + /// - category: The category that the room belongs to. + /// - allowSearch: Whether this room allows to be searched. + public init( + name: String, + roomDescription: String, + creatorDisplayName: String, + category: OpenChatCategory, + allowSearch: Bool + ) + { + self.init( + name: name, + roomDescription: roomDescription, + creatorDisplayName: creatorDisplayName, + category: category.rawValue, + allowSearch: allowSearch + ) + } + + /// Creates an item holds the information used when creating the Open Chat room. + /// - Parameters: + /// - name: The name the room. + /// - roomDescription: The room description. + /// - creatorDisplayName: The creator name of the room. + /// - category: The category index that the room belongs to. + /// - allowSearch: Whether this room allows to be searched. + public init( + name: String, + roomDescription: String, + creatorDisplayName: String, + category: Int, + allowSearch: Bool + ) + { + self.name = name + self.roomDescription = roomDescription + self.creatorDisplayName = creatorDisplayName + self.category = category + self.allowSearch = allowSearch + } +} diff --git a/LineSDK/LineSDK/OpenChat/Request/GetOpenChatRoomJoinTypeRequest.swift b/LineSDK/LineSDK/OpenChat/Request/GetOpenChatRoomJoinTypeRequest.swift new file mode 100644 index 00000000..ee8e9b83 --- /dev/null +++ b/LineSDK/LineSDK/OpenChat/Request/GetOpenChatRoomJoinTypeRequest.swift @@ -0,0 +1,73 @@ +// +// GetOpenChatRoomJoinTypeRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +public struct GetOpenChatRoomJoinTypeRequest: Request { + + /// The joining type of an Open Chat room. The value indicates what is required if a user want to join the room. + public enum RoomType: String, Decodable { + /// The room is public and open for anyone to join. + case `default` = "NONE" + /// A user needs to request to join the room, only approved user can join. The admins or authority users of the + /// room can approve the request. + case approval = "APPROVAL" + /// A user needs to input the join code to join the room. + case code = "CODE" + /// The received state is not defined yet in current version. + /// Try to upgrade to the latest SDK version if you encountered this. + case undefined + + /// :nodoc: + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let value = try container.decode(String.self) + self = RoomType(rawValue: value) ?? .undefined + } + } + + /// The response of a `GetOpenChatRoomJoinTypeRequest`. + public struct Response: Decodable { + /// The room joining type of the requested Open Chat room. + public let type: RoomType + } + + /// :nodoc: + public let method: HTTPMethod = .get + /// :nodoc: + public var path: String { return "/openchat/v1/openchats/\(openChatId)/type" } + /// :nodoc: + public let authentication: AuthenticateMethod = .token + + /// The Open Chat room ID. + public let openChatId: EntityID + + /// Creates a request. + /// - Parameter openChatId: The Open Chat room ID. + public init(openChatId: EntityID) throws { + guard openChatId.isValid else { + throw LineSDKError.requestFailed(reason: + .invalidParameter([.invalidEntityID("openChatId", value: openChatId)]) + ) + } + self.openChatId = openChatId + } +} diff --git a/LineSDK/LineSDK/OpenChat/Request/GetOpenChatRoomMembershipStateRequest.swift b/LineSDK/LineSDK/OpenChat/Request/GetOpenChatRoomMembershipStateRequest.swift new file mode 100644 index 00000000..c146bdf5 --- /dev/null +++ b/LineSDK/LineSDK/OpenChat/Request/GetOpenChatRoomMembershipStateRequest.swift @@ -0,0 +1,72 @@ +// +// GetOpenChatRoomMembershipStateRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// Represents a request for getting membership of current user to a given Open Chat room. +public struct GetOpenChatRoomMembershipStateRequest: Request { + + /// The membership state of current user to the room. + public enum State: String, Decodable { + /// The user has already joined the room. + case joined = "JOINED" + /// The user is not a member of the room yet. + case notJoined = "NOT_JOINED" + /// The received state is not defined yet in current version. + /// Try to upgrade to the latest SDK version if you encountered this. + case undefined + + /// :nodoc: + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let value = try container.decode(String.self) + self = State(rawValue: value) ?? .undefined + } + } + + /// The response of a `GetOpenChatRoomMembershipStateRequest`. + public struct Response: Decodable { + /// The membership state of current user. + public let state: State + } + + /// :nodoc: + public let method: HTTPMethod = .get + /// :nodoc: + public var path: String { return "/openchat/v1/openchats/\(openChatId)/members/me/membership" } + /// :nodoc: + public let authentication: AuthenticateMethod = .token + + /// The Open Chat room ID. + public let openChatId: EntityID + + /// Creates a request. + /// - Parameter openChatId: The Open Chat room ID. + public init(openChatId: EntityID) throws { + guard openChatId.isValid else { + throw LineSDKError.requestFailed(reason: + .invalidParameter([.invalidEntityID("openChatId", value: openChatId)]) + ) + } + self.openChatId = openChatId + } + +} diff --git a/LineSDK/LineSDK/OpenChat/Request/GetOpenChatRoomStatusRequest.swift b/LineSDK/LineSDK/OpenChat/Request/GetOpenChatRoomStatusRequest.swift new file mode 100644 index 00000000..90e17e1f --- /dev/null +++ b/LineSDK/LineSDK/OpenChat/Request/GetOpenChatRoomStatusRequest.swift @@ -0,0 +1,72 @@ +// +// GetOpenChatRoomStatusRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// Represents a request for getting the status of a given Open Chat room. +public struct GetOpenChatRoomStatusRequest: Request { + + /// The status of an Open Chat room. + public enum Status: String, Codable { + /// The room is alive. Other users can join it. + case alive = "ALIVE" + /// The room is already deleted. + case deleted = "DELETED" + /// The room is suspended for some reason. + case suspended = "SUSPENDED" + /// The received state is not defined yet in current version. + /// Try to upgrade to the latest SDK version if you encountered this. + case undefined + + /// :nodoc: + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let value = try container.decode(String.self) + self = Status(rawValue: value) ?? .undefined + } + } + + /// The response of a `GetOpenChatRoomStatusRequest`. + public struct Response: Codable { + /// The status of the requested room. + public let status: Status + } + /// :nodoc: + public let method: HTTPMethod = .get + /// :nodoc: + public var path: String { return "/openchat/v1/openchats/\(openChatId)/status" } + /// :nodoc: + public let authentication: AuthenticateMethod = .token + + /// The Open Chat room ID. + public let openChatId: EntityID + + /// Creates a request. + /// - Parameter openChatId: The Open Chat room ID. + public init(openChatId: EntityID) throws { + guard openChatId.isValid else { + throw LineSDKError.requestFailed(reason: + .invalidParameter([.invalidEntityID("openChatId", value: openChatId)]) + ) + } + self.openChatId = openChatId + } +} diff --git a/LineSDK/LineSDK/OpenChat/Request/GetOpenChatTermAgreementStatusRequest.swift b/LineSDK/LineSDK/OpenChat/Request/GetOpenChatTermAgreementStatusRequest.swift new file mode 100644 index 00000000..393dddc1 --- /dev/null +++ b/LineSDK/LineSDK/OpenChat/Request/GetOpenChatTermAgreementStatusRequest.swift @@ -0,0 +1,42 @@ +// +// GetOpenChatTermAgreementStatusRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// Represents a request for getting Open Chat user term agreement status of current user. +/// Unless the user has agreed the use term, creating an Open Chat room is not permitted. +public struct GetOpenChatTermAgreementStatusRequest: Request { + + /// The response of a `GetOpenChatTermAgreementStatusRequest`. + public struct Response: Decodable { + /// Whether the user has already agreed the use term. + public let agreed: Bool + } + + /// :nodoc: + public let method: HTTPMethod = .get + /// :nodoc: + public let path = "/openchat/v1/terms/agreement" + /// :nodoc: + public let authentication: AuthenticateMethod = .token + /// :nodoc: + public init() { } +} diff --git a/LineSDK/LineSDK/OpenChat/Request/PostOpenChatCreateRequest.swift b/LineSDK/LineSDK/OpenChat/Request/PostOpenChatCreateRequest.swift new file mode 100644 index 00000000..06e44387 --- /dev/null +++ b/LineSDK/LineSDK/OpenChat/Request/PostOpenChatCreateRequest.swift @@ -0,0 +1,74 @@ +// +// PostOpenChatCreateRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// The basic Open Chat room information. +public struct OpenChatRoomInfo: Decodable { + + /// The identifier of this Open Chat room. + public let openChatId: String + + /// The URL of this Open Chat room. Open this URL will navigate to LINE app (if installed) or a web page + /// for the Open Chat room. + public let url: URL + + enum CodingKeys: String, CodingKey { + case openChatId = "openchatId" + case url + } +} + +/// Represents a request for creating an Open Chat room. +public struct PostOpenChatCreateRequest: Request { + /// :nodoc: + public typealias Response = OpenChatRoomInfo + /// :nodoc: + public let method: HTTPMethod = .post + /// :nodoc: + public let path = "/openchat/v1/openchats" + /// :nodoc: + public let authentication: AuthenticateMethod = .token + + /// The room information will be used to create the room. + public let room: OpenChatRoomCreatingItem + + /// :nodoc: + public init(room: OpenChatRoomCreatingItem) { + self.room = room + } + /// :nodoc: + public var parameters: Parameters? { + return room.toDictionary + } +} + +extension OpenChatRoomCreatingItem { + fileprivate var toDictionary: [String: Any] { + return [ + "name": name, + "description": roomDescription, + "creatorDisplayName": creatorDisplayName, + "category": category, + "allowSearch": allowSearch + ] + } +} diff --git a/LineSDK/LineSDK/OpenChat/Request/PostOpenChatRoomJoinRequest.swift b/LineSDK/LineSDK/OpenChat/Request/PostOpenChatRoomJoinRequest.swift new file mode 100644 index 00000000..59f17a3d --- /dev/null +++ b/LineSDK/LineSDK/OpenChat/Request/PostOpenChatRoomJoinRequest.swift @@ -0,0 +1,59 @@ +// +// PostOpenChatRoomJoinRequest.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// Represents a request for joining an Open Chat room. +public struct PostOpenChatRoomJoinRequest: Request { + /// :nodoc: + public typealias Response = Unit + /// :nodoc: + public let method: HTTPMethod = .post + /// :nodoc: + public var path: String { return "/openchat/v1/openchats/\(openChatId)/join" } + /// :nodoc: + public let authentication: AuthenticateMethod = .token + + /// The identifier of the joining Open Chat room. + public let openChatId: EntityID + + /// The desired display name of current user in the Open Chat room. + public let displayName: String + + /// :nodoc: + public init(openChatId: EntityID, displayName: String) throws { + guard openChatId.isValid else { + throw LineSDKError.requestFailed(reason: + .invalidParameter([.invalidEntityID("openChatId", value: openChatId)]) + ) + } + self.openChatId = openChatId + self.displayName = displayName + } + + public var prefixPipelines: [ResponsePipeline]? { + return [emptyDataTransformer] + } + + public var parameters: Parameters? { + return ["displayName": displayName] + } +} diff --git a/LineSDK/LineSDK/Resource.bundle/PrivacyInfo.xcprivacy b/LineSDK/LineSDK/Resource.bundle/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..5ce715a0 --- /dev/null +++ b/LineSDK/LineSDK/Resource.bundle/PrivacyInfo.xcprivacy @@ -0,0 +1,36 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyTrackingDomains + + NSPrivacyTracking + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + + diff --git a/LineSDK/LineSDK/Resource.bundle/ar.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/ar.lproj/Localizable.strings old mode 100644 new mode 100755 index bc368451..b118b3fc --- a/LineSDK/LineSDK/Resource.bundle/ar.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/ar.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "تسجيل دخول باستخدام LINE"; +"chat.multi.fwd.confirm" = "يمكنك تحديد عددٍ يصل إلى %d."; +"common.action.close" = "إغلاق"; +"common.action.send" = "إرسال"; +"common.cancel" = "إلغاء"; +"common.next" = "التالي"; +"common.ok" = "موافق"; +"friends.share.search" = "بحث"; +"linesdk.login.button.login" = "تسجيل دخول باستخدام LINE"; +"open.line" = "فتح LINE"; +"openchat.create.profile.input.guide" = "تعيين اسمك المستعار لاستخدامه في \"%@\"."; +"openchat.create.profile.input.max.count" = "تم بلوغ الحد الأقصى للطول."; +"openchat.create.profile.input.placeholder" = "أدخل الاسم المستعار"; +"openchat.create.profile.title" = "ملفي التعريفي"; +"openchat.create.room.category" = "الفئة"; +"openchat.create.room.category.guide" = "سيتم عرض OpenChat الخاص بك في الفئة المحددة."; +"openchat.create.room.description.guide" = "أدخل كلمات مفتاحية باستخدام وسوم#"; +"openchat.create.room.description.placeholder" = "أدخل وصفا"; +"openchat.create.room.name.placeholder" = "اسم OpenChat"; +"openchat.create.room.search" = "تمكين البحث"; +"openchat.create.room.search.guide" = "سيتمكن الآخرون من البحث عن OpenChat الخاص بك."; +"openchat.create.room.title" = "إنشاء OpenChat"; +"openchat.not.agree.with.terms" = "أنت لم توافق على شروط وبنود استخدام OpenChat حتى الآن.\nاذهب إلى OpenChat في تطبيق LINE ووافق على شروط وبنود الاستخدام للاستمرار ."; +"search.no.result" = "لا توجد نتائج"; +"shareRecipient.section.friends.title" = "الأصدقاء"; +"shareRecipient.section.groups.title" = "المجموعات"; +"square.create.category.all" = "الكل"; +"square.create.category.alumnus" = "حاصل على شهادة جامعية"; +"square.create.category.ani" = "رسوم متحركة وفكاهة"; +"square.create.category.art" = "الفن"; +"square.create.category.artculture" = "فن/ثقافة"; +"square.create.category.baby" = "أطفال"; +"square.create.category.beauty" = "الجمال"; +"square.create.category.book" = "الكتب"; +"square.create.category.car" = "سيارات"; +"square.create.category.celebrity" = "وجوه معروفة"; +"square.create.category.company" = "الشركة"; +"square.create.category.economy" = "الاقتصاد"; +"square.create.category.entertainer" = "المشاهير"; +"square.create.category.etc" = "أخرى"; +"square.create.category.exercise" = "اللياقة"; +"square.create.category.family" = "العائلة"; +"square.create.category.fan" = "نادي مشجعين"; +"square.create.category.fashion" = "الموضة والتجميل"; +"square.create.category.finance" = "الشؤون المالية والأعمال"; +"square.create.category.food" = "الطعام"; +"square.create.category.friend" = "الأصدقاء"; +"square.create.category.game" = "ألعاب"; +"square.create.category.health" = "الصحة"; +"square.create.category.history" = "التاريخ"; +"square.create.category.hobby" = "الهوايات"; +"square.create.category.it" = "التكنولوجيا"; +"square.create.category.jpop" = "موسيقى البوب اليابانية/الدراما اليابانية"; +"square.create.category.kpop" = "موسيقى البوب الكورية/الدراما الكورية"; +"square.create.category.medicine" = "طبي"; +"square.create.category.movies" = "الأفلام"; +"square.create.category.music" = "الموسيقى"; +"square.create.category.notselected" = "غير محدد (لا يظهر في أي فئة)"; +"square.create.category.org" = "المؤسسة"; +"square.create.category.pet" = "الحيوانات الأليفة"; +"square.create.category.photo" = "الصور"; +"square.create.category.recipe" = "الطهي"; +"square.create.category.region" = "محلي"; +"square.create.category.school" = "مدارس"; +"square.create.category.science" = "العلوم"; +"square.create.category.social" = "اجتماعي"; +"square.create.category.society" = "النادي"; +"square.create.category.sports" = "الرياضات"; +"square.create.category.study" = "الدراسة"; +"square.create.category.travel" = "السفر"; +"square.create.category.trending" = "محتوى شائع"; +"square.create.category.tv" = "عروض تليفزيونية"; diff --git a/LineSDK/LineSDK/Resource.bundle/de.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/de.lproj/Localizable.strings old mode 100644 new mode 100755 index 803453b3..93f3025b --- a/LineSDK/LineSDK/Resource.bundle/de.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/de.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "Mit LINE anmelden"; +"chat.multi.fwd.confirm" = "Sie können bis zu %d auswählen."; +"common.action.close" = "Schließen"; +"common.action.send" = "Senden"; +"common.cancel" = "Abbrechen"; +"common.next" = "Weiter"; +"common.ok" = "OK"; +"friends.share.search" = "Nach Namen suchen"; +"linesdk.login.button.login" = "Mit LINE anmelden"; +"open.line" = "LINE öffnen"; +"openchat.create.profile.input.guide" = "Lege den Alias fest, der in „%@“ verwendet werden soll."; +"openchat.create.profile.input.max.count" = "Maximale Länge erreicht."; +"openchat.create.profile.input.placeholder" = "Alias eingeben"; +"openchat.create.profile.title" = "Mein Profil"; +"openchat.create.room.category" = "Kategorie"; +"openchat.create.room.category.guide" = "Dein OpenChat wird in der ausgewählten Kategorie angezeigt."; +"openchat.create.room.description.guide" = "Schlagwörter mit #hashtags eingeben"; +"openchat.create.room.description.placeholder" = "Beschreibung eingeben"; +"openchat.create.room.name.placeholder" = "Name des OpenChat"; +"openchat.create.room.search" = "Suche zulassen"; +"openchat.create.room.search.guide" = "Andere können nach deinem OpenChat suchen."; +"openchat.create.room.title" = "OpenChat erstellen"; +"openchat.not.agree.with.terms" = "Du hast den OpenChat-Nutzungsbedingungen noch nicht zugestimmt.\nUm fortzufahren, navigiere zu OpenChat in der LINE-App und stimme den Nutzungsbedingungen zu."; +"search.no.result" = "Keine Ergebnisse"; +"shareRecipient.section.friends.title" = "Freunde"; +"shareRecipient.section.groups.title" = "Gruppen"; +"square.create.category.all" = "Alle"; +"square.create.category.alumnus" = "Ehemalige Mitschüler/Kommilitonen"; +"square.create.category.ani" = "Animation und Comics"; +"square.create.category.art" = "Kunst"; +"square.create.category.artculture" = "Kunst/Kultur"; +"square.create.category.baby" = "Kinder"; +"square.create.category.beauty" = "Beauty"; +"square.create.category.book" = "Bücher"; +"square.create.category.car" = "Autos"; +"square.create.category.celebrity" = "Berühmte Personen"; +"square.create.category.company" = "Unternehmen"; +"square.create.category.economy" = "Wirtschaft"; +"square.create.category.entertainer" = "Prominente"; +"square.create.category.etc" = "Sonstiges"; +"square.create.category.exercise" = "Fitness"; +"square.create.category.family" = "Familie"; +"square.create.category.fan" = "Fanclub"; +"square.create.category.fashion" = "Mode und Beauty"; +"square.create.category.finance" = "Finanzen und Geschäfte"; +"square.create.category.food" = "Essen"; +"square.create.category.friend" = "Freunde"; +"square.create.category.game" = "Spiele"; +"square.create.category.health" = "Gesundheit"; +"square.create.category.history" = "Geschichte"; +"square.create.category.hobby" = "Hobbys"; +"square.create.category.it" = "Technik"; +"square.create.category.jpop" = "J-Pop/J-Drama"; +"square.create.category.kpop" = "K-Pop/K-Drama"; +"square.create.category.medicine" = "Medizin"; +"square.create.category.movies" = "Filme"; +"square.create.category.music" = "Musik"; +"square.create.category.notselected" = "Nicht ausgewählt (in keiner Kategorie angezeigt)"; +"square.create.category.org" = "Organisation"; +"square.create.category.pet" = "Haustiere"; +"square.create.category.photo" = "Fotos"; +"square.create.category.recipe" = "Kochen"; +"square.create.category.region" = "Lokal"; +"square.create.category.school" = "Schule"; +"square.create.category.science" = "Wissenschaft"; +"square.create.category.social" = "Soziales"; +"square.create.category.society" = "Club"; +"square.create.category.sports" = "Sport"; +"square.create.category.study" = "Lernen"; +"square.create.category.travel" = "Reise"; +"square.create.category.trending" = "Populär"; +"square.create.category.tv" = "TV-Shows"; diff --git a/LineSDK/LineSDK/Resource.bundle/en.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/en.lproj/Localizable.strings old mode 100644 new mode 100755 index cfa9897a..f2aedcc5 --- a/LineSDK/LineSDK/Resource.bundle/en.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/en.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "Log in with LINE"; +"chat.multi.fwd.confirm" = "You can select up to %d."; +"common.action.close" = "Close"; +"common.action.send" = "Send"; +"common.cancel" = "Cancel"; +"common.next" = "Next"; +"common.ok" = "OK"; +"friends.share.search" = "Search by name"; +"linesdk.login.button.login" = "Log in with LINE"; +"open.line" = "Open LINE"; +"openchat.create.profile.input.guide" = "Set your nickname to be used in \"%@\"."; +"openchat.create.profile.input.max.count" = "Maximum length reached."; +"openchat.create.profile.input.placeholder" = "Enter nickname"; +"openchat.create.profile.title" = "My profile"; +"openchat.create.room.category" = "Category"; +"openchat.create.room.category.guide" = "Your OpenChat will be displayed in the selected category."; +"openchat.create.room.description.guide" = "Enter keywords using #hashtags"; +"openchat.create.room.description.placeholder" = "Enter description"; +"openchat.create.room.name.placeholder" = "OpenChat name"; +"openchat.create.room.search" = "Allow search"; +"openchat.create.room.search.guide" = "Others will be able to search for your OpenChat."; +"openchat.create.room.title" = "Create OpenChat"; +"openchat.not.agree.with.terms" = "You haven\'t agreed to the OpenChat Terms and Conditions of Use yet.\nGo to OpenChat in the LINE app and agree to the Terms and Conditions of Use to continue."; +"search.no.result" = "No results."; +"shareRecipient.section.friends.title" = "Friends"; +"shareRecipient.section.groups.title" = "Groups"; +"square.create.category.all" = "All"; +"square.create.category.alumnus" = "Alumni"; +"square.create.category.ani" = "Animation & comics"; +"square.create.category.art" = "Art"; +"square.create.category.artculture" = "Arts & books"; +"square.create.category.baby" = "Kids"; +"square.create.category.beauty" = "Beauty"; +"square.create.category.book" = "Books"; +"square.create.category.car" = "Automotive"; +"square.create.category.celebrity" = "Famous people"; +"square.create.category.company" = "Company"; +"square.create.category.economy" = "Economy"; +"square.create.category.entertainer" = "Fan clubs"; +"square.create.category.etc" = "Other"; +"square.create.category.exercise" = "Fitness"; +"square.create.category.family" = "Family"; +"square.create.category.fan" = "Fan clubs"; +"square.create.category.fashion" = "Fashion & beauty"; +"square.create.category.finance" = "Finance & business"; +"square.create.category.food" = "Food"; +"square.create.category.friend" = "Friends"; +"square.create.category.game" = "Games"; +"square.create.category.health" = "Health"; +"square.create.category.history" = "History"; +"square.create.category.hobby" = "Hobbies"; +"square.create.category.it" = "Tech"; +"square.create.category.jpop" = "J-pop/J-drama"; +"square.create.category.kpop" = "K-pop/K-drama"; +"square.create.category.medicine" = "Medical"; +"square.create.category.movies" = "Movies"; +"square.create.category.music" = "Music"; +"square.create.category.notselected" = "Unselected (not shown in any category)"; +"square.create.category.org" = "Organizations"; +"square.create.category.pet" = "Pets"; +"square.create.category.photo" = "Photos"; +"square.create.category.recipe" = "Cooking"; +"square.create.category.region" = "Local"; +"square.create.category.school" = "Schools"; +"square.create.category.science" = "Science"; +"square.create.category.social" = "Social"; +"square.create.category.society" = "Club"; +"square.create.category.sports" = "Sports"; +"square.create.category.study" = "Study"; +"square.create.category.travel" = "Travel"; +"square.create.category.trending" = "Trending"; +"square.create.category.tv" = "TV shows"; diff --git a/LineSDK/LineSDK/Resource.bundle/es-419.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/es-419.lproj/Localizable.strings new file mode 100755 index 00000000..a8beb19d --- /dev/null +++ b/LineSDK/LineSDK/Resource.bundle/es-419.lproj/Localizable.strings @@ -0,0 +1,71 @@ +"chat.multi.fwd.confirm" = "Puedes seleccionar hasta %d."; +"common.action.close" = "Cerrar"; +"common.action.send" = "Enviar"; +"common.cancel" = "Cancelar"; +"common.next" = "Siguiente"; +"common.ok" = "Aceptar"; +"friends.share.search" = "Buscar por nombre"; +"linesdk.login.button.login" = "Iniciar sesión con LINE"; +"open.line" = "Abrir LINE"; +"openchat.create.profile.input.guide" = "Ponte un nickname para \"%@\"."; +"openchat.create.profile.input.max.count" = "Pon algo más corto."; +"openchat.create.profile.input.placeholder" = "Nickname"; +"openchat.create.profile.title" = "Mi perfil"; +"openchat.create.room.category" = "Categoría"; +"openchat.create.room.category.guide" = "Tu OpenChat se mostrará en la categoría seleccionada."; +"openchat.create.room.description.guide" = "Ingresa palabras clave usando #hashtags"; +"openchat.create.room.description.placeholder" = "Descripción"; +"openchat.create.room.name.placeholder" = "Nombre OpenChat"; +"openchat.create.room.search" = "Búsqueda abierta"; +"openchat.create.room.search.guide" = "Podrán buscar tu OpenChat."; +"openchat.create.room.title" = "Crear OpenChat"; +"openchat.not.agree.with.terms" = "En tu LINE, abre OpenChat y acepta los términos de uso de OpenChat.\n"; +"search.no.result" = "Parece que no hay nada."; +"shareRecipient.section.friends.title" = "Amigos"; +"shareRecipient.section.groups.title" = "Grupos"; +"square.create.category.all" = "Todas"; +"square.create.category.alumnus" = "Compañeros"; +"square.create.category.ani" = "Animes y cómics"; +"square.create.category.art" = "Arte"; +"square.create.category.artculture" = "Arte y cultura"; +"square.create.category.baby" = "Infantil"; +"square.create.category.beauty" = "Belleza"; +"square.create.category.book" = "Libros"; +"square.create.category.car" = "Automovilismo"; +"square.create.category.celebrity" = "Famosos"; +"square.create.category.company" = "Empresa"; +"square.create.category.economy" = "Economía"; +"square.create.category.entertainer" = "Farándula"; +"square.create.category.etc" = "Otras"; +"square.create.category.exercise" = "Fitness"; +"square.create.category.family" = "Familia"; +"square.create.category.fan" = "Club de fan"; +"square.create.category.fashion" = "Moda y belleza"; +"square.create.category.finance" = "Finanzas"; +"square.create.category.food" = "Gourmet"; +"square.create.category.friend" = "Amigos"; +"square.create.category.game" = "Juegos"; +"square.create.category.health" = "Salud"; +"square.create.category.history" = "Historia"; +"square.create.category.hobby" = "Pasatiempos"; +"square.create.category.it" = "Tecnología"; +"square.create.category.jpop" = "J-pop/J-drama"; +"square.create.category.kpop" = "K-pop/K-drama"; +"square.create.category.medicine" = "Medicina"; +"square.create.category.movies" = "Pelis"; +"square.create.category.music" = "Música"; +"square.create.category.notselected" = "Sin seleccionar (no sale en las categorías)"; +"square.create.category.org" = "Organización"; +"square.create.category.pet" = "Mascotas"; +"square.create.category.photo" = "Fotos"; +"square.create.category.recipe" = "Cocina"; +"square.create.category.region" = "Regional"; +"square.create.category.school" = "Escuelas"; +"square.create.category.science" = "Ciencia"; +"square.create.category.social" = "Social"; +"square.create.category.society" = "Club"; +"square.create.category.sports" = "Deportes"; +"square.create.category.study" = "Estudio"; +"square.create.category.travel" = "Viajes"; +"square.create.category.trending" = "Tendencias"; +"square.create.category.tv" = "Shows TV"; diff --git a/LineSDK/LineSDK/Resource.bundle/es-MX.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/es-MX.lproj/Localizable.strings new file mode 100755 index 00000000..a8beb19d --- /dev/null +++ b/LineSDK/LineSDK/Resource.bundle/es-MX.lproj/Localizable.strings @@ -0,0 +1,71 @@ +"chat.multi.fwd.confirm" = "Puedes seleccionar hasta %d."; +"common.action.close" = "Cerrar"; +"common.action.send" = "Enviar"; +"common.cancel" = "Cancelar"; +"common.next" = "Siguiente"; +"common.ok" = "Aceptar"; +"friends.share.search" = "Buscar por nombre"; +"linesdk.login.button.login" = "Iniciar sesión con LINE"; +"open.line" = "Abrir LINE"; +"openchat.create.profile.input.guide" = "Ponte un nickname para \"%@\"."; +"openchat.create.profile.input.max.count" = "Pon algo más corto."; +"openchat.create.profile.input.placeholder" = "Nickname"; +"openchat.create.profile.title" = "Mi perfil"; +"openchat.create.room.category" = "Categoría"; +"openchat.create.room.category.guide" = "Tu OpenChat se mostrará en la categoría seleccionada."; +"openchat.create.room.description.guide" = "Ingresa palabras clave usando #hashtags"; +"openchat.create.room.description.placeholder" = "Descripción"; +"openchat.create.room.name.placeholder" = "Nombre OpenChat"; +"openchat.create.room.search" = "Búsqueda abierta"; +"openchat.create.room.search.guide" = "Podrán buscar tu OpenChat."; +"openchat.create.room.title" = "Crear OpenChat"; +"openchat.not.agree.with.terms" = "En tu LINE, abre OpenChat y acepta los términos de uso de OpenChat.\n"; +"search.no.result" = "Parece que no hay nada."; +"shareRecipient.section.friends.title" = "Amigos"; +"shareRecipient.section.groups.title" = "Grupos"; +"square.create.category.all" = "Todas"; +"square.create.category.alumnus" = "Compañeros"; +"square.create.category.ani" = "Animes y cómics"; +"square.create.category.art" = "Arte"; +"square.create.category.artculture" = "Arte y cultura"; +"square.create.category.baby" = "Infantil"; +"square.create.category.beauty" = "Belleza"; +"square.create.category.book" = "Libros"; +"square.create.category.car" = "Automovilismo"; +"square.create.category.celebrity" = "Famosos"; +"square.create.category.company" = "Empresa"; +"square.create.category.economy" = "Economía"; +"square.create.category.entertainer" = "Farándula"; +"square.create.category.etc" = "Otras"; +"square.create.category.exercise" = "Fitness"; +"square.create.category.family" = "Familia"; +"square.create.category.fan" = "Club de fan"; +"square.create.category.fashion" = "Moda y belleza"; +"square.create.category.finance" = "Finanzas"; +"square.create.category.food" = "Gourmet"; +"square.create.category.friend" = "Amigos"; +"square.create.category.game" = "Juegos"; +"square.create.category.health" = "Salud"; +"square.create.category.history" = "Historia"; +"square.create.category.hobby" = "Pasatiempos"; +"square.create.category.it" = "Tecnología"; +"square.create.category.jpop" = "J-pop/J-drama"; +"square.create.category.kpop" = "K-pop/K-drama"; +"square.create.category.medicine" = "Medicina"; +"square.create.category.movies" = "Pelis"; +"square.create.category.music" = "Música"; +"square.create.category.notselected" = "Sin seleccionar (no sale en las categorías)"; +"square.create.category.org" = "Organización"; +"square.create.category.pet" = "Mascotas"; +"square.create.category.photo" = "Fotos"; +"square.create.category.recipe" = "Cocina"; +"square.create.category.region" = "Regional"; +"square.create.category.school" = "Escuelas"; +"square.create.category.science" = "Ciencia"; +"square.create.category.social" = "Social"; +"square.create.category.society" = "Club"; +"square.create.category.sports" = "Deportes"; +"square.create.category.study" = "Estudio"; +"square.create.category.travel" = "Viajes"; +"square.create.category.trending" = "Tendencias"; +"square.create.category.tv" = "Shows TV"; diff --git a/LineSDK/LineSDK/Resource.bundle/es.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/es.lproj/Localizable.strings old mode 100644 new mode 100755 index 08e315e8..9e3b3471 --- a/LineSDK/LineSDK/Resource.bundle/es.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/es.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "Iniciar sesión con LINE"; +"chat.multi.fwd.confirm" = "Puedes seleccionar hasta %d."; +"common.action.close" = "Cerrar"; +"common.action.send" = "Enviar"; +"common.cancel" = "Cancelar"; +"common.next" = "Siguiente"; +"common.ok" = "Aceptar"; +"friends.share.search" = "Buscar por nombre"; +"linesdk.login.button.login" = "Iniciar sesión con LINE"; +"open.line" = "Abrir LINE"; +"openchat.create.profile.input.guide" = "Ponte un alias para \"%@\"."; +"openchat.create.profile.input.max.count" = "Uf, muy largo."; +"openchat.create.profile.input.placeholder" = "Alias"; +"openchat.create.profile.title" = "Mi perfil"; +"openchat.create.room.category" = "Categoría"; +"openchat.create.room.category.guide" = "Tu OpenChat se mostrará en la categoría seleccionada."; +"openchat.create.room.description.guide" = "Introduce palabras clave usando #hashtags"; +"openchat.create.room.description.placeholder" = "Descripción"; +"openchat.create.room.name.placeholder" = "Nombre OpenChat"; +"openchat.create.room.search" = "Búsqueda abierta"; +"openchat.create.room.search.guide" = "Podrán buscar tu OpenChat."; +"openchat.create.room.title" = "Crear OpenChat"; +"openchat.not.agree.with.terms" = "En tu LINE, abre OpenChat y acepta los términos de uso de OpenChat.\n"; +"search.no.result" = "Parece que no hay nada."; +"shareRecipient.section.friends.title" = "Amigos"; +"shareRecipient.section.groups.title" = "Grupos"; +"square.create.category.all" = "All"; +"square.create.category.alumnus" = "Compañeros"; +"square.create.category.ani" = "Anime y cómics"; +"square.create.category.art" = "Arte"; +"square.create.category.artculture" = "Arts & books"; +"square.create.category.baby" = "Infantil"; +"square.create.category.beauty" = "Belleza"; +"square.create.category.book" = "Libros"; +"square.create.category.car" = "Automovilismo"; +"square.create.category.celebrity" = "Famosos"; +"square.create.category.company" = "Empresa"; +"square.create.category.economy" = "Economía"; +"square.create.category.entertainer" = "Farándula"; +"square.create.category.etc" = "Otras"; +"square.create.category.exercise" = "Fitness"; +"square.create.category.family" = "Familia"; +"square.create.category.fan" = "Club de fans"; +"square.create.category.fashion" = "Moda/Belleza"; +"square.create.category.finance" = "Finanzas"; +"square.create.category.food" = "Gourmet"; +"square.create.category.friend" = "Amigos"; +"square.create.category.game" = "Juegos"; +"square.create.category.health" = "Salud"; +"square.create.category.history" = "Historia"; +"square.create.category.hobby" = "Pasatiempos"; +"square.create.category.it" = "Tecnología"; +"square.create.category.jpop" = "J-pop/J-drama"; +"square.create.category.kpop" = "K-pop/K-drama"; +"square.create.category.medicine" = "Medicina"; +"square.create.category.movies" = "Pelis"; +"square.create.category.music" = "Música"; +"square.create.category.notselected" = "Sin seleccionar (no sale en lista)"; +"square.create.category.org" = "Organización"; +"square.create.category.pet" = "Mascotas"; +"square.create.category.photo" = "Fotos"; +"square.create.category.recipe" = "Cocina"; +"square.create.category.region" = "Regional"; +"square.create.category.school" = "Colegio/Compañeros"; +"square.create.category.science" = "Ciencia"; +"square.create.category.social" = "Social"; +"square.create.category.society" = "Club"; +"square.create.category.sports" = "Deportes"; +"square.create.category.study" = "Estudio"; +"square.create.category.travel" = "Viajes"; +"square.create.category.trending" = "Tendencias"; +"square.create.category.tv" = "Shows"; diff --git a/LineSDK/LineSDK/Resource.bundle/fr.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/fr.lproj/Localizable.strings old mode 100644 new mode 100755 index b74107cf..6c2297c8 --- a/LineSDK/LineSDK/Resource.bundle/fr.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/fr.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "Connexion avec LINE"; +"chat.multi.fwd.confirm" = "Vous pouvez en sélectionner jusqu\'à %d."; +"common.action.close" = "Fermer"; +"common.action.send" = "Envoyer"; +"common.cancel" = "Annuler"; +"common.next" = "Suivant"; +"common.ok" = "OK"; +"friends.share.search" = "Rechercher par nom"; +"linesdk.login.button.login" = "Connexion avec LINE"; +"open.line" = "Ouvrir LINE"; +"openchat.create.profile.input.guide" = "Définissez le pseudonyme à utiliser dans « %@ »."; +"openchat.create.profile.input.max.count" = "Longueur maximale atteinte."; +"openchat.create.profile.input.placeholder" = "Saisissez un pseudonyme"; +"openchat.create.profile.title" = "Mon profil"; +"openchat.create.room.category" = "Catégorie"; +"openchat.create.room.category.guide" = "Votre OpenChat s\'affichera dans la catégorie sélectionnée."; +"openchat.create.room.description.guide" = "Saisissez des mots clés sous forme de #hashtags"; +"openchat.create.room.description.placeholder" = "Saisissez une description"; +"openchat.create.room.name.placeholder" = "Nom de l\'OpenChat"; +"openchat.create.room.search" = "Autoriser la recherche"; +"openchat.create.room.search.guide" = "Les autres personnes pourront rechercher votre OpenChat."; +"openchat.create.room.title" = "Créer un OpenChat"; +"openchat.not.agree.with.terms" = "Vous n\'avez pas encore accepté les conditions d\'utilisation d\'OpenChat.\nAccédez à OpenChat dans l\'app LINE et acceptez les conditions d\'utilisation pour continuer."; +"search.no.result" = "Aucun résultat"; +"shareRecipient.section.friends.title" = "Amis"; +"shareRecipient.section.groups.title" = "Groupes"; +"square.create.category.all" = "Tout"; +"square.create.category.alumnus" = "Anciens élèves"; +"square.create.category.ani" = "Animations et bandes dessinées"; +"square.create.category.art" = "Art"; +"square.create.category.artculture" = "Art/Culture"; +"square.create.category.baby" = "Enfants"; +"square.create.category.beauty" = "Beauté"; +"square.create.category.book" = "Livres"; +"square.create.category.car" = "Automobile"; +"square.create.category.celebrity" = "Personnalités connues"; +"square.create.category.company" = "Entreprise"; +"square.create.category.economy" = "Économie"; +"square.create.category.entertainer" = "Célébrités"; +"square.create.category.etc" = "Autre"; +"square.create.category.exercise" = "Fitness"; +"square.create.category.family" = "Famille"; +"square.create.category.fan" = "Fan club"; +"square.create.category.fashion" = "Mode et beauté"; +"square.create.category.finance" = "Finance et économie"; +"square.create.category.food" = "Nourriture"; +"square.create.category.friend" = "Amis"; +"square.create.category.game" = "Jeux"; +"square.create.category.health" = "Santé"; +"square.create.category.history" = "Histoire"; +"square.create.category.hobby" = "Loisirs"; +"square.create.category.it" = "Technologie"; +"square.create.category.jpop" = "Pop/Théâtre japonais"; +"square.create.category.kpop" = "Pop/Théâtre coréen"; +"square.create.category.medicine" = "Médecine"; +"square.create.category.movies" = "Films"; +"square.create.category.music" = "Musique"; +"square.create.category.notselected" = "Non sélectionnée (n\'apparaît dans aucune catégorie)"; +"square.create.category.org" = "Organisation"; +"square.create.category.pet" = "Animaux de compagnie"; +"square.create.category.photo" = "Photos"; +"square.create.category.recipe" = "Cuisine"; +"square.create.category.region" = "Locale"; +"square.create.category.school" = "Écoles"; +"square.create.category.science" = "Sciences"; +"square.create.category.social" = "Réseaux sociaux"; +"square.create.category.society" = "Club"; +"square.create.category.sports" = "Sports"; +"square.create.category.study" = "Études"; +"square.create.category.travel" = "Voyage"; +"square.create.category.trending" = "Tendances"; +"square.create.category.tv" = "Émissions télévisées"; diff --git a/LineSDK/LineSDK/Resource.bundle/id.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/id.lproj/Localizable.strings old mode 100644 new mode 100755 index e7fff4bf..1556da3e --- a/LineSDK/LineSDK/Resource.bundle/id.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/id.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "Masuk dengan LINE"; +"chat.multi.fwd.confirm" = "Anda dapat memilih maksimum %d."; +"common.action.close" = "Tutup"; +"common.action.send" = "Kirim"; +"common.cancel" = "Batal"; +"common.next" = "Berikutnya"; +"common.ok" = "OK"; +"friends.share.search" = "Cari dengan Nama"; +"linesdk.login.button.login" = "Masuk dengan LINE"; +"open.line" = "Buka LINE"; +"openchat.create.profile.input.guide" = "Tetapkan nama panggilan yang akan digunakan di \"%@\"."; +"openchat.create.profile.input.max.count" = "Panjang maksimum terlampaui."; +"openchat.create.profile.input.placeholder" = "Masukkan nama panggilan"; +"openchat.create.profile.title" = "Profil Saya"; +"openchat.create.room.category" = "Kategori"; +"openchat.create.room.category.guide" = "OpenChat Anda akan ditampilkan di kategori terpilih."; +"openchat.create.room.description.guide" = "Masukkan kata kunci menggunakan #hashtag"; +"openchat.create.room.description.placeholder" = "Masukkan deskripsi"; +"openchat.create.room.name.placeholder" = "Nama OpenChat"; +"openchat.create.room.search" = "Izinkan pencarian"; +"openchat.create.room.search.guide" = "OpenChat Anda dapat dicari oleh pengguna lain."; +"openchat.create.room.title" = "Buat OpenChat"; +"openchat.not.agree.with.terms" = "Syarat dan Ketentuan Penggunaan OpenChat belum disetujui.\nBuka OpenChat di aplikasi LINE dan setujui Syarat dan Ketentuan Penggunaan untuk melanjutkan."; +"search.no.result" = "Tak Ada Hasil"; +"shareRecipient.section.friends.title" = "Teman"; +"shareRecipient.section.groups.title" = "Grup"; +"square.create.category.all" = "Semua"; +"square.create.category.alumnus" = "Alumni"; +"square.create.category.ani" = "Animasi & Komik"; +"square.create.category.art" = "Seni"; +"square.create.category.artculture" = "Seni & Buku"; +"square.create.category.baby" = "Anak"; +"square.create.category.beauty" = "Kecantikan"; +"square.create.category.book" = "Seni & Budaya"; +"square.create.category.car" = "Otomotif"; +"square.create.category.celebrity" = "Orang Terkenal"; +"square.create.category.company" = "Perusahaan"; +"square.create.category.economy" = "Ekonomi"; +"square.create.category.entertainer" = "Klub Penggemar"; +"square.create.category.etc" = "Lain-lain "; +"square.create.category.exercise" = "Kebugaran"; +"square.create.category.family" = "Keluarga"; +"square.create.category.fan" = "Klub penggemar"; +"square.create.category.fashion" = "Mode & Kecantikan"; +"square.create.category.finance" = "Keuangan & Bisnis"; +"square.create.category.food" = "Makanan"; +"square.create.category.friend" = "Teman"; +"square.create.category.game" = "Permainan"; +"square.create.category.health" = "Kesehatan"; +"square.create.category.history" = "Sejarah"; +"square.create.category.hobby" = "Hobi"; +"square.create.category.it" = "Teknologi"; +"square.create.category.jpop" = "J-pop/J-drama"; +"square.create.category.kpop" = "K-pop/K-drama"; +"square.create.category.medicine" = "Medis"; +"square.create.category.movies" = "Film"; +"square.create.category.music" = "Musik"; +"square.create.category.notselected" = "Tak dipilih (tidak muncul di kategori mana pun)"; +"square.create.category.org" = "Sosial"; +"square.create.category.pet" = "Peliharaan"; +"square.create.category.photo" = "Fotografi"; +"square.create.category.recipe" = "Masak"; +"square.create.category.region" = "Area"; +"square.create.category.school" = "Sekolah"; +"square.create.category.science" = "Sains"; +"square.create.category.social" = "Sosial"; +"square.create.category.society" = "Klub"; +"square.create.category.sports" = "Olahraga"; +"square.create.category.study" = "Pendidikan"; +"square.create.category.travel" = "Wisata"; +"square.create.category.trending" = "Populer"; +"square.create.category.tv" = "Acara TV"; diff --git a/LineSDK/LineSDK/Resource.bundle/it.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/it.lproj/Localizable.strings old mode 100644 new mode 100755 index 96f5e687..2128b823 --- a/LineSDK/LineSDK/Resource.bundle/it.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/it.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "Login con LINE"; +"chat.multi.fwd.confirm" = "Puoi selezionarne fino a %d."; +"common.action.close" = "Chiudi"; +"common.action.send" = "Invia"; +"common.cancel" = "Annulla"; +"common.next" = "Avanti"; +"common.ok" = "OK"; +"friends.share.search" = "Cerca per nome"; +"linesdk.login.button.login" = "Login con LINE"; +"open.line" = "Apri LINE"; +"openchat.create.profile.input.guide" = "Imposta il nickname da usare in \"%@\"."; +"openchat.create.profile.input.max.count" = "Hai raggiunto la lunghezza massima."; +"openchat.create.profile.input.placeholder" = "Inserisci nickname"; +"openchat.create.profile.title" = "Mio profilo"; +"openchat.create.room.category" = "Categoria"; +"openchat.create.room.category.guide" = "La tua OpenChat verrà visualizzata nella categoria selezionata."; +"openchat.create.room.description.guide" = "Inserisci le parole chiave usando #hashtag"; +"openchat.create.room.description.placeholder" = "Inserisci descrizione"; +"openchat.create.room.name.placeholder" = "Nome OpenChat"; +"openchat.create.room.search" = "Consenti ricerca"; +"openchat.create.room.search.guide" = "Gli altri potranno cercare la tua OpenChat."; +"openchat.create.room.title" = "Crea OpenChat"; +"openchat.not.agree.with.terms" = "Non hai ancora accettato Termini e condizioni d\'uso di OpenChat.\nAccedi a OpenChat nell\'app LINE e accetta i termini e le condizioni d\'uso per continuare."; +"search.no.result" = "Nessun risultato"; +"shareRecipient.section.friends.title" = "Amici"; +"shareRecipient.section.groups.title" = "Gruppi"; +"square.create.category.all" = "Tutto"; +"square.create.category.alumnus" = "Alumni"; +"square.create.category.ani" = "Animazione e fumetti"; +"square.create.category.art" = "Arte"; +"square.create.category.artculture" = "Arte/cultura"; +"square.create.category.baby" = "Bambini"; +"square.create.category.beauty" = "Bellezza"; +"square.create.category.book" = "Libri"; +"square.create.category.car" = "Automotive"; +"square.create.category.celebrity" = "VIP"; +"square.create.category.company" = "Azienda"; +"square.create.category.economy" = "Economia"; +"square.create.category.entertainer" = "Celebrità"; +"square.create.category.etc" = "Altro"; +"square.create.category.exercise" = "Fitness"; +"square.create.category.family" = "Famiglia"; +"square.create.category.fan" = "Fan club"; +"square.create.category.fashion" = "Moda e bellezza"; +"square.create.category.finance" = "Finanza e affari"; +"square.create.category.food" = "Cibo"; +"square.create.category.friend" = "Amici"; +"square.create.category.game" = "Giochi"; +"square.create.category.health" = "Salute"; +"square.create.category.history" = "Storia"; +"square.create.category.hobby" = "Hobby"; +"square.create.category.it" = "Tecnologia"; +"square.create.category.jpop" = "J-pop/J-drama"; +"square.create.category.kpop" = "K-pop/K-drama"; +"square.create.category.medicine" = "Medicina"; +"square.create.category.movies" = "Film"; +"square.create.category.music" = "Musica"; +"square.create.category.notselected" = "Non selezionata (nessuna categoria)"; +"square.create.category.org" = "Organizzazione"; +"square.create.category.pet" = "Animali domestici"; +"square.create.category.photo" = "Foto"; +"square.create.category.recipe" = "Cucina"; +"square.create.category.region" = "Rubrica"; +"square.create.category.school" = "Scuole"; +"square.create.category.science" = "Scienza"; +"square.create.category.social" = "Vita sociale"; +"square.create.category.society" = "Club"; +"square.create.category.sports" = "Sport"; +"square.create.category.study" = "Studio"; +"square.create.category.travel" = "Viaggi"; +"square.create.category.trending" = "Popolari"; +"square.create.category.tv" = "Programmi TV"; diff --git a/LineSDK/LineSDK/Resource.bundle/ja.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/ja.lproj/Localizable.strings old mode 100644 new mode 100755 index c0daea96..5d3d9cef --- a/LineSDK/LineSDK/Resource.bundle/ja.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/ja.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "LINEでログイン"; +"chat.multi.fwd.confirm" = "最大%d件まで選択できます。"; +"common.action.close" = "閉じる"; +"common.action.send" = "送信"; +"common.cancel" = "キャンセル"; +"common.next" = "次へ"; +"common.ok" = "OK"; +"friends.share.search" = "名前で検索"; +"linesdk.login.button.login" = "LINEでログイン"; +"open.line" = "LINEで開く"; +"openchat.create.profile.input.guide" = "「%@」で使用するニックネームを設定できます。"; +"openchat.create.profile.input.max.count" = "これ以上入力できません"; +"openchat.create.profile.input.placeholder" = "ニックネームを入力"; +"openchat.create.profile.title" = "プロフィール"; +"openchat.create.room.category" = "カテゴリー"; +"openchat.create.room.category.guide" = "指定したカテゴリーの一覧にこのオープンチャットが表示されます。"; +"openchat.create.room.description.guide" = "「#」から始まるハッシュタグを入力"; +"openchat.create.room.description.placeholder" = "説明文を入力"; +"openchat.create.room.name.placeholder" = "オープンチャット名を入力"; +"openchat.create.room.search" = "検索を許可"; +"openchat.create.room.search.guide" = "他のユーザーがメイン画面からこのオープンチャットを検索することができます。"; +"openchat.create.room.title" = "オープンチャットを作成"; +"openchat.not.agree.with.terms" = "まだLINEオープンチャットの利用規約への同意が完了していません。\nLINEアプリでオープンチャットを開き、利用規約への同意を完了してください。"; +"search.no.result" = "検索結果がありません"; +"shareRecipient.section.friends.title" = "友だち"; +"shareRecipient.section.groups.title" = "グループ"; +"square.create.category.all" = "すべて"; +"square.create.category.alumnus" = "同窓会"; +"square.create.category.ani" = "アニメ・漫画"; +"square.create.category.art" = "イラスト"; +"square.create.category.artculture" = "アート・カルチャー"; +"square.create.category.baby" = "妊活・子育て"; +"square.create.category.beauty" = "美容"; +"square.create.category.book" = "本"; +"square.create.category.car" = "乗り物"; +"square.create.category.celebrity" = "芸能人・有名人"; +"square.create.category.company" = "働き方・仕事"; +"square.create.category.economy" = "経済"; +"square.create.category.entertainer" = "芸能人"; +"square.create.category.etc" = "その他"; +"square.create.category.exercise" = "フィットネス"; +"square.create.category.family" = "家族"; +"square.create.category.fan" = "ファンクラブ"; +"square.create.category.fashion" = "ファッション・美容"; +"square.create.category.finance" = "金融・ビジネス"; +"square.create.category.food" = "料理・グルメ"; +"square.create.category.friend" = "同世代"; +"square.create.category.game" = "ゲーム"; +"square.create.category.health" = "健康"; +"square.create.category.history" = "歴史"; +"square.create.category.hobby" = "趣味"; +"square.create.category.it" = "IT・インターネット"; +"square.create.category.jpop" = "J-POP・日本ドラマ"; +"square.create.category.kpop" = "K-POP・韓国ドラマ"; +"square.create.category.medicine" = "医療"; +"square.create.category.movies" = "映画・舞台"; +"square.create.category.music" = "音楽"; +"square.create.category.notselected" = "指定なし(カテゴリーに表示されません)"; +"square.create.category.org" = "団体"; +"square.create.category.pet" = "動物・ペット"; +"square.create.category.photo" = "写真"; +"square.create.category.recipe" = "料理"; +"square.create.category.region" = "地域・暮らし"; +"square.create.category.school" = "学校・同窓会"; +"square.create.category.science" = "科学"; +"square.create.category.social" = "ソーシャル"; +"square.create.category.society" = "同好会・サークル"; +"square.create.category.sports" = "スポーツ"; +"square.create.category.study" = "研究・学習"; +"square.create.category.travel" = "旅行"; +"square.create.category.trending" = "急上昇"; +"square.create.category.tv" = "TV・VOD"; diff --git a/LineSDK/LineSDK/Resource.bundle/ko.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/ko.lproj/Localizable.strings old mode 100644 new mode 100755 index 7534352a..c4b92317 --- a/LineSDK/LineSDK/Resource.bundle/ko.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/ko.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "LINE으로 로그인"; +"chat.multi.fwd.confirm" = "최대 %d개까지 선택할 수 있습니다."; +"common.action.close" = "닫기"; +"common.action.send" = "전송"; +"common.cancel" = "취소"; +"common.next" = "다음"; +"common.ok" = "확인"; +"friends.share.search" = "이름으로 검색"; +"linesdk.login.button.login" = "LINE으로 로그인"; +"open.line" = "LINE 열기"; +"openchat.create.profile.input.guide" = "%@에서 사용할 닉네임을 설정할 수 있습니다."; +"openchat.create.profile.input.max.count" = "더 이상 입력할 수 없습니다."; +"openchat.create.profile.input.placeholder" = "닉네임을 입력해 주세요."; +"openchat.create.profile.title" = "내 프로필"; +"openchat.create.room.category" = "카테고리"; +"openchat.create.room.category.guide" = "선택한 카테고리의 목록에 내 오픈챗이 표시됩니다."; +"openchat.create.room.description.guide" = "이 오픈챗에 어울리는 해시태그(#)를 입력해 주세요."; +"openchat.create.room.description.placeholder" = "오픈챗 소개"; +"openchat.create.room.name.placeholder" = "오픈챗 이름"; +"openchat.create.room.search" = "검색 허용"; +"openchat.create.room.search.guide" = "다른 사용자가 내 오픈챗을 검색할 수 있습니다."; +"openchat.create.room.title" = "오픈챗 만들기"; +"openchat.not.agree.with.terms" = "아직 오픈챗 이용약관에 동의하지 않았습니다.\nLINE 앱에서 오픈챗을 열고 이용약관에 동의해 주세요."; +"search.no.result" = "검색 결과 없음"; +"shareRecipient.section.friends.title" = "친구"; +"shareRecipient.section.groups.title" = "그룹"; +"square.create.category.all" = "전체"; +"square.create.category.alumnus" = "동창"; +"square.create.category.ani" = "애니메이션&만화"; +"square.create.category.art" = "예술"; +"square.create.category.artculture" = "예술/문화"; +"square.create.category.baby" = "키즈"; +"square.create.category.beauty" = "뷰티"; +"square.create.category.book" = "도서"; +"square.create.category.car" = "자동차&바이크"; +"square.create.category.celebrity" = "유명인"; +"square.create.category.company" = "회사"; +"square.create.category.economy" = "경제"; +"square.create.category.entertainer" = "연예인"; +"square.create.category.etc" = "기타"; +"square.create.category.exercise" = "피트니스"; +"square.create.category.family" = "가족"; +"square.create.category.fan" = "팬클럽"; +"square.create.category.fashion" = "패션&뷰티"; +"square.create.category.finance" = "금융&비즈니스"; +"square.create.category.food" = "음식"; +"square.create.category.friend" = "친구"; +"square.create.category.game" = "게임"; +"square.create.category.health" = "건강"; +"square.create.category.history" = "역사"; +"square.create.category.hobby" = "취미"; +"square.create.category.it" = "IT&인터넷"; +"square.create.category.jpop" = "J-Pop/J-Drama"; +"square.create.category.kpop" = "K-Pop/K-Drama"; +"square.create.category.medicine" = "의료"; +"square.create.category.movies" = "영화"; +"square.create.category.music" = "음악"; +"square.create.category.notselected" = "선택 안 함(카테고리에서 제외)"; +"square.create.category.org" = "단체"; +"square.create.category.pet" = "애완동물"; +"square.create.category.photo" = "사진"; +"square.create.category.recipe" = "요리 레시피"; +"square.create.category.region" = "지역"; +"square.create.category.school" = "학교&동창"; +"square.create.category.science" = "과학"; +"square.create.category.social" = "소셜"; +"square.create.category.society" = "동호회"; +"square.create.category.sports" = "스포츠"; +"square.create.category.study" = "스터디"; +"square.create.category.travel" = "여행"; +"square.create.category.trending" = "인기 토픽"; +"square.create.category.tv" = "방송&공연"; diff --git a/LineSDK/LineSDK/Resource.bundle/ms.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/ms.lproj/Localizable.strings old mode 100644 new mode 100755 index a473a44c..5717a5f7 --- a/LineSDK/LineSDK/Resource.bundle/ms.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/ms.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "Log masuk dengan LINE"; +"chat.multi.fwd.confirm" = "Anda boleh pilih sehingga %d."; +"common.action.close" = "Tutup"; +"common.action.send" = "Kirim"; +"common.cancel" = "Batal"; +"common.next" = "Seterusnya"; +"common.ok" = "OKE"; +"friends.share.search" = "Cari mengikut Nama"; +"linesdk.login.button.login" = "Log masuk dengan LINE"; +"open.line" = "Buka LINE"; +"openchat.create.profile.input.guide" = "Tetapkan nama panggilan anda untuk digunakan dalam \"%@\"."; +"openchat.create.profile.input.max.count" = "Panjang maksimum dicapai."; +"openchat.create.profile.input.placeholder" = "Masukkan nama panggilan"; +"openchat.create.profile.title" = "Profil saya"; +"openchat.create.room.category" = "Kategori"; +"openchat.create.room.category.guide" = "OpenChat anda akan dipaparkan dalam kategori yang dipilih."; +"openchat.create.room.description.guide" = "Masukkan kata kunci menggunakan #tandapagar"; +"openchat.create.room.description.placeholder" = "Masukkan penerangan"; +"openchat.create.room.name.placeholder" = "Nama OpenChat"; +"openchat.create.room.search" = "Izinkan carian"; +"openchat.create.room.search.guide" = "Orang lain akan dapat mencari OpenChat anda."; +"openchat.create.room.title" = "Cipta OpenChat"; +"openchat.not.agree.with.terms" = "Anda belum bersetuju dengan Terma dan Syarat Penggunaan OpenChat.\nPergi ke OpenChat pada aplikasi LINE dan bersetuju dengan Terma dan Syarat Penggunaan untuk meneruskan."; +"search.no.result" = "Tiada Hasil"; +"shareRecipient.section.friends.title" = "Teman"; +"shareRecipient.section.groups.title" = "Grup"; +"square.create.category.all" = "Semua"; +"square.create.category.alumnus" = "Alumni"; +"square.create.category.ani" = "Animasi & komik"; +"square.create.category.art" = "Seni"; +"square.create.category.artculture" = "Seni/Budaya"; +"square.create.category.baby" = "Kanak-kanak"; +"square.create.category.beauty" = "Kecantikan"; +"square.create.category.book" = "Buku"; +"square.create.category.car" = "Automotif"; +"square.create.category.celebrity" = "Orang terkenal"; +"square.create.category.company" = "Syarikat"; +"square.create.category.economy" = "Ekonomi"; +"square.create.category.entertainer" = "Selebriti"; +"square.create.category.etc" = "Lain-lain"; +"square.create.category.exercise" = "Kecergasan"; +"square.create.category.family" = "Keluarga"; +"square.create.category.fan" = "Kelab peminat"; +"square.create.category.fashion" = "Fesyen & kecantikan"; +"square.create.category.finance" = "Kewangan & perniagaan"; +"square.create.category.food" = "Makanan"; +"square.create.category.friend" = "Rakan"; +"square.create.category.game" = "Permainan"; +"square.create.category.health" = "Kesihatan"; +"square.create.category.history" = "Sejarah"; +"square.create.category.hobby" = "Hobi"; +"square.create.category.it" = "Teknologi"; +"square.create.category.jpop" = "J-pop/J-drama"; +"square.create.category.kpop" = "K-pop/K-drama"; +"square.create.category.medicine" = "Perubatan"; +"square.create.category.movies" = "Filem"; +"square.create.category.music" = "Muzik"; +"square.create.category.notselected" = "Tidak dipilih (tidak ditunjukkan dalam mana-mana kategori)"; +"square.create.category.org" = "Organisasi"; +"square.create.category.pet" = "Haiwan kesayangan"; +"square.create.category.photo" = "Foto"; +"square.create.category.recipe" = "Memasak"; +"square.create.category.region" = "Tempatan"; +"square.create.category.school" = "Sekolah"; +"square.create.category.science" = "Sains"; +"square.create.category.social" = "Sosial"; +"square.create.category.society" = "Kelab"; +"square.create.category.sports" = "Sukan"; +"square.create.category.study" = "Belajar"; +"square.create.category.travel" = "Pelancongan"; +"square.create.category.trending" = "Popular"; +"square.create.category.tv" = "Rancangan TV"; diff --git a/LineSDK/LineSDK/Resource.bundle/pt-BR.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/pt-BR.lproj/Localizable.strings deleted file mode 100644 index 5cddebe8..00000000 --- a/LineSDK/LineSDK/Resource.bundle/pt-BR.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ -"linesdk.login.button.login" = "Login com o LINE"; diff --git a/LineSDK/LineSDK/Resource.bundle/pt-PT.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/pt-PT.lproj/Localizable.strings old mode 100644 new mode 100755 index d8f98e6f..8f9f48f5 --- a/LineSDK/LineSDK/Resource.bundle/pt-PT.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/pt-PT.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "Iniciar sessão com o LINE"; +"chat.multi.fwd.confirm" = "Podes selecionar até %d."; +"common.action.close" = "Fechar"; +"common.action.send" = "Enviar"; +"common.cancel" = "Cancelar"; +"common.next" = "Seguinte"; +"common.ok" = "OK"; +"friends.share.search" = "Proc. por nome"; +"linesdk.login.button.login" = "Iniciar sessão com o LINE"; +"open.line" = "Abrir o LINE"; +"openchat.create.profile.input.guide" = "Defina a alcunha a utilizar em \"%@\"."; +"openchat.create.profile.input.max.count" = "Limite máximo atingido."; +"openchat.create.profile.input.placeholder" = "Introduzir alcunha"; +"openchat.create.profile.title" = "O meu perfil"; +"openchat.create.room.category" = "Categoria"; +"openchat.create.room.category.guide" = "O seu OpenChat será apresentado na categoria selecionada."; +"openchat.create.room.description.guide" = "Introduzir palavras-chave com #hashtags"; +"openchat.create.room.description.placeholder" = "Introduzir descrição"; +"openchat.create.room.name.placeholder" = "Nome do OpenChat"; +"openchat.create.room.search" = "Permitir procura"; +"openchat.create.room.search.guide" = "As outras pessoas poderão procurar o seu OpenChat."; +"openchat.create.room.title" = "Criar OpenChat"; +"openchat.not.agree.with.terms" = "Ainda não aceitou os Termos e Condições de Utilização do OpenChat.\nAceda ao OpenChat na aplicação LINE e aceite os Termos e Condições de Utilização, para continuar."; +"search.no.result" = "Sem resultados"; +"shareRecipient.section.friends.title" = "Amigos"; +"shareRecipient.section.groups.title" = "Grupos"; +"square.create.category.all" = "Todos"; +"square.create.category.alumnus" = "Ex-alunos"; +"square.create.category.ani" = "Animação e banda desenhada"; +"square.create.category.art" = "Arte"; +"square.create.category.artculture" = "Arte/Cultura"; +"square.create.category.baby" = "Crianças"; +"square.create.category.beauty" = "Beleza"; +"square.create.category.book" = "Livros"; +"square.create.category.car" = "Automóvel"; +"square.create.category.celebrity" = "Pessoas famosas"; +"square.create.category.company" = "Empresa"; +"square.create.category.economy" = "Economia"; +"square.create.category.entertainer" = "Celebridades"; +"square.create.category.etc" = "Outros"; +"square.create.category.exercise" = "Boa Forma"; +"square.create.category.family" = "Família"; +"square.create.category.fan" = "Clube de fãs"; +"square.create.category.fashion" = "Moda e beleza"; +"square.create.category.finance" = "Finanças e empresas"; +"square.create.category.food" = "Alimentação"; +"square.create.category.friend" = "Amigos"; +"square.create.category.game" = "Jogos"; +"square.create.category.health" = "Saúde"; +"square.create.category.history" = "História"; +"square.create.category.hobby" = "Passatempos"; +"square.create.category.it" = "Tecnologia"; +"square.create.category.jpop" = "Pop/Drama Japoneses"; +"square.create.category.kpop" = "Pop/Drama Coreanos"; +"square.create.category.medicine" = "Médico"; +"square.create.category.movies" = "Filmes"; +"square.create.category.music" = "Música"; +"square.create.category.notselected" = "Não selecionada (não apresentado em nenhuma categoria)"; +"square.create.category.org" = "Organização"; +"square.create.category.pet" = "Animais de Estimação"; +"square.create.category.photo" = "Fotos"; +"square.create.category.recipe" = "Culinária"; +"square.create.category.region" = "Local"; +"square.create.category.school" = "Escolas"; +"square.create.category.science" = "Ciências"; +"square.create.category.social" = "Social"; +"square.create.category.society" = "Clube"; +"square.create.category.sports" = "Desporto"; +"square.create.category.study" = "Estudos"; +"square.create.category.travel" = "Viagens"; +"square.create.category.trending" = "Tendências"; +"square.create.category.tv" = "Programas de TV"; diff --git a/LineSDK/LineSDK/Resource.bundle/pt.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/pt.lproj/Localizable.strings new file mode 100755 index 00000000..bb58867b --- /dev/null +++ b/LineSDK/LineSDK/Resource.bundle/pt.lproj/Localizable.strings @@ -0,0 +1,71 @@ +"chat.multi.fwd.confirm" = "Podes selecionar até %d."; +"common.action.close" = "Fechar"; +"common.action.send" = "Enviar"; +"common.cancel" = "Cancelar"; +"common.next" = "Próximo"; +"common.ok" = "OK"; +"friends.share.search" = "Pesq. por nome"; +"linesdk.login.button.login" = "Login com o LINE"; +"open.line" = "Abrir o LINE"; +"openchat.create.profile.input.guide" = "Defina seu apelido que será usado em \"%@\"."; +"openchat.create.profile.input.max.count" = "Foi atingido o comprimento máximo."; +"openchat.create.profile.input.placeholder" = "Inserir apelido"; +"openchat.create.profile.title" = "Meu perfil"; +"openchat.create.room.category" = "Categoria"; +"openchat.create.room.category.guide" = "Seu OpenChat será exibido na categoria selecionada."; +"openchat.create.room.description.guide" = "Inserir as palavras-chave usando #hashtags"; +"openchat.create.room.description.placeholder" = "Inserir descrição"; +"openchat.create.room.name.placeholder" = "Nome do OpenChat"; +"openchat.create.room.search" = "Permitir busca"; +"openchat.create.room.search.guide" = "Outras pessoas poderão procurar por seu OpenChat."; +"openchat.create.room.title" = "Criar OpenChat"; +"openchat.not.agree.with.terms" = "Você ainda não aceitou os Termos e Condições de Uso do OpenChat.\nAcesse o OpenChat no aplicativo do LINE e aceite os Termos e Condições de Uso para continuar."; +"search.no.result" = " Sem resultados"; +"shareRecipient.section.friends.title" = "Amigos"; +"shareRecipient.section.groups.title" = "Grupos"; +"square.create.category.all" = "Todas"; +"square.create.category.alumnus" = "Ex-alunos"; +"square.create.category.ani" = "Animação e quadrinhos"; +"square.create.category.art" = "Arte"; +"square.create.category.artculture" = "Arte/Cultura"; +"square.create.category.baby" = "Crianças"; +"square.create.category.beauty" = "Beleza"; +"square.create.category.book" = "Livros"; +"square.create.category.car" = "Automóvel"; +"square.create.category.celebrity" = "Famosos"; +"square.create.category.company" = "Empresa"; +"square.create.category.economy" = "Economia"; +"square.create.category.entertainer" = "Celebridades"; +"square.create.category.etc" = "Outro"; +"square.create.category.exercise" = "Fitness"; +"square.create.category.family" = "Família"; +"square.create.category.fan" = "Fã-clube"; +"square.create.category.fashion" = "Moda e beleza"; +"square.create.category.finance" = "Finanças e negócios"; +"square.create.category.food" = "Comida"; +"square.create.category.friend" = "Amigos"; +"square.create.category.game" = "Jogos"; +"square.create.category.health" = "Saúde"; +"square.create.category.history" = "História"; +"square.create.category.hobby" = "Hobbies"; +"square.create.category.it" = "Tecnologia"; +"square.create.category.jpop" = "J-pop/J-drama"; +"square.create.category.kpop" = "K-pop/K-drama"; +"square.create.category.medicine" = "Medicina"; +"square.create.category.movies" = "Filmes"; +"square.create.category.music" = "Música"; +"square.create.category.notselected" = "Não selecionado (não apresentado em nenhuma categoria)"; +"square.create.category.org" = "Organização"; +"square.create.category.pet" = "Animais de estimação"; +"square.create.category.photo" = "Fotos"; +"square.create.category.recipe" = "Cozinhar"; +"square.create.category.region" = "Local"; +"square.create.category.school" = "Escolas"; +"square.create.category.science" = "Ciência"; +"square.create.category.social" = "Social"; +"square.create.category.society" = "Clube"; +"square.create.category.sports" = "Esportes"; +"square.create.category.study" = "Estudo"; +"square.create.category.travel" = "Viagens"; +"square.create.category.trending" = "Populares"; +"square.create.category.tv" = "Apresentações de TV"; diff --git a/LineSDK/LineSDK/Resource.bundle/ru.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/ru.lproj/Localizable.strings old mode 100644 new mode 100755 index 8debbe15..00510488 --- a/LineSDK/LineSDK/Resource.bundle/ru.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/ru.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "Войти в LINE"; +"chat.multi.fwd.confirm" = "Можно выбрать не более %d."; +"common.action.close" = "Закрыть"; +"common.action.send" = "Отправить"; +"common.cancel" = "Отмена"; +"common.next" = "Далее"; +"common.ok" = "ОК"; +"friends.share.search" = "Поиск по имени"; +"linesdk.login.button.login" = "Войти в LINE"; +"open.line" = "Открыть LINE"; +"openchat.create.profile.input.guide" = "Укажите свой псевдоним для клуба \"%@\"."; +"openchat.create.profile.input.max.count" = "Достигнута максимальная длина записи."; +"openchat.create.profile.input.placeholder" = "Введите псевдоним"; +"openchat.create.profile.title" = "Мой профиль"; +"openchat.create.room.category" = "Категория"; +"openchat.create.room.category.guide" = "Ваш OpenChat будет отображаться в выбранной категории."; +"openchat.create.room.description.guide" = "Введите ключевые слова с помощью #хештегов"; +"openchat.create.room.description.placeholder" = "Введите описание"; +"openchat.create.room.name.placeholder" = "Название OpenChat"; +"openchat.create.room.search" = "Показывать в поиске"; +"openchat.create.room.search.guide" = "Ваш OpenChat будет доступен для поиска."; +"openchat.create.room.title" = "Создать группу OpenChat"; +"openchat.not.agree.with.terms" = "Вы еще не согласились с Правилами и условиями использования OpenChat.\nЧтобы продолжить, перейдите в OpenChat в приложении LINE и примите Правила и условия использования."; +"search.no.result" = "Ничего не найдено"; +"shareRecipient.section.friends.title" = "Друзья"; +"shareRecipient.section.groups.title" = "Группы"; +"square.create.category.all" = "Все"; +"square.create.category.alumnus" = "Выпускники"; +"square.create.category.ani" = "Анимация и комиксы"; +"square.create.category.art" = "Искусство"; +"square.create.category.artculture" = "Искусство и культура"; +"square.create.category.baby" = "Дети"; +"square.create.category.beauty" = "Красота"; +"square.create.category.book" = "Книги"; +"square.create.category.car" = "Автомобили"; +"square.create.category.celebrity" = "Известные люди"; +"square.create.category.company" = "Компания"; +"square.create.category.economy" = "Экономика"; +"square.create.category.entertainer" = "Знаменитости"; +"square.create.category.etc" = "Другое"; +"square.create.category.exercise" = "Фитнес"; +"square.create.category.family" = "Семья"; +"square.create.category.fan" = "Фан-клуб"; +"square.create.category.fashion" = "Мода и красота"; +"square.create.category.finance" = "Финансы и бизнес"; +"square.create.category.food" = "Еда"; +"square.create.category.friend" = "Друзья"; +"square.create.category.game" = "Игры"; +"square.create.category.health" = "Здоровье"; +"square.create.category.history" = "История"; +"square.create.category.hobby" = "Хобби"; +"square.create.category.it" = "Технологии"; +"square.create.category.jpop" = "Японская поп- и фильмы"; +"square.create.category.kpop" = "Корейская поп-музыка и фильмы"; +"square.create.category.medicine" = "Медицина"; +"square.create.category.movies" = "Фильмы"; +"square.create.category.music" = "Музыка"; +"square.create.category.notselected" = "Не выбрано (не отображается ни в одной категории)"; +"square.create.category.org" = "Организация"; +"square.create.category.pet" = "Домашние питомцы"; +"square.create.category.photo" = "Фотографии"; +"square.create.category.recipe" = "Кулинария"; +"square.create.category.region" = "Соседи"; +"square.create.category.school" = "Обучение"; +"square.create.category.science" = "Наука"; +"square.create.category.social" = "Общество"; +"square.create.category.society" = "Клуб"; +"square.create.category.sports" = "Спорт"; +"square.create.category.study" = "Исследование"; +"square.create.category.travel" = "Туризм"; +"square.create.category.trending" = "В тренде"; +"square.create.category.tv" = "Телевидение"; diff --git a/LineSDK/LineSDK/Resource.bundle/th.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/th.lproj/Localizable.strings old mode 100644 new mode 100755 index d11cf4d5..57d96c7f --- a/LineSDK/LineSDK/Resource.bundle/th.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/th.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "ล็อกอินด้วย LINE"; +"chat.multi.fwd.confirm" = "คุณสามารถเลือกได้ไม่เกิน %d รายการ"; +"common.action.close" = "ปิด"; +"common.action.send" = "ส่ง"; +"common.cancel" = "ยกเลิก"; +"common.next" = "ต่อไป"; +"common.ok" = "ตกลง"; +"friends.share.search" = "ค้นหาด้วยชื่อ"; +"linesdk.login.button.login" = "ล็อกอินด้วย LINE"; +"open.line" = "เปิด LINE"; +"openchat.create.profile.input.guide" = "ตั้งชื่อเล่นเพื่อใช้ใน \"%@\""; +"openchat.create.profile.input.max.count" = "ข้อความยาวเกินไป"; +"openchat.create.profile.input.placeholder" = "ใส่ชื่อเล่น"; +"openchat.create.profile.title" = "โปรไฟล์"; +"openchat.create.room.category" = "หมวดหมู่"; +"openchat.create.room.category.guide" = "โอเพนแชทของคุณจะแสดงในหมวดหมู่ที่คุณเลือก"; +"openchat.create.room.description.guide" = "ใส่คีย์เวิร์ดโดยใช้ #แฮชแท็ก"; +"openchat.create.room.description.placeholder" = "ใส่คำอธิบาย"; +"openchat.create.room.name.placeholder" = "ชื่อโอเพนแชท"; +"openchat.create.room.search" = "อนุญาตให้ค้นหา"; +"openchat.create.room.search.guide" = "คนอื่นๆ สามารถค้นหาโอเพนแชทของคุณได้"; +"openchat.create.room.title" = "สร้างโอเพนแชท"; +"openchat.not.agree.with.terms" = "คุณยังไม่ได้ยอมรับข้อกำหนดการใช้บริการของโอเพนแชท\nโปรดเปิดโอเพนแชทจากแอป LINE แล้วยอมรับข้อกำหนด"; +"search.no.result" = "ไม่พบผลการค้นหา"; +"shareRecipient.section.friends.title" = "เพื่อน"; +"shareRecipient.section.groups.title" = "กลุ่ม"; +"square.create.category.all" = "ทั้งหมด"; +"square.create.category.alumnus" = "ศิษย์เก่า"; +"square.create.category.ani" = "อนิเมะ & การ์ตูน"; +"square.create.category.art" = "ศิลปะ"; +"square.create.category.artculture" = "ศิลปะวัฒนธรรม"; +"square.create.category.baby" = "เด็ก"; +"square.create.category.beauty" = "ความงาม"; +"square.create.category.book" = "หนังสือ"; +"square.create.category.car" = "รถยนต์"; +"square.create.category.celebrity" = "คนดัง"; +"square.create.category.company" = "บริษัท"; +"square.create.category.economy" = "เศรษฐกิจ"; +"square.create.category.entertainer" = "ดารา"; +"square.create.category.etc" = "อื่นๆ"; +"square.create.category.exercise" = "ออกกำลังกาย"; +"square.create.category.family" = "ครอบครัว"; +"square.create.category.fan" = "แฟนคลับ"; +"square.create.category.fashion" = "แฟชั่น & บิวตี้"; +"square.create.category.finance" = "การเงิน & ธุรกิจ"; +"square.create.category.food" = "อาหาร"; +"square.create.category.friend" = "เพื่อน"; +"square.create.category.game" = "เกม"; +"square.create.category.health" = "สุขภาพ"; +"square.create.category.history" = "ประวัติศาสตร์"; +"square.create.category.hobby" = "งานอดิเรก"; +"square.create.category.it" = "เทคโนโลยี"; +"square.create.category.jpop" = "เจป็อป ละครญี่ปุ่น"; +"square.create.category.kpop" = "เคป็อป ละครเกาหลี"; +"square.create.category.medicine" = "การแพทย์"; +"square.create.category.movies" = "ภาพยนตร์"; +"square.create.category.music" = "เพลง"; +"square.create.category.notselected" = "ไม่เลือก (ไม่แสดงในหมวดหมู่ใดๆ)"; +"square.create.category.org" = "องค์กร"; +"square.create.category.pet" = "สัตว์เลี้ยง"; +"square.create.category.photo" = "การถ่ายภาพ"; +"square.create.category.recipe" = "การทำอาหาร"; +"square.create.category.region" = "ท้องถิ่น"; +"square.create.category.school" = "โรงเรียน"; +"square.create.category.science" = "วิทยาศาสตร์"; +"square.create.category.social" = "สังคม"; +"square.create.category.society" = "ชมรม"; +"square.create.category.sports" = "กีฬา"; +"square.create.category.study" = "การศึกษา"; +"square.create.category.travel" = "ท่องเที่ยว"; +"square.create.category.trending" = "มาแรง"; +"square.create.category.tv" = "รายการทีวี"; diff --git a/LineSDK/LineSDK/Resource.bundle/tr.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/tr.lproj/Localizable.strings old mode 100644 new mode 100755 index 059279b1..cb8c5524 --- a/LineSDK/LineSDK/Resource.bundle/tr.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/tr.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "LINE ile oturum açın"; +"chat.multi.fwd.confirm" = "En çok %d mesaj seçin."; +"common.action.close" = "Kapat"; +"common.action.send" = "Gönder"; +"common.cancel" = "İptal"; +"common.next" = "İleri"; +"common.ok" = "Tamam"; +"friends.share.search" = "İsme Göre Ara"; +"linesdk.login.button.login" = "LINE ile oturum aテァトアn"; +"open.line" = "LINE\'ı Aç"; +"openchat.create.profile.input.guide" = "\"%@\" içinde kullanılacak takma adınızı belirleyin."; +"openchat.create.profile.input.max.count" = "Uzunluk sınırına ulaşıldı."; +"openchat.create.profile.input.placeholder" = "Takma adı girin"; +"openchat.create.profile.title" = "Profilim"; +"openchat.create.room.category" = "Kategori"; +"openchat.create.room.category.guide" = "OpenChat\'iniz seçilen kategoride gösterilecek."; +"openchat.create.room.description.guide" = "#hashtag kullanarak anahtar kelimeler girin"; +"openchat.create.room.description.placeholder" = "Açıklama girin"; +"openchat.create.room.name.placeholder" = "OpenChat adı"; +"openchat.create.room.search" = "Aramaya izin ver"; +"openchat.create.room.search.guide" = "Diğer kişiler OpenChat\'iniz için arama yapabilecek."; +"openchat.create.room.title" = "OpenChat oluştur"; +"openchat.not.agree.with.terms" = "Henüz OpenChat Kullanım Koşulları ve Şartları\'nı kabul etmediniz.\nDevam etmek için LINE uygulamasında OpenChat\'e gidip Kullanım Koşulları ve Şartları\'nı kabul edin."; +"search.no.result" = "Sonuç Yok"; +"shareRecipient.section.friends.title" = "Arkadaşlar"; +"shareRecipient.section.groups.title" = "Gruplar"; +"square.create.category.all" = "Tümü"; +"square.create.category.alumnus" = "Mezun"; +"square.create.category.ani" = "Animasyon ve çizgi roman"; +"square.create.category.art" = "Sanat"; +"square.create.category.artculture" = "Sanat/Kültür"; +"square.create.category.baby" = "Çocuklar"; +"square.create.category.beauty" = "Güzellik"; +"square.create.category.book" = "Kitaplar"; +"square.create.category.car" = "Otomotiv"; +"square.create.category.celebrity" = "Ünlü kişiler"; +"square.create.category.company" = "Şirket"; +"square.create.category.economy" = "Ekonomi"; +"square.create.category.entertainer" = "Ünlüler"; +"square.create.category.etc" = "Diğer"; +"square.create.category.exercise" = "Fitness"; +"square.create.category.family" = "Aile"; +"square.create.category.fan" = "Fan kulübü"; +"square.create.category.fashion" = "Moda ve güzellik"; +"square.create.category.finance" = "Finans ve iş dünyası"; +"square.create.category.food" = "Yemek"; +"square.create.category.friend" = "Arkadaşlar"; +"square.create.category.game" = "Oyunlar"; +"square.create.category.health" = "Sağlık"; +"square.create.category.history" = "Tarih"; +"square.create.category.hobby" = "Hobiler"; +"square.create.category.it" = "Teknoloji"; +"square.create.category.jpop" = "J-pop/J-drama"; +"square.create.category.kpop" = "K-pop/K-drama"; +"square.create.category.medicine" = "Tıp"; +"square.create.category.movies" = "Filmler"; +"square.create.category.music" = "Müzik"; +"square.create.category.notselected" = "Seçilmedi (hiçbir kategoride gösterilmiyor)"; +"square.create.category.org" = "Kuruluş"; +"square.create.category.pet" = "Evcil hayvanlar"; +"square.create.category.photo" = "Fotoğraflar"; +"square.create.category.recipe" = "Yemek Pişirme"; +"square.create.category.region" = "Yerel"; +"square.create.category.school" = "Okul"; +"square.create.category.science" = "Bilim"; +"square.create.category.social" = "Sosyal"; +"square.create.category.society" = "Kulüp"; +"square.create.category.sports" = "Spor"; +"square.create.category.study" = "Çalışma"; +"square.create.category.travel" = "Seyahat"; +"square.create.category.trending" = "En Popüler"; +"square.create.category.tv" = "TV programları"; diff --git a/LineSDK/LineSDK/Resource.bundle/vi.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/vi.lproj/Localizable.strings old mode 100644 new mode 100755 index 484ac489..a2db8f57 --- a/LineSDK/LineSDK/Resource.bundle/vi.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/vi.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "Đăng nhập với LINE"; +"chat.multi.fwd.confirm" = "Có thể chọn tối đa %d."; +"common.action.close" = "Đóng"; +"common.action.send" = "Gửi"; +"common.cancel" = "Hủy"; +"common.next" = "Tiếp theo"; +"common.ok" = "OK"; +"friends.share.search" = "Tìm theo tên"; +"linesdk.login.button.login" = "Đăng nhập với LINE"; +"open.line" = "Mở LINE"; +"openchat.create.profile.input.guide" = "Đặt biệt danh bạn sẽ sử dụng trong \"%@\"."; +"openchat.create.profile.input.max.count" = "Đã đạt tới độ dài tối đa."; +"openchat.create.profile.input.placeholder" = "Nhập biệt danh"; +"openchat.create.profile.title" = "Hồ sơ của tôi"; +"openchat.create.room.category" = "Danh mục"; +"openchat.create.room.category.guide" = "OpenChat của bạn sẽ hiển thị trong danh mục đã chọn."; +"openchat.create.room.description.guide" = "Nhập từ khóa bằng #hashtag"; +"openchat.create.room.description.placeholder" = "Nhập mô tả"; +"openchat.create.room.name.placeholder" = "Tên OpenChat"; +"openchat.create.room.search" = "Cho phép tìm kiếm"; +"openchat.create.room.search.guide" = "Những người khác sẽ có thể tìm kiếm OpenChat của bạn."; +"openchat.create.room.title" = "Tạo OpenChat"; +"openchat.not.agree.with.terms" = "Bạn chưa chấp nhận Điều khoản và điều kiện sử dụng của OpenChat.\nVào OpenChat trong ứng dụng LINE và chấp nhận Điều khoản và điều kiện sử dụng để tiếp tục."; +"search.no.result" = "Không có kết quả"; +"shareRecipient.section.friends.title" = "Bạn bè"; +"shareRecipient.section.groups.title" = "Nhóm"; +"square.create.category.all" = "Tất cả"; +"square.create.category.alumnus" = "Cựu sinh viên"; +"square.create.category.ani" = "Hoạt hình & truyện tranh"; +"square.create.category.art" = "Nghệ thuật"; +"square.create.category.artculture" = "Nghệ thuật/Văn hóa"; +"square.create.category.baby" = "Trẻ em"; +"square.create.category.beauty" = "Làm đẹp"; +"square.create.category.book" = "Sách"; +"square.create.category.car" = "Ô tô"; +"square.create.category.celebrity" = "Người nổi tiếng"; +"square.create.category.company" = "Công ty"; +"square.create.category.economy" = "Kinh tế"; +"square.create.category.entertainer" = "Nhân vật nổi tiếng"; +"square.create.category.etc" = "Khác"; +"square.create.category.exercise" = "Thể dục"; +"square.create.category.family" = "Gia đình"; +"square.create.category.fan" = "Câu lạc bộ người hâm mộ"; +"square.create.category.fashion" = "Thời trang & làm đẹp"; +"square.create.category.finance" = "Tài chính & kinh doanh"; +"square.create.category.food" = "Ẩm thực"; +"square.create.category.friend" = "Bạn bè"; +"square.create.category.game" = "Trò chơi"; +"square.create.category.health" = "Sức khỏe"; +"square.create.category.history" = "Lịch sử"; +"square.create.category.hobby" = "Sở thích"; +"square.create.category.it" = "Công nghệ"; +"square.create.category.jpop" = "J-pop/J-drama"; +"square.create.category.kpop" = "K-pop/K-drama"; +"square.create.category.medicine" = "Y tế"; +"square.create.category.movies" = "Phim"; +"square.create.category.music" = "Âm nhạc"; +"square.create.category.notselected" = "Chưa chọn (không hiển thị trong danh mục nào)"; +"square.create.category.org" = "Tổ chức"; +"square.create.category.pet" = "Thú cưng"; +"square.create.category.photo" = "Ảnh"; +"square.create.category.recipe" = "Nấu ăn"; +"square.create.category.region" = "Địa phương"; +"square.create.category.school" = "Trường học"; +"square.create.category.science" = "Khoa học"; +"square.create.category.social" = "Xã hội"; +"square.create.category.society" = "Câu lạc bộ"; +"square.create.category.sports" = "Thể thao"; +"square.create.category.study" = "Học tập"; +"square.create.category.travel" = "Du lịch"; +"square.create.category.trending" = "Xu hướng"; +"square.create.category.tv" = "Chương trình truyền hình"; diff --git a/LineSDK/LineSDK/Resource.bundle/zh-Hans.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/zh-Hans.lproj/Localizable.strings old mode 100644 new mode 100755 index d6e0ef74..86e3676c --- a/LineSDK/LineSDK/Resource.bundle/zh-Hans.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/zh-Hans.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "用LINE帐号登录"; +"chat.multi.fwd.confirm" = "最多可选择%d个。"; +"common.action.close" = "关闭"; +"common.action.send" = "发送"; +"common.cancel" = "取消"; +"common.next" = "下一步"; +"common.ok" = "确认"; +"friends.share.search" = "使用姓名搜索"; +"linesdk.login.button.login" = "用LINE帐号登录"; +"open.line" = "打开LINE"; +"openchat.create.profile.input.guide" = "设置要用于“%@”的昵称。"; +"openchat.create.profile.input.max.count" = "已达到字数上限。"; +"openchat.create.profile.input.placeholder" = "输入昵称"; +"openchat.create.profile.title" = "个人资料"; +"openchat.create.room.category" = "分类"; +"openchat.create.room.category.guide" = "您的OpenChat将显示在所选类别中。"; +"openchat.create.room.description.guide" = "输入以“#”标签开头的关键词"; +"openchat.create.room.description.placeholder" = "输入简介"; +"openchat.create.room.name.placeholder" = "OpenChat名称"; +"openchat.create.room.search" = "允许搜索"; +"openchat.create.room.search.guide" = "其他人将可以搜索到您的OpenChat。"; +"openchat.create.room.title" = "创建OpenChat"; +"openchat.not.agree.with.terms" = "您尚未同意“OpenChat服务条款”。\n请在LINE应用中打开OpenChat,同意“OpenChat服务条款”以继续。"; +"search.no.result" = "无结果"; +"shareRecipient.section.friends.title" = "好友"; +"shareRecipient.section.groups.title" = "群"; +"square.create.category.all" = "全部"; +"square.create.category.alumnus" = "校友"; +"square.create.category.ani" = "动画/漫画"; +"square.create.category.art" = "艺术"; +"square.create.category.artculture" = "艺术/文化"; +"square.create.category.baby" = "儿童"; +"square.create.category.beauty" = "美容"; +"square.create.category.book" = "书籍"; +"square.create.category.car" = "汽车"; +"square.create.category.celebrity" = "名人"; +"square.create.category.company" = "公司"; +"square.create.category.economy" = "经济"; +"square.create.category.entertainer" = "艺人"; +"square.create.category.etc" = "其他"; +"square.create.category.exercise" = "健身"; +"square.create.category.family" = "家人"; +"square.create.category.fan" = "粉丝俱乐部"; +"square.create.category.fashion" = "时尚/美容"; +"square.create.category.finance" = "金融/商业"; +"square.create.category.food" = "美食"; +"square.create.category.friend" = "好友"; +"square.create.category.game" = "游戏"; +"square.create.category.health" = "健康"; +"square.create.category.history" = "历史"; +"square.create.category.hobby" = "业余爱好"; +"square.create.category.it" = "科技"; +"square.create.category.jpop" = "日本流行音乐/日剧"; +"square.create.category.kpop" = "韩国流行音乐/韩剧"; +"square.create.category.medicine" = "医疗"; +"square.create.category.movies" = "电影"; +"square.create.category.music" = "音乐"; +"square.create.category.notselected" = "不选择(不显示在任何分类中)"; +"square.create.category.org" = "组织"; +"square.create.category.pet" = "宠物"; +"square.create.category.photo" = "照片"; +"square.create.category.recipe" = "烹饪"; +"square.create.category.region" = "地区"; +"square.create.category.school" = "学校"; +"square.create.category.science" = "科学"; +"square.create.category.social" = "社交"; +"square.create.category.society" = "社交俱乐部"; +"square.create.category.sports" = "运动"; +"square.create.category.study" = "学习"; +"square.create.category.travel" = "旅行"; +"square.create.category.trending" = "热门"; +"square.create.category.tv" = "电视节目"; diff --git a/LineSDK/LineSDK/Resource.bundle/zh-Hant.lproj/Localizable.strings b/LineSDK/LineSDK/Resource.bundle/zh-Hant.lproj/Localizable.strings old mode 100644 new mode 100755 index 2d8750cd..d048c301 --- a/LineSDK/LineSDK/Resource.bundle/zh-Hant.lproj/Localizable.strings +++ b/LineSDK/LineSDK/Resource.bundle/zh-Hant.lproj/Localizable.strings @@ -1 +1,71 @@ -"linesdk.login.button.login" = "與LINE連動"; +"chat.multi.fwd.confirm" = "最多可選擇%d筆。"; +"common.action.close" = "關閉"; +"common.action.send" = "傳送"; +"common.cancel" = "取消"; +"common.next" = "下一步"; +"common.ok" = "確定"; +"friends.share.search" = "利用名稱搜尋"; +"linesdk.login.button.login" = "與LINE連動"; +"open.line" = "打開LINE"; +"openchat.create.profile.input.guide" = "請設定您要在「%@」中使用的暱稱。"; +"openchat.create.profile.input.max.count" = "已達字數上限。"; +"openchat.create.profile.input.placeholder" = "輸入暱稱"; +"openchat.create.profile.title" = "個人檔案"; +"openchat.create.room.category" = "類別"; +"openchat.create.room.category.guide" = "您的OpenChat將會顯示於所選類別中。"; +"openchat.create.room.description.guide" = "輸入「#」開頭的標籤關鍵字"; +"openchat.create.room.description.placeholder" = "輸入簡介"; +"openchat.create.room.name.placeholder" = "OpenChat名稱"; +"openchat.create.room.search" = "開放搜尋"; +"openchat.create.room.search.guide" = "允許此OpenChat聊天室出現在搜尋結果中。"; +"openchat.create.room.title" = "建立OpenChat"; +"openchat.not.agree.with.terms" = "您尚未同意OpenChat服務條款。\n請先在LINE應用程式上打開OpenChat並同意服務條款,才可繼續操作。"; +"search.no.result" = "找不到任何結果"; +"shareRecipient.section.friends.title" = "好友"; +"shareRecipient.section.groups.title" = "群組"; +"square.create.category.all" = "全部"; +"square.create.category.alumnus" = "校友"; +"square.create.category.ani" = "動畫/漫畫"; +"square.create.category.art" = "藝術"; +"square.create.category.artculture" = "藝術/文化"; +"square.create.category.baby" = "兒童"; +"square.create.category.beauty" = "美容"; +"square.create.category.book" = "書籍"; +"square.create.category.car" = "汽機車"; +"square.create.category.celebrity" = "名人"; +"square.create.category.company" = "公司"; +"square.create.category.economy" = "經濟"; +"square.create.category.entertainer" = "藝人"; +"square.create.category.etc" = "其他"; +"square.create.category.exercise" = "健身"; +"square.create.category.family" = "家人"; +"square.create.category.fan" = "粉絲團"; +"square.create.category.fashion" = "流行/美妝"; +"square.create.category.finance" = "金融/商業"; +"square.create.category.food" = "美食"; +"square.create.category.friend" = "好友"; +"square.create.category.game" = "遊戲"; +"square.create.category.health" = "健康"; +"square.create.category.history" = "歷史"; +"square.create.category.hobby" = "興趣"; +"square.create.category.it" = "科技"; +"square.create.category.jpop" = "日本流行音樂/日劇"; +"square.create.category.kpop" = "韓流/韓劇"; +"square.create.category.medicine" = "醫療"; +"square.create.category.movies" = "電影"; +"square.create.category.music" = "音樂"; +"square.create.category.notselected" = "不選擇(將不顯示於任何類別中)"; +"square.create.category.org" = "團體"; +"square.create.category.pet" = "寵物"; +"square.create.category.photo" = "照片"; +"square.create.category.recipe" = "烹飪"; +"square.create.category.region" = "地區"; +"square.create.category.school" = "學校/校友"; +"square.create.category.science" = "科學"; +"square.create.category.social" = "社交"; +"square.create.category.society" = "同好會"; +"square.create.category.sports" = "運動"; +"square.create.category.study" = "學習"; +"square.create.category.travel" = "旅遊"; +"square.create.category.trending" = "熱門"; +"square.create.category.tv" = "電視節目"; diff --git a/LineSDK/LineSDK/Utils/CallbackQueue.swift b/LineSDK/LineSDK/Utils/CallbackQueue.swift index 73554e13..78032f64 100644 --- a/LineSDK/LineSDK/Utils/CallbackQueue.swift +++ b/LineSDK/LineSDK/Utils/CallbackQueue.swift @@ -1,13 +1,13 @@ // // CallbackQueue.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Utils/Colors.swift b/LineSDK/LineSDK/Utils/Colors.swift new file mode 100644 index 00000000..e117be96 --- /dev/null +++ b/LineSDK/LineSDK/Utils/Colors.swift @@ -0,0 +1,112 @@ +// +// Colors.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +extension UIColor { + + static var LineSDKPanelBorder: UIColor { + return compatibleColor(light: .init(hex6: 0xE6E7EA), dark: .init(hex6: 0x1D1D1E)) + } + + static var LineSDKPanelBackground: UIColor { + return compatibleColor(light: .init(hex6: 0xF7F8FA), dark: .init(hex6: 0x2C2C2E)) + } + + static func compatibleColor( + light: @autoclosure @escaping () -> UIColor, + dark: @autoclosure @escaping () -> UIColor + ) -> UIColor { + return UIColor { trait in + trait.userInterfaceStyle == .light ? light() : dark() + } + } + + static func compatibleColor(light: UInt64, dark: UInt64) -> UIColor { + return compatibleColor(light: .init(hex6: light), dark: .init(hex6: dark)) + } +} + +extension UIColor { + + convenience init(hex6: UInt64, alpha: CGFloat = 1) { + let divisor = CGFloat(255) + let red = CGFloat((hex6 & 0xFF0000) >> 16) / divisor + let green = CGFloat((hex6 & 0x00FF00) >> 8) / divisor + let blue = CGFloat( hex6 & 0x0000FF ) / divisor + self.init(red: red, green: green, blue: blue, alpha: alpha) + } + + convenience init(hex8: UInt64) { + let divisor = CGFloat(255) + let red = CGFloat((hex8 & 0xFF000000) >> 24) / divisor + let green = CGFloat((hex8 & 0x00FF0000) >> 16) / divisor + let blue = CGFloat((hex8 & 0x0000FF00) >> 8) / divisor + let alpha = CGFloat( hex8 & 0x000000FF ) / divisor + self.init(red: red, green: green, blue: blue, alpha: alpha) + } + + convenience init(rgb: String, default color: UIColor = .white) { + guard rgb.hasPrefix("#") else { + self.init(cgColor: color.cgColor) + return + } + + let hexString = String(rgb.dropFirst()) + var hexValue: UInt64 = 0 + + guard Scanner(string: hexString).scanHexInt64(&hexValue) else { + self.init(cgColor: color.cgColor) + return + } + switch (hexString.count) { + case 6: self.init(hex6: hexValue) + case 8: self.init(hex8: hexValue) + default: self.init(cgColor: color.cgColor) + } + } + + func hexString(_ includeAlpha: Bool = false) -> String { + var r: CGFloat = 0 + var g: CGFloat = 0 + var b: CGFloat = 0 + var a: CGFloat = 0 + + self.getRed(&r, green: &g, blue: &b, alpha: &a) + + guard r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1 else { return "#FFFFFF" } + + if (includeAlpha) { + return String(format: "#%02X%02X%02X%02X", Int(r * 255), Int(g * 255), Int(b * 255), Int(a * 255)) + } else { + return String(format: "#%02X%02X%02X", Int(r * 255), Int(g * 255), Int(b * 255)) + } + } +} + +extension UIColor { + func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage { + return UIGraphicsImageRenderer(size: size).image { rendererContext in + self.setFill() + rendererContext.fill(CGRect(origin: .zero, size: size)) + } + } +} diff --git a/LineSDK/LineSDK/Utils/Constant.swift b/LineSDK/LineSDK/Utils/Constant.swift index 7a70830c..0a34f85d 100644 --- a/LineSDK/LineSDK/Utils/Constant.swift +++ b/LineSDK/LineSDK/Utils/Constant.swift @@ -1,13 +1,13 @@ // // Constant.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -24,15 +24,10 @@ import Foundation /// Constants used in the LINE SDK. public struct Constant { + // This version number is bumped by `bump_constant_version` lane when releasing a new version. + // If you change the name or location of this variable, also update the lane in the Fastfile. /// The version of the current LINE SDK. - public static let SDKVersion: String = { - let bundle = Bundle.frameworkBundle - guard let version = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { - Log.fatalError("SDK resource bundle cannot be loaded, please verify your installation is not corrupted and try to reinstall LineSDK.") - } - - return version - }() + public static let SDKVersion = "5.11.2" static var SDKVersionString: String { return "LINE SDK iOS v\(SDKVersion)" @@ -53,15 +48,3 @@ public struct Constant { return URL(string: "\(Constant.lineAuthV2Scheme)://authorize/")! } } - -extension Bundle { - static let frameworkBundle: Bundle = { - let frameworkBundle = Bundle(for: LoginManager.self) - guard let path = frameworkBundle.path(forResource: "Resource", ofType: "bundle"), - let bundle = Bundle(path: path) else - { - Log.fatalError("SDK resource bundle cannot be found, please verify your installation is not corrupted and try to reinstall LineSDK.") - } - return bundle - }() -} diff --git a/LineSDK/LineSDK/Utils/Delegate.swift b/LineSDK/LineSDK/Utils/Delegate.swift index b9ce8594..9da15bab 100644 --- a/LineSDK/LineSDK/Utils/Delegate.swift +++ b/LineSDK/LineSDK/Utils/Delegate.swift @@ -1,13 +1,13 @@ // // Delegate.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Utils/Helpers.swift b/LineSDK/LineSDK/Utils/Helpers.swift index 4f67e6a3..8f3c4ca7 100644 --- a/LineSDK/LineSDK/Utils/Helpers.swift +++ b/LineSDK/LineSDK/Utils/Helpers.swift @@ -1,13 +1,13 @@ // // Helpers.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,17 +19,36 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import Foundation +import UIKit -struct Log { - static func assertionFailure(_ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) { +enum Log { + static func assertionFailure( + _ message: @autoclosure () -> String, + file: StaticString = #file, + line: UInt = #line) + { Swift.assertionFailure("[LineSDK] \(message())", file: file, line: line) } - static func fatalError(_ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) -> Never { + static func fatalError( + _ message: @autoclosure () -> String, + file: StaticString = #file, + line: UInt = #line + ) -> Never + { Swift.fatalError("[LineSDK] \(message())", file: file, line: line) } + static func precondition( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String, + file: StaticString = #file, + line: UInt = #line + ) + { + Swift.precondition(condition(), "[LineSDK] \(message())", file: file, line: line) + } + static func print(_ items: Any...) { let s = items.reduce("") { result, next in return result + String(describing: next) @@ -41,28 +60,88 @@ struct Log { /// Possible keys in the `userInfo` property of notifications related to the LINE Platform. public struct LineSDKNotificationKey {} -extension UIAlertController { - static func presentAlert(in viewController: UIViewController?, - title: String?, - message: String?, - style: UIAlertController.Style = .alert, - actions: [UIAlertAction]) -> Bool - { - guard let presenting = viewController ?? .topMost else { - return false - } - let alert = UIAlertController(title: title, message: message, preferredStyle: style) - actions.forEach(alert.addAction) - presenting.present(alert, animated: true, completion: nil) - return true - } -} - extension UIApplication { func openLINEInAppStore() { let url = URL(string: "https://itunes.apple.com/app/id443904275?mt=8")! open(url, options: [:], completionHandler: nil) } + + func openLINEApp() { + let url = URL(string: "\(Constant.lineAuthV2Scheme)://")! + open(url, options: [:], completionHandler: nil) + } +} + +extension UIView { + var safeLeadingAnchor: NSLayoutXAxisAnchor { + return safeAreaLayoutGuide.leadingAnchor + } + + var safeTrailingAnchor: NSLayoutXAxisAnchor { + return safeAreaLayoutGuide.trailingAnchor + } +} + +extension UIViewController { + var safeTopAnchor: NSLayoutYAxisAnchor { + return view.safeAreaLayoutGuide.topAnchor + } + + var safeBottomAnchor: NSLayoutYAxisAnchor { + return view.safeAreaLayoutGuide.bottomAnchor + } + + var safeLeadingAnchor: NSLayoutXAxisAnchor { + return view.safeAreaLayoutGuide.leadingAnchor + } + + var safeTrailingAnchor: NSLayoutXAxisAnchor { + return view.safeAreaLayoutGuide.trailingAnchor + } + + var safeAreaInsets: UIEdgeInsets { + return view.safeAreaInsets + } + + func addChild(_ viewController: UIViewController, to containerView: UIView) { + addChild(viewController) + containerView.addChildSubview(viewController.view) + viewController.didMove(toParent: self) + } + + func addChild(_ viewController: UIViewController, to layoutGuide: UILayoutGuide) { + addChild(viewController) + view.addSubview(viewController.view) + viewController.didMove(toParent: self) + + viewController.view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + viewController.view.topAnchor.constraint(equalTo: layoutGuide.topAnchor), + viewController.view.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor), + viewController.view.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor), + viewController.view.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor), + ]) + } +} + +extension UIViewController { + var expectedSearchBarHeight: CGFloat { + return 54 + } +} + +extension UIView { + // Add a subview as `self` is a container. Layout the added `child` to match `self` size. + func addChildSubview(_ child: UIView) { + child.translatesAutoresizingMaskIntoConstraints = false + addSubview(child) + NSLayoutConstraint.activate([ + child.topAnchor .constraint(equalTo: topAnchor), + child.leadingAnchor .constraint(equalTo: leadingAnchor), + child.trailingAnchor.constraint(equalTo: trailingAnchor), + child.bottomAnchor .constraint(equalTo: bottomAnchor), + ]) + } } func guardSharedProperty(_ input: T?) -> T { @@ -72,3 +151,9 @@ func guardSharedProperty(_ input: T?) -> T { } return shared } + +extension Constant { + static var isLINEInstalled: Bool { + return UIApplication.shared.canOpenURL(Constant.lineAppAuthURLv2) + } +} diff --git a/LineSDK/LineSDK/Utils/KeyboardObservable.swift b/LineSDK/LineSDK/Utils/KeyboardObservable.swift new file mode 100644 index 00000000..9360eb43 --- /dev/null +++ b/LineSDK/LineSDK/Utils/KeyboardObservable.swift @@ -0,0 +1,79 @@ +// +// KeyboardObservable.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +protocol KeyboardObservable: AnyObject { + + var keyboardObservers: [NotificationToken] { get set } + + func addKeyboardObserver() + + func removeKeyboardObserver() + + func keyboardInfoWillChange(keyboardInfo: KeyboardInfo) +} + +extension KeyboardObservable { + func addKeyboardObserver() { + keyboardObservers = [ + NotificationCenter.default.addObserver( + forName: UIResponder.keyboardWillChangeFrameNotification, + object: nil, + queue: .main, + using: { [unowned self] in + guard let userInfo = $0.userInfo else { return } + if let keyboardInfo = KeyboardInfo(from: userInfo) { + self.keyboardInfoWillChange(keyboardInfo: keyboardInfo) + } + } + ) + ] + } + + func removeKeyboardObserver() { + keyboardObservers.removeAll() + } +} + +struct KeyboardInfo { + let isVisible: Bool + let endFrame: CGRect? + let duration: TimeInterval + let animationCurve: UIView.AnimationCurve + let isLocal: Bool? + + init?(from userInfo: [AnyHashable : Any]) { + guard let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { + return nil + } + self.endFrame = endFrame + isVisible = endFrame.minY < UIScreen.main.bounds.maxY + duration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0 + isLocal = (userInfo[UIResponder.keyboardIsLocalUserInfoKey] as? NSNumber)?.boolValue + + if let rawValue = (userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.intValue { + animationCurve = UIView.AnimationCurve(rawValue: rawValue) ?? .linear + } else { + animationCurve = .linear + } + } +} diff --git a/LineSDK/LineSDK/Utils/KeychainStore.swift b/LineSDK/LineSDK/Utils/KeychainStore.swift index 9d8b6e85..d74580e5 100644 --- a/LineSDK/LineSDK/Utils/KeychainStore.swift +++ b/LineSDK/LineSDK/Utils/KeychainStore.swift @@ -1,13 +1,13 @@ // // KeychainStore.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Utils/LoadingIndicator.swift b/LineSDK/LineSDK/Utils/LoadingIndicator.swift new file mode 100644 index 00000000..eef2ae8e --- /dev/null +++ b/LineSDK/LineSDK/Utils/LoadingIndicator.swift @@ -0,0 +1,81 @@ +// +// LoadingIndicator.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit + +class LoadingIndicator { + + static var indicators = NSMapTable.weakToStrongObjects() + + var count: Int + let container: UIView + let indicator: UIActivityIndicatorView + + weak var ownerView: UIView? + + static func add(to view: UIView) -> LoadingIndicator { + if let indicator = indicators.object(forKey: view) { + indicator.count += 1 + return indicator + } else { + let indicator = LoadingIndicator() + indicator.add(to: view) + indicators.setObject(indicator, forKey: view) + return indicator + } + } + + init() { + count = 1 + container = UIView() + indicator = UIActivityIndicatorView(style: .large) + indicator.color = .gray + } + + func add(to view: UIView) { + + indicator.translatesAutoresizingMaskIntoConstraints = false + container.addSubview(indicator) + NSLayoutConstraint.activate([ + indicator.centerXAnchor.constraint(equalTo: container.centerXAnchor), + indicator.centerYAnchor.constraint(equalTo: container.centerYAnchor), + ]) + + view.addChildSubview(container) + indicator.startAnimating() + + ownerView = view + } + + func remove() { + guard let ownerView = ownerView, + let indicator = LoadingIndicator.indicators.object(forKey: ownerView) else + { + return + } + + indicator.count -= 1 + if indicator.count <= 0 { + container.removeFromSuperview() + LoadingIndicator.indicators.removeObject(forKey: ownerView) + } + } +} diff --git a/LineSDK/LineSDK/Utils/NotificationToken.swift b/LineSDK/LineSDK/Utils/NotificationToken.swift index d7d24a34..3da50101 100644 --- a/LineSDK/LineSDK/Utils/NotificationToken.swift +++ b/LineSDK/LineSDK/Utils/NotificationToken.swift @@ -1,13 +1,13 @@ // // NotificationToken.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -38,7 +38,11 @@ class NotificationToken { } extension NotificationCenter { - func addObserver(forName name: Notification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Swift.Void) -> NotificationToken + func addObserver( + forName name: Notification.Name?, + object obj: Any?, + queue: OperationQueue?, + using block: @escaping (Notification) -> Swift.Void) -> NotificationToken { let token: NSObjectProtocol = addObserver(forName: name, object: obj, queue: queue, using: block) return NotificationToken(token: token, in: self) diff --git a/LineSDK/LineSDK/Utils/Result.swift b/LineSDK/LineSDK/Utils/Result.swift deleted file mode 100644 index cb3ae5d1..00000000 --- a/LineSDK/LineSDK/Utils/Result.swift +++ /dev/null @@ -1,240 +0,0 @@ -// -// Result.swift -// -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. -// -// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, -// copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. -// -// As with any software that integrates with the LINE Corporation platform, your use of this software -// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. -// This copyright notice shall be included in all copies or substantial portions of the software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -// This is an identical implementaion for SE-0235 and https://github.com/apple/swift/pull/19982/ -// We could add a conditional flag to the whole file once `Result` contained in the Swift Standard Library. - -import Foundation - -/// The possible results of an operation, whether it is successful or not. -/// -/// - success: The operation was successful and an associated value is available. -/// - failure: The operation failed and an associated error is available. -public enum Result { - - /// The operation was successful and an associated value is available. - case success(Value) - - /// The operation failed and an associated error is available. - case failure(Error) - - /// Checks and returns whether the result is a success. - public var isSuccess: Bool { - if case .success = self { - return true - } - return false - } - - /// Checks and returns the associated value if the result is a success; `nil` otherwise. - public var value: Value? { - if case .success(let v) = self { - return v - } - return nil - } - - /// Returns whether the result is a failure; `false` otherwise. - public var failure: Bool { - return !isSuccess - } - - /// Checks and returns the associated error value if the result is a failure; `nil` otherwise. - public var error: Error? { - if case .failure(let e) = self { - return e - } - return nil - } - - /// Maps the result to a `Result` object. If the result is `.success`, `transform` is called with the - /// associated value and new `.success` with the transformed value is returned. If the result is `.failure`, - /// the original error is returned without transformation. - /// - /// - Parameter transform: A closure that takes the `.success` value of the result. - /// - Returns: The `Result` object containing the result of the given closure. If the result is a - /// failure, the `Result` object contains the original failure. - public func map(_ transform: (Value) -> NewValue) -> Result { - switch self { - case .success(let value): return .success(transform(value)) - case .failure(let error): return .failure(error) - } - } - - /// Evaluates the given transform closure when this `Result` instance is `.failure`, passing the error - /// as a parameter. - /// - /// Use the `mapError` method with a closure that returns a non-`Result` value. - /// - /// - Parameter transform: A closure that takes the failure value of the instance. - /// - Returns: A new `Result` instance with the result of the transform, if it was applied. - public func mapError(_ transform: (Error) -> NewError) -> Result { - switch self { - case .success(let value): return .success(value) - case .failure(let error): return .failure(transform(error)) - } - } - - /// Evaluates the given transform closure when this `Result` instance is - /// `.success`, passing the value as a parameter and flattening the result. - /// - /// - Parameter transform: A closure that takes the successful value of the - /// instance. - /// - Returns: A new `Result` instance, either from the transform or from - /// the previous error value. - public func flatMap( - _ transform: (Value) -> Result - ) -> Result { - switch self { - case let .success(value): - return transform(value) - case let .failure(error): - return .failure(error) - } - } - - /// Evaluates the given transform closure when this `Result` instance is - /// `.failure`, passing the error as a parameter and flattening the result. - /// - /// - Parameter transform: A closure that takes the error value of the - /// instance. - /// - Returns: A new `Result` instance, either from the transform or from - /// the previous success value. - public func flatMapError( - _ transform: (Error) -> Result - ) -> Result { - switch self { - case let .success(value): - return .success(value) - case let .failure(error): - return transform(error) - } - } - - /// Evaluates the given transform closures to create a single output value. - /// - /// - Parameters: - /// - onSuccess: A closure that transforms the success value. - /// - onFailure: A closure that transforms the error value. - /// - Returns: A single `Output` value. - public func fold( - onSuccess: (Value) -> Output, - onFailure: (Error) -> Output - ) -> Output { - switch self { - case let .success(value): - return onSuccess(value) - case let .failure(error): - return onFailure(error) - } - } -} - -extension Result where Error : Swift.Error { - /// Unwraps the `Result` and includes it into a throwing expression. - /// - /// - Returns: The success value, if the instance is a success. - /// - Throws: The error value, if the instance is a failure. - public func unwrapped() throws -> Value { - switch self { - case let .success(value): - return value - case let .failure(error): - throw error - } - } -} - -extension Result where Error == Swift.Error { - /// Create an instance by capturing the output of a throwing closure. - /// - /// - Parameter throwing: A throwing closure to evaluate. - @_transparent - public init(_ throwing: () throws -> Value) { - do { - let value = try throwing() - self = .success(value) - } catch { - self = .failure(error) - } - } - - /// Unwraps the `Result` into a throwing expression. - /// - /// - Returns: The success value, if the instance is a success. - /// - Throws: The error value, if the instance is a failure. - public func unwrapped() throws -> Value { - switch self { - case let .success(value): - return value - case let .failure(error): - throw error - } - } - - /// Evaluates the given transform closure when this `Result` instance is - /// `.success`, passing the value as a parameter and flattening the result. - /// - /// - Parameter transform: A closure that takes the successful value of the - /// instance. - /// - Returns: A new `Result` instance, either from the transform or from - /// the previous error value. - public func flatMap( - _ transform: (Value) throws -> NewValue - ) -> Result { - switch self { - case let .success(value): - do { - return .success(try transform(value)) - } catch { - return .failure(error) - } - case let .failure(error): - return .failure(error) - } - } -} - -extension Result : Equatable where Value : Equatable, Error : Equatable { } - -extension Result : Hashable where Value : Hashable, Error : Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(value) - hasher.combine(error) - } -} - -extension Result : CustomDebugStringConvertible { - public var debugDescription: String { - var output = "Result." - switch self { - case let .success(value): - output += "success(" - debugPrint(value, terminator: "", to: &output) - case let .failure(error): - output += "failure(" - debugPrint(error, terminator: "", to: &output) - } - output += ")" - - return output - } -} diff --git a/LineSDK/LineSDK/Utils/ResultUtils.swift b/LineSDK/LineSDK/Utils/ResultUtils.swift new file mode 100644 index 00000000..2008cb87 --- /dev/null +++ b/LineSDK/LineSDK/Utils/ResultUtils.swift @@ -0,0 +1,130 @@ +// +// ResultUtils.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// A set of convenience methods for manipulating the `Result` type. +public enum ResultUtil { + + /// Evaluates the given transform closures to create a single output value. + /// - Parameters: + /// - result: The input `Result` value. + /// - onSuccess: A closure that transforms the success value. + /// - onFailure: A closure that transforms the error value. + /// - Returns: A single `Output` value returned by either the `onSuccess` block or `onFailure` block. + public static func match( + result: Result, + onSuccess: (Success) -> Output, + onFailure: (Failure) -> Output) -> Output + { + switch result { + case let .success(value): + return onSuccess(value) + case let .failure(error): + return onFailure(error) + } + } + + /// Evaluates the given transform closures, converting the success value with the use of a + /// folder to create a single output value. + /// - Parameters: + /// - result: The input `Result` value. + /// - folder: A closure that takes an optional value of the result. If the `result` resolves to `.success`, it + /// takes the unwrapped value. Otherwise, it takes `nil`. + /// - Returns: A single `Output` value returned by the `folder`. + public static func matchSuccess( + result: Result, + with folder: (Success?) -> Output) -> Output + { + return match( + result: result, + onSuccess: { value in folder(value) }, + onFailure: { _ in folder(nil) } + ) + } + + /// Evaluates the given transform closures, converting the failure value with the use of a + /// folder to create a single output value. + /// - Parameters: + /// - result: The input `Result` value. + /// - folder: A closure that takes an optional value of the result. If the `result` resolves to `.failure`, it + /// takes the unwrapped error. Otherwise, it takes `nil`. + /// - Returns: A single `Output` value returned by the `folder`. + public static func matchFailure( + result: Result, + with folder: (Error?) -> Output) -> Output + { + return match( + result: result, + onSuccess: { _ in folder(nil) }, + onFailure: { error in folder(error) } + ) + } + + /// Evaluates the given transform closures, converting the result pair with the use of a folder + /// to create a single output value. + /// - Parameters: + /// - result: The input `Result` value. + /// - folder: A closure that takes a tuple consisted by two optional values. + /// The first value in the tuple is the unwrapped value if `result` resolves to `.success`. + /// The second value is the unwrapped value if `result` resolves to `.failure`. + /// - Returns: A single `Output` value returned by the `folder`. + public static func match( + result: Result, + with folder: (Success?, Error?) -> Output) -> Output + { + return match( + result: result, + onSuccess: { folder($0, nil) }, + onFailure: { folder(nil, $0) } + ) + } +} + +// These helper methods are not public since we do not want them to be exposed or cause any conflicts. +// They are merely a wrapper for the `ResultUtil` static methods. +extension Result { + + /// Evaluates the given transform closures to create a single output value. + /// + /// - Parameters: + /// - onSuccess: A closure that transforms the success value. + /// - onFailure: A closure that transforms the error value. + /// - Returns: A single `Output` value. + func match( + onSuccess: (Success) -> Output, + onFailure: (Failure) -> Output) -> Output + { + return ResultUtil.match(result: self, onSuccess: onSuccess, onFailure: onFailure) + } + + func matchSuccess(with folder: (Success?) -> Output) -> Output { + return ResultUtil.matchSuccess(result: self, with: folder) + } + + func matchFailure(with folder: (Error?) -> Output) -> Output { + return ResultUtil.matchFailure(result: self, with: folder) + } + + func match(with folder: (Success?, Error?) -> Output) -> Output { + return ResultUtil.match(result: self, with: folder) + } +} diff --git a/LineSDK/LineSDK/Utils/String.swift b/LineSDK/LineSDK/Utils/String.swift new file mode 100644 index 00000000..12f6da90 --- /dev/null +++ b/LineSDK/LineSDK/Utils/String.swift @@ -0,0 +1,54 @@ +// +// String.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +extension String { + + // Remove all prefix white space. + var prefixNormalized: String { + return String(drop { $0.isWhitespace }) + } + + // Remove all leading and trailing white space. + var normalized: String { + return trimmingCharacters(in: .whitespaces) + } + + func trimming(upper count: Int) -> String { + if let endIndex = index(startIndex, offsetBy: count, limitedBy: endIndex) { + return String(self[startIndex..CFBundlePackageType FMWK CFBundleShortVersionString - 5.0.0 + 5.11.2 CFBundleVersion - 402 + 1208 NSPrincipalClass diff --git a/LineSDK/LineSDKObjC/LineSDKUI/LineSDKAuthorizationStatus.swift b/LineSDK/LineSDKObjC/LineSDKUI/LineSDKAuthorizationStatus.swift new file mode 100644 index 00000000..a041ef2e --- /dev/null +++ b/LineSDK/LineSDKObjC/LineSDKUI/LineSDKAuthorizationStatus.swift @@ -0,0 +1,42 @@ +// +// LineSDKAuthorizationStatus.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public final class LineSDKAuthorizationStatus: NSObject { + + public let rawValue: Int + + public static let authorized = LineSDKAuthorizationStatus(rawValue: 1 << 0) + public static let lackOfToken = LineSDKAuthorizationStatus(rawValue: 1 << 1) + public static let lackOfPermissions = LineSDKAuthorizationStatus(rawValue: 1 << 2) + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static func status(from s: AuthorizationStatus) -> LineSDKAuthorizationStatus { + switch s { + case .authorized: return .authorized + case .lackOfToken: return .lackOfToken + case .lackOfPermissions(_): return .lackOfPermissions + } + } +} diff --git a/LineSDK/LineSDKObjC/Login/LineSDKLoginButton.swift b/LineSDK/LineSDKObjC/LineSDKUI/LineSDKLoginButton.swift similarity index 67% rename from LineSDK/LineSDKObjC/Login/LineSDKLoginButton.swift rename to LineSDK/LineSDKObjC/LineSDKUI/LineSDKLoginButton.swift index 919aafc8..209db786 100644 --- a/LineSDK/LineSDKObjC/Login/LineSDKLoginButton.swift +++ b/LineSDK/LineSDKObjC/LineSDKUI/LineSDKLoginButton.swift @@ -1,13 +1,13 @@ // // LineSDKLoginButton.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,18 +19,18 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - -@objc public protocol LineSDKLoginButtonDelegate: class { +@objc public protocol LineSDKLoginButtonDelegate: AnyObject { func loginButtonDidStartLogin(_ button: LineSDKLoginButton) func loginButton(_ button: LineSDKLoginButton, didSucceedLogin loginResult: LineSDKLoginResult?) func loginButton(_ button: LineSDKLoginButton, didFailLogin error: Error?) } @objcMembers -public class LineSDKLoginButton: LoginButton { +public class LineSDKLoginButton: NSObject { + + public var button: UIButton { return _binaryCompatibleButton } + // A wrapper for providing a binary-compatible version of the SDK. + private var _binaryCompatibleButton: LoginButton @objc public enum LineSDKLoginButtonSize: Int { case small @@ -55,35 +55,35 @@ public class LineSDKLoginButton: LoginButton { public weak var buttonPresentingViewController: UIViewController? public var loginPermissions: Set = [.profile] - public var loginManagerOptions: [LineSDKLoginManagerOptions]? + public var loginManagerParameters = LineSDKLoginManagerParameters() public var buttonSizeValue: LineSDKLoginButtonSize { set { - buttonSize = newValue.unwrapped + _binaryCompatibleButton.buttonSize = newValue.unwrapped } get { - return LineSDKLoginButtonSize(buttonSize) + return LineSDKLoginButtonSize(_binaryCompatibleButton.buttonSize) } } public var buttonTextValue: String? { set { - buttonText = newValue + _binaryCompatibleButton.buttonText = newValue } get { - return buttonText + return _binaryCompatibleButton.buttonText } } + + public override init() { + _binaryCompatibleButton = LoginButton() + } - override public func login() { - if LineSDKLoginManager.sharedManager.isAuthorized { - return - } - isUserInteractionEnabled = false + public func login() { LineSDKLoginManager.sharedManager.login( permissions: loginPermissions, inViewController: buttonPresentingViewController, - options: loginManagerOptions + parameters: loginManagerParameters ) { result, error in if error == nil { @@ -91,10 +91,13 @@ public class LineSDKLoginButton: LoginButton { } else { self.loginDelegate?.loginButton(self, didFailLogin: error) } - self.isUserInteractionEnabled = true } self.loginDelegate?.loginButtonDidStartLogin(self) } + + // MARK: - Deprecated + /// - Warning: Deprecated. Use `loginManagerParameters` instead. + /// + @available(*, deprecated, message: "Use `LineSDKLoginButton.loginManagerParameters` instead.") + public var loginManagerOptions: [LineSDKLoginManagerOptions]? } - - diff --git a/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatCreatingController.swift b/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatCreatingController.swift new file mode 100644 index 00000000..ecb6b1ca --- /dev/null +++ b/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatCreatingController.swift @@ -0,0 +1,120 @@ +// +// LineSDKOpenChatCreatingController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public class LineSDKOpenChatCreatingController: NSObject { + + let _value: OpenChatCreatingController + + public override init() { + _value = OpenChatCreatingController() + } + + var delegateProxy: LineSDKOpenChatCreatingControllerDelegateProxy? + + public var delegate: LineSDKOpenChatCreatingControllerDelegate? { + get { return delegateProxy?.proxy } + set { + delegateProxy = newValue.map { .init(proxy: $0, owner: self) } + _value.delegate = delegateProxy + } + } + + public var suggestedCategory: Int { + get { return _value.suggestedCategory.rawValue } + set { + guard let category = OpenChatCategory(rawValue: newValue) else { + return + } + _value.suggestedCategory = category + } + } + + public func loadAndPresent( + in viewController: UIViewController, + presentedHandler handler: @escaping (UIViewController?, Error?) -> Void + ) + { + _value.loadAndPresent(in: viewController) { result in + result.match(with: handler) + } + } + + public static func localAuthorizationStatusForCreatingOpenChat() -> LineSDKAuthorizationStatus + { + return LineSDKAuthorizationStatus.status( + from: OpenChatCreatingController.localAuthorizationStatusForCreatingOpenChat() + ) + } +} + +class LineSDKOpenChatCreatingControllerDelegateProxy: OpenChatCreatingControllerDelegate { + weak var proxy: LineSDKOpenChatCreatingControllerDelegate? + unowned var owner: LineSDKOpenChatCreatingController + + init(proxy: LineSDKOpenChatCreatingControllerDelegate, owner: LineSDKOpenChatCreatingController) { + self.proxy = proxy + self.owner = owner + } + + func openChatCreatingController( + _ controller: OpenChatCreatingController, + didCreateChatRoom room: OpenChatRoomInfo, + withCreatingItem item: OpenChatRoomCreatingItem + ) + { + proxy?.openChatCreatingController?(owner, didCreateChatRoom: .init(room), withCreatingItem: .init(item)) + } + + func openChatCreatingController( + _ controller: OpenChatCreatingController, + didFailWithError error: LineSDKError, + withCreatingItem item: OpenChatRoomCreatingItem, + presentingViewController: UIViewController + ) + { + proxy?.openChatCreatingController?( + owner, + didFailWithError: error, + withCreatingItem: .init(item), + presentingViewController: presentingViewController + ) + } + + func openChatCreatingController( + _ controller: OpenChatCreatingController, + shouldPreventUserTermAlertFrom presentingViewController: UIViewController + ) -> Bool { + return proxy?.openChatCreatingController?(owner, shouldPreventUserTermAlertFrom: presentingViewController) + ?? false + } + + func openChatCreatingControllerDidCancelCreating(_ controller: OpenChatCreatingController) { + proxy?.openChatCreatingControllerDidCancelCreating?(owner) + } + + func openChatCreatingController( + _ controller: OpenChatCreatingController, + willPresentCreatingNavigationController navigationController: OpenChatCreatingNavigationController) + { + proxy?.openChatCreatingController?(owner, willPresentCreatingNavigationController: navigationController) + } +} diff --git a/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatCreatingControllerDelegate.swift b/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatCreatingControllerDelegate.swift new file mode 100644 index 00000000..69149af2 --- /dev/null +++ b/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatCreatingControllerDelegate.swift @@ -0,0 +1,47 @@ +// +// LineSDKOpenChatCreatingControllerDelegate.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objc public protocol LineSDKOpenChatCreatingControllerDelegate: AnyObject { + @objc optional func openChatCreatingController( + _ controller: LineSDKOpenChatCreatingController, + didCreateChatRoom room: LineSDKOpenChatRoomInfo, + withCreatingItem item: LineSDKOpenChatRoomCreatingItem + ) + + @objc optional func openChatCreatingController( + _ controller: LineSDKOpenChatCreatingController, + didFailWithError error: Error, + withCreatingItem item: LineSDKOpenChatRoomCreatingItem, + presentingViewController: UIViewController + ) + + @objc optional func openChatCreatingController( + _ controller: LineSDKOpenChatCreatingController, + shouldPreventUserTermAlertFrom presentingViewController: UIViewController + ) -> Bool + + @objc optional func openChatCreatingControllerDidCancelCreating(_ controller: LineSDKOpenChatCreatingController) + + @objc optional func openChatCreatingController( + _ controller: LineSDKOpenChatCreatingController, + willPresentCreatingNavigationController navigationController: UINavigationController + ) +} diff --git a/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatRoomCreatingItem.swift b/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatRoomCreatingItem.swift new file mode 100644 index 00000000..54448cd3 --- /dev/null +++ b/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatRoomCreatingItem.swift @@ -0,0 +1,49 @@ +// +// LineSDKOpenChatRoomCreatingItem.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public class LineSDKOpenChatRoomCreatingItem: NSObject { + let _value: OpenChatRoomCreatingItem + init(_ value: OpenChatRoomCreatingItem) { _value = value } + + public var name: String { return _value.name } + public var roomDescription: String { return _value.roomDescription } + public var creatorDisplayName: String { return _value.creatorDisplayName } + public var category: Int { return _value.category } + public var allowSearch: Bool { return _value.allowSearch } + + public init( + name: String, + roomDescription: String, + creatorDisplayName: String, + category: Int, + allowSearch: Bool + ) + { + _value = OpenChatRoomCreatingItem( + name: name, + roomDescription: roomDescription, + creatorDisplayName: creatorDisplayName, + category: category, + allowSearch: allowSearch + ) + } +} diff --git a/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatRoomInfo.swift b/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatRoomInfo.swift new file mode 100644 index 00000000..e0f8a834 --- /dev/null +++ b/LineSDK/LineSDKObjC/LineSDKUI/OpenChatUI/LineSDKOpenChatRoomInfo.swift @@ -0,0 +1,29 @@ +// +// LineSDKOpenChatRoomInfo.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public class LineSDKOpenChatRoomInfo: NSObject { + let _value: OpenChatRoomInfo + init(_ value: OpenChatRoomInfo) { _value = value } + + public var openChatId: String { return _value.openChatId } + public var url: URL { return _value.url } +} diff --git a/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKMessageShareTargetType.swift b/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKMessageShareTargetType.swift new file mode 100644 index 00000000..c9b43309 --- /dev/null +++ b/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKMessageShareTargetType.swift @@ -0,0 +1,32 @@ +// +// LineSDKMessageShareTargetType.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objc public enum LineSDKMessageShareTargetType: Int { + case friends + case groups + + init(_ value: MessageShareTargetType) { + switch value { + case .friends: self = .friends + case .groups: self = .groups + } + } +} diff --git a/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKShareTarget.swift b/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKShareTarget.swift new file mode 100644 index 00000000..34235da3 --- /dev/null +++ b/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKShareTarget.swift @@ -0,0 +1,49 @@ +// +// LineSDKShareTarget.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objc public protocol LineSDKShareTarget { + var targetID: String { get } + var displayName: String { get } + var avatarURL: URL? { get } +} + +extension LineSDKUser: LineSDKShareTarget { + public var targetID: String { return userID } + public var avatarURL: URL? { return pictureURLSmall } +} + +extension LineSDKGroup: LineSDKShareTarget { + public var targetID: String { return groupID } + public var displayName: String { return groupName } + public var avatarURL: URL? { return pictureURLSmall } +} + +extension ShareTarget { + var sdkShareTarget: LineSDKShareTarget { + if let user = self as? User { + return LineSDKUser(user) + } else if let group = self as? Group { + return LineSDKGroup(group) + } else { + Log.fatalError("Cannot convert share target to objc compatible target. \(self)") + } + } +} diff --git a/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKShareViewController.swift b/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKShareViewController.swift new file mode 100644 index 00000000..fa7b457b --- /dev/null +++ b/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKShareViewController.swift @@ -0,0 +1,151 @@ +// +// LineSDKShareViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public class LineSDKShareViewController: NSObject { + + private var _binaryCompatibleViewController: ShareViewController + public var viewController: UIViewController { + return _binaryCompatibleViewController + } + + var delegateProxy: LineSDKShareViewControllerDelegateProxy? + + public var shareNavigationBarTintColor: UIColor { + get { return _binaryCompatibleViewController.navigationBarTintColor } + set { _binaryCompatibleViewController.navigationBarTintColor = newValue } + } + + public var shareNavigationBarTextColor: UIColor { + get { return _binaryCompatibleViewController.navigationBarTextColor } + set { _binaryCompatibleViewController.navigationBarTextColor = newValue } + } + + public var shareStatusBarStyle: UIStatusBarStyle { + get { return _binaryCompatibleViewController.statusBarStyle } + set { _binaryCompatibleViewController.statusBarStyle = newValue } + } + + public var shareMessages: [LineSDKMessage]? { + get { + return _binaryCompatibleViewController.messages?.compactMap { .message(with: $0) } + } + set { + _binaryCompatibleViewController.messages = newValue?.compactMap { $0.unwrapped } + } + } + + public var delegate: LineSDKShareViewControllerDelegate? { + get { return delegateProxy?.proxy } + set { + delegateProxy = newValue.map { .init(proxy: $0, owner: self) } + _binaryCompatibleViewController.shareDelegate = delegateProxy + } + } + + public override init() { + _binaryCompatibleViewController = ShareViewController() + } + + @objc public static func localAuthorizationStatusForSendingMessage() + -> LineSDKAuthorizationStatus + { + return LineSDKAuthorizationStatus.status( + from: ShareViewController.localAuthorizationStatusForSendingMessage() + ) + } +} + +class LineSDKShareViewControllerDelegateProxy: ShareViewControllerDelegate { + + weak var proxy: LineSDKShareViewControllerDelegate? + unowned var owner: LineSDKShareViewController + + init(proxy: LineSDKShareViewControllerDelegate, owner: LineSDKShareViewController) { + self.proxy = proxy + self.owner = owner + } + + func shareViewController( + _ controller: ShareViewController, + didFailLoadingListType shareType: MessageShareTargetType, + withError error: LineSDKError) + { + proxy?.shareViewController?(owner, didFailLoadingListType: .init(shareType), withError: error) + } + + func shareViewControllerDidCancelSharing(_ controller: ShareViewController) { + if let proxy = proxy { + proxy.shareViewControllerDidCancelSharing?(owner) ?? owner.viewController.dismiss(animated: true) + } else { + owner.viewController.dismiss(animated: true) + } + } + + func shareViewController( + _ controller: ShareViewController, + didFailSendingMessages messages: [MessageConvertible], + toTargets targets: [ShareTarget], + withError error: LineSDKError) + { + proxy?.shareViewController?( + owner, + didFailSendingMessages: messages.compactMap { LineSDKMessage.message(with: $0) }, + toTargets: targets.map { $0.sdkShareTarget }, + withError: error) + } + + func shareViewController( + _ controller: ShareViewController, + didSendMessages messages: [MessageConvertible], + toTargets targets: [ShareTarget]) + { + proxy?.shareViewController?( + owner, + didSendMessages: messages.compactMap { LineSDKMessage.message(with: $0) }, + toTargets: targets.map { $0.sdkShareTarget } + ) + } + + func shareViewController( + _ controller: ShareViewController, + messagesForSendingToTargets targets: [ShareTarget]) -> [MessageConvertible] + { + guard let messages = controller.messages else { + Log.fatalError( + """ + You need at least set the `ShareViewController.message` or implement + `shareViewController(:messageForSendingToTargets:)` before sharing a message.) + """ + ) + } + guard let proxy = proxy else { return messages } + let targets = targets.map { $0.sdkShareTarget } + guard let sdkMessages = proxy.shareViewController?(owner, messagesForSendingToTargets: targets) else { + return messages + } + return sdkMessages.map { $0.unwrapped } + } + + func shareViewControllerShouldDismissAfterSending(_ controller: ShareViewController) -> Bool { + return proxy?.shareViewControllerShouldDismissAfterSending?(owner) ?? true + } +} diff --git a/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKShareViewControllerDelegate.swift b/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKShareViewControllerDelegate.swift new file mode 100644 index 00000000..26187f41 --- /dev/null +++ b/LineSDK/LineSDKObjC/LineSDKUI/SharingUI/LineSDKShareViewControllerDelegate.swift @@ -0,0 +1,45 @@ +// +// LineSDKShareViewControllerDelegate.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objc public protocol LineSDKShareViewControllerDelegate: AnyObject { + @objc optional func shareViewController( + _ controller: LineSDKShareViewController, + didFailLoadingListType shareType: LineSDKMessageShareTargetType, + withError error: Error) + @objc optional func shareViewControllerDidCancelSharing(_ controller: LineSDKShareViewController) + @objc optional func shareViewController( + _ controller: LineSDKShareViewController, + didFailSendingMessages messages: [LineSDKMessage], + toTargets targets: [LineSDKShareTarget], + withError error: Error) + + @objc optional func shareViewController( + _ controller: LineSDKShareViewController, + didSendMessages messages: [LineSDKMessage], + toTargets targets: [LineSDKShareTarget]) + + @objc optional func shareViewController( + _ controller: LineSDKShareViewController, + messagesForSendingToTargets targets: [LineSDKShareTarget]) -> [LineSDKMessage] + + @objc optional func shareViewControllerShouldDismissAfterSending( + _ controller: LineSDKShareViewController) -> Bool +} diff --git a/LineSDK/LineSDKObjC/Login/LineSDKLoginManager.swift b/LineSDK/LineSDKObjC/Login/LineSDKLoginManager.swift index 8de206b4..e2ea306a 100644 --- a/LineSDK/LineSDKObjC/Login/LineSDKLoginManager.swift +++ b/LineSDK/LineSDKObjC/Login/LineSDKLoginManager.swift @@ -1,13 +1,13 @@ // // LineSDKLoginManager.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKLoginManager: NSObject { let _value: LoginManager @@ -33,32 +29,57 @@ public class LineSDKLoginManager: NSObject { public var isSetupFinished: Bool { return _value.isSetupFinished } public var isAuthorized: Bool { return _value.isAuthorized } public var isAuthorizing: Bool { return _value.isAuthorizing } + + @available(*, deprecated, + message: "Set `preferredWebPageLanguage` in `LineSDKLoginManagerParameters` instead.") + public var preferredWebPageLanguage: String? { + get { return _value.preferredWebPageLanguage?.rawValue } + set { _value.preferredWebPageLanguage = newValue.map { .init(rawValue: $0) } } + } + public func setup(channelID: String, universalLinkURL: URL?) { _value.setup(channelID: channelID, universalLinkURL: universalLinkURL) } + @discardableResult public func login( permissions: Set?, inViewController viewController: UIViewController?, - options: [LineSDKLoginManagerOptions]?, - completionHandler completion: @escaping (LineSDKLoginResult?, Error?) -> Void) -> LineSDKLoginProcess? + completionHandler completion: @escaping (LineSDKLoginResult?, Error?) -> Void + ) -> LineSDKLoginProcess? + { + let parameters = LineSDKLoginManagerParameters() + return login( + permissions: permissions, + inViewController: viewController, + parameters: parameters, + completionHandler: completion + ) + } + + @discardableResult + public func login( + permissions: Set?, + inViewController viewController: UIViewController?, + parameters: LineSDKLoginManagerParameters, + completionHandler completion: @escaping (LineSDKLoginResult?, Error?) -> Void + ) -> LineSDKLoginProcess? { - let options: LoginManagerOptions = (options ?? []).reduce([]) { (result, option) in - result.union(option.unwrapped) - } let process = _value.login( permissions: Set((permissions ?? [.profile]).map { $0.unwrapped }), in: viewController, - options: options) + parameters: parameters._value) { result in - completion(result.value.map { .init($0) }, result.error) + result + .map(LineSDKLoginResult.init) + .match(with: completion) } return process.map { .init($0) } } - + public func logout(completionHandler completion: @escaping (Error?) -> Void) { - _value.logout { result in completion(result.error) } + _value.logout { result in result.matchFailure(with: completion) } } public func application( @@ -68,4 +89,34 @@ public class LineSDKLoginManager: NSObject { { return _value.application(app, open: url, options: options) } + + // MARK: - Deprecated + + @available(*, deprecated, message: """ + Convert the `options` to a `LoginManager.Parameters` value and + use `login(permissions:inViewController:parameters:completionHandler:)` instead.") + """) + @discardableResult + public func login( + permissions: Set?, + inViewController viewController: UIViewController?, + options: [LineSDKLoginManagerOptions]?, + completionHandler completion: @escaping (LineSDKLoginResult?, Error?) -> Void + ) -> LineSDKLoginProcess? + { + let options: LoginManagerOptions = (options ?? []).reduce([]) { (result, option) in + result.union(option.unwrapped) + } + + let parameters = LoginManager.Parameters( + options: options, + language: preferredWebPageLanguage.map { .init(rawValue: $0) } + ) + return login( + permissions: permissions, + inViewController: viewController, + parameters: .init(parameters), + completionHandler: completion + ) + } } diff --git a/LineSDK/LineSDKObjC/Login/LineSDKLoginManagerOptions.swift b/LineSDK/LineSDKObjC/Login/LineSDKLoginManagerOptions.swift index c8036c97..281bf51b 100644 --- a/LineSDK/LineSDKObjC/Login/LineSDKLoginManagerOptions.swift +++ b/LineSDK/LineSDKObjC/Login/LineSDKLoginManagerOptions.swift @@ -1,13 +1,13 @@ // // LineSDKLoginManagerOptions.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - +@available(*, deprecated, message: "Use `LineSDKLoginManagerParameters` instead.") @objcMembers public class LineSDKLoginManagerOptions: NSObject { let _value: LoginManagerOptions diff --git a/LineSDK/LineSDKObjC/Login/LineSDKLoginManagerParameters.swift b/LineSDK/LineSDKObjC/Login/LineSDKLoginManagerParameters.swift new file mode 100644 index 00000000..0389b43b --- /dev/null +++ b/LineSDK/LineSDKObjC/Login/LineSDKLoginManagerParameters.swift @@ -0,0 +1,86 @@ +// +// LineSDKLoginManagerParameters.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public class LineSDKLoginManagerParameters: NSObject { + + var _value: LoginManager.Parameters + + public override init() { + _value = LoginManager.Parameters() + } + + init(_ value: LoginManager.Parameters) { _value = value } + + public var onlyWebLogin: Bool { + get { return _value.onlyWebLogin } + set { _value.onlyWebLogin = newValue } + } + + public var botPromptStyle: LineSDKLoginManagerBotPrompt? { + get { return _value.botPromptStyle.map(LineSDKLoginManagerBotPrompt.init) } + set { _value.botPromptStyle = newValue?._value } + } + + public var promptBotID: String? { + get { return _value.promptBotID } + set { _value.promptBotID = newValue } + } + + public var initialWebAuthenticationMethod: LineSDKLoginManagerWebAuthenticationMethod { + get { return LineSDKLoginManagerWebAuthenticationMethod(_value.initialWebAuthenticationMethod) } + set { _value.initialWebAuthenticationMethod = newValue._value } + } + + public var preferredWebPageLanguage: String? { + get { return _value.preferredWebPageLanguage?.rawValue } + set { _value.preferredWebPageLanguage = newValue.map { .init(rawValue: $0) } } + } + + public var IDTokenNonce: String? { + get { return _value.IDTokenNonce } + set { _value.IDTokenNonce = newValue } + } +} + +@objcMembers +public class LineSDKLoginManagerBotPrompt: NSObject { + + let _value: LoginManager.BotPrompt + init(_ value: LoginManager.BotPrompt) { _value = value } + + public static let normal = LineSDKLoginManagerBotPrompt(.normal) + public static let aggressive = LineSDKLoginManagerBotPrompt(.aggressive) + + public var rawValue: String { return _value.rawValue } +} + +@objcMembers +public class LineSDKLoginManagerWebAuthenticationMethod: NSObject { + + let _value: LoginManager.WebAuthenticationMethod + init(_ value: LoginManager.WebAuthenticationMethod) { _value = value } + + public static let email = LineSDKLoginManagerWebAuthenticationMethod(.email) + public static let qrCode = LineSDKLoginManagerWebAuthenticationMethod(.qrCode) + + public var rawValue: String { return _value.rawValue } +} diff --git a/LineSDK/LineSDKObjC/Login/LineSDKLoginPermission.swift b/LineSDK/LineSDKObjC/Login/LineSDKLoginPermission.swift index ee78d8be..817e84b7 100644 --- a/LineSDK/LineSDKObjC/Login/LineSDKLoginPermission.swift +++ b/LineSDK/LineSDKObjC/Login/LineSDKLoginPermission.swift @@ -1,13 +1,13 @@ // // LineSDKLoginPermission.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKLoginPermission: NSObject { let _value: LoginPermission @@ -33,18 +29,34 @@ public class LineSDKLoginPermission: NSObject { _value = .init(rawValue: rawValue) } - public static let openID = LineSDKLoginPermission(.openID) - public static let profile = LineSDKLoginPermission(.profile) - public static let friends = LineSDKLoginPermission(.friends) - public static let groups = LineSDKLoginPermission(.groups) - public static let messageWrite = LineSDKLoginPermission(.messageWrite) + public static func permissions(from string: String) -> Set { + let permissions = string.split(separator: " ").map { LineSDKLoginPermission(rawValue: String($0)) } + return Set(permissions) + } + + public static let openID = LineSDKLoginPermission(.openID) + public static let profile = LineSDKLoginPermission(.profile) + public static let friends = LineSDKLoginPermission(.friends) + public static let groups = LineSDKLoginPermission(.groups) + public static let oneTimeShare = LineSDKLoginPermission(.oneTimeShare) + public static let messageWrite = LineSDKLoginPermission(.messageWrite) + + public static let email = LineSDKLoginPermission(.email) + public static let phone = LineSDKLoginPermission(.phone) + public static let gender = LineSDKLoginPermission(.gender) + public static let birthdate = LineSDKLoginPermission(.birthdate) + public static let address = LineSDKLoginPermission(.address) + public static let realName = LineSDKLoginPermission(.realName) + + public static let openChatTermStatus = LineSDKLoginPermission(.openChatTermStatus) + public static let openChatRoomCreateAndJoin = LineSDKLoginPermission(.openChatRoomCreateAndJoin) + public static let openChatInfo = LineSDKLoginPermission(.openChatInfo) - public static let email = LineSDKLoginPermission(.email) - public static let phone = LineSDKLoginPermission(.phone) - public static let gender = LineSDKLoginPermission(.gender) - public static let birthdate = LineSDKLoginPermission(.birthdate) - public static let address = LineSDKLoginPermission(.address) - public static let realName = LineSDKLoginPermission(.realName) + public static let openChatPlugManagement = LineSDKLoginPermission(.openChatPlugManagement) + public static let openChatPlugInfo = LineSDKLoginPermission(.openChatPlugInfo) + public static let openChatPlugProfile = LineSDKLoginPermission(.openChatPlugProfile) + public static let openChatPlugSendMessage = LineSDKLoginPermission(.openChatPlugSendMessage) + public static let openChatPlugReceiveMessageEvent = LineSDKLoginPermission(.openChatPlugReceiveMessageEvent) var unwrapped: LoginPermission { return _value } } diff --git a/LineSDK/LineSDKObjC/Login/LineSDKLoginProcess.swift b/LineSDK/LineSDKObjC/Login/LineSDKLoginProcess.swift index 3df725a2..7c8e4db8 100644 --- a/LineSDK/LineSDKObjC/Login/LineSDKLoginProcess.swift +++ b/LineSDK/LineSDKObjC/Login/LineSDKLoginProcess.swift @@ -1,13 +1,13 @@ // // LineSDKLoginProcess.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,14 +19,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKLoginProcess: NSObject { let _value: LoginProcess init(_ value: LoginProcess) { _value = value } - + + public var loginRoute: String? { return _value.loginRoute?.rawValue } + public func stop() { _value.stop() } } diff --git a/LineSDK/LineSDKObjC/Login/LineSDKLoginResult.swift b/LineSDK/LineSDKObjC/Login/LineSDKLoginResult.swift index 0eb65d0f..42248b71 100644 --- a/LineSDK/LineSDKObjC/Login/LineSDKLoginResult.swift +++ b/LineSDK/LineSDKObjC/Login/LineSDKLoginResult.swift @@ -1,13 +1,13 @@ // // LineSDKLoginResult.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKLoginResult: NSObject { let _value: LoginResult @@ -32,4 +28,7 @@ public class LineSDKLoginResult: NSObject { public var permissions: Set { return Set(_value.permissions.map { .init($0) }) } public var userProfile: LineSDKUserProfile? { return _value.userProfile.map { .init($0) } } public var friendshipStatusChanged: NSNumber? { return _value.friendshipStatusChanged.map { .init(value: $0) } } + public var IDTokenNonce: String? { return _value.IDTokenNonce } + + public var json: String? { return toJSON(_value) } } diff --git a/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessToken.swift b/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessToken.swift index 8c2ed086..6595cb67 100644 --- a/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessToken.swift +++ b/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessToken.swift @@ -1,13 +1,13 @@ // // LineSDKAccessToken.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKAccessToken: NSObject { let _value: AccessToken @@ -31,7 +27,10 @@ public class LineSDKAccessToken: NSObject { public var value: String { return _value.value } public var createdAt: Date { return _value.createdAt } public var IDToken: LineSDKJWT? { return _value.IDToken.map { .init($0) } } - public var refreshToken: String { return _value.refreshToken } + public var IDTokenRaw: String? { return _value.IDTokenRaw } + public var permissions: [LineSDKLoginPermission] { return _value.permissions.map { .init($0) } } public var expiresAt: Date { return _value.expiresAt } + + public var json: String? { return toJSON(_value) } } diff --git a/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessTokenStore.swift b/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessTokenStore.swift index c692d190..06be36c1 100644 --- a/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessTokenStore.swift +++ b/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessTokenStore.swift @@ -1,13 +1,13 @@ // // LineSDKAccessTokenStore.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objc extension NSNotification { public static let LineSDKAccessTokenDidUpdate = Notification.Name.LineSDKAccessTokenDidUpdate public static let LineSDKAccessTokenDidRemove = Notification.Name.LineSDKAccessTokenDidRemove diff --git a/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessTokenVerifyResult.swift b/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessTokenVerifyResult.swift index f077adc3..c0b08871 100644 --- a/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessTokenVerifyResult.swift +++ b/LineSDK/LineSDKObjC/Login/Model/LineSDKAccessTokenVerifyResult.swift @@ -1,13 +1,13 @@ // // LineSDKAccessTokenVerifyResult.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKAccessTokenVerifyResult: NSObject { let _value: AccessTokenVerifyResult @@ -33,4 +29,6 @@ public class LineSDKAccessTokenVerifyResult: NSObject { public var channelID: String { return _value.channelID } public var permissions: [LineSDKLoginPermission] { return _value.permissions.map { .init($0) } } public var expiresIn: TimeInterval { return _value.expiresIn } + + public var json: String? { return toJSON(_value) } } diff --git a/LineSDK/LineSDKObjC/Login/Model/LineSDKJWT.swift b/LineSDK/LineSDKObjC/Login/Model/LineSDKJWT.swift index f2fd5ca5..0fb843a1 100644 --- a/LineSDK/LineSDKObjC/Login/Model/LineSDKJWT.swift +++ b/LineSDK/LineSDKObjC/Login/Model/LineSDKJWT.swift @@ -1,13 +1,13 @@ // // LineSDKJWT.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKJWT: NSObject { public let payload: LineSDKJWTPayload @@ -63,4 +59,5 @@ public class LineSDKJWTPayload: NSObject { public var name: String? { return value.name } public var picture: URL? { return value.pictureURL } public var email: String? { return value.email } + public var amr: [String]? { return value.amr } } diff --git a/LineSDK/LineSDKObjC/Login/Model/LineSDKUserProfile.swift b/LineSDK/LineSDKObjC/Login/Model/LineSDKUserProfile.swift index 6c2c0c7f..703d7a33 100644 --- a/LineSDK/LineSDKObjC/Login/Model/LineSDKUserProfile.swift +++ b/LineSDK/LineSDKObjC/Login/Model/LineSDKUserProfile.swift @@ -1,13 +1,13 @@ // // LineSDKUserProfile.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKUserProfile: NSObject { let _value: UserProfile @@ -34,4 +30,6 @@ public class LineSDKUserProfile: NSObject { public var pictureURLLarge: URL? { return _value.pictureURLLarge } public var pictureURLSmall: URL? { return _value.pictureURLSmall } public var statusMessage: String? { return _value.statusMessage } + + public var json: String? { return toJSON(_value) } } diff --git a/LineSDK/LineSDKObjC/Messaging/Actions/LineSDKMessageAction.swift b/LineSDK/LineSDKObjC/Messaging/Actions/LineSDKMessageAction.swift index edabb1f8..9e73c7c1 100644 --- a/LineSDK/LineSDKObjC/Messaging/Actions/LineSDKMessageAction.swift +++ b/LineSDK/LineSDKObjC/Messaging/Actions/LineSDKMessageAction.swift @@ -1,13 +1,13 @@ // // LineSDKMessageAction.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - extension MessageAction { var wrapped: LineSDKMessageAction { switch self { @@ -45,14 +41,14 @@ public class LineSDKMessageAction: NSObject { @objcMembers public class LineSDKMessageURIAction: LineSDKMessageAction { - public var label: String + public var label: String? public var uri: URL convenience init(_ value: MessageURIAction) { self.init(label: value.label, uri: value.uri) } - public init(label: String, uri: URL) { + public init(label: String?, uri: URL) { self.label = label self.uri = uri } diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexBoxComponent.swift b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexBoxComponent.swift index 413a874b..ffcda526 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexBoxComponent.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexBoxComponent.swift @@ -1,13 +1,13 @@ // // LineSDKFlexBoxComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKFlexBoxComponent: LineSDKFlexMessageComponent { public let layout: LineSDKFlexMessageComponentLayout diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexButtonComponent.swift b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexButtonComponent.swift index 3b699852..5cc8f3e8 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexButtonComponent.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexButtonComponent.swift @@ -1,13 +1,13 @@ // // LineSDKFlexButtonComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objc public enum LineSDKFlexButtonComponentStyle: Int { case none diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexFillerComponent.swift b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexFillerComponent.swift index a48db73e..b2ff650e 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexFillerComponent.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexFillerComponent.swift @@ -1,13 +1,13 @@ // // LineSDKFlexFillerComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKFlexFillerComponent: LineSDKFlexMessageComponent { diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexIconComponent.swift b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexIconComponent.swift index c7db6f99..f6ac6abe 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexIconComponent.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexIconComponent.swift @@ -1,13 +1,13 @@ // // LineSDKFlexIconComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKFlexIconComponent: LineSDKFlexMessageComponent { public let url: URL diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexImageComponent.swift b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexImageComponent.swift index ec97d355..f26a7ede 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexImageComponent.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexImageComponent.swift @@ -1,13 +1,13 @@ // // LineSDKFlexImageComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKFlexImageComponent: LineSDKFlexMessageComponent { diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexMessageComponent.swift b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexMessageComponent.swift index 47e08e2f..ef830c01 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexMessageComponent.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexMessageComponent.swift @@ -1,13 +1,13 @@ // // LineSDKFlexMessageComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - extension FlexMessageComponent { var wrapped: LineSDKFlexMessageComponent { switch self { diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexSeparatorComponent.swift b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexSeparatorComponent.swift index afa7ca73..5c83c893 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexSeparatorComponent.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexSeparatorComponent.swift @@ -1,13 +1,13 @@ // // LineSDKFlexSeparatorComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKFlexSeparatorComponent: LineSDKFlexMessageComponent { public var margin: LineSDKFlexMessageComponentMargin = .none diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexTextComponent.swift b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexTextComponent.swift index 470d6ff1..c3aa2872 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexTextComponent.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKFlexTextComponent.swift @@ -1,13 +1,13 @@ // // LineSDKFlexTextComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKFlexTextComponent: LineSDKFlexMessageComponent { diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKSpacerComponent.swift b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKSpacerComponent.swift index a98b9164..7907cdb8 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKSpacerComponent.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/Component/LineSDKSpacerComponent.swift @@ -1,13 +1,13 @@ // // LineSDKSpacerComponent.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKFlexSpacerComponent: LineSDKFlexMessageComponent { public var size: LineSDKFlexMessageComponentSize diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexBubbleContainer.swift b/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexBubbleContainer.swift index fc112d80..520a8488 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexBubbleContainer.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexBubbleContainer.swift @@ -1,13 +1,13 @@ // // LineSDKFlexBubbleContainer.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKFlexBubbleContainer: LineSDKFlexMessageContainer { diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexCarouselContainer.swift b/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexCarouselContainer.swift index ec25048d..8eb0fbd2 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexCarouselContainer.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexCarouselContainer.swift @@ -1,13 +1,13 @@ // // LineSDKFlexCarouselContainer.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKFlexCarouselContainer: LineSDKFlexMessageContainer { public var contents: [LineSDKFlexBubbleContainer] diff --git a/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexMessageContainer.swift b/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexMessageContainer.swift index d825dffb..e21e8528 100644 --- a/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexMessageContainer.swift +++ b/LineSDK/LineSDKObjC/Messaging/Flex/LineSDKFlexMessageContainer.swift @@ -1,13 +1,13 @@ // // LineSDKFlexMessageContainer.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - extension FlexMessageContainer { var wrapped: LineSDKFlexMessageContainer { switch self { diff --git a/LineSDK/LineSDKObjC/Messaging/LineSDKAudioMessage.swift b/LineSDK/LineSDKObjC/Messaging/LineSDKAudioMessage.swift index 7ff903b0..cbc85786 100644 --- a/LineSDK/LineSDKObjC/Messaging/LineSDKAudioMessage.swift +++ b/LineSDK/LineSDKObjC/Messaging/LineSDKAudioMessage.swift @@ -1,13 +1,13 @@ // // LineSDKAudioMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKAudioMessage: LineSDKMessage { diff --git a/LineSDK/LineSDKObjC/Messaging/LineSDKFlexMessage.swift b/LineSDK/LineSDKObjC/Messaging/LineSDKFlexMessage.swift index c8c27c77..ccdb9b75 100644 --- a/LineSDK/LineSDKObjC/Messaging/LineSDKFlexMessage.swift +++ b/LineSDK/LineSDKObjC/Messaging/LineSDKFlexMessage.swift @@ -1,13 +1,13 @@ // // LineSDKFlexMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKFlexMessage: LineSDKMessage { diff --git a/LineSDK/LineSDKObjC/Messaging/LineSDKImageMessage.swift b/LineSDK/LineSDKObjC/Messaging/LineSDKImageMessage.swift index 413ab81a..3d66a56d 100644 --- a/LineSDK/LineSDKObjC/Messaging/LineSDKImageMessage.swift +++ b/LineSDK/LineSDKObjC/Messaging/LineSDKImageMessage.swift @@ -1,13 +1,13 @@ // // LineSDKImageMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKImageMessage: LineSDKMessage { public let originalContentURL: URL diff --git a/LineSDK/LineSDKObjC/Messaging/LineSDKLocationMessage.swift b/LineSDK/LineSDKObjC/Messaging/LineSDKLocationMessage.swift index d706f46e..d58496f5 100644 --- a/LineSDK/LineSDKObjC/Messaging/LineSDKLocationMessage.swift +++ b/LineSDK/LineSDKObjC/Messaging/LineSDKLocationMessage.swift @@ -1,13 +1,13 @@ // // LineSDKLocationMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKLocationMessage: LineSDKMessage { diff --git a/LineSDK/LineSDKObjC/Messaging/LineSDKMessage.swift b/LineSDK/LineSDKObjC/Messaging/LineSDKMessage.swift index a336d347..3bbeb532 100644 --- a/LineSDK/LineSDKObjC/Messaging/LineSDKMessage.swift +++ b/LineSDK/LineSDKObjC/Messaging/LineSDKMessage.swift @@ -1,13 +1,13 @@ // // LineSDKMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,13 +19,22 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKMessage: NSObject { + public static func message(with input: MessageConvertible) -> LineSDKMessage? { + switch input.message { + case .text(let m): return LineSDKTextMessage(m) + case .image(let m): return LineSDKImageMessage(m) + case .video(let m): return LineSDKVideoMessage(m) + case .audio(let m): return LineSDKAudioMessage(m) + case .location(let m): return LineSDKLocationMessage(m) + case .template(let m): return LineSDKTemplateMessage(m) + case .flex(let m): return LineSDKFlexMessage(m) + case .unknown: return nil + } + } + public var textMessage: LineSDKTextMessage? { return unwrapped.asTextMessage.map { .init($0) } } diff --git a/LineSDK/LineSDKObjC/Messaging/LineSDKMessagingResponse.swift b/LineSDK/LineSDKObjC/Messaging/LineSDKMessagingResponse.swift index 87781373..72a4fbc0 100644 --- a/LineSDK/LineSDKObjC/Messaging/LineSDKMessagingResponse.swift +++ b/LineSDK/LineSDKObjC/Messaging/LineSDKMessagingResponse.swift @@ -1,13 +1,13 @@ // // LineSDKMessagingResponse.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKMessageSendingStatus: NSObject { let _value: MessageSendingStatus diff --git a/LineSDK/LineSDKObjC/Messaging/LineSDKTemplateMessage.swift b/LineSDK/LineSDKObjC/Messaging/LineSDKTemplateMessage.swift index ab392c44..571d0d53 100644 --- a/LineSDK/LineSDKObjC/Messaging/LineSDKTemplateMessage.swift +++ b/LineSDK/LineSDKObjC/Messaging/LineSDKTemplateMessage.swift @@ -1,13 +1,13 @@ // // LineSDKTemplateMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKTemplateMessage: LineSDKMessage { diff --git a/LineSDK/LineSDKObjC/Messaging/LineSDKTextMessage.swift b/LineSDK/LineSDKObjC/Messaging/LineSDKTextMessage.swift index 13e436fe..92012e57 100644 --- a/LineSDK/LineSDKObjC/Messaging/LineSDKTextMessage.swift +++ b/LineSDK/LineSDKObjC/Messaging/LineSDKTextMessage.swift @@ -1,13 +1,13 @@ // // LineSDKTextMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKTextMessage: LineSDKMessage { diff --git a/LineSDK/LineSDKObjC/Messaging/LineSDKVideoMessage.swift b/LineSDK/LineSDKObjC/Messaging/LineSDKVideoMessage.swift index a7114fd3..6a62dd56 100644 --- a/LineSDK/LineSDKObjC/Messaging/LineSDKVideoMessage.swift +++ b/LineSDK/LineSDKObjC/Messaging/LineSDKVideoMessage.swift @@ -1,13 +1,13 @@ // // LineSDKVideoMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKVideoMessage: LineSDKMessage { diff --git a/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateButtonsPayload.swift b/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateButtonsPayload.swift index 21661b33..5dec7168 100644 --- a/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateButtonsPayload.swift +++ b/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateButtonsPayload.swift @@ -1,13 +1,13 @@ // // LineSDKTemplateButtonsPayload.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKTemplateButtonsPayload: LineSDKTemplateMessagePayload { diff --git a/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateCarouselPayload.swift b/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateCarouselPayload.swift index 03c32161..38ab8520 100644 --- a/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateCarouselPayload.swift +++ b/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateCarouselPayload.swift @@ -1,13 +1,13 @@ // // LineSDKTemplateCarouselPayload.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKTemplateCarouselPayloadColumn: NSObject { public var text: String diff --git a/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateConfirmPayload.swift b/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateConfirmPayload.swift index 2f103af9..079e8d76 100644 --- a/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateConfirmPayload.swift +++ b/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateConfirmPayload.swift @@ -1,13 +1,13 @@ // // LineSDKTemplateConfirmPayload.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKTemplateConfirmPayload: LineSDKTemplateMessagePayload { public var text: String diff --git a/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateImageCarouselPayload.swift b/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateImageCarouselPayload.swift index 6cb54a69..772a9519 100644 --- a/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateImageCarouselPayload.swift +++ b/LineSDK/LineSDKObjC/Messaging/Template/LineSDKTemplateImageCarouselPayload.swift @@ -1,13 +1,13 @@ // // LineSDKTemplateImageCarouselPayload.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKTemplateImageCarouselPayloadColumn: NSObject { public var imageURL: URL diff --git a/LineSDK/LineSDKObjC/Networking/CustomizeCoding/LineSDKHexColor.swift b/LineSDK/LineSDKObjC/Networking/CustomizeCoding/LineSDKHexColor.swift index fa04c41d..7de782a0 100644 --- a/LineSDK/LineSDKObjC/Networking/CustomizeCoding/LineSDKHexColor.swift +++ b/LineSDK/LineSDKObjC/Networking/CustomizeCoding/LineSDKHexColor.swift @@ -1,13 +1,13 @@ // // LineSDKHexColor.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKHexColor: NSObject { let _value: HexColor diff --git a/LineSDK/LineSDKObjC/Networking/LineSDKAPI.swift b/LineSDK/LineSDKObjC/Networking/LineSDKAPI.swift index c2c86291..501a2d66 100644 --- a/LineSDK/LineSDKObjC/Networking/LineSDKAPI.swift +++ b/LineSDK/LineSDKObjC/Networking/LineSDKAPI.swift @@ -1,13 +1,13 @@ // // LineSDKAPI.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,86 +19,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKAPI: NSObject { - // - MARK: refreshAccessToken - public static func refreshAccessToken( - completionHandler completion: @escaping (LineSDKAccessToken?, Error?) -> Void) - { - refreshAccessToken(nil, completionHandler: completion) - } - - public static func refreshAccessToken( - _ refreshToken: String?, - completionHandler completion: @escaping (LineSDKAccessToken?, Error?) -> Void) - { - refreshAccessToken(refreshToken, callbackQueue: .currentMainOrAsync, completionHandler: completion) - } - - public static func refreshAccessToken( - _ refreshToken: String?, - callbackQueue queue: LineSDKCallbackQueue, - completionHandler completion: @escaping (LineSDKAccessToken?, Error?) -> Void) - { - API.refreshAccessToken(refreshToken, callbackQueue: queue.unwrapped) { result in - completion(result.value.map { .init($0) }, result.error) - } - } - - // - MARK: revokeAccessToken - public static func revokeAccessToken( - completionHandler completion: @escaping (Error?) -> Void) - { - revokeAccessToken(nil, completionHandler: completion) - } - - public static func revokeAccessToken( - _ token: String?, - completionHandler completion: @escaping (Error?) -> Void) - { - revokeAccessToken(token, callbackQueue: .currentMainOrAsync, completionHandler: completion) - } - - public static func revokeAccessToken( - _ token: String?, - callbackQueue queue: LineSDKCallbackQueue, - completionHandler completion: @escaping (Error?) -> Void) - { - API.revokeAccessToken(token, callbackQueue: queue.unwrapped) { result in - completion(result.error) - } - } - - // - MARK: verifyAccessToken - public static func verifyAccessToken( - completionHandler completion: @escaping (LineSDKAccessTokenVerifyResult?, Error?) -> Void) - { - verifyAccessToken(nil, completionHandler: completion) - } - - public static func verifyAccessToken( - _ token: String?, - completionHandler completion: @escaping (LineSDKAccessTokenVerifyResult?, Error?) -> Void) - { - verifyAccessToken(token, callbackQueue: .currentMainOrAsync, completionHandler: completion) - } - - public static func verifyAccessToken( - _ token: String?, - callbackQueue queue: LineSDKCallbackQueue, - completionHandler completion: @escaping (LineSDKAccessTokenVerifyResult?, Error?) -> Void) - { - API.verifyAccessToken(token, callbackQueue: queue.unwrapped) { result in - completion(result.value.map { .init($0) }, result.error) - } - } - - // - MARK: getProfile + // MARK: - getProfile public static func getProfile( completionHandler completion: @escaping (LineSDKUserProfile?, Error?) -> Void) { @@ -110,11 +34,11 @@ public class LineSDKAPI: NSObject { completionHandler completion: @escaping (LineSDKUserProfile?, Error?) -> Void) { API.getProfile(callbackQueue: queue.unwrapped) { result in - completion(result.value.map { .init($0) }, result.error) + result.map(LineSDKUserProfile.init).match(with: completion) } } - // - MARK: getFriends + // MARK: - getFriends public static func getFriends( pageToken: String?, completionHandler completion: @escaping (LineSDKGetFriendsResponse?, Error?) -> Void) @@ -137,11 +61,11 @@ public class LineSDKAPI: NSObject { completionHandler completion: @escaping (LineSDKGetFriendsResponse?, Error?) -> Void) { API.getFriends(sort: sort.unwrapped, pageToken: pageToken, callbackQueue: queue.unwrapped) { result in - completion(result.value.map { .init($0) }, result.error) + result.map(LineSDKGetFriendsResponse.init).match(with: completion) } } - // - MARK: getApproversInFriends + // MARK: - getApproversInFriends public static func getApproversInFriends( pageToken: String?, completionHandler completion: @escaping (LineSDKGetApproversInFriendsResponse?, Error?) -> Void) @@ -155,11 +79,11 @@ public class LineSDKAPI: NSObject { completionHandler completion: @escaping (LineSDKGetApproversInFriendsResponse?, Error?) -> Void) { API.getApproversInFriends(pageToken: pageToken, callbackQueue: queue.unwrapped) { result in - completion(result.value.map { .init($0) }, result.error) + result.map(LineSDKGetApproversInFriendsResponse.init).match(with: completion) } } - // - MARK: getGroups + // MARK: - getGroups public static func getGroups( pageToken: String?, completionHandler completion: @escaping (LineSDKGetGroupsResponse?, Error?) -> Void) @@ -173,11 +97,11 @@ public class LineSDKAPI: NSObject { completionHandler completion: @escaping (LineSDKGetGroupsResponse?, Error?) -> Void) { API.getGroups(pageToken: pageToken, callbackQueue: queue.unwrapped) { result in - completion(result.value.map { .init($0) }, result.error) + result.map(LineSDKGetGroupsResponse.init).match(with: completion) } } - // - MARK: getApproversInGroups + // MARK: - getApproversInGroups public static func getApproversInGroup( groupID: String, pageToken: String?, @@ -194,11 +118,11 @@ public class LineSDKAPI: NSObject { completionHandler completion: @escaping (LineSDKGetApproversInGroupResponse?, Error?) -> Void) { API.getApproversInGroup(groupID: groupID, pageToken: pageToken, callbackQueue: queue.unwrapped) { result in - completion(result.value.map { .init($0) }, result.error) + result.map(LineSDKGetApproversInGroupResponse.init).match(with: completion) } } - // - MARK: sendMessages + // MARK: - sendMessages public static func sendMessages( _ messages: [LineSDKMessage], to chatID: String, @@ -214,11 +138,30 @@ public class LineSDKAPI: NSObject { completionHandler completion: @escaping (LineSDKPostSendMessagesResponse?, Error?) -> Void) { API.sendMessages(messages.map { $0.unwrapped }, to: chatID, callbackQueue: queue.unwrapped) { result in - completion(result.value.map { .init($0) }, result.error) + result.map(LineSDKPostSendMessagesResponse.init).match(with: completion) + } + } + + public static func multiSendMessages( + _ messages: [LineSDKMessage], + to userIDs: [String], + completionHandler completion: @escaping (LineSDKPostMultisendMessagesResponse?, Error?) -> Void) + { + multiSendMessages(messages, to: userIDs, callbackQueue: .currentMainOrAsync, completionHandler: completion) + } + + public static func multiSendMessages( + _ messages: [LineSDKMessage], + to userIDs: [String], + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (LineSDKPostMultisendMessagesResponse?, Error?) -> Void) + { + API.multiSendMessages(messages.map { $0.unwrapped }, to: userIDs, callbackQueue: queue.unwrapped) { result in + result.map(LineSDKPostMultisendMessagesResponse.init).match(with: completion) } } - // - MARK: Friendship + // MARK: - Friendship public static func getBotFriendshipStatus( completionHandler completion: @escaping (LineSDKGetBotFriendshipStatusResponse?, Error?) -> Void) { @@ -230,8 +173,256 @@ public class LineSDKAPI: NSObject { completionHandler completion: @escaping (LineSDKGetBotFriendshipStatusResponse?, Error?) -> Void) { API.getBotFriendshipStatus(callbackQueue: queue.unwrapped) { result in - completion(result.value.map { .init($0) }, result.error) + result.map(LineSDKGetBotFriendshipStatusResponse.init).match(with: completion) + } + } + + // MARK: - Sharing + public static func getMessageSendingOneTimeToken( + userIDs: [String], + completionHander completion: @escaping (LineSDKMessageSendingToken?, Error?) -> Void) + { + getMessageSendingOneTimeToken( + userIDs: userIDs, callbackQueue: .currentMainOrAsync, completionHander: completion) + } + + public static func getMessageSendingOneTimeToken( + userIDs: [String], + callbackQueue queue: LineSDKCallbackQueue, + completionHander completion: @escaping (LineSDKMessageSendingToken?, Error?) -> Void) + { + API.getMessageSendingOneTimeToken(userIDs: userIDs, callbackQueue: queue.unwrapped) { result in + result.map(LineSDKMessageSendingToken.init).match(with: completion) + } + } + + public static func multiSendMessages( + _ messages: [LineSDKMessage], + withMessageToken token: LineSDKMessageSendingToken, + completionHandler completion: @escaping (Error?) -> Void) + { + multiSendMessages( + messages, withMessageToken: token, callbackQueue: .currentMainOrAsync, completionHandler: completion) + } + + public static func multiSendMessages( + _ messages: [LineSDKMessage], + withMessageToken token: LineSDKMessageSendingToken, + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (Error?) -> Void) + { + API.multiSendMessages( + messages.map { $0.unwrapped }, + withMessageToken: token._value, + callbackQueue: queue.unwrapped) { result in result.matchFailure(with: completion) } + } + + // MARK: - Open Chat + public static func getOpenChatRoomStatus( + openChatId: String, + completionHandler completion: @escaping (LineSDKOpenChatRoomStatus?, Error?) -> Void + ) + { + getOpenChatRoomStatus( + openChatId: openChatId, callbackQueue: .currentMainOrAsync, completionHandler: completion + ) + } + + public static func getOpenChatRoomStatus( + openChatId: String, + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (LineSDKOpenChatRoomStatus?, Error?) -> Void + ) + { + API.getOpenChatRoomStatus(openChatId: openChatId, callbackQueue: queue.unwrapped) { result in + result.map(LineSDKOpenChatRoomStatus.init).match(with: completion) + } + } + + public static func getOpenChatRoomMembershipState( + openChatId: String, + completionHandler completion: @escaping (LineSDKOpenChatRoomMembershipState?, Error?) -> Void + ) + { + getOpenChatRoomMembershipState( + openChatId: openChatId, callbackQueue: .currentMainOrAsync, completionHandler: completion + ) + } + + public static func getOpenChatRoomMembershipState( + openChatId: String, + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (LineSDKOpenChatRoomMembershipState?, Error?) -> Void + ) + { + API.getOpenChatRoomMembershipState(openChatId: openChatId, callbackQueue: queue.unwrapped) { result in + result.map(LineSDKOpenChatRoomMembershipState.init).match(with: completion) + } + } + + public static func getOpenChatRoomJoinType( + openChatId: String, + completionHandler completion: @escaping (LineSDKOpenChatRoomJoinType?, Error?) -> Void + ) + { + getOpenChatRoomJoinType(openChatId: openChatId, callbackQueue: .currentMainOrAsync, completionHandler: completion) + } + + public static func getOpenChatRoomJoinType( + openChatId: String, + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (LineSDKOpenChatRoomJoinType?, Error?) -> Void + ) + { + API.getOpenChatRoomJoinType(openChatId: openChatId, callbackQueue: queue.unwrapped) { result in + result.map(LineSDKOpenChatRoomJoinType.init).match(with: completion) + } + } + + public static func postOpenChatRoomJoin( + openChatId: String, + displayName: String, + completionHandler completion: @escaping (Error?) -> Void + ) + { + postOpenChatRoomJoin( + openChatId: openChatId, + displayName: displayName, + callbackQueue: .currentMainOrAsync, + completionHandler: completion + ) + } + + public static func postOpenChatRoomJoin( + openChatId: String, + displayName: String, + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (Error?) -> Void + ) + { + API.postOpenChatRoomJoin(openChatId: openChatId, displayName: displayName, callbackQueue: queue.unwrapped) { + result in + switch result { + case .success: completion(nil) + case .failure(let error): completion(error) + } } } } +// MARK: - getGroups +extension LineSDKAPI { + // MARK: - refreshAccessToken + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods in `LineSDKAuthAPI` instead. + """, + renamed: "LineSDKAuthAPI.refreshAccessToken" + ) + public static func refreshAccessToken( + completionHandler completion: @escaping (LineSDKAccessToken?, Error?) -> Void) + { + refreshAccessToken(callbackQueue: .currentMainOrAsync, completionHandler: completion) + } + + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods in `LineSDKAuthAPI` instead. + """, + renamed: "LineSDKAuthAPI.refreshAccessToken" + ) + public static func refreshAccessToken( + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (LineSDKAccessToken?, Error?) -> Void) + { + LineSDKAuthAPI.refreshAccessToken(callbackQueue: queue, completionHandler: completion) + } + + // MARK: - revokeAccessToken + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods in `LineSDKAuthAPI` instead. + """, + renamed: "LineSDKAuthAPI.revokeAccessToken" + ) + public static func revokeAccessToken( + completionHandler completion: @escaping (Error?) -> Void) + { + revokeAccessToken(nil, completionHandler: completion) + } + + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods in `LineSDKAuthAPI` instead. + """, + renamed: "LineSDKAuthAPI.revokeAccessToken" + ) + public static func revokeAccessToken( + _ token: String?, + completionHandler completion: @escaping (Error?) -> Void) + { + revokeAccessToken(token, callbackQueue: .currentMainOrAsync, completionHandler: completion) + } + + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods in `LineSDKAuthAPI` instead. + """, + renamed: "LineSDKAuthAPI.revokeAccessToken" + ) + public static func revokeAccessToken( + _ token: String?, + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (Error?) -> Void) + { + LineSDKAuthAPI.revokeAccessToken(token, callbackQueue: queue, completionHandler: completion) + } + + // MARK: - verifyAccessToken + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods in `LineSDKAuthAPI` instead. + """, + renamed: "LineSDKAuthAPI.verifyAccessToken" + ) + public static func verifyAccessToken( + completionHandler completion: @escaping (LineSDKAccessTokenVerifyResult?, Error?) -> Void) + { + verifyAccessToken(nil, completionHandler: completion) + } + + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods in `LineSDKAuthAPI` instead. + """, + renamed: "LineSDKAuthAPI.verifyAccessToken" + ) + public static func verifyAccessToken( + _ token: String?, + completionHandler completion: @escaping (LineSDKAccessTokenVerifyResult?, Error?) -> Void) + { + verifyAccessToken(token, callbackQueue: .currentMainOrAsync, completionHandler: completion) + } + + @available(*, deprecated, + message: """ + Auth-related APIs don't refresh access tokens automatically. + Make sure you don't need token refreshing as a side effect, then use methods in `LineSDKAuthAPI` instead. + """, + renamed: "LineSDKAuthAPI.verifyAccessToken" + ) + public static func verifyAccessToken( + _ token: String?, + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (LineSDKAccessTokenVerifyResult?, Error?) -> Void) + { + LineSDKAuthAPI.verifyAccessToken(token, callbackQueue: queue, completionHandler: completion) + } +} diff --git a/LineSDK/LineSDKObjC/Networking/LineSDKAPIError.swift b/LineSDK/LineSDKObjC/Networking/LineSDKAPIError.swift index c6280aa5..33242d20 100644 --- a/LineSDK/LineSDKObjC/Networking/LineSDKAPIError.swift +++ b/LineSDK/LineSDKObjC/Networking/LineSDKAPIError.swift @@ -1,13 +1,13 @@ // // LineSDKAPIError.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKAPIError: NSObject { let _value: APIError diff --git a/LineSDK/LineSDKObjC/Networking/LineSDKAuthAPI.swift b/LineSDK/LineSDKObjC/Networking/LineSDKAuthAPI.swift new file mode 100644 index 00000000..cfc976ec --- /dev/null +++ b/LineSDK/LineSDKObjC/Networking/LineSDKAuthAPI.swift @@ -0,0 +1,119 @@ +// +// LineSDKAuthAPI.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public class LineSDKAuthAPI: NSObject { + // MARK: - refreshAccessToken + public static func refreshAccessToken( + completionHandler completion: @escaping (LineSDKAccessToken?, Error?) -> Void + ) + { + refreshAccessToken(callbackQueue: .currentMainOrAsync, completionHandler: completion) + } + + public static func refreshAccessToken( + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (LineSDKAccessToken?, Error?) -> Void + ) + { + API.Auth.refreshAccessToken(callbackQueue: queue.unwrapped) { result in + result.map(LineSDKAccessToken.init).match(with: completion) + } + } + + // MARK: - revokeAccessToken + public static func revokeAccessToken( + completionHandler completion: @escaping (Error?) -> Void + ) + { + revokeAccessToken(nil, completionHandler: completion) + } + + public static func revokeAccessToken( + _ token: String?, + completionHandler completion: @escaping (Error?) -> Void + ) + { + revokeAccessToken(token, callbackQueue: .currentMainOrAsync, completionHandler: completion) + } + + public static func revokeAccessToken( + _ token: String?, + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (Error?) -> Void + ) + { + API.Auth.revokeAccessToken(token, callbackQueue: queue.unwrapped) { result in + result.matchFailure(with: completion) + } + } + + // MARK: - revokeRefreshToken + public static func revokeRefreshToken( + completionHandler completion: @escaping (Error?) -> Void + ) + { + revokeRefreshToken(nil, completionHandler: completion) + } + + public static func revokeRefreshToken( + _ token: String?, + completionHandler completion: @escaping (Error?) -> Void + ) + { + revokeRefreshToken(token, callbackQueue: .currentMainOrAsync, completionHandler: completion) + } + + public static func revokeRefreshToken( + _ token: String?, + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (Error?) -> Void + ) + { + API.Auth.revokeRefreshToken(token, callbackQueue: queue.unwrapped) { result in + result.matchFailure(with: completion) + } + } + + // MARK: - verifyAccessToken + public static func verifyAccessToken( + completionHandler completion: @escaping (LineSDKAccessTokenVerifyResult?, Error?) -> Void) + { + verifyAccessToken(nil, completionHandler: completion) + } + + public static func verifyAccessToken( + _ token: String?, + completionHandler completion: @escaping (LineSDKAccessTokenVerifyResult?, Error?) -> Void) + { + verifyAccessToken(token, callbackQueue: .currentMainOrAsync, completionHandler: completion) + } + + public static func verifyAccessToken( + _ token: String?, + callbackQueue queue: LineSDKCallbackQueue, + completionHandler completion: @escaping (LineSDKAccessTokenVerifyResult?, Error?) -> Void) + { + API.Auth.verifyAccessToken(token, callbackQueue: queue.unwrapped) { result in + result.map(LineSDKAccessTokenVerifyResult.init).match(with: completion) + } + } +} diff --git a/LineSDK/LineSDKObjC/OpenChat/LineSDKOpenChatRoomJoinType.swift b/LineSDK/LineSDKObjC/OpenChat/LineSDKOpenChatRoomJoinType.swift new file mode 100644 index 00000000..47ae402e --- /dev/null +++ b/LineSDK/LineSDKObjC/OpenChat/LineSDKOpenChatRoomJoinType.swift @@ -0,0 +1,28 @@ +// +// LineSDKOpenChatRoomJoinType.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public class LineSDKOpenChatRoomJoinType: NSObject { + let _value: GetOpenChatRoomJoinTypeRequest.Response + init(_ value: GetOpenChatRoomJoinTypeRequest.Response) { _value = value } + + public var type: String { return _value.type.rawValue } +} diff --git a/LineSDK/LineSDKObjC/OpenChat/LineSDKOpenChatRoomMembershipState.swift b/LineSDK/LineSDKObjC/OpenChat/LineSDKOpenChatRoomMembershipState.swift new file mode 100644 index 00000000..e39db078 --- /dev/null +++ b/LineSDK/LineSDKObjC/OpenChat/LineSDKOpenChatRoomMembershipState.swift @@ -0,0 +1,28 @@ +// +// LineSDKOpenChatRoomMembershipState.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public class LineSDKOpenChatRoomMembershipState: NSObject { + let _value: GetOpenChatRoomMembershipStateRequest.Response + init(_ value: GetOpenChatRoomMembershipStateRequest.Response) { _value = value } + + public var state: String { return _value.state.rawValue } +} diff --git a/LineSDK/LineSDKObjC/OpenChat/LineSDKOpenChatRoomStatus.swift b/LineSDK/LineSDKObjC/OpenChat/LineSDKOpenChatRoomStatus.swift new file mode 100644 index 00000000..3038c34a --- /dev/null +++ b/LineSDK/LineSDKObjC/OpenChat/LineSDKOpenChatRoomStatus.swift @@ -0,0 +1,29 @@ +// +// LineSDKOpenChatRoomStatus.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public class LineSDKOpenChatRoomStatus: NSObject { + let _value: GetOpenChatRoomStatusRequest.Response + init(_ value: GetOpenChatRoomStatusRequest.Response) { _value = value } + public var status: String { return _value.status.rawValue } + + public var json: String? { return toJSON(_value) } +} diff --git a/LineSDK/LineSDKObjC/Sharing/LineSDKMessageSendingToken.swift b/LineSDK/LineSDKObjC/Sharing/LineSDKMessageSendingToken.swift new file mode 100644 index 00000000..fffff723 --- /dev/null +++ b/LineSDK/LineSDKObjC/Sharing/LineSDKMessageSendingToken.swift @@ -0,0 +1,29 @@ +// +// LineSDKMessageSendingToken.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +@objcMembers +public class LineSDKMessageSendingToken: NSObject { + let _value: MessageSendingToken + init(_ value: MessageSendingToken) { _value = value } + public var token: String { return _value.token } + + public var json: String? { return toJSON(_value) } +} diff --git a/LineSDK/LineSDKObjC/Social/LineSDKGraphResponse.swift b/LineSDK/LineSDKObjC/Social/LineSDKGraphResponse.swift index 2b3cd275..ef7db5ab 100644 --- a/LineSDK/LineSDKObjC/Social/LineSDKGraphResponse.swift +++ b/LineSDK/LineSDKObjC/Social/LineSDKGraphResponse.swift @@ -1,13 +1,13 @@ // // LineSDKGetFriendsResponse.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKGetFriendsResponse: NSObject { let _value: GetFriendsRequest.Response @@ -63,17 +59,20 @@ public class LineSDKGetApproversInGroupResponse: NSObject { public enum LineSDKGetFriendsRequestSort: Int { case none case name + case relation var unwrapped: GetFriendsRequest.Sort? { switch self { case .none: return nil case .name: return .name + case .relation: return .relation } } init(_ value: GetFriendsRequest.Sort?) { switch value { case .name?: self = .name + case .relation?: self = .relation case nil: self = .none } } diff --git a/LineSDK/LineSDKObjC/Social/LineSDKGroup.swift b/LineSDK/LineSDKObjC/Social/LineSDKGroup.swift index eb196bac..3a4fa019 100644 --- a/LineSDK/LineSDKObjC/Social/LineSDKGroup.swift +++ b/LineSDK/LineSDKObjC/Social/LineSDKGroup.swift @@ -1,13 +1,13 @@ // // LineSDKGroup.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKGroup: NSObject { let _value: Group @@ -31,4 +27,5 @@ public class LineSDKGroup: NSObject { public var groupID: String { return _value.groupID } public var groupName: String { return _value.groupName } public var pictureURL: URL? { return _value.pictureURL } + public var pictureURLSmall: URL? { return _value.pictureURLSmall } } diff --git a/LineSDK/LineSDKObjC/Social/LineSDKUser.swift b/LineSDK/LineSDKObjC/Social/LineSDKUser.swift index 4b0e33fb..f0091738 100644 --- a/LineSDK/LineSDKObjC/Social/LineSDKUser.swift +++ b/LineSDK/LineSDKObjC/Social/LineSDKUser.swift @@ -1,13 +1,13 @@ // // LineSDKUser.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKUser: NSObject { let _value: User @@ -30,5 +26,8 @@ public class LineSDKUser: NSObject { public var userID: String { return _value.userID } public var displayName: String { return _value.displayName } + public var displayNameOriginal: String { return _value.displayNameOriginal } + public var displayNameOverridden: String? { return _value.displayNameOverridden } public var pictureURL: URL? { return _value.pictureURL } + public var pictureURLSmall: URL? { return _value.pictureURLSmall } } diff --git a/LineSDKSample/LineSDKSampleUITests/Script/LineSDKScript.swift b/LineSDK/LineSDKObjC/Utils/JSONConverter.swift similarity index 63% rename from LineSDKSample/LineSDKSampleUITests/Script/LineSDKScript.swift rename to LineSDK/LineSDKObjC/Utils/JSONConverter.swift index cd166bba..9ba12932 100644 --- a/LineSDKSample/LineSDKSampleUITests/Script/LineSDKScript.swift +++ b/LineSDK/LineSDKObjC/Utils/JSONConverter.swift @@ -1,13 +1,13 @@ // -// LineSDKScript.swift +// JSONConverter.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,13 +19,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import XCTest +import Foundation -class LineSDKScript { - - static func logout(app: XCUIApplication, loginPage: LoginPage) { - loginPage.tapLogoutButton() - app.alerts.buttons["Logout"].tap() - app.alerts.buttons["OK"].tap() +private let encoder = JSONEncoder() + +func toJSON(_ value: T) -> String? { + guard let data = try? encoder.encode(value) else { + return nil } + return String(data: data, encoding: .utf8) } diff --git a/LineSDK/LineSDKObjC/Utils/LineSDKConstant.swift b/LineSDK/LineSDKObjC/Utils/LineSDKConstant.swift index 29fe4aee..9503f06d 100644 --- a/LineSDK/LineSDKObjC/Utils/LineSDKConstant.swift +++ b/LineSDK/LineSDKObjC/Utils/LineSDKConstant.swift @@ -1,13 +1,13 @@ // // LineSDKConstant.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,9 +19,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import Foundation -#if !LineSDKCocoaPods -import LineSDK +@_exported import UIKit + +#if !LineSDKCocoaPods && !LineSDKBinary +@_exported import LineSDK #endif @objcMembers diff --git a/LineSDK/LineSDKObjC/Utils/LinsSDKCallbackQueue.swift b/LineSDK/LineSDKObjC/Utils/LinsSDKCallbackQueue.swift index eb422cf4..906714e5 100644 --- a/LineSDK/LineSDKObjC/Utils/LinsSDKCallbackQueue.swift +++ b/LineSDK/LineSDKObjC/Utils/LinsSDKCallbackQueue.swift @@ -1,13 +1,13 @@ // // LinsSDKCallbackQueue.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,10 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if !LineSDKCocoaPods -import LineSDK -#endif - @objcMembers public class LineSDKCallbackQueue: NSObject { let _value: CallbackQueue diff --git a/LineSDK/LineSDKObjC/Utils/Log.swift b/LineSDK/LineSDKObjC/Utils/Log.swift index e2c87cf8..fabdea1d 100644 --- a/LineSDK/LineSDKObjC/Utils/Log.swift +++ b/LineSDK/LineSDKObjC/Utils/Log.swift @@ -1,13 +1,13 @@ // // Log.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,9 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import Foundation - -#if !LineSDKCocoaPods +#if !LineSDKCocoaPods && !LineSDKBinary struct Log { static func assertionFailure(_ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) { Swift.assertionFailure("[LineSDK] \(message())", file: file, line: line) diff --git a/LineSDK/LineSDKObjC/Utils/ResultExtensions.swift b/LineSDK/LineSDKObjC/Utils/ResultExtensions.swift new file mode 100644 index 00000000..2f214dd0 --- /dev/null +++ b/LineSDK/LineSDKObjC/Utils/ResultExtensions.swift @@ -0,0 +1,56 @@ +// +// ResultExtensions.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#if !LineSDKCocoaPods && !LineSDKBinary + +import LineSDK +// These helper methods are not public since we do not want them to be exposed or cause any conflicts. +// However, they are just wrapper of `ResultUtil` static methods. +// +// When compiling with CocoaPods, the extensions under LineSDK target should be used. +extension Result { + + /// Evaluates the given transform closures to create a single output value. + /// + /// - Parameters: + /// - onSuccess: A closure that transforms the success value. + /// - onFailure: A closure that transforms the error value. + /// - Returns: A single `Output` value. + func match( + onSuccess: (Success) -> Output, + onFailure: (Failure) -> Output) -> Output + { + return ResultUtil.match(result: self, onSuccess: onSuccess, onFailure: onFailure) + } + + func matchSuccess(with folder: (Success?) -> Output) -> Output { + return ResultUtil.matchSuccess(result: self, with: folder) + } + + func matchFailure(with folder: (Error?) -> Output) -> Output { + return ResultUtil.matchFailure(result: self, with: folder) + } + + func match(with folder: (Success?, Error?) -> Output) -> Output { + return ResultUtil.match(result: self, with: folder) + } +} +#endif diff --git a/LineSDK/LineSDKObjCInterfaceTests/Info.plist b/LineSDK/LineSDKObjCInterfaceTests/Info.plist index 4e490980..9ff466d8 100644 --- a/LineSDK/LineSDKObjCInterfaceTests/Info.plist +++ b/LineSDK/LineSDKObjCInterfaceTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.0.0 + 5.11.2 CFBundleVersion - 402 + 1208 diff --git a/LineSDK/LineSDKObjCInterfaceTests/LineSDKAPIInterfaceTests.m b/LineSDK/LineSDKObjCInterfaceTests/LineSDKAPIInterfaceTests.m index ac7a7a0e..07601550 100644 --- a/LineSDK/LineSDKObjCInterfaceTests/LineSDKAPIInterfaceTests.m +++ b/LineSDK/LineSDKObjCInterfaceTests/LineSDKAPIInterfaceTests.m @@ -1,13 +1,13 @@ // // LineSDKAPIInterfaceTests.m // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -20,6 +20,7 @@ // // Tests in this file should only compile but not run, since it will send an actual request. +// This test case just ensures we can compile and use these interfaces in ObjC. #import @import LineSDKObjC; @@ -31,28 +32,34 @@ @interface LineSDKAPIInterfaceTests : XCTestCase @implementation LineSDKAPIInterfaceTests - (void)_testRefreshAccessTokenInterface { - [LineSDKAPI refreshAccessTokenWithCompletionHandler:^(LineSDKAccessToken * token, NSError * error) {}]; - [LineSDKAPI refreshAccessToken:nil - completionHandler:^(LineSDKAccessToken * token, NSError * error) {}]; - [LineSDKAPI refreshAccessToken:nil - callbackQueue:[LineSDKCallbackQueue asyncMain] - completionHandler:^(LineSDKAccessToken * token, NSError * error) {}]; + [LineSDKAuthAPI refreshAccessTokenWithCompletionHandler:^(LineSDKAccessToken * token, NSError * error) {}]; + [LineSDKAuthAPI refreshAccessTokenWithCallbackQueue:[LineSDKCallbackQueue asyncMain] + completionHandler:^(LineSDKAccessToken * token, NSError * error) {}]; } - (void)_testRevokeAccessTokenInterface { - [LineSDKAPI revokeAccessTokenWithCompletionHandler:^(NSError * error) {}]; - [LineSDKAPI revokeAccessToken:nil + [LineSDKAuthAPI revokeAccessTokenWithCompletionHandler:^(NSError * error) {}]; + [LineSDKAuthAPI revokeAccessToken:nil completionHandler:^(NSError * error) {}]; - [LineSDKAPI revokeAccessToken:nil + [LineSDKAuthAPI revokeAccessToken:nil + callbackQueue:[LineSDKCallbackQueue asyncMain] + completionHandler:^(NSError * error) {}]; +} + +- (void)_testRevokeRefreshTokenInterface { + [LineSDKAuthAPI revokeRefreshTokenWithCompletionHandler:^(NSError * error) {}]; + [LineSDKAuthAPI revokeRefreshToken:nil + completionHandler:^(NSError * error) {}]; + [LineSDKAuthAPI revokeRefreshToken:nil callbackQueue:[LineSDKCallbackQueue asyncMain] completionHandler:^(NSError * error) {}]; } - (void)_testVerifyAccessTokenInterface { - [LineSDKAPI verifyAccessTokenWithCompletionHandler:^(LineSDKAccessTokenVerifyResult *result, NSError * error) {}]; - [LineSDKAPI verifyAccessToken:nil + [LineSDKAuthAPI verifyAccessTokenWithCompletionHandler:^(LineSDKAccessTokenVerifyResult *result, NSError * error) {}]; + [LineSDKAuthAPI verifyAccessToken:nil completionHandler:^(LineSDKAccessTokenVerifyResult *result, NSError * error) {}]; - [LineSDKAPI verifyAccessToken:nil + [LineSDKAuthAPI verifyAccessToken:nil callbackQueue:[LineSDKCallbackQueue asyncMain] completionHandler:^(LineSDKAccessTokenVerifyResult *result, NSError * error) {}]; } @@ -63,7 +70,7 @@ - (void)_testGetProfileInterface { completionHandler:^(LineSDKUserProfile *result, NSError * error) {}]; } -- (void)_testGetFriendsInterfale { +- (void)_testGetFriendsInterface { [LineSDKAPI getFriendsWithPageToken:nil completionHandler:^(LineSDKGetFriendsResponse *result, NSError *error) {}]; [LineSDKAPI getFriendsWithSort:LineSDKGetFriendsRequestSortName @@ -112,6 +119,20 @@ - (void)_testSendMessagesInterface { completionHandler:^(LineSDKPostSendMessagesResponse *response, NSError *error) {}]; } +- (void)_testMultiSendMessagesInterface { + LineSDKTextMessage *message = [[LineSDKTextMessage alloc] initWithText:@"hello" sender:nil]; + NSArray *userIDs = @[@"123", @"456"]; + + [LineSDKAPI multiSendMessages:@[message] + to:userIDs + completionHandler:^(LineSDKPostMultisendMessagesResponse *response, NSError *error) {}]; + [LineSDKAPI multiSendMessages:@[message] + to:userIDs + callbackQueue:[LineSDKCallbackQueue asyncMain] + completionHandler:^(LineSDKPostMultisendMessagesResponse *response, NSError *error) {}]; + +} + - (void)_testGetBotFriendshipInterface { [LineSDKAPI getBotFriendshipStatusWithCompletionHandler:^(LineSDKGetBotFriendshipStatusResponse *response, NSError *error) {}]; @@ -120,4 +141,83 @@ - (void)_testGetBotFriendshipInterface { completionHandler:^(LineSDKGetBotFriendshipStatusResponse *response, NSError *error) {}]; } +- (void)_testGetMessageSendingOneTimeTokenInterface { + [LineSDKAPI getMessageSendingOneTimeTokenWithUserIDs:@[@"123", @"456"] + completionHander:^(LineSDKMessageSendingToken *token, NSError *error) {}]; + [LineSDKAPI getMessageSendingOneTimeTokenWithUserIDs:@[@"123", @"456"] + callbackQueue:[LineSDKCallbackQueue asyncMain] + completionHander:^(LineSDKMessageSendingToken *token, NSError *error) {}]; +} + +- (void)_testMultiSendMessageWithMessageTokenInterface { + LineSDKTextMessage *message = [[LineSDKTextMessage alloc] initWithText:@"hello" sender:nil]; + LineSDKMessageSendingToken *token = nil; + + [LineSDKAPI multiSendMessages:@[message] + withMessageToken:token + completionHandler:^(NSError *error) {}]; + [LineSDKAPI multiSendMessages:@[message] + withMessageToken:token + callbackQueue:[LineSDKCallbackQueue asyncMain] + completionHandler:^(NSError *error) {}]; +} + +- (void)_testGetOpenChatRoomStatusInterface { + [LineSDKAPI + getOpenChatRoomStatusWithOpenChatId:@"" + completionHandler:^(LineSDKOpenChatRoomStatus *result, NSError *error) { + NSLog(@"%@", result.status); + }]; + + [LineSDKAPI + getOpenChatRoomStatusWithOpenChatId:@"" + callbackQueue:[LineSDKCallbackQueue asyncMain] + completionHandler:^(LineSDKOpenChatRoomStatus *result, NSError *error) { + NSLog(@"%@", result.status); + }]; +} + +- (void)_testGetOpenChatRoomMembershipState { + [LineSDKAPI + getOpenChatRoomMembershipStateWithOpenChatId:@"" + completionHandler:^(LineSDKOpenChatRoomMembershipState *result, NSError *error) { + NSLog(@"%@", result.state); + }]; + + [LineSDKAPI + getOpenChatRoomMembershipStateWithOpenChatId:@"" + callbackQueue:[LineSDKCallbackQueue asyncMain] + completionHandler:^(LineSDKOpenChatRoomMembershipState *result, NSError *error) { + NSLog(@"%@", result.state); + }]; +} + +- (void)_testGetOpenChatRoomJoinType { + [LineSDKAPI + getOpenChatRoomJoinTypeWithOpenChatId:@"" + completionHandler:^(LineSDKOpenChatRoomJoinType *result, NSError *error) { + NSLog(@"%@", result.type); + }]; + [LineSDKAPI + getOpenChatRoomJoinTypeWithOpenChatId:@"" + callbackQueue:[LineSDKCallbackQueue asyncMain] + completionHandler:^(LineSDKOpenChatRoomJoinType *result, NSError *error) { + NSLog(@"%@", result.type); + }]; +} + +- (void)_testPostOpenChatRoomJoin { + [LineSDKAPI + postOpenChatRoomJoinWithOpenChatId:@"" + displayName:@"" + completionHandler:^(NSError *error) {} + ]; + [LineSDKAPI + postOpenChatRoomJoinWithOpenChatId:@"" + displayName:@"" + callbackQueue:[LineSDKCallbackQueue asyncMain] + completionHandler:^(NSError *error) {} + ]; +} + @end diff --git a/LineSDK/LineSDKObjCInterfaceTests/LineSDKMessagingModelTests.m b/LineSDK/LineSDKObjCInterfaceTests/LineSDKMessagingModelTests.m index 367529cc..90e2bcec 100644 --- a/LineSDK/LineSDKObjCInterfaceTests/LineSDKMessagingModelTests.m +++ b/LineSDK/LineSDKObjCInterfaceTests/LineSDKMessagingModelTests.m @@ -1,13 +1,13 @@ // // LineSDKMessagingModelTests.m // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -132,8 +132,8 @@ - (void)testMessageActionInterface { initWithLabel:@"action" uri:[NSURL URLWithString:@"https://example.com"]]; XCTAssertEqual(action.label, @"action"); - XCTAssertEqual(action.uri.absoluteString, @"https://example.com"); - + XCTAssertEqualObjects(action.uri.absoluteString, @"https://example.com"); + XCTAssertNotEqual([action URIAction], action); XCTAssertEqual([action URIAction].label, action.label); @@ -252,7 +252,7 @@ - (void)testTemplateImageCarouselPayloadColumnInterface { LineSDKTemplateImageCarouselPayloadColumn *column = [[LineSDKTemplateImageCarouselPayloadColumn alloc] initWithImageURL:[NSURL URLWithString:@"https://image.com"] action:action]; - XCTAssertEqual([column imageURL].absoluteString, @"https://image.com"); + XCTAssertEqualObjects([column imageURL].absoluteString, @"https://image.com"); XCTAssertNotNil(column.action); } @@ -429,8 +429,8 @@ - (void)testFlexImageComponentInterface { component.aspectRatio = LineSDKFlexMessageComponentAspectRatioRatio_1x3; component.aspectMode = LineSDKFlexMessageComponentAspectModeFill; component.backgroundColor = nil; - XCTAssertEqual(component.url.absoluteString, @"https://example.com"); - XCTAssertEqual([component imageComponent].url.absoluteString, @"https://example.com"); + XCTAssertEqualObjects(component.url.absoluteString, @"https://example.com"); + XCTAssertEqualObjects([component imageComponent].url.absoluteString, @"https://example.com"); } - (void)testFlexFillerComponentInterface { diff --git a/LineSDK/LineSDKObjCInterfaceTests/LineSDKModelInterfaceTests.m b/LineSDK/LineSDKObjCInterfaceTests/LineSDKModelInterfaceTests.m index 35959621..4e3458b7 100644 --- a/LineSDK/LineSDKObjCInterfaceTests/LineSDKModelInterfaceTests.m +++ b/LineSDK/LineSDKObjCInterfaceTests/LineSDKModelInterfaceTests.m @@ -1,13 +1,13 @@ // // LineSDKModelInterfaceTests.m // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -46,6 +46,12 @@ - (void)testLoginPermissionInterface { XCTAssertNotNil([LineSDKLoginPermission birthdate]); XCTAssertNotNil([LineSDKLoginPermission address]); XCTAssertNotNil([LineSDKLoginPermission realName]); + + XCTAssertNotNil([LineSDKLoginPermission openChatTermStatus]); + XCTAssertNotNil([LineSDKLoginPermission openChatRoomCreateAndJoin]); + XCTAssertNotNil([LineSDKLoginPermission openChatInfo]); + + XCTAssertEqual([LineSDKLoginPermission permissionsFrom:@"profile email abcd"].count, 3); } - (void)testAccessTokenInterface { @@ -53,9 +59,10 @@ - (void)testAccessTokenInterface { XCTAssertNil(token.value); XCTAssertNil(token.createdAt); XCTAssertNil(token.IDToken); - XCTAssertNil(token.refreshToken); + XCTAssertNil(token.IDTokenRaw); XCTAssertNil(token.permissions); XCTAssertNil(token.expiresAt); + XCTAssertNil(token.json); } - (void)testAccessTokenStoreInterface { @@ -88,12 +95,22 @@ - (void)testAccessTokenVerifyResultInterface { XCTAssertEqual(result.expiresIn, 0); } -- (void)testLoginManagerOptionInterface { - XCTAssertNotNil(LineSDKLoginManagerOptions.onlyWebLogin); - XCTAssertNotNil(LineSDKLoginManagerOptions.botPromptNormal); - XCTAssertNotNil(LineSDKLoginManagerOptions.botPromptAggressive); +- (void)testLoginManagerParametersInterface { + LineSDKLoginManagerParameters *param = [[LineSDKLoginManagerParameters alloc] init]; + XCTAssertNotNil(param); - XCTAssertNotNil([[LineSDKLoginManagerOptions alloc] initWithRawValue:1]); + param.onlyWebLogin = YES; + param.botPromptStyle = [LineSDKLoginManagerBotPrompt normal]; + param.preferredWebPageLanguage = @"ja"; + param.IDTokenNonce = @"test"; + param.promptBotID = @"@abc123"; + param.initialWebAuthenticationMethod = [LineSDKLoginManagerWebAuthenticationMethod qrCode]; + + XCTAssertTrue([param onlyWebLogin]); + XCTAssertTrue([[param.botPromptStyle rawValue] isEqualToString: @"normal"]); + XCTAssertTrue([param.preferredWebPageLanguage isEqualToString: @"ja"]); + XCTAssertTrue([param.IDTokenNonce isEqualToString: @"test"]); + XCTAssertTrue([[param.initialWebAuthenticationMethod rawValue] isEqualToString:@"qrCode"]); } - (void)testLoginResultInterface { @@ -101,10 +118,12 @@ - (void)testLoginResultInterface { XCTAssertNil(result.accessToken); XCTAssertNil(result.permissions); XCTAssertNil(result.userProfile); + XCTAssertNil(result.IDTokenNonce); } - (void)testLoginProcessInterface { LineSDKLoginProcess *process = nil; + XCTAssertNil(process.loginRoute); [process stop]; } @@ -115,10 +134,23 @@ - (void)testLoginManagerInterface { XCTAssertFalse(manager.isSetupFinished); XCTAssertFalse(manager.isAuthorized); XCTAssertFalse(manager.isAuthorizing); + [manager setupWithChannelID:@"" universalLinkURL:nil]; + + [manager loginWithPermissions:nil + inViewController:nil + completionHandler:^(LineSDKLoginResult *result, NSError *error) + { + XCTAssertNil([result accessToken]); + XCTAssertNil([result permissions]); + XCTAssertNil([result userProfile]); + XCTAssertNil([result friendshipStatusChanged]); + }]; + + LineSDKLoginManagerParameters *parameters = [[LineSDKLoginManagerParameters alloc] init]; [manager loginWithPermissions:nil inViewController:nil - options:nil + parameters:parameters completionHandler:^(LineSDKLoginResult *result, NSError *error) { XCTAssertNil([result accessToken]); @@ -169,7 +201,10 @@ - (void)testUserInterface { LineSDKUser *user = nil; XCTAssertNil(user.userID); XCTAssertNil(user.displayName); + XCTAssertNil(user.displayNameOriginal); + XCTAssertNil(user.displayNameOverridden); XCTAssertNil(user.pictureURL); + XCTAssertNil(user.pictureURLSmall); } - (void)testGroupInterface { @@ -177,6 +212,7 @@ - (void)testGroupInterface { XCTAssertNil(group.groupID); XCTAssertNil(group.groupName); XCTAssertNil(group.pictureURL); + XCTAssertNil(group.pictureURLSmall); } - (void)testGetFriendsResponseInterface { @@ -250,6 +286,7 @@ - (void)testJWTInterface { XCTAssertNil(payload.name); XCTAssertNil(payload.picture); XCTAssertNil(payload.email); + XCTAssertNil(payload.amr); } - (void)testLoginButtonInterface { @@ -258,7 +295,7 @@ - (void)testLoginButtonInterface { button.buttonPresentingViewController = nil; button.loginDelegate = nil; button.loginPermissions = [NSSet setWithObject:[LineSDKLoginPermission profile]]; - button.loginManagerOptions = @[[LineSDKLoginManagerOptions onlyWebLogin]]; + button.loginManagerParameters = [[LineSDKLoginManagerParameters alloc] init]; button.buttonSizeValue = LineSDKLoginButtonSizeSmall; button.buttonTextValue = @"Hello"; button = nil; @@ -269,6 +306,38 @@ - (void)testConstantInterface { XCTAssertNotNil(LineSDKConstant.SDKVersion); } +- (void)testShareViewControllerAuthorizationStatus { + LineSDKAuthorizationStatus *status = + [LineSDKShareViewController localAuthorizationStatusForSendingMessage]; + XCTAssertEqual(status, [LineSDKAuthorizationStatus lackOfToken]); +} + +- (void)testLineSDKMessageSendingToken { + LineSDKMessageSendingToken *token = nil; + XCTAssertNil(token.token); +} + +- (void)testOpenChatCreatingControllerAuthorizationStatus { + LineSDKAuthorizationStatus *status = + [LineSDKOpenChatCreatingController localAuthorizationStatusForCreatingOpenChat]; + XCTAssertEqual(status, [LineSDKAuthorizationStatus lackOfToken]); +} + +- (void)testOpenChatRoomCreatingItem { + LineSDKOpenChatRoomCreatingItem *item = [[LineSDKOpenChatRoomCreatingItem alloc] initWithName: @"name" + roomDescription: @"room" + creatorDisplayName: @"creator" + category: 1 + allowSearch: YES + ]; + XCTAssertNotNil(item); + XCTAssertEqual(item.name, @"name"); + XCTAssertEqual(item.roomDescription, @"room"); + XCTAssertEqual(item.creatorDisplayName, @"creator"); + XCTAssertEqual(item.category, 1); + XCTAssertEqual(item.allowSearch, YES); +} + @end diff --git a/LineSDK/LineSDKObjCInterfaceTests/LineSDKViewControllerInterfaceTests.m b/LineSDK/LineSDKObjCInterfaceTests/LineSDKViewControllerInterfaceTests.m new file mode 100644 index 00000000..03618ea6 --- /dev/null +++ b/LineSDK/LineSDKObjCInterfaceTests/LineSDKViewControllerInterfaceTests.m @@ -0,0 +1,84 @@ +// +// LineSDKViewControllerInterfaceTests.m +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import +@import LineSDKObjC; + +@interface LineSDKViewControllerInterfaceTests : XCTestCase + +@end + +@implementation LineSDKViewControllerInterfaceTests + +- (void)testShareViewControllerCreating { + LineSDKShareViewController *controller = [[LineSDKShareViewController alloc] init]; + XCTAssertNotNil(controller); +} + +- (void)testShareViewControllerPropertiesSetting { + + LineSDKShareViewController *controller = [[LineSDKShareViewController alloc] init]; + + UIColor *color = [UIColor redColor]; + XCTAssertNotEqual(controller.shareNavigationBarTintColor, color); + controller.shareNavigationBarTintColor = color; + XCTAssertEqual(controller.shareNavigationBarTintColor, color); + + XCTAssertNotEqual(controller.shareNavigationBarTextColor, color); + controller.shareNavigationBarTextColor = color; + XCTAssertEqual(controller.shareNavigationBarTextColor, color); + + XCTAssertEqual(controller.shareStatusBarStyle, UIStatusBarStyleLightContent); + controller.shareStatusBarStyle = UIStatusBarStyleDefault; + XCTAssertEqual(controller.shareStatusBarStyle, UIStatusBarStyleDefault); + + XCTAssertNil(controller.shareMessages); + + LineSDKTextMessage *m1 = [[LineSDKTextMessage alloc] initWithText:@"test"]; + + LineSDKFlexBubbleContainer *container = [[LineSDKFlexBubbleContainer alloc] init]; + LineSDKFlexMessage *m2 = [[LineSDKFlexMessage alloc] initWithAltText:@"flex" container:container]; + + controller.shareMessages = @[m1, m2]; + XCTAssertEqual(controller.shareMessages.count, 2); + + XCTAssertNil(controller.delegate); + controller.delegate = self; + XCTAssertNotNil(controller.delegate); +} + +- (void)testOpenChatControllerCreating { + LineSDKOpenChatCreatingController *controller = [[LineSDKOpenChatCreatingController alloc] init]; + XCTAssertNotNil(controller); +} + +- (void)testOpenChatCreatingControllerInterface { + LineSDKOpenChatCreatingController *controller = nil; + UIViewController *vc = [UIViewController new]; + [controller loadAndPresentIn:vc presentedHandler:^(UIViewController *presented, NSError *error) { + + }]; + XCTAssertNil(controller.delegate); + XCTAssertEqual(controller.suggestedCategory, 0); + +} + +@end diff --git a/LineSDK/LineSDKTests/API/GetApproversInFriendsTests.swift b/LineSDK/LineSDKTests/API/GetApproversInFriendsTests.swift index c2cdb457..5fbccf30 100644 --- a/LineSDK/LineSDKTests/API/GetApproversInFriendsTests.swift +++ b/LineSDK/LineSDKTests/API/GetApproversInFriendsTests.swift @@ -1,13 +1,13 @@ // // GetApproversInFriendsTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/API/GetApproversInGroupRequestTests.swift b/LineSDK/LineSDKTests/API/GetApproversInGroupRequestTests.swift index a6ef7bfe..b06e2559 100644 --- a/LineSDK/LineSDKTests/API/GetApproversInGroupRequestTests.swift +++ b/LineSDK/LineSDKTests/API/GetApproversInGroupRequestTests.swift @@ -1,13 +1,13 @@ // // GetApproversInGroupRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -30,7 +30,7 @@ class GetApproversInGroupRequestTests: APITests { func testSuccess() { - let r = GetApproversInGroupRequest(groupID: "") + let r = try! GetApproversInGroupRequest(groupID: "abc") GetApproversInGroupRequest.success = """ { diff --git a/LineSDK/LineSDKTests/API/GetBotFriendshipStatusTests.swift b/LineSDK/LineSDKTests/API/GetBotFriendshipStatusTests.swift index 3ab507d7..121d0ca8 100644 --- a/LineSDK/LineSDKTests/API/GetBotFriendshipStatusTests.swift +++ b/LineSDK/LineSDKTests/API/GetBotFriendshipStatusTests.swift @@ -1,13 +1,13 @@ // // GetBotFriendshipStatusTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/API/GetFriendsRequestTests.swift b/LineSDK/LineSDKTests/API/GetFriendsRequestTests.swift index 45cc11a3..1d07536e 100644 --- a/LineSDK/LineSDKTests/API/GetFriendsRequestTests.swift +++ b/LineSDK/LineSDKTests/API/GetFriendsRequestTests.swift @@ -1,13 +1,13 @@ // // GetFriendsRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -43,13 +43,34 @@ class GetFriendsRequestTests: APITests { { "displayName": "Sally", "userId": "cccc" - } + }, + { + "displayName": "Original Name", + "displayNameOverridden": "New Name", + "userId": "cccc" + }, ] } """ runTestSuccess(for: r) { response in - XCTAssertEqual(response.friends.count, 2) - XCTAssertEqual(response.friends.first?.userID, "aaaa") + XCTAssertEqual(response.friends.count, 3) + + let friend0 = response.friends[0] + XCTAssertEqual(friend0.userID, "aaaa") + XCTAssertEqual(friend0.displayName, "Brown") + XCTAssertEqual(friend0.displayNameOriginal, "Brown") + XCTAssertEqual(friend0.displayNameOverridden, nil) + + let friend1 = response.friends[1] + XCTAssertEqual(friend1.displayName, "Sally") + XCTAssertEqual(friend1.displayName, "Sally") + XCTAssertEqual(friend1.displayNameOriginal, "Sally") + XCTAssertEqual(friend1.displayNameOverridden, nil) + + let friend2 = response.friends[2] + XCTAssertEqual(friend2.displayName, "New Name") + XCTAssertEqual(friend2.displayNameOriginal, "Original Name") + XCTAssertEqual(friend2.displayNameOverridden, "New Name") } } diff --git a/LineSDK/LineSDKTests/API/GetGroupsRequestTests.swift b/LineSDK/LineSDKTests/API/GetGroupsRequestTests.swift index 724741ef..c74cdf96 100644 --- a/LineSDK/LineSDKTests/API/GetGroupsRequestTests.swift +++ b/LineSDK/LineSDKTests/API/GetGroupsRequestTests.swift @@ -1,13 +1,13 @@ // // GetGroupsRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/API/GetUserProfileRequestTests.swift b/LineSDK/LineSDKTests/API/GetUserProfileRequestTests.swift index 75fe3920..e9e58066 100644 --- a/LineSDK/LineSDKTests/API/GetUserProfileRequestTests.swift +++ b/LineSDK/LineSDKTests/API/GetUserProfileRequestTests.swift @@ -1,13 +1,13 @@ // // GetUserProfileRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/API/GetVerifyTokenRequestTests.swift b/LineSDK/LineSDKTests/API/GetVerifyTokenRequestTests.swift index 9733553f..57659762 100644 --- a/LineSDK/LineSDKTests/API/GetVerifyTokenRequestTests.swift +++ b/LineSDK/LineSDKTests/API/GetVerifyTokenRequestTests.swift @@ -1,13 +1,13 @@ // // GetVerifyTokenRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/API/PostMessageSendingTokenRequestTests.swift b/LineSDK/LineSDKTests/API/PostMessageSendingTokenRequestTests.swift new file mode 100644 index 00000000..2a53329e --- /dev/null +++ b/LineSDK/LineSDKTests/API/PostMessageSendingTokenRequestTests.swift @@ -0,0 +1,40 @@ +// +// PostMessageSendingTokenRequestTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +extension PostMessageSendingTokenIssueRequest: ResponseDataStub { + static let success = """ + { + "token": "abc123" + } + """ +} + +class PostMessageSendingTokenRequestTests: APITests { + func testSuccess() { + let request = PostMessageSendingTokenIssueRequest(userIDs: ["1", "2"]) + runTestSuccess(for: request) { result in + XCTAssertEqual(result.token, "abc123") + } + } +} diff --git a/LineSDK/LineSDKTests/API/PostMultisendMessagesRequestTests.swift b/LineSDK/LineSDKTests/API/PostMultisendMessagesRequestTests.swift index 7d678d6b..dc5f911b 100644 --- a/LineSDK/LineSDKTests/API/PostMultisendMessagesRequestTests.swift +++ b/LineSDK/LineSDKTests/API/PostMultisendMessagesRequestTests.swift @@ -1,13 +1,13 @@ // // PostMultisendMessagesRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/API/PostOTPRequestTests.swift b/LineSDK/LineSDKTests/API/PostOTPRequestTests.swift deleted file mode 100644 index 935704d9..00000000 --- a/LineSDK/LineSDKTests/API/PostOTPRequestTests.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// PostOTPRequestTests.swift -// -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. -// -// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, -// copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. -// -// As with any software that integrates with the LINE Corporation platform, your use of this software -// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. -// This copyright notice shall be included in all copies or substantial portions of the software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import XCTest -@testable import LineSDK - -extension PostOTPRequest: ResponseDataStub { - static let success = """ - { - "otpId": "7IMDGquTwIkgPGM2Z0ZXIGYnhIo=", - "otp": "mggOa8NxWrrlcd0rhLTt" - } - """ - - static let fail = """ - { - "error": "invalid_request", - "error_description": "some error" - } - """ -} - -class PostOTPRequestTests: APITests { - - func testSuccess() { - let request = PostOTPRequest(channelID: config.channelID) - runTestSuccess(for: request) { result in - XCTAssertEqual(result.otpId, "7IMDGquTwIkgPGM2Z0ZXIGYnhIo=") - XCTAssertEqual(result.otp, "mggOa8NxWrrlcd0rhLTt") - } - } - - func testFail() { - let expect = expectation(description: "\(#file)_\(#line)") - let session = Session.stub(configuration: config, string: PostOTPRequest.fail, statusCode: 400) - session.send(PostOTPRequest(channelID: config.channelID)) { result in - - guard case .responseFailed( - reason: .invalidHTTPStatusAPIError(let detail)) = result.error! else - { - XCTFail("Error reason should be .invalidHTTPStatusAPIError") - return - } - - XCTAssertEqual(detail.code, 400) - XCTAssertEqual(detail.error!.error, "invalid_request") - XCTAssertEqual(detail.error!.detail, "some error") - - expect.fulfill() - } - waitForExpectations(timeout: 1.0, handler: nil) - } -} diff --git a/LineSDK/LineSDKTests/API/PostRefreshTokenRequestTests.swift b/LineSDK/LineSDKTests/API/PostRefreshTokenRequestTests.swift index b1223a29..17dd0ddb 100644 --- a/LineSDK/LineSDKTests/API/PostRefreshTokenRequestTests.swift +++ b/LineSDK/LineSDKTests/API/PostRefreshTokenRequestTests.swift @@ -1,13 +1,13 @@ // // PostRefreshTokenRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -46,7 +46,7 @@ class PostRefreshTokenRequestTests: APITests { let request = PostRefreshTokenRequest(channelID: "abc", refreshToken: "123123") runTestSuccess(for: request) { token in XCTAssertEqual(token.value, "123") - XCTAssertEqual(token.refreshToken, "abc") + XCTAssertEqual(token._refreshToken, "abc") XCTAssertEqual(token.tokenType, "Bearer") XCTAssertEqual(token.permissions, [LoginPermission.profile, LoginPermission.openID]) XCTAssertEqual(token.expiresAt, token.createdAt.addingTimeInterval(token.expiresIn)) diff --git a/LineSDK/LineSDKTests/API/PostRevokeTokenRequestTests.swift b/LineSDK/LineSDKTests/API/PostRevokeTokenRequestTests.swift index 474206d5..1c14658b 100644 --- a/LineSDK/LineSDKTests/API/PostRevokeTokenRequestTests.swift +++ b/LineSDK/LineSDKTests/API/PostRevokeTokenRequestTests.swift @@ -1,13 +1,13 @@ // // PostRevokeTokenRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -52,7 +52,7 @@ class PostRevokeTokenRequestTests: APITests { Session._shared = Session(configuration: config, delegate: stub) setupTestToken() - API.revokeAccessToken {result in + API.Auth.revokeAccessToken {result in XCTAssertNotNil(result.value) expect.fulfill() } diff --git a/LineSDK/LineSDKTests/API/PostSendMessagesRequestTests.swift b/LineSDK/LineSDKTests/API/PostSendMessagesRequestTests.swift index d3d502ec..73ea117d 100644 --- a/LineSDK/LineSDKTests/API/PostSendMessagesRequestTests.swift +++ b/LineSDK/LineSDKTests/API/PostSendMessagesRequestTests.swift @@ -1,13 +1,13 @@ // // PostSendMessagesRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/API/PostTokenExchangeRequestTests.swift b/LineSDK/LineSDKTests/API/PostTokenExchangeRequestTests.swift index 9c7deeb7..a1f073e8 100644 --- a/LineSDK/LineSDKTests/API/PostTokenExchangeRequestTests.swift +++ b/LineSDK/LineSDKTests/API/PostTokenExchangeRequestTests.swift @@ -1,13 +1,13 @@ // // PostExchangeTokenRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -52,7 +52,7 @@ extension PostExchangeTokenRequest: ResponseDataStub { "access_token":"\(successToken)", "refresh_token":"abc", "token_type":"Bearer", - "scope":"profile", + "scope":"profile abcd", "id_token": "\(LINEIDToken)", "expires_in":2592000 } @@ -65,14 +65,14 @@ class PostExchangeTokenRequestTests: APITests { let request = PostExchangeTokenRequest( channelID: config.channelID, code: "abcabc", - otpValue: "123123", + codeVerifier: PKCE().codeVerifier, redirectURI: "urlurl", optionalRedirectURI: "universal") runTestSuccess(for: request) { token in XCTAssertEqual(token.value, "123") - XCTAssertEqual(token.refreshToken, "abc") + XCTAssertEqual(token._refreshToken, "abc") XCTAssertEqual(token.tokenType, "Bearer") - XCTAssertEqual(token.permissions, [LoginPermission.profile]) + XCTAssertEqual(token.permissions, [LoginPermission.profile, LoginPermission(rawValue: "abcd")]) XCTAssertEqual(token.expiresAt, token.createdAt.addingTimeInterval(token.expiresIn)) XCTAssertEqual(token.IDToken?.payload.issuer, "https://access.line.me") } diff --git a/LineSDK/LineSDKTests/ConstantTests.swift b/LineSDK/LineSDKTests/ConstantTests.swift index e102b9ec..79b57985 100644 --- a/LineSDK/LineSDKTests/ConstantTests.swift +++ b/LineSDK/LineSDKTests/ConstantTests.swift @@ -1,13 +1,13 @@ // // ConstantTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Crypto/ECDSAKeyTests.swift b/LineSDK/LineSDKTests/Crypto/ECDSAKeyTests.swift index d665c9fd..55517591 100644 --- a/LineSDK/LineSDKTests/Crypto/ECDSAKeyTests.swift +++ b/LineSDK/LineSDKTests/Crypto/ECDSAKeyTests.swift @@ -1,13 +1,13 @@ // // ECDSAKeyTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Crypto/ECDSATests.swift b/LineSDK/LineSDKTests/Crypto/ECDSATests.swift index 0c8b515b..01a7a26a 100644 --- a/LineSDK/LineSDKTests/Crypto/ECDSATests.swift +++ b/LineSDK/LineSDKTests/Crypto/ECDSATests.swift @@ -1,13 +1,13 @@ // // ECDSATests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Crypto/GetDiscoveryDocumentRequestTests.swift b/LineSDK/LineSDKTests/Crypto/GetDiscoveryDocumentRequestTests.swift index e1f7b9ca..6bdf92ba 100644 --- a/LineSDK/LineSDKTests/Crypto/GetDiscoveryDocumentRequestTests.swift +++ b/LineSDK/LineSDKTests/Crypto/GetDiscoveryDocumentRequestTests.swift @@ -1,13 +1,13 @@ // // GetDiscoveryDocumentRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Crypto/GetJWKSetRequestTests.swift b/LineSDK/LineSDKTests/Crypto/GetJWKSetRequestTests.swift index 5f23b727..641f586b 100644 --- a/LineSDK/LineSDKTests/Crypto/GetJWKSetRequestTests.swift +++ b/LineSDK/LineSDKTests/Crypto/GetJWKSetRequestTests.swift @@ -1,13 +1,13 @@ // // GetJWKSetRequestTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Crypto/JWKDataTests.swift b/LineSDK/LineSDKTests/Crypto/JWKDataTests.swift index 319b7d07..105e0321 100644 --- a/LineSDK/LineSDKTests/Crypto/JWKDataTests.swift +++ b/LineSDK/LineSDKTests/Crypto/JWKDataTests.swift @@ -1,13 +1,13 @@ // // JWKDataTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -95,8 +95,7 @@ class JWKDataTests: XCTestCase { let key4 = keySet.getKeyByID("3829b108279b26bcfcc8971e348d116727d20773f06a41c5e4e9706f7a0dc966") XCTAssertNotNil(key4) let data4 = try! key4!.getKeyData() - let k = try! Crypto.ECDSAPublicKey(der: data4) - print(k) + _ = try! Crypto.ECDSAPublicKey(der: data4) let key6 = keySet.getKeyByID("16e04d4e56783a792dcb4684d86d179dc7abc0bb909d96ee12ef07097cddff0c") XCTAssertNotNil(key6) diff --git a/LineSDK/LineSDKTests/Crypto/JWKTests.swift b/LineSDK/LineSDKTests/Crypto/JWKTests.swift index 1babbcf3..09a55945 100644 --- a/LineSDK/LineSDKTests/Crypto/JWKTests.swift +++ b/LineSDK/LineSDKTests/Crypto/JWKTests.swift @@ -1,13 +1,13 @@ // // JWKTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Crypto/JWTECTests.swift b/LineSDK/LineSDKTests/Crypto/JWTECTests.swift index e53da2b9..c673e71a 100644 --- a/LineSDK/LineSDKTests/Crypto/JWTECTests.swift +++ b/LineSDK/LineSDKTests/Crypto/JWTECTests.swift @@ -1,13 +1,13 @@ // // JWTECTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Crypto/JWTRSATests.swift b/LineSDK/LineSDKTests/Crypto/JWTRSATests.swift index a85e2f8e..60110b7c 100644 --- a/LineSDK/LineSDKTests/Crypto/JWTRSATests.swift +++ b/LineSDK/LineSDKTests/Crypto/JWTRSATests.swift @@ -1,13 +1,13 @@ // // JWTTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -62,10 +62,11 @@ o2kQ+X5xK9cipRgEKwIDAQAB "sub": "1234567890", "name": "John Doe", "admin": true, - "iat": 1516239022 + "iat": 1516239022, + "amr": ["pwd", "lineautologin", "lineqr"] } */ -private let sample = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.TCYt5XsITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUcX16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtjPAYuNzVBAh4vGHSrQyHUdBBPM" +private let sample = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiYW1yIjpbInB3ZCIsImxpbmVhdXRvbG9naW4iLCJsaW5lcXIiXX0.ENGgzjwLiupqIWlZfBQDyGlVyyxJbabmaq06oOpmSN_WsccH2lFRbpfMWpbC_Ir0uxu_PYFXJnS22lthPUJAEjnRU_fRY42cwtuWvtPgqDXTTm5E2ux5rNJnxvdfKtCKpNogd3okgLuu4is3g14bgIpisadq2oqJwTsgHRfZcEg" /* { @@ -96,9 +97,11 @@ class JWTRSATests: XCTestCase { XCTAssertEqual(token.header.tokenType, "JWT") XCTAssertEqual(token.payload["name", String.self], "John Doe") - XCTAssertEqual(token.payload["sub", String.self], "1234567890") + XCTAssertEqual(token.payload.name, "John Doe") + XCTAssertEqual(token.payload.subject, "1234567890") XCTAssertEqual(token.payload["iat", Int64.self], 1516239022) XCTAssertTrue(token.payload["admin", Bool.self]!) + XCTAssertEqual(token.payload.amr, ["pwd", "lineautologin", "lineqr"]) } func testJWTSignatureVerify() { diff --git a/LineSDK/LineSDKTests/Crypto/RSAKeyTests.swift b/LineSDK/LineSDKTests/Crypto/RSAKeyTests.swift index 99fc0555..b7376261 100644 --- a/LineSDK/LineSDKTests/Crypto/RSAKeyTests.swift +++ b/LineSDK/LineSDKTests/Crypto/RSAKeyTests.swift @@ -1,13 +1,13 @@ // // RSAKeyTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Crypto/RSATests.swift b/LineSDK/LineSDKTests/Crypto/RSATests.swift index 684d5c81..18ef964d 100644 --- a/LineSDK/LineSDKTests/Crypto/RSATests.swift +++ b/LineSDK/LineSDKTests/Crypto/RSATests.swift @@ -1,13 +1,13 @@ // // RSATests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Info.plist b/LineSDK/LineSDKTests/Info.plist index 862e5255..e957abf4 100644 --- a/LineSDK/LineSDKTests/Info.plist +++ b/LineSDK/LineSDKTests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.linecorp.linesdk.test + com.linecorp.LineSDKTests CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.0.0 + 5.11.2 CFBundleVersion - 402 + 1208 diff --git a/LineSDK/LineSDKTests/KeychainStoreTests.swift b/LineSDK/LineSDKTests/KeychainStoreTests.swift index 5657689e..eb987094 100644 --- a/LineSDK/LineSDKTests/KeychainStoreTests.swift +++ b/LineSDK/LineSDKTests/KeychainStoreTests.swift @@ -1,13 +1,13 @@ // // KeychainStoreTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/LineSDKErrorTests.swift b/LineSDK/LineSDKTests/LineSDKErrorTests.swift index 0d032491..28ec178b 100644 --- a/LineSDK/LineSDKTests/LineSDKErrorTests.swift +++ b/LineSDK/LineSDKTests/LineSDKErrorTests.swift @@ -1,13 +1,13 @@ // // LineSDKErrorTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -57,6 +57,12 @@ class LineSDKErrorTests: XCTestCase { XCTAssertTrue(error.isResponseError(statusCode: 123)) XCTAssertFalse(error.isResponseError(statusCode: 321)) } + + func testURLSessionError() { + let networkLostError = NSError(domain: NSURLErrorDomain, code: -1005, userInfo: nil) + let error = LineSDKError.responseFailed(reason: .URLSessionError(networkLostError)) + XCTAssertTrue(error.isURLSessionErrorCode(sessionErrorCode: NSURLErrorNetworkConnectionLost)) + } func testIsBadRequest() { let err = APIError(InternalAPIError(message: "Bad request")) @@ -79,7 +85,7 @@ class LineSDKErrorTests: XCTestCase { func testIsRefreshTokenError() { let err = APIError(InternalAPIError(message: "The refresh token expired")) let refresh = PostRefreshTokenRequest(channelID: "", refreshToken: "") - let urlString = refresh.baseURL.appendingPathComponentIfNotEmpty(refresh.path).absoluteString + let urlString = refresh.baseURL.appendingPathComponentIfNotEmpty(refresh).absoluteString let response = HTTPURLResponse.responseFromCode(400, urlString: urlString) let detail = LineSDKError.ResponseErrorReason.APIErrorDetail( code: 400, error: err, raw: response, rawString: "raw") diff --git a/LineSDK/LineSDKTests/LineSDKTests.swift b/LineSDK/LineSDKTests/LineSDKTests.swift index fa281d42..76119303 100644 --- a/LineSDK/LineSDKTests/LineSDKTests.swift +++ b/LineSDK/LineSDKTests/LineSDKTests.swift @@ -1,13 +1,13 @@ // // LineSDKTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Login/LoginConfigurationTests.swift b/LineSDK/LineSDKTests/Login/LoginConfigurationTests.swift index 6d37eccb..a1bcc72b 100644 --- a/LineSDK/LineSDKTests/Login/LoginConfigurationTests.swift +++ b/LineSDK/LineSDKTests/Login/LoginConfigurationTests.swift @@ -1,13 +1,13 @@ // // LoginConfigurationTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -71,14 +71,4 @@ class LoginConfigurationTests: XCTestCase { let result = config.isValidUniversalLinkURL(url: URL(string: "https://example.com")!) XCTAssertEqual(result, false) } - - func testValidSourceApplication() { - let config = LoginConfiguration(channelID: "123", universalLinkURL: nil) - let results = [ - "jp.naver.line", - "com.apple.hello", - "com.company.app" - ].map(config.isValidSourceApplication) - XCTAssertEqual(results, [true, true, false]) - } } diff --git a/LineSDK/LineSDKTests/Login/LoginFlowTests.swift b/LineSDK/LineSDKTests/Login/LoginFlowTests.swift index 4be9786b..e1acfd38 100644 --- a/LineSDK/LineSDKTests/Login/LoginFlowTests.swift +++ b/LineSDK/LineSDKTests/Login/LoginFlowTests.swift @@ -1,13 +1,13 @@ // // LoginFlowTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -30,11 +30,76 @@ class LoginFlowTests: XCTestCase, ViewControllerCompatibleTest { channelID: "123", universalLinkURL: nil, scopes: [.profile, .openID], - otp: .init(otpId: "321", otp: "aaa"), + pkce: PKCE(), processID: "abc", nonce: "kkk", - botPrompt: .normal) - + loginParameter: { + var p = LoginManager.Parameters() + p.botPromptStyle = .normal + return p + }() + ) + + let parameterWithLanguage = LoginProcess.FlowParameters( + channelID: "123", + universalLinkURL: nil, + scopes: [.profile, .openID], + pkce: PKCE(), + processID: "abc", + nonce: "kkk", + loginParameter: { + var p = LoginManager.Parameters() + p.botPromptStyle = .normal + p.preferredWebPageLanguage = .chineseSimplified + return p + }() + ) + + let parameterWithOnlyWebLogin = LoginProcess.FlowParameters( + channelID: "123", + universalLinkURL: nil, + scopes: [.profile, .openID], + pkce: PKCE(), + processID: "abc", + nonce: "kkk", + loginParameter: { + var p = LoginManager.Parameters() + p.botPromptStyle = .normal + p.onlyWebLogin = true + return p + }() + ) + + let parameterWithPromptBotID = LoginProcess.FlowParameters( + channelID: "123", + universalLinkURL: nil, + scopes: [.profile, .openID], + pkce: PKCE(), + processID: "abc", + nonce: "kkk", + loginParameter: { + var p = LoginManager.Parameters() + p.botPromptStyle = .normal + p.promptBotID = "@abc123" + return p + }() + ) + + let parameterWithInitialQRMethod = LoginProcess.FlowParameters( + channelID: "123", + universalLinkURL: nil, + scopes: [.profile, .openID], + pkce: PKCE(), + processID: "abc", + nonce: "kkk", + loginParameter: { + var p = LoginManager.Parameters() + p.botPromptStyle = .normal + p.initialWebAuthenticationMethod = .qrCode + return p + }() + ) + // Login URL has a double escaped query. func testLoginQueryURLEncode() { @@ -46,27 +111,89 @@ class LoginFlowTests: XCTestCase, ViewControllerCompatibleTest { let components = URLComponents(url: result, resolvingAgainstBaseURL: false) let items = components!.queryItems! - XCTAssertEqual(items.count, 2) + XCTAssertEqual(items.count, ["loginChannelId", "returnUri"].count) - var hit = 0 - for item in items { - if item.name == "loginChannelId" { - hit += 1 - XCTAssertEqual(item.value, "123") - } - if (item.name == "returnUri") { - hit += 1 - - XCTAssertNotEqual(item.value, item.value?.removingPercentEncoding) - - // Should be already fully decoded (no double encoding in the url) - XCTAssertEqual(item.value?.removingPercentEncoding, - item.value?.removingPercentEncoding?.removingPercentEncoding) - } - } - XCTAssertEqual(hit, 2) + var item: URLQueryItem + + item = items.first { $0.name == "loginChannelId" }! + XCTAssertEqual(item.value, "123") + + item = items.first { $0.name == "returnUri" }! + XCTAssertNotEqual(item.value, item.value?.removingPercentEncoding) + + // Should be already fully decoded (no double encoding in the url) + XCTAssertEqual(item.value?.removingPercentEncoding, + item.value?.removingPercentEncoding?.removingPercentEncoding) } - + + func testLoginQueryWithLangURLEncode() { + + let baseURL = URL(string: Constant.lineWebAuthUniversalURL)! + let result = baseURL.appendedLoginQuery(parameterWithLanguage) + + let urlString = result.absoluteString.removingPercentEncoding + XCTAssertNotNil(urlString) + + let components = URLComponents(url: result, resolvingAgainstBaseURL: false) + let items = components!.queryItems! + XCTAssertEqual(items.count, ["loginChannelId", "returnUri", "ui_locales"].count) + + var item: URLQueryItem + + item = items.first { $0.name == "loginChannelId" }! + XCTAssertEqual(item.value, "123") + + item = items.first { $0.name == "returnUri" }! + XCTAssertNotEqual(item.value, item.value?.removingPercentEncoding) + + // Should be already fully decoded (no double encoding in the url) + XCTAssertEqual(item.value?.removingPercentEncoding, + item.value?.removingPercentEncoding?.removingPercentEncoding) + + item = items.first { $0.name == "ui_locales" }! + XCTAssertEqual(item.value, "zh-Hans") + } + + func testLoginQueryWithOnlyWebLoginURLEncode() { + + let baseURL = URL(string: Constant.lineWebAuthUniversalURL)! + let result = baseURL.appendedLoginQuery(parameterWithOnlyWebLogin) + + let urlString = result.absoluteString.removingPercentEncoding + XCTAssertNotNil(urlString) + + let components = URLComponents(url: result, resolvingAgainstBaseURL: false) + let items = components!.queryItems! + XCTAssertEqual(items.count, ["loginChannelId", "returnUri", "disable_ios_auto_login"].count) + + var item: URLQueryItem + + item = items.first { $0.name == "loginChannelId" }! + XCTAssertEqual(item.value, "123") + + item = items.first { $0.name == "returnUri" }! + XCTAssertNotEqual(item.value, item.value?.removingPercentEncoding) + + item = items.first { $0.name == "disable_ios_auto_login" }! + XCTAssertEqual(item.value, "true") + } + + func testLoginQueryWithPromptBotID() { + let baseURL = URL(string: Constant.lineWebAuthUniversalURL)! + let result = baseURL.appendedLoginQuery(parameterWithPromptBotID) + + let urlString = result.absoluteString.removingPercentEncoding + XCTAssertNotNil(urlString) + + let components = URLComponents(url: result, resolvingAgainstBaseURL: false) + let items = components!.queryItems! + XCTAssertEqual(items.count, ["loginChannelId", "returnUri"].count) + + let item = items.first { $0.name == "returnUri" }! + XCTAssertNotEqual(item.value, item.value?.removingPercentEncoding) + XCTAssertTrue(item.value!.removingPercentEncoding!.contains("prompt_bot_id=@abc123")) + } + // URL Scheme has a triple escaped query. func testURLSchemeQueryEncode() { let baseURL = Constant.lineAppAuthURLv2 @@ -77,22 +204,16 @@ class LoginFlowTests: XCTestCase, ViewControllerCompatibleTest { let components = URLComponents(url: result, resolvingAgainstBaseURL: false) let items = components!.queryItems! - XCTAssertEqual(items.count, 1) - - var hit = 0 - for item in items { - if (item.name == "loginUrl") { - hit += 1 - XCTAssertNotEqual(item.value, item.value?.removingPercentEncoding) - XCTAssertNotEqual(item.value?.removingPercentEncoding, - item.value?.removingPercentEncoding?.removingPercentEncoding) - - // Should be already fully decoded (no double encoding in the url) - XCTAssertEqual(item.value?.removingPercentEncoding?.removingPercentEncoding, - item.value?.removingPercentEncoding?.removingPercentEncoding?.removingPercentEncoding) - } - } - XCTAssertEqual(hit, 1) + XCTAssertEqual(items.count, ["loginChannelId"].count) + + let item = items.first { $0.name == "loginUrl" }! + XCTAssertNotEqual(item.value, item.value?.removingPercentEncoding) + XCTAssertNotEqual(item.value?.removingPercentEncoding, + item.value?.removingPercentEncoding?.removingPercentEncoding) + + // Should be already fully decoded (no double encoding in the url) + XCTAssertEqual(item.value?.removingPercentEncoding?.removingPercentEncoding, + item.value?.removingPercentEncoding?.removingPercentEncoding?.removingPercentEncoding) } func testAppUniversalLinkFlow() { @@ -153,7 +274,36 @@ class LoginFlowTests: XCTestCase, ViewControllerCompatibleTest { flow.start(in: rootViewController) waitForExpectations(timeout: 1.0, handler: nil) } - + + func testWebLoginFlowWithQRCodeFirst() { + let expect = expectation(description: "\(#file)_\(#line)") + let flow = WebLoginFlow(parameter: parameterWithInitialQRMethod) + let webURL = URL(string: Constant.lineWebAuthURL)! + let components = URLComponents(url: flow.url, resolvingAgainstBaseURL: false) + XCTAssertEqual(components?.scheme, "https") + XCTAssertEqual(components?.host, webURL.host) + XCTAssertEqual(components?.path, webURL.path) + + XCTAssertEqual(components?.fragment, "/qr") + XCTAssertTrue(flow.url.absoluteString.contains("#/qr")) + + let rootViewController = setupViewController() + + flow.onNext.delegate(on: self) { [unowned flow] (self, next) in + expect.fulfill() + self.resetViewController() + switch next { + case .safariViewController: + XCTAssertEqual(rootViewController.presentedViewController, flow.safariViewController) + default: + XCTFail("Should present a safari web view controller.") + } + } + + flow.start(in: rootViewController) + waitForExpectations(timeout: 1.0, handler: nil) + } + func testAppSwitchingObserver() { let expect = expectation(description: "\(#file)_\(#line)") let observer = LoginProcess.AppSwitchingObserver() diff --git a/LineSDK/LineSDKTests/Login/LoginManagerTests.swift b/LineSDK/LineSDKTests/Login/LoginManagerTests.swift index c29c5de0..174886b8 100644 --- a/LineSDK/LineSDKTests/Login/LoginManagerTests.swift +++ b/LineSDK/LineSDKTests/Login/LoginManagerTests.swift @@ -1,13 +1,13 @@ // // LoginManagerTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -22,6 +22,16 @@ import XCTest @testable import LineSDK +let sampleFlowParameters = LoginProcess.FlowParameters( + channelID: "", + universalLinkURL: nil, + scopes: [], + pkce: .init(), + processID: "", + nonce: nil, + loginParameter: .init() +) + class LoginManagerTests: XCTestCase, ViewControllerCompatibleTest { var window: UIWindow! @@ -53,7 +63,6 @@ class LoginManagerTests: XCTestCase, ViewControllerCompatibleTest { XCTAssertFalse(LoginManager.shared.isAuthorizing) let delegateStub = SessionDelegateStub(stubs: [ - .init(data: PostOTPRequest.successData, responseCode: 200), .init(data: PostExchangeTokenRequest.successData, responseCode: 200), .init(data: GetUserProfileRequest.successData, responseCode: 200) ]) @@ -61,8 +70,9 @@ class LoginManagerTests: XCTestCase, ViewControllerCompatibleTest { configuration: LoginConfiguration.shared, delegate: delegateStub ) - - let process = LoginManager.shared.login(permissions: [.profile], in: setupViewController()) { + + var process: LoginProcess! + process = LoginManager.shared.login(permissions: [.profile], in: setupViewController()) { loginResult in XCTAssertNotNil(loginResult.value) @@ -72,22 +82,74 @@ class LoginManagerTests: XCTestCase, ViewControllerCompatibleTest { XCTAssertTrue(LoginManager.shared.isAuthorized) XCTAssertFalse(LoginManager.shared.isAuthorizing) - + + // IDTokenNonce should be `nil` when `.openID` not required. + XCTAssertNil(result.IDTokenNonce) + + XCTAssertEqual(process.loginRoute, .appUniversalLink) + try! AccessTokenStore.shared.removeCurrentAccessToken() expect.fulfill() }! - + + // Set a sample value for checking `loginRoute` in the result. + process.appUniversalLinkFlow = AppUniversalLinkFlow(parameter: sampleFlowParameters) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { XCTAssertFalse(LoginManager.shared.isAuthorized) XCTAssertTrue(LoginManager.shared.isAuthorizing) - + + // Simulate auth result let urlString = "\(Constant.thirdPartyAppReturnURL)?code=123&state=\(process.processID)" - let handled = process.resumeOpenURL(url: URL(string: urlString)!, sourceApplication: "com.apple.safari") + let handled = process.resumeOpenURL(url: URL(string: urlString)!) XCTAssertTrue(handled) } - waitForExpectations(timeout: 1, handler: nil) + waitForExpectations(timeout: 2, handler: nil) + } + + func testLoginActionWithOpenID() { + let expect = expectation(description: "\(#file)_\(#line)") + + XCTAssertFalse(LoginManager.shared.isAuthorized) + XCTAssertFalse(LoginManager.shared.isAuthorizing) + + let delegateStub = SessionDelegateStub(stubs: [ + .init(data: PostExchangeTokenRequest.successData, responseCode: 200), + .init(data: GetUserProfileRequest.successData, responseCode: 200) + ]) + Session._shared = Session( + configuration: LoginConfiguration.shared, + delegate: delegateStub + ) + + var process: LoginProcess! + process = LoginManager.shared.login(permissions: [.profile, .openID], in: setupViewController()) { + loginResult in + XCTAssertNotNil(loginResult.value) + + let result = loginResult.value! + + // IDTokenNonce should be `nil` when `.openID` not required. + XCTAssertNotNil(result.IDTokenNonce) + XCTAssertEqual(result.IDTokenNonce, process!.IDTokenNonce) + + try! AccessTokenStore.shared.removeCurrentAccessToken() + expect.fulfill() + }! + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + + XCTAssertFalse(LoginManager.shared.isAuthorized) + XCTAssertTrue(LoginManager.shared.isAuthorizing) + + let urlString = "\(Constant.thirdPartyAppReturnURL)?code=123&state=\(process.processID)" + let handled = process.resumeOpenURL(url: URL(string: urlString)!) + XCTAssertTrue(handled) + } + + waitForExpectations(timeout: 2, handler: nil) } func testLogout() { @@ -104,6 +166,58 @@ class LoginManagerTests: XCTestCase, ViewControllerCompatibleTest { } waitForExpectations(timeout: 1, handler: nil) } - + + func testRefreshNotOverwriteStoredIDToken() { + + let expect = expectation(description: "\(#file)_\(#line)") + + setupTestToken() + XCTAssertTrue(LoginManager.shared.isAuthorized) + XCTAssertNotNil(AccessTokenStore.shared.current?.IDToken) + + let delegateStub = SessionDelegateStub( + stubs: [.init(data: PostRefreshTokenRequest.successData, responseCode: 200)]) + Session._shared = Session( + configuration: LoginConfiguration.shared, + delegate: delegateStub + ) + + API.Auth.refreshAccessToken { result in + XCTAssertNotNil(AccessTokenStore.shared.current?.IDToken) + expect.fulfill() + } + + waitForExpectations(timeout: 1, handler: nil) + } + + func testLoginProcessRouteSetting() { + XCTContext.runActivity(named: "app universal link") { _ in + let process = LoginProcess( + configuration: .shared, scopes: [], parameters: .init(), viewController: setupViewController() + ) + XCTAssertNil(process.loginRoute) + process.appUniversalLinkFlow = AppUniversalLinkFlow(parameter: sampleFlowParameters) + XCTAssertEqual(process.loginRoute, .appUniversalLink) + } + + XCTContext.runActivity(named: "app auth") { _ in + let process = LoginProcess( + configuration: .shared, scopes: [], parameters: .init(), viewController: setupViewController() + ) + XCTAssertNil(process.loginRoute) + process.appAuthSchemeFlow = AppAuthSchemeFlow(parameter: sampleFlowParameters) + XCTAssertEqual(process.loginRoute, .appAuthScheme) + } + + XCTContext.runActivity(named: "web login") { _ in + let process = LoginProcess( + configuration: .shared, scopes: [], parameters: .init(), viewController: setupViewController() + ) + XCTAssertNil(process.loginRoute) + process.webLoginFlow = WebLoginFlow(parameter: sampleFlowParameters) + XCTAssertEqual(process.loginRoute, .webLogin) + } + } + } diff --git a/LineSDK/LineSDKTests/Login/LoginProcessURLResponseTests.swift b/LineSDK/LineSDKTests/Login/LoginProcessURLResponseTests.swift index 5f621715..02b8d5b0 100644 --- a/LineSDK/LineSDKTests/Login/LoginProcessURLResponseTests.swift +++ b/LineSDK/LineSDKTests/Login/LoginProcessURLResponseTests.swift @@ -1,13 +1,13 @@ // // LoginProcessURLResponseTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Login/PKCETests.swift b/LineSDK/LineSDKTests/Login/PKCETests.swift new file mode 100644 index 00000000..10586440 --- /dev/null +++ b/LineSDK/LineSDKTests/Login/PKCETests.swift @@ -0,0 +1,47 @@ +// +// PKCETests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class PKCETests: XCTestCase { + + /// code_verifier + /// high-entropy cryptographic random STRING + /// with a minimum length of 43 characters and a maximum length of 128 characters + /// + /// Ref: https://tools.ietf.org/html/rfc7636#section-4.1 + /// + func testCodeVerifierLength() { + for _ in 0...1000 { + autoreleasepool { + let codeVerifier = PKCE().codeVerifier + XCTAssertTrue(43...128 ~= codeVerifier.count) + } + } + } + + func testCodeChallenge() { + let codeVerifier = "ksl2M8Qvw6Ith2hYslVx7XUmtDjt2RvVUzMk8UUgQHc" + let codeChallenge = PKCE.generateCodeChallenge(codeVerifier: codeVerifier) + XCTAssertEqual(codeChallenge, "x0ecinHXuDev1f89OvD8rzH4FzKNiv2I07qIdZSuStA") + } +} diff --git a/LineSDK/LineSDKTests/Message/AudioMessageTests.swift b/LineSDK/LineSDKTests/Message/AudioMessageTests.swift index 144d5618..3be124ce 100644 --- a/LineSDK/LineSDKTests/Message/AudioMessageTests.swift +++ b/LineSDK/LineSDKTests/Message/AudioMessageTests.swift @@ -1,13 +1,13 @@ // // AudioMessageTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/FlexBoxComponentTests.swift b/LineSDK/LineSDKTests/Message/FlexBoxComponentTests.swift index a4cbb5b9..e760c687 100644 --- a/LineSDK/LineSDKTests/Message/FlexBoxComponentTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexBoxComponentTests.swift @@ -1,13 +1,13 @@ // // FlexBoxComponentTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/FlexBubbleContainerTests.swift b/LineSDK/LineSDKTests/Message/FlexBubbleContainerTests.swift index 05ef5883..acaa9d2f 100644 --- a/LineSDK/LineSDKTests/Message/FlexBubbleContainerTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexBubbleContainerTests.swift @@ -1,13 +1,13 @@ // // FlexBubbleContainerTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/FlexButtonComponentTests.swift b/LineSDK/LineSDKTests/Message/FlexButtonComponentTests.swift index 31c21a29..ab5ff683 100644 --- a/LineSDK/LineSDKTests/Message/FlexButtonComponentTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexButtonComponentTests.swift @@ -1,13 +1,13 @@ // // FlexButtonComponentTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/FlexCarouselContainerTests.swift b/LineSDK/LineSDKTests/Message/FlexCarouselContainerTests.swift index 2acc229c..2ef73eb7 100644 --- a/LineSDK/LineSDKTests/Message/FlexCarouselContainerTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexCarouselContainerTests.swift @@ -1,13 +1,13 @@ // // FlexCarouselContainerTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/FlexComponentMessageTests.swift b/LineSDK/LineSDKTests/Message/FlexComponentMessageTests.swift index f3b745cd..70c219b2 100644 --- a/LineSDK/LineSDKTests/Message/FlexComponentMessageTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexComponentMessageTests.swift @@ -1,13 +1,13 @@ // // FlexComponentMessageTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -39,7 +39,7 @@ class FlexComponentMessageTests: XCTestCase { XCTAssertEqual(results, [.a, .b, .hello, .a]) } - func testMarginDeccode() { + func testMarginDecode() { typealias Property = FlexMessageComponent.Margin @@ -51,7 +51,7 @@ class FlexComponentMessageTests: XCTestCase { XCTAssertEqual(results, [.none, .xs, .sm, .md, .lg, .xl, .xxl, .none]) } - func testSizeDeccode() { + func testSizeDecode() { typealias Property = FlexMessageComponent.Size @@ -64,7 +64,7 @@ class FlexComponentMessageTests: XCTestCase { XCTAssertEqual(results, [.xxs, .xs, .sm, .md, .lg, .xl, .xxl, .xl3, .xl4, .xl5, .full, .md]) } - func testAlignDeccode() { + func testAlignDecode() { typealias Property = FlexMessageComponent.Alignment @@ -76,7 +76,7 @@ class FlexComponentMessageTests: XCTestCase { XCTAssertEqual(results, [.start, .end, .center, .start]) } - func testGravityDeccode() { + func testGravityDecode() { typealias Property = FlexMessageComponent.Gravity @@ -88,7 +88,7 @@ class FlexComponentMessageTests: XCTestCase { XCTAssertEqual(results, [.top, .bottom, .center, .top]) } - func testWeightDeccode() { + func testWeightDecode() { typealias Property = FlexMessageComponent.Weight diff --git a/LineSDK/LineSDKTests/Message/FlexFillerComponentTests.swift b/LineSDK/LineSDKTests/Message/FlexFillerComponentTests.swift index 12af7738..16ffd66e 100644 --- a/LineSDK/LineSDKTests/Message/FlexFillerComponentTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexFillerComponentTests.swift @@ -1,13 +1,13 @@ // // FlexFillerComponentTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/FlexIconCompomentTests.swift b/LineSDK/LineSDKTests/Message/FlexIconComponentTests.swift similarity index 90% rename from LineSDK/LineSDKTests/Message/FlexIconCompomentTests.swift rename to LineSDK/LineSDKTests/Message/FlexIconComponentTests.swift index d18962f2..bbc5f78a 100644 --- a/LineSDK/LineSDKTests/Message/FlexIconCompomentTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexIconComponentTests.swift @@ -1,13 +1,13 @@ // -// FlexIconCompomentTests.swift +// FlexIconComponentTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/FlexImageComponentTests.swift b/LineSDK/LineSDKTests/Message/FlexImageComponentTests.swift index 28b189f7..48325500 100644 --- a/LineSDK/LineSDKTests/Message/FlexImageComponentTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexImageComponentTests.swift @@ -1,13 +1,13 @@ // // FlexImageComponentTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/FlexSeparatorComponentTests.swift b/LineSDK/LineSDKTests/Message/FlexSeparatorComponentTests.swift index fc6caacc..ba69d381 100644 --- a/LineSDK/LineSDKTests/Message/FlexSeparatorComponentTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexSeparatorComponentTests.swift @@ -1,13 +1,13 @@ // // FlexSeparatorComponentTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -38,7 +38,7 @@ extension FlexSeparatorComponent: MessageSample { class FlexSeparatorComponentTests: XCTestCase { - func testSperatorComponentEncode() { + func testSeparatorComponentEncode() { let component = FlexSeparatorComponent(margin: .lg, color: HexColor(.red)) let dic = FlexMessageComponent.separator(component).json assertEqual(in: dic, forKey: "type", value: "separator") @@ -46,7 +46,7 @@ class FlexSeparatorComponentTests: XCTestCase { assertEqual(in: dic, forKey: "color", value: "#FF0000") } - func testSperatorComponentDecode() { + func testSeparatorComponentDecode() { let decoder = JSONDecoder() let result = FlexSeparatorComponent.samplesData .map { try! decoder.decode(FlexMessageComponent.self, from: $0) } diff --git a/LineSDK/LineSDKTests/Message/FlexSpacerComponentTests.swift b/LineSDK/LineSDKTests/Message/FlexSpacerComponentTests.swift index e771acc5..181205a3 100644 --- a/LineSDK/LineSDKTests/Message/FlexSpacerComponentTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexSpacerComponentTests.swift @@ -1,13 +1,13 @@ // // FlexSpacerComponentTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/FlexTextComponentTests.swift b/LineSDK/LineSDKTests/Message/FlexTextComponentTests.swift index c17e7001..f2b444c0 100644 --- a/LineSDK/LineSDKTests/Message/FlexTextComponentTests.swift +++ b/LineSDK/LineSDKTests/Message/FlexTextComponentTests.swift @@ -1,13 +1,13 @@ // // FlexTextComponentTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/HexColorTests.swift b/LineSDK/LineSDKTests/Message/HexColorTests.swift index a77b085c..cd867b35 100644 --- a/LineSDK/LineSDKTests/Message/HexColorTests.swift +++ b/LineSDK/LineSDKTests/Message/HexColorTests.swift @@ -1,13 +1,13 @@ // // HexColorTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/ImageMessageTests.swift b/LineSDK/LineSDKTests/Message/ImageMessageTests.swift index e5f91a73..a5ffc4a0 100644 --- a/LineSDK/LineSDKTests/Message/ImageMessageTests.swift +++ b/LineSDK/LineSDKTests/Message/ImageMessageTests.swift @@ -1,13 +1,13 @@ // // ImageMessageTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/LocationMessageTests.swift b/LineSDK/LineSDKTests/Message/LocationMessageTests.swift index ad903bea..b826283c 100644 --- a/LineSDK/LineSDKTests/Message/LocationMessageTests.swift +++ b/LineSDK/LineSDKTests/Message/LocationMessageTests.swift @@ -1,13 +1,13 @@ // // LocationMessageTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/TemplateButtonsPayloadTests.swift b/LineSDK/LineSDKTests/Message/TemplateButtonsPayloadTests.swift index 6830c77f..80776cca 100644 --- a/LineSDK/LineSDKTests/Message/TemplateButtonsPayloadTests.swift +++ b/LineSDK/LineSDKTests/Message/TemplateButtonsPayloadTests.swift @@ -1,13 +1,13 @@ // // TemplateButtonsPayloadTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -74,7 +74,7 @@ extension TemplateButtonsPayload: MessageSample { class TemplateButtonsPayloadTests: XCTestCase { func testTemplateButtonsPayloadEncoding() { - let uriAction = MessageURIAction(label: "Cacnel", uri: URL(string: "scheme://action")!) + let uriAction = MessageURIAction(label: "Cancel", uri: URL(string: "scheme://action")!) let action = MessageAction.URI(uriAction) var message = TemplateButtonsPayload(title: "world", text: "hello", actions: [action]) diff --git a/LineSDK/LineSDKTests/Message/TemplateCarouselPayloadTests.swift b/LineSDK/LineSDKTests/Message/TemplateCarouselPayloadTests.swift index 6867fa1a..f9df5abf 100644 --- a/LineSDK/LineSDKTests/Message/TemplateCarouselPayloadTests.swift +++ b/LineSDK/LineSDKTests/Message/TemplateCarouselPayloadTests.swift @@ -1,13 +1,13 @@ // // TemplateCarouselPayloadTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -37,7 +37,7 @@ extension TemplateCarouselPayload: MessageSample { { "type": "uri", "label": "action 1", - "uri": "http://action1.com/" + "uri": "https://example.com/action1/" } ] }, @@ -48,12 +48,12 @@ extension TemplateCarouselPayload: MessageSample { { "type": "uri", "label": "action 2", - "uri": "http://action2.com/" + "uri": "https://example.com/action2" }, { "type": "uri", "label": "action 3", - "uri": "http://action3.com/" + "uri": "https://example.com/action3/" } ] } @@ -67,7 +67,7 @@ extension TemplateCarouselPayload: MessageSample { class TemplateCarouselPayloadTests: XCTestCase { func testTemplateCarouselPayloadEncoding() { - let uriAction = MessageURIAction(label: "Cacnel", uri: URL(string: "scheme://action")!) + let uriAction = MessageURIAction(label: "Cancel", uri: URL(string: "scheme://action")!) let action = MessageAction.URI(uriAction) var column = TemplateCarouselPayload.Column(text: "hello", actions: [action]) column.defaultAction = action diff --git a/LineSDK/LineSDKTests/Message/TemplateConfirmPayloadTests.swift b/LineSDK/LineSDKTests/Message/TemplateConfirmPayloadTests.swift index 721b25a7..98d57ee5 100644 --- a/LineSDK/LineSDKTests/Message/TemplateConfirmPayloadTests.swift +++ b/LineSDK/LineSDKTests/Message/TemplateConfirmPayloadTests.swift @@ -1,13 +1,13 @@ // // TemplateConfirmPayloadTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/TemplateImageCarouselPayloadTests.swift b/LineSDK/LineSDKTests/Message/TemplateImageCarouselPayloadTests.swift index 0539dbc6..6b300f5e 100644 --- a/LineSDK/LineSDKTests/Message/TemplateImageCarouselPayloadTests.swift +++ b/LineSDK/LineSDKTests/Message/TemplateImageCarouselPayloadTests.swift @@ -1,13 +1,13 @@ // // TemplateImageCarouselPayloadTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -54,7 +54,7 @@ extension TemplateImageCarouselPayload: MessageSample { class TemplateImageCarouselPayloadTests: XCTestCase { func testTemplateImageCarouselMessageEncoding() { - let uriAction = MessageURIAction(label: "Cacnel", uri: URL(string: "scheme://action")!) + let uriAction = MessageURIAction(label: "Cancel", uri: URL(string: "scheme://action")!) let action = MessageAction.URI(uriAction) let column = try! TemplateImageCarouselPayload.Column( @@ -80,7 +80,7 @@ class TemplateImageCarouselPayloadTests: XCTestCase { let column1 = columns[0] assertEqual(in: column1, forKey: "imageUrl", value: "https://example.com") let actionInColumn1 = column1["action"] as! [String: Any] - assertEqual(in: actionInColumn1, forKey: "label", value: "Cacnel") + assertEqual(in: actionInColumn1, forKey: "label", value: "Cancel") assertEqual(in: actionInColumn1, forKey: "uri", value: "scheme://action") diff --git a/LineSDK/LineSDKTests/Message/TemplateMessage.swift b/LineSDK/LineSDKTests/Message/TemplateMessage.swift index 7d0d75d1..35851c8e 100644 --- a/LineSDK/LineSDKTests/Message/TemplateMessage.swift +++ b/LineSDK/LineSDKTests/Message/TemplateMessage.swift @@ -1,13 +1,13 @@ // // TemplateMessage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/TemplateMessageActionTests.swift b/LineSDK/LineSDKTests/Message/TemplateMessageActionTests.swift index af854ac0..6358e41a 100644 --- a/LineSDK/LineSDKTests/Message/TemplateMessageActionTests.swift +++ b/LineSDK/LineSDKTests/Message/TemplateMessageActionTests.swift @@ -1,13 +1,13 @@ // // TemplateMessageActionTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/TextMessageTests.swift b/LineSDK/LineSDKTests/Message/TextMessageTests.swift index ec8ca97a..4b4acd57 100644 --- a/LineSDK/LineSDKTests/Message/TextMessageTests.swift +++ b/LineSDK/LineSDKTests/Message/TextMessageTests.swift @@ -1,13 +1,13 @@ // // TextMessageTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Message/UIColorExtensionTests.swift b/LineSDK/LineSDKTests/Message/UIColorExtensionTests.swift new file mode 100644 index 00000000..09c70094 --- /dev/null +++ b/LineSDK/LineSDKTests/Message/UIColorExtensionTests.swift @@ -0,0 +1,54 @@ +// +// UIColorExtensionTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class UIColorExtensionTests: XCTestCase { + + func testHex6() { + let white = UIColor(rgb: "#FFFFFF") + XCTAssertEqual(white.rgbComponents, UIColor.white.rgbComponents) + + let black = UIColor(rgb: "#000000") + XCTAssertEqual(black.rgbComponents, UIColor.black.rgbComponents) + + let red = UIColor(rgb: "#FF0000") + XCTAssertEqual(red.rgbComponents, UIColor.red.rgbComponents) + } + + func testHex8() { + let white = UIColor(rgb: "#FFFFFFFF") + XCTAssertEqual(white.rgbComponents, UIColor.white.rgbComponents) + + let black = UIColor(rgb: "#000000FF") + XCTAssertEqual(black.rgbComponents, UIColor.black.rgbComponents) + + let red = UIColor(rgb: "#FF0000FF") + XCTAssertEqual(red.rgbComponents, UIColor.red.rgbComponents) + } +} + +extension UIColor { + var rgbComponents: [CGFloat]? { + cgColor.converted(to: CGColorSpaceCreateDeviceRGB(), intent: .defaultIntent, options: nil)?.components + } +} diff --git a/LineSDK/LineSDKTests/Message/VideoMessageTests.swift b/LineSDK/LineSDKTests/Message/VideoMessageTests.swift index 72743f7c..ccff5118 100644 --- a/LineSDK/LineSDKTests/Message/VideoMessageTests.swift +++ b/LineSDK/LineSDKTests/Message/VideoMessageTests.swift @@ -1,13 +1,13 @@ // // VideoMessageTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Networking/AdapterTests.swift b/LineSDK/LineSDKTests/Networking/AdapterTests.swift index 70adeb09..05da5e8f 100644 --- a/LineSDK/LineSDKTests/Networking/AdapterTests.swift +++ b/LineSDK/LineSDKTests/Networking/AdapterTests.swift @@ -1,13 +1,13 @@ // // AdapterTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -82,7 +82,7 @@ class AdapterTests: XCTestCase { XCTAssertEqual(result[3].httpMethod, "DELETE") } - func testContentTypeAdapater() { + func testContentTypeAdapter() { let types: [ContentType] = [.none, .formUrlEncoded, .json] let requests: [URLRequest] = .init( repeating: URLRequest(url: URL(string: "linesdktest://sampleurl")!), diff --git a/LineSDK/LineSDKTests/Networking/ChainedPaginatedRequestsTests.swift b/LineSDK/LineSDKTests/Networking/ChainedPaginatedRequestsTests.swift new file mode 100644 index 00000000..0bd5c727 --- /dev/null +++ b/LineSDK/LineSDKTests/Networking/ChainedPaginatedRequestsTests.swift @@ -0,0 +1,171 @@ +// +// ChainedPaginatedRequestsTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class ChainedPaginatedRequestsTests: XCTestCase { + + private var request: ChainedPaginatedRequest! + let config = LoginConfiguration(channelID: "1", universalLinkURL: nil) + + override func setUp() { + super.setUp() + let r = PaginatedRequest() + request = ChainedPaginatedRequest(originalRequest: r) + } + + override func tearDown() { + request = nil + super.tearDown() + } + + func testChainedRequestSuccessOneRequest() { + let delegate = SessionDelegateStub(stubItems: [ + paginatedResponseStub(values: "[1,2,3]") + ]) + let session = Session(configuration: config, delegate: delegate) + session.send(request) { result in + XCTAssertEqual(result.value, [1,2,3]) + } + } + + func testChainedRequestSuccessMultipleRequests() { + let delegate = SessionDelegateStub(stubItems: [ + paginatedResponseStub(values: "[1,2,3]", token: "hello") { + XCTAssertFalse($0.containsPageToken("hello")) + }, + paginatedResponseStub(values: "[4,5,6]") { + XCTAssertTrue($0.containsPageToken("hello")) + } + ]) + let session = Session(configuration: config, delegate: delegate) + session.send(request) { result in + XCTAssertEqual(result.value, [1,2,3,4,5,6]) + } + } + + func testChainedRequestFailsAtBeginning() { + let delegate = SessionDelegateStub(stubItems: [ + paginatedResponseStub(values: "[1,2,3]", code: 500) + ]) + let session = Session(configuration: config, delegate: delegate) + session.send(request) { result in + XCTAssertNotNil(result.error) + XCTAssertTrue(result.error!.isResponseError(statusCode: 500)) + } + } + + func testChainedRequestFailsDuringRequest() { + let delegate = SessionDelegateStub(stubItems: [ + paginatedResponseStub(values: "[1,2,3]", token: "hello"), + paginatedResponseStub(values: "[4,5,6]", code: 500) + ]) + let session = Session(configuration: config, delegate: delegate) + session.send(request) { result in + XCTAssertNotNil(result.error) + XCTAssertTrue(result.error!.isResponseError(statusCode: 500)) + } + } + + func testChainedRequestCallsPageLoadedOnEachParsing() { + let delegate = SessionDelegateStub(stubItems: [ + paginatedResponseStub(values: "[1,2,3]", token: "hello") { + XCTAssertFalse($0.containsPageToken("hello")) + }, + paginatedResponseStub(values: "[4,5,6]") { + XCTAssertTrue($0.containsPageToken("hello")) + } + ]) + let session = Session(configuration: config, delegate: delegate) + var count = 0 + var values: [[Int]] = [] + request.onPageLoaded.delegate(on: self) { (self, response) in + count += 1 + values.append(response.values) + } + session.send(request) { result in + XCTAssertEqual(result.value, [1,2,3,4,5,6]) + XCTAssertEqual(count, 2) + XCTAssertEqual(values, [[1,2,3], [4,5,6]]) + } + } +} + +extension SessionTask { + func containsPageToken(_ value: String) -> Bool { + return self.request.url?.absoluteString.contains("pageToken=\(value)") ?? false + } +} + +private func paginatedResponseStub(values: String, code: Int = 200, verifier: ((SessionTask) throws -> Void)? = nil) -> SessionDelegateStub.StubItem { + let payload = """ + { + "values": \(values) + } + """ + return paginatedResponseStub(payload: payload, code: code, verifier: verifier) +} + +private func paginatedResponseStub( + values: String, + token: String, + code: Int = 200, + verifier: ((SessionTask) throws -> Void)? = nil) -> SessionDelegateStub.StubItem +{ + let payload = """ + { + "values": \(values), + "pageToken": "\(token)" + } + """ + return paginatedResponseStub(payload: payload, code: code, verifier: verifier) +} + +private func paginatedResponseStub( + payload: String, + code: Int = 200, + verifier: ((SessionTask) throws -> Void)? = nil) -> SessionDelegateStub.StubItem +{ + let requestVerifier: SessionDelegateStub.RequestTaskVerifier + if let v = verifier { + requestVerifier = .init(block: v) + } else { + requestVerifier = .empty + } + return SessionDelegateStub.StubItem( + action: .init(string: payload, responseCode: code), + verifier: requestVerifier) +} + +private struct PaginatedRequest: Request { + + let method: HTTPMethod = .get + let path: String = "/" + let authentication: AuthenticateMethod = .none + + struct Response: Decodable, PaginatedResponse { + let values: [Int] + let pageToken: String? + + var paginatedValues: [Int] { return values } + } +} diff --git a/LineSDK/LineSDKTests/Networking/ParameterEncoderTests.swift b/LineSDK/LineSDKTests/Networking/ParameterEncoderTests.swift index ec84fd23..c225d3e5 100644 --- a/LineSDK/LineSDKTests/Networking/ParameterEncoderTests.swift +++ b/LineSDK/LineSDKTests/Networking/ParameterEncoderTests.swift @@ -1,13 +1,13 @@ // // ParameterEncoderTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Networking/PipelineTests.swift b/LineSDK/LineSDKTests/Networking/PipelineTests.swift index 4db89a89..fd4dd48e 100644 --- a/LineSDK/LineSDKTests/Networking/PipelineTests.swift +++ b/LineSDK/LineSDKTests/Networking/PipelineTests.swift @@ -1,13 +1,13 @@ // // PipelineTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -149,8 +149,14 @@ class PipelineTests: XCTestCase { let result1 = pipeline.shouldApply(request: request, data: Data(), response: response) XCTAssertTrue(result1) - - let result2 = pipeline.shouldApply(request: request, data: Data(bytes: [1,2,3]), response: response) + + #if swift(>=5.0) + let data = Data([1,2,3]) + #else + let data = Data(bytes: [1,2,3]) + #endif + + let result2 = pipeline.shouldApply(request: request, data: data, response: response) XCTAssertFalse(result2) try! pipeline.redirect(request: request, data: Data(), response: response) { action in diff --git a/LineSDK/LineSDKTests/Networking/RefreshTokenPipelineTests.swift b/LineSDK/LineSDKTests/Networking/RefreshTokenPipelineTests.swift index caab7ad2..33245138 100644 --- a/LineSDK/LineSDKTests/Networking/RefreshTokenPipelineTests.swift +++ b/LineSDK/LineSDKTests/Networking/RefreshTokenPipelineTests.swift @@ -1,13 +1,13 @@ // // RefreshTokenPipelineTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -57,7 +57,7 @@ class RefreshTokenPipelineTests: XCTestCase { case .restartWithout(let p): XCTAssertNotNil(AccessTokenStore.shared.current) XCTAssertEqual(p, .redirector(self.pipeline)) - XCTAssertTrue(delegate.stubs.isEmpty) + XCTAssertTrue(delegate.stubItems.isEmpty) default: XCTFail("Refresh token pipeline should success.") } diff --git a/LineSDK/LineSDKTests/Networking/SessionTests.swift b/LineSDK/LineSDKTests/Networking/SessionTests.swift index 7c7faf00..673882dc 100644 --- a/LineSDK/LineSDKTests/Networking/SessionTests.swift +++ b/LineSDK/LineSDKTests/Networking/SessionTests.swift @@ -1,13 +1,13 @@ // // SessionTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -232,7 +232,7 @@ class SessionTests: XCTestCase { XCTFail("Should parse to final result after restarting for once.") return } - XCTAssertTrue(delegate.stubs.isEmpty) + XCTAssertTrue(delegate.stubItems.isEmpty) XCTAssertEqual(value.foo, "bar") } waitForExpectations(timeout: 1, handler: nil) @@ -292,7 +292,7 @@ class SessionTests: XCTestCase { XCTFail("Should parse to final result after restarting for once.") return } - XCTAssertTrue(delegate.stubs.isEmpty) + XCTAssertTrue(delegate.stubItems.isEmpty) XCTAssertEqual(value.foo, "bar") } waitForExpectations(timeout: 1, handler: nil) @@ -321,5 +321,36 @@ class SessionTests: XCTestCase { } waitForExpectations(timeout: 1, handler: nil) } + + func testSessionDelegateLock() { + + // There should be no deadlock when sending requests from concurrent queue. + + let expect = expectation(description: "\(#file)_\(#line)") + + let range = 0 ..< 1000 + let delegate = SessionDelegate() + let queue = DispatchQueue(label: "test", attributes: .concurrent) + + let group = DispatchGroup() + for _ in range { + group.enter() + queue.async { + delegate.add( + SessionTask( + session: URLSession.shared, + request: URLRequest(url: URL(string: "https://example.com")!) + ) + ) + group.leave() + } + } + + group.notify(queue: .main) { + expect.fulfill() + } + + waitForExpectations(timeout: 1, handler: nil) + } } diff --git a/LineSDK/LineSDKTests/OpenChat/CountLimitedTextViewTests.swift b/LineSDK/LineSDKTests/OpenChat/CountLimitedTextViewTests.swift new file mode 100644 index 00000000..9902a172 --- /dev/null +++ b/LineSDK/LineSDKTests/OpenChat/CountLimitedTextViewTests.swift @@ -0,0 +1,88 @@ +// +// CountLimitedTextViewTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class CountLimitedTextViewTests: XCTestCase { + + var textView: CountLimitedTextView! + + override func setUp() { + super.setUp() + let style = OpenChatRoomNameTableViewCell.TextViewStyle() + textView = CountLimitedTextView(style: style) + } + + override func tearDown() { + textView = nil + super.tearDown() + } + + func testTextViewCanUpdate() { + var result = "" + textView.onTextUpdated.delegate(on: self) { (self, value) in + result = value + } + textView.text = "LINE SDK" + XCTAssertEqual(result, "LINE SDK") + } + + func testTextViewCanSetMaximumCount() { + XCTAssertNil(textView.maximumCount) + textView.maximumCount = 4 + XCTAssertEqual(textView.maximumCount, 4) + + var result = "" + textView.onTextUpdated.delegate(on: self) { (self, value) in + result = value + } + textView.text = "LINE SDK" + XCTAssertEqual(textView.text, "LINE") + XCTAssertEqual(result, "LINE") + } + + func testTextViewCanTruncateExistingTextOnSetMaximumCount() { + var result = "" + textView.onTextUpdated.delegate(on: self) { (self, value) in + result = value + } + + textView.text = "LINE SDK" + XCTAssertEqual(textView.text, "LINE SDK") + + textView.maximumCount = 4 + XCTAssertEqual(textView.text, "LINE") + XCTAssertEqual(result, "LINE") + } + + func testTextViewNotAcceptingLeadingSpaces() { + textView.maximumCount = 20 + var result = "" + textView.onTextUpdated.delegate(on: self) { (self, value) in + result = value + } + + textView.text = " LINE SDK " + XCTAssertEqual(textView.text, "LINE SDK ") + XCTAssertEqual(result, "LINE SDK ") + } +} diff --git a/LineSDK/LineSDKTests/OpenChat/FormEntryTests.swift b/LineSDK/LineSDKTests/OpenChat/FormEntryTests.swift new file mode 100644 index 00000000..6bd56307 --- /dev/null +++ b/LineSDK/LineSDKTests/OpenChat/FormEntryTests.swift @@ -0,0 +1,105 @@ +// +// FormEntryTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class FormEntryTests: XCTestCase, ViewControllerCompatibleTest { + + var window: UIWindow! + + override func tearDown() { + resetViewController() + super.tearDown() + } + + func testRoomNameTextCanUpdate() { + let text = RoomNameText() + var result = "" + text.onTextUpdated.delegate(on: self) { (self, value) in + result = value + } + + let cell = text.cell as! OpenChatRoomNameTableViewCell + XCTAssertEqual(cell.textView.maximumCount, 50) + cell.textView.text = "LINE SDK" + XCTAssertEqual(result, "LINE SDK") + } + + func testRoomDescriptionTextCanUpdate() { + let text = RoomDescriptionText() + var result = "" + text.onTextUpdated.delegate(on: self) { (self, value) in + result = value + } + + let cell = text.cell as! OpenChatRoomDescriptionTableViewCell + XCTAssertEqual(cell.textView.maximumCount, 200) + cell.textView.text = "LINE SDK Description" + XCTAssertEqual(result, "LINE SDK Description") + } + + func testOptionCanSelect() { + let option = Option(title: "test", options: [1,2,3]) + var result: Int? + option.onValueChange.delegate(on: self) { (self, value) in + result = value + } + XCTAssertEqual(option.selectedOption, 1) + option.selectedOption = 3 + XCTAssertEqual(result, 3) + } + + func testOptionCanPresentOptionSelecting() { + let expect = expectation(description: "\(#file)_\(#line)") + let option = Option(title: "test", options: [1,2,3]) + let viewController = setupViewController() + option.onPresenting.delegate(on: self) { (self, _) -> UIViewController in + return viewController + } + + option.tapCell() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + XCTAssertNotNil(viewController.presentedViewController) + XCTAssertViewController( + viewController.presentedViewController!, + isKindOf: OptionSelectingViewController.self + ) + expect.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + } + + func testToggleCanUpdateValue() throws { + let toggle = Toggle(title: "test", initialValue: true) + + var result: Bool? + toggle.onValueChange.delegate(on: self) { (self, value) in + result = value + } + + let toggleView = toggle.cell.accessoryView as! UISwitch + XCTAssertTrue(toggleView.isOn) + toggleView.isOn = false + toggle.switchValueDidChange(toggleView) + XCTAssertFalse(result!) + } +} diff --git a/LineSDK/LineSDKTests/OpenChat/OpenChatControllerTests.swift b/LineSDK/LineSDKTests/OpenChat/OpenChatControllerTests.swift new file mode 100644 index 00000000..a1beb258 --- /dev/null +++ b/LineSDK/LineSDKTests/OpenChat/OpenChatControllerTests.swift @@ -0,0 +1,150 @@ +// +// OpenChatCreatingControllerTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class OpenChatCreatingControllerTests: XCTestCase, ViewControllerCompatibleTest { + + var window: UIWindow! + + override func setUp() { + super.setUp() + LoginManager.shared.setup(channelID: "123", universalLinkURL: nil) + } + + override func tearDown() { + LoginManager.shared.reset() + resetViewController() + super.tearDown() + } + + func testLocalAuthorizationStatus() { + let status1 = OpenChatCreatingController + .localAuthorizationStatusForOpenChat(permissions: []) + guard case .lackOfPermissions(let p1) = status1 else { + XCTFail() + return + } + XCTAssertEqual(p1, [.openChatTermStatus, .openChatRoomCreateAndJoin]) + + let status2 = OpenChatCreatingController + .localAuthorizationStatusForOpenChat( + permissions: [.openChatTermStatus, .openChatRoomCreateAndJoin]) + guard case .authorized = status2 else { + XCTFail() + return + } + + let status3 = OpenChatCreatingController + .localAuthorizationStatusForOpenChat(permissions: [.openChatTermStatus]) + guard case .lackOfPermissions(let p2) = status3 else { + XCTFail() + return + } + XCTAssertEqual(p2, [.openChatRoomCreateAndJoin]) + } + + func testCanPresentTermAgreementAlertControllerWhenNotAgreed() { + + let expect = expectation(description: "\(#file)_\(#line)") + + let delegateStub = SessionDelegateStub(stubs: [ + // GetOpenChatTermAgreementStatusRequest -> false + .init(data: "{\"agreed\": false}".data(using: .utf8)!, responseCode: 200) + ]) + Session._shared = Session( + configuration: LoginConfiguration.shared, + delegate: delegateStub + ) + setupTestToken() + + let viewController = setupViewController() + + let controller = OpenChatCreatingController() + controller.loadAndPresent(in: viewController) { result in + expect.fulfill() + switch result { + case .success(let resultVC): + XCTAssertNotNil(viewController.presentedViewController) + XCTAssertEqual(resultVC, viewController.presentedViewController) + XCTAssertViewController( + resultVC, + isKindOf: UIAlertController.self + ) + case .failure(let error): + XCTFail("\(error)") + } + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func testCanPresentCreatingViewControllerWhenAgreed() { + let expect = expectation(description: "\(#file)_\(#line)") + + let delegateStub = SessionDelegateStub(stubs: [ + // GetOpenChatTermAgreementStatusRequest -> true + .init(data: "{\"agreed\": true}".data(using: .utf8)!, responseCode: 200) + ]) + Session._shared = Session( + configuration: LoginConfiguration.shared, + delegate: delegateStub + ) + setupTestToken() + + let viewController = setupViewController() + + let controller = OpenChatCreatingController() + controller.loadAndPresent(in: viewController) { result in + expect.fulfill() + switch result { + case .success: + XCTAssertNotNil(viewController.presentedViewController) + XCTAssertViewController( + viewController.presentedViewController!, + isKindOf: OpenChatRoomInfoViewController.self + ) + case .failure(let error): + XCTFail("\(error)") + } + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func testOptionSelectingCanSelectOption() { + let optionSelecting = OptionSelectingViewController.createViewController( + data: [1,2,3], selected: 2 + ) + let tableViewController = optionSelecting.1 + + var result: Int? + tableViewController.onSelected.delegate(on: self) { (self, value) in + result = value + } + tableViewController.tableView.delegate?.tableView?( + tableViewController.tableView, didSelectRowAt: .init(row: 0, section: 0) + ) + + XCTAssertEqual(result, 1) + } +} diff --git a/LineSDK/LineSDKTests/OpenChat/OpenChatCreatingFormItemTests.swift b/LineSDK/LineSDKTests/OpenChat/OpenChatCreatingFormItemTests.swift new file mode 100644 index 00000000..1900bb58 --- /dev/null +++ b/LineSDK/LineSDKTests/OpenChat/OpenChatCreatingFormItemTests.swift @@ -0,0 +1,45 @@ +// +// OpenChatCreatingFormItemTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class OpenChatCreatingFormItemTests: XCTestCase { + + func testCanNormalize() { + var item = OpenChatCreatingFormItem( + roomName: "Hello ", + roomDescription: " world ", + category: .notSelected, + allowSearch: false, + userName: "my name " + ) + item.normalize() + + XCTAssertEqual(item.roomName, "Hello") + XCTAssertEqual(item.roomDescription, "world") + XCTAssertEqual(item.userName, "my name") + + XCTAssertEqual(item.category, .notSelected) + XCTAssertFalse(item.allowSearch) + } + +} diff --git a/LineSDK/LineSDKTests/Sharing/ColumnDataStoreTests.swift b/LineSDK/LineSDKTests/Sharing/ColumnDataStoreTests.swift new file mode 100644 index 00000000..66a04a3e --- /dev/null +++ b/LineSDK/LineSDKTests/Sharing/ColumnDataStoreTests.swift @@ -0,0 +1,194 @@ +// +// ColumnDataStoreTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class ColumnDataStoreTests: XCTestCase { + + var store: ColumnDataStore! + var token: NotificationToken! + + override func setUp() { + super.setUp() + store = ColumnDataStore(columnCount: 3) + } + + override func tearDown() { + store = nil + token = nil + super.tearDown() + } + + func testAppendData() { + store.append(data: [1,2,3], to: 0) + store.append(data: [4,5,6], to: 1) + store.append(data: [7,8,9], to: 2) + + XCTAssertEqual(store.data(atColumn: 0), [1, 2, 3]) + XCTAssertEqual(store.data(atColumn: 1), [4, 5, 6]) + XCTAssertEqual(store.data(atColumn: 2), [7, 8, 9]) + + store.append(data: [3,2,1], to: 0) + XCTAssertEqual(store.data(atColumn: 0), [1, 2, 3, 3, 2, 1]) + } + + func testGetData() { + store.append(data: [1,2,3], to: 0) + XCTAssertEqual(store.data(atColumn: 0), [1, 2, 3]) + XCTAssertEqual(store.data(atColumn: 1), []) + + XCTAssertEqual(store.data(atColumn: 0, row: 1), 2) + + let index = ColumnDataStore.ColumnIndex(column: 0, row: 1) + XCTAssertEqual(store.data(at: index), 2) + } + + func testSelectData() { + store.append(data: [1,2,3], to: 0) + XCTAssertTrue(store.selectedIndexes.isEmpty) + + let performed = store.toggleSelect(atColumn: 0, row: 1) + XCTAssertTrue(performed) + + XCTAssertEqual(store.selectedIndexes.count, 1) + XCTAssertTrue(store.isSelected(at: .init(column: 0, row: 1))) + + let deselectPerformed = store.toggleSelect(atColumn: 0, row: 1) + XCTAssertTrue(deselectPerformed) + XCTAssertEqual(store.selectedIndexes.count, 0) + XCTAssertFalse(store.isSelected(at: .init(column: 0, row: 1))) + } + + func testMaximumSelection() { + store.maximumSelectedCount = 2 + store.append(data: [1,2,3], to: 0) + + // Select two elements. + XCTAssertTrue(store.toggleSelect(atColumn: 0, row: 0)) + XCTAssertTrue(store.toggleSelect(atColumn: 0, row: 1)) + + // `maximumSelectedCount` count reached. + XCTAssertFalse(store.toggleSelect(atColumn: 0, row: 2)) + + // Unselect one. + XCTAssertTrue(store.toggleSelect(atColumn: 0, row: 1)) + // Select the one failed again. + XCTAssertTrue(store.toggleSelect(atColumn: 0, row: 2)) + + // `maximumSelectedCount` count reached. + XCTAssertFalse(store.toggleSelect(atColumn: 0, row: 1)) + + XCTAssertTrue(store.isSelected(at: .init(column: 0, row: 0))) + XCTAssertFalse(store.isSelected(at: .init(column: 0, row: 1))) + XCTAssertTrue(store.isSelected(at: .init(column: 0, row: 2))) + } + + func testFilterValues() { + store.append(data: [1,2,3], to: 0) + store.append(data: [4,5,6], to: 1) + store.append(data: [7,8,9], to: 2) + + let result = store.indexes { $0 % 2 == 0 } + let values = result.map { indexes in indexes.map { store.data(at: $0) } } + XCTAssertEqual(values, [[2], [4, 6], [8]]) + } + + func testAppendingDataNotification() { + let expect = expectation(description: "\(#file)_\(#line)") + + token = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidAppendData, + object: nil, + queue: .main) + { + noti in + let range = noti.userInfo?[LineSDKNotificationKey.appendDataIndexRange] + as? ColumnDataStore.AppendingIndexRange + XCTAssertNotNil(range) + XCTAssertEqual(range!.column, 0) + XCTAssertEqual(range!.startIndex, 0) + XCTAssertEqual(range!.endIndex, 3) + expect.fulfill() + } + + store.append(data: [1,2,3], to: 0) + waitForExpectations(timeout: 1, handler: nil) + } + + func testSelectingDataNotification() { + let expect = expectation(description: "\(#file)_\(#line)") + + token = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidSelect, + object: nil, + queue: .main) + { + noti in + let index = noti.userInfo?[LineSDKNotificationKey.selectingIndex] + as? ColumnDataStore.ColumnIndex + XCTAssertNotNil(index) + XCTAssertEqual(index!.column, 0) + XCTAssertEqual(index!.row, 2) + expect.fulfill() + } + + store.append(data: [1,2,3], to: 0) + store.toggleSelect(atColumn: 0, row: 2) + waitForExpectations(timeout: 1, handler: nil) + } + + func testDeselectingDataNotification() { + let expect = expectation(description: "\(#file)_\(#line)") + + token = NotificationCenter.default.addObserver( + forName: .columnDataStoreDidDeselect, + object: nil, + queue: .main) + { + noti in + let index = noti.userInfo?[LineSDKNotificationKey.selectingIndex] + as? ColumnDataStore.ColumnIndex + XCTAssertNotNil(index) + XCTAssertEqual(index!.column, 0) + XCTAssertEqual(index!.row, 2) + expect.fulfill() + } + + store.append(data: [1,2,3], to: 0) + store.toggleSelect(atColumn: 0, row: 2) + store.toggleSelect(atColumn: 0, row: 2) + waitForExpectations(timeout: 1, handler: nil) + } + + func testAllSelectedData() { + store.append(data: [1,2,3], to: 0) + store.append(data: [4,5,6], to: 1) + store.append(data: [7,8,9], to: 2) + + store.toggleSelect(atColumn: 0, row: 0) + store.toggleSelect(atColumn: 1, row: 1) + store.toggleSelect(atColumn: 2, row: 2) + + let allSelected = store.selectedData + XCTAssertEqual(allSelected, [1, 5, 9]) + } +} diff --git a/LineSDK/LineSDKTests/Sharing/PageTabViewTests.swift b/LineSDK/LineSDKTests/Sharing/PageTabViewTests.swift new file mode 100644 index 00000000..f6f0119d --- /dev/null +++ b/LineSDK/LineSDKTests/Sharing/PageTabViewTests.swift @@ -0,0 +1,47 @@ +// +// PageTabViewTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class PageTabViewTests: XCTestCase { + + func testUnderlineWidth() { + let titleWidths: [CGFloat] = [20,40,60] + + // test leftmost + XCTAssertEqual(20, PageTabView.Underline.preferredWidth(progress: -0.1, titleWidths: titleWidths)) + + // test rightmost + XCTAssertEqual(60, PageTabView.Underline.preferredWidth(progress: 2.1, titleWidths: titleWidths)) + + // test middle + XCTAssertEqual(40, PageTabView.Underline.preferredWidth(progress: 1, titleWidths: titleWidths)) + + // test left half + var w: CGFloat = 0.9 * 20 + 0.1 * 40 + XCTAssertEqual(w, PageTabView.Underline.preferredWidth(progress: 0.1, titleWidths: titleWidths)) + + // test right half + w = 0.8 * 40 + 0.2 * 60 + XCTAssertEqual(w, PageTabView.Underline.preferredWidth(progress: 1.2, titleWidths: titleWidths)) + } +} diff --git a/LineSDK/LineSDKTests/Sharing/ShareControllerTests.swift b/LineSDK/LineSDKTests/Sharing/ShareControllerTests.swift new file mode 100644 index 00000000..3c33a9f2 --- /dev/null +++ b/LineSDK/LineSDKTests/Sharing/ShareControllerTests.swift @@ -0,0 +1,56 @@ +// +// ShareControllerTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class ShareControllerTests: XCTestCase { + + func testLocalAuthorizationStatus() { + + let status1 = ShareViewController + .localAuthorizationStatusForSendingMessage(permissions: []) + guard case .lackOfPermissions(let p1) = status1 else { + XCTFail() + return + } + XCTAssertEqual(p1, [.oneTimeShare]) + + let status2 = ShareViewController + .localAuthorizationStatusForSendingMessage(permissions: [.oneTimeShare]) + guard case .authorized = status2 else { + XCTFail() + return + } + } + + func testNoTokenStatus() { + LoginManager.shared.setup(channelID: "123", universalLinkURL: nil) + defer { LoginManager.shared.reset() } + + let status = ShareViewController + .localAuthorizationStatusForSendingMessage() + guard case .lackOfToken = status else { + XCTFail() + return + } + } +} diff --git a/LineSDK/LineSDKTests/Utils/APITests.swift b/LineSDK/LineSDKTests/Utils/APITests.swift index b485d234..70e2984f 100644 --- a/LineSDK/LineSDKTests/Utils/APITests.swift +++ b/LineSDK/LineSDKTests/Utils/APITests.swift @@ -1,13 +1,13 @@ // // APITests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Utils/AssertionHelpers.swift b/LineSDK/LineSDKTests/Utils/AssertionHelpers.swift new file mode 100644 index 00000000..bc8cf41d --- /dev/null +++ b/LineSDK/LineSDKTests/Utils/AssertionHelpers.swift @@ -0,0 +1,49 @@ +// +// AssertionHelpers.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import XCTest + +func XCTAssertViewController( + _ input: @autoclosure () -> UIViewController, + isKindOf viewControllerType: T.Type, + file: StaticString = #file, + line: UInt = #line +) +{ + let inputViewController = input() + if inputViewController is T { + return + } + + if let navigation = inputViewController as? UINavigationController, + navigation.topViewController is T + { + return + } + + XCTFail( + "The input view controller (\(inputViewController)) or its top view controller is not an instance " + + "of `\(viewControllerType)` or any class that inherits from that class", + file: file, + line: line + ) +} diff --git a/LineSDK/LineSDKTests/Utils/LoginManagerExtension.swift b/LineSDK/LineSDKTests/Utils/LoginManagerExtension.swift index 3d643c18..47662987 100644 --- a/LineSDK/LineSDKTests/Utils/LoginManagerExtension.swift +++ b/LineSDK/LineSDKTests/Utils/LoginManagerExtension.swift @@ -1,13 +1,13 @@ // // LoginManagerExtension.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Utils/MessageSample.swift b/LineSDK/LineSDKTests/Utils/MessageSample.swift index f2cb68bd..25346768 100644 --- a/LineSDK/LineSDKTests/Utils/MessageSample.swift +++ b/LineSDK/LineSDKTests/Utils/MessageSample.swift @@ -1,13 +1,13 @@ // // MessageSample.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Utils/RequestStubs.swift b/LineSDK/LineSDKTests/Utils/RequestStubs.swift index f17a8f54..448293cd 100644 --- a/LineSDK/LineSDKTests/Utils/RequestStubs.swift +++ b/LineSDK/LineSDKTests/Utils/RequestStubs.swift @@ -1,13 +1,13 @@ // // RequestStubs.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDKTests/Utils/ResponseDataStub.swift b/LineSDK/LineSDKTests/Utils/ResponseDataStub.swift index 505eced9..f686c072 100644 --- a/LineSDK/LineSDKTests/Utils/ResponseDataStub.swift +++ b/LineSDK/LineSDKTests/Utils/ResponseDataStub.swift @@ -1,13 +1,13 @@ // // ResponseDataStub.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/LineSDK/Login/Request/PostOTPRequest.swift b/LineSDK/LineSDKTests/Utils/ResultUtils.swift similarity index 58% rename from LineSDK/LineSDK/Login/Request/PostOTPRequest.swift rename to LineSDK/LineSDKTests/Utils/ResultUtils.swift index 25d790c9..b2aa7765 100644 --- a/LineSDK/LineSDK/Login/Request/PostOTPRequest.swift +++ b/LineSDK/LineSDKTests/Utils/ResultUtils.swift @@ -1,13 +1,14 @@ + // -// PostOTPRequest.swift +// ResultUtils.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -20,17 +21,21 @@ // import Foundation +import LineSDK -struct PostOTPRequest: Request { - let channelID: String - - let method: HTTPMethod = .post - let path = "/oauth2/v2.1/otp" - let contentType: ContentType = .formUrlEncoded - let authentication: AuthenticateMethod = .none - - var parameters: [String : Any]? { return ["client_id": channelID] } +extension Result { - typealias Response = OneTimePassword -} + var value: Success? { + switch self { + case .success(let success): return success + case .failure: return nil + } + } + var error: Failure? { + switch self { + case .success: return nil + case .failure(let failure): return failure + } + } +} diff --git a/LineSDK/LineSDKTests/Utils/ResultUtilsTests.swift b/LineSDK/LineSDKTests/Utils/ResultUtilsTests.swift new file mode 100644 index 00000000..e41d1144 --- /dev/null +++ b/LineSDK/LineSDKTests/Utils/ResultUtilsTests.swift @@ -0,0 +1,156 @@ +// +// ResultUtilsTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class ResultUtilsTests: XCTestCase { + + enum E: Error { + case foo + case bar + } + + enum AnotherE: Error { + case baz + } + + let success = Result.success(1) + let failure = Result.failure(.foo) + + func testResultGet() { + + let value = try! success.get() + XCTAssertEqual(value, 1) + + do { + _ = try failure.get() + XCTFail("Cannot failure value.") + } catch { + XCTAssertEqual(error as! E, E.foo) + } + } + + func testResultMap() { + let value = success.map { $0 + 1 } + XCTAssertEqual(value, .success(2)) + + let fValue = failure.map { $0 + 1 } + XCTAssertEqual(fValue, .failure(.foo)) + } + + func testResultMapError() { + let value = success.mapError { _ in return E.bar } + XCTAssertEqual(value, .success(1)) + + let fValue = failure.mapError { _ in return E.bar } + XCTAssertEqual(fValue, .failure(.bar)) + } + + func testResultFlatMap() { + let value = success.flatMap { .success(String($0)) } + XCTAssertEqual(value, .success("1")) + + let fValue = failure.flatMap { .success(String($0)) } + XCTAssertEqual(fValue, .failure(.foo)) + } + + func testResultFlatMapError() { + let value = success.flatMapError { _ in return .failure(AnotherE.baz) } + XCTAssertEqual(value, .success(1)) + + let fValue = failure.flatMapError { _ in return .failure(AnotherE.baz) } + XCTAssertEqual(fValue, .failure(.baz)) + } + + func testResultMatchSuccess() { + let value = success.match( + onSuccess: { (num: Int) -> Int in + XCTAssertEqual(num, 1) + return num + 1 + }, + onFailure: { _ in + XCTFail() + return -1 + } + ) + XCTAssertEqual(value, 2) + } + + func testResultMatchFailure() { + let value = failure.match( + onSuccess: { _ -> Int in + XCTFail() + return -1 + }, + onFailure: { error in + XCTAssertEqual(error, .foo) + return 0 + } + ) + XCTAssertEqual(value, 0) + } + + func testResultMatchWithFolder() { + let folder: (Int?, Error?) -> Bool = {num, err in + if let _ = num { + return true + } else { + return false + } + } + let value1 = success.match(with: folder) + XCTAssertEqual(value1, true) + + let value2 = failure.match(with: folder) + XCTAssertEqual(value2, false) + } + + func testResultMatchSuccessWithFolder() { + let folder: (Int?) -> Bool = {num in + if let _ = num { + return true + } else { + return false + } + } + let value1 = success.matchSuccess(with: folder) + XCTAssertEqual(value1, true) + + let value2 = failure.matchSuccess(with: folder) + XCTAssertEqual(value2, false) + } + + func testResultMatchFailureWithFolder() { + let folder: (Error?) -> Bool = {error in + if let _ = error { + return false + } else { + return true + } + } + let value1 = success.matchFailure(with: folder) + XCTAssertEqual(value1, true) + + let value2 = failure.matchFailure(with: folder) + XCTAssertEqual(value2, false) + } +} diff --git a/LineSDK/LineSDKTests/Utils/SessionDelegateStub.swift b/LineSDK/LineSDKTests/Utils/SessionDelegateStub.swift index e69d67f0..a076259f 100644 --- a/LineSDK/LineSDKTests/Utils/SessionDelegateStub.swift +++ b/LineSDK/LineSDKTests/Utils/SessionDelegateStub.swift @@ -1,13 +1,13 @@ // // SessionDelegateStub.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -33,13 +33,26 @@ extension HTTPURLResponse { } class SessionDelegateStub: NSObject, SessionDelegateType { - + + struct StubItem { + let action: Either + let verifier: RequestTaskVerifier + } + + struct RequestTaskVerifier { + let block: (SessionTask) throws -> Void + func verify(sessionTask: SessionTask) throws { + try block(sessionTask) + } + + static let empty = RequestTaskVerifier { _ in } + } + enum Either { case response(Data, HTTPURLResponse) case error(Error) - init(data: Data, response: HTTPURLResponse? = nil) { - let response = response ?? .responseFromCode(200) + init(data: Data, response: HTTPURLResponse) { self = .response(data, response) } @@ -48,7 +61,7 @@ class SessionDelegateStub: NSObject, SessionDelegateType { self.init(data: data, response: response) } - init(string: String, response: HTTPURLResponse? = nil) { + init(string: String, response: HTTPURLResponse) { let data = string.data(using: .utf8)! self.init(data: data, response: response) } @@ -59,23 +72,34 @@ class SessionDelegateStub: NSObject, SessionDelegateType { } } - var stubs: [Either] + var stubItems: [StubItem] - init(stub: Either) { - self.stubs = [stub] + convenience init(stub: Either) { + self.init(stubs: [stub]) } - init(stubs: [Either]) { - self.stubs = stubs + convenience init(stubs: [Either]) { + self.init(stubItems: stubs.map { StubItem(action: $0, verifier: .empty) }) + } + + init(stubItems: [StubItem]) { + self.stubItems = stubItems } func shouldTaskStart(_ task: SessionTask) -> Bool { - guard !stubs.isEmpty else { + guard !stubItems.isEmpty else { fatalError("Stubs are not enough. Make sure you have enough response stubs prepared for task: \(task)") } - let stub = stubs.removeFirst() - switch stub { + let stub = stubItems.removeFirst() + + do { + try stub.verifier.verify(sessionTask: task) + } catch { + fatalError("Stub request verifying failed without handling for task: \(task)") + } + + switch stub.action { case .response(let data, let response): task.onResult.call((data, response, nil)) case .error(let error): diff --git a/LineSDK/LineSDKTests/Utils/StringExtensionTests.swift b/LineSDK/LineSDKTests/Utils/StringExtensionTests.swift new file mode 100644 index 00000000..e2673f5f --- /dev/null +++ b/LineSDK/LineSDKTests/Utils/StringExtensionTests.swift @@ -0,0 +1,60 @@ +// +// StringExtensionTests.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest +@testable import LineSDK + +class StringExtensionTests: XCTestCase { + + func testEntityIDValid() { + + XCTAssertFalse("".isValid) + XCTAssertFalse("ABC_123".isValid) + XCTAssertFalse("ABC 123".isValid) + XCTAssertFalse("../abc".isValid) + XCTAssertFalse("U88becdf05a8afd6_cf578703fd22f461e".isValid) + + XCTAssertTrue("U88becdf05a8afd6cf578703fd22f461e".isValid) + } + + func testPrefixNormalized() { + XCTAssertEqual("abc".prefixNormalized, "abc") + XCTAssertEqual(" abc".prefixNormalized, "abc") + XCTAssertEqual("abc ".prefixNormalized, "abc ") + XCTAssertEqual(" abc ".prefixNormalized, "abc ") + } + + func testNormalized() { + XCTAssertEqual("abc".normalized, "abc") + XCTAssertEqual(" abc".normalized, "abc") + XCTAssertEqual("abc ".normalized, "abc") + XCTAssertEqual(" abc ".normalized, "abc") + } + + func testTrimmingUpperCount() { + XCTAssertEqual("0123456".trimming(upper: 100), "0123456") + XCTAssertEqual("0123456".trimming(upper: 3), "012") + XCTAssertEqual("0123456".trimming(upper: 7), "0123456") + XCTAssertEqual("0123456".trimming(upper: 0), "") + + XCTAssertEqual("一二三四五六".trimming(upper: 3), "一二三") + } +} diff --git a/LineSDK/LineSDKTests/Utils/ViewControllerCompatibleTest.swift b/LineSDK/LineSDKTests/Utils/ViewControllerCompatibleTest.swift index 1f70fa43..e48397eb 100644 --- a/LineSDK/LineSDKTests/Utils/ViewControllerCompatibleTest.swift +++ b/LineSDK/LineSDKTests/Utils/ViewControllerCompatibleTest.swift @@ -1,13 +1,13 @@ // // ViewControllerCompatibleTest.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -22,7 +22,7 @@ import Foundation import UIKit -protocol ViewControllerCompatibleTest: class { +protocol ViewControllerCompatibleTest: AnyObject { var window: UIWindow! { get set } } @@ -30,12 +30,17 @@ extension ViewControllerCompatibleTest { func setupViewController() -> UIViewController { let rootViewController = UIViewController() - rootViewController.loadViewIfNeeded() + return setupViewController(rootViewController) + } + + func setupViewController(_ input: UIViewController) -> UIViewController { + + input.loadViewIfNeeded() window = UIWindow(frame: UIScreen.main.bounds) - window.rootViewController = rootViewController + window.rootViewController = input window.makeKeyAndVisible() - return rootViewController + return input } func resetViewController() { diff --git a/LineSDK/TestHost/AppDelegate.swift b/LineSDK/TestHost/AppDelegate.swift index c48b872b..5c45a929 100644 --- a/LineSDK/TestHost/AppDelegate.swift +++ b/LineSDK/TestHost/AppDelegate.swift @@ -1,13 +1,13 @@ // // AppDelegate.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDK/TestHost/Info.plist b/LineSDK/TestHost/Info.plist index d73b0211..169e6c71 100644 --- a/LineSDK/TestHost/Info.plist +++ b/LineSDK/TestHost/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.0.0 + 5.11.2 CFBundleVersion - 402 + 1208 LSRequiresIPhoneOS UIRequiredDeviceCapabilities diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/project.pbxproj b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/project.pbxproj new file mode 100644 index 00000000..2b7c12d3 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/project.pbxproj @@ -0,0 +1,434 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 5E2BB2A0286765790098FB56 /* LineSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E2BB29F286765790098FB56 /* LineSDK.framework */; }; + 5E2BB2A1286765790098FB56 /* LineSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5E2BB29F286765790098FB56 /* LineSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 5E2BB2A52867C5D30098FB56 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2BB2A42867C5D30098FB56 /* API.swift */; }; + 5E2BB2A72868349D0098FB56 /* LineLoginButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2BB2A62868349D0098FB56 /* LineLoginButton.swift */; }; + 5E2BB2A9286898600098FB56 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2BB2A8286898600098FB56 /* ProfileView.swift */; }; + 5E2BB2AB28689ADA0098FB56 /* AuthorizatonStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2BB2AA28689ADA0098FB56 /* AuthorizatonStore.swift */; }; + 5E2BB2AD28689D6F0098FB56 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2BB2AC28689D6F0098FB56 /* LoginView.swift */; }; + 5E2BB2B1286949C90098FB56 /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2BB2B0286949C90098FB56 /* AlertItem.swift */; }; + 5E8DCD242861D48500E6F704 /* LineSDKSampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8DCD232861D48500E6F704 /* LineSDKSampleApp.swift */; }; + 5E8DCD262861D48500E6F704 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8DCD252861D48500E6F704 /* ContentView.swift */; }; + 5E8DCD282861D48700E6F704 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E8DCD272861D48700E6F704 /* Assets.xcassets */; }; + 5E8DCD2B2861D48800E6F704 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E8DCD2A2861D48800E6F704 /* Preview Assets.xcassets */; }; + 5E9284CC28726EE2001EFE25 /* LoginManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E9284CB28726EE2001EFE25 /* LoginManager+Extension.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 5E2BB2A2286765790098FB56 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 5E2BB2A1286765790098FB56 /* LineSDK.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 5E2BB29A28675BEC0098FB56 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 5E2BB29F286765790098FB56 /* LineSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LineSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5E2BB2A42867C5D30098FB56 /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; + 5E2BB2A62868349D0098FB56 /* LineLoginButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineLoginButton.swift; sourceTree = ""; }; + 5E2BB2A8286898600098FB56 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; + 5E2BB2AA28689ADA0098FB56 /* AuthorizatonStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizatonStore.swift; sourceTree = ""; }; + 5E2BB2AC28689D6F0098FB56 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; + 5E2BB2B0286949C90098FB56 /* AlertItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertItem.swift; sourceTree = ""; }; + 5E8DCD202861D48400E6F704 /* LineSDKSample-SwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "LineSDKSample-SwiftUI.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5E8DCD232861D48500E6F704 /* LineSDKSampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKSampleApp.swift; sourceTree = ""; }; + 5E8DCD252861D48500E6F704 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 5E8DCD272861D48700E6F704 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 5E8DCD2A2861D48800E6F704 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 5E9284CB28726EE2001EFE25 /* LoginManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginManager+Extension.swift"; sourceTree = ""; }; + 5EA44CC92865BEF300AF73FB /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5E8DCD1D2861D48400E6F704 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E2BB2A0286765790098FB56 /* LineSDK.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5E2BB29B2867627B0098FB56 /* Login */ = { + isa = PBXGroup; + children = ( + 5E2BB2AA28689ADA0098FB56 /* AuthorizatonStore.swift */, + 5E2BB2A62868349D0098FB56 /* LineLoginButton.swift */, + 5E2BB2AC28689D6F0098FB56 /* LoginView.swift */, + 5E2BB2A8286898600098FB56 /* ProfileView.swift */, + ); + path = Login; + sourceTree = ""; + }; + 5E2BB29E286765790098FB56 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5E2BB29F286765790098FB56 /* LineSDK.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5E2BB2A32867C5B20098FB56 /* API */ = { + isa = PBXGroup; + children = ( + 5E2BB2A42867C5D30098FB56 /* API.swift */, + ); + path = API; + sourceTree = ""; + }; + 5E2BB2AF286949AC0098FB56 /* Utils */ = { + isa = PBXGroup; + children = ( + 5E2BB2B0286949C90098FB56 /* AlertItem.swift */, + 5E9284CB28726EE2001EFE25 /* LoginManager+Extension.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 5E8DCD172861D48400E6F704 = { + isa = PBXGroup; + children = ( + 5E8DCD222861D48500E6F704 /* LineSDKSample-SwiftUI */, + 5E8DCD212861D48400E6F704 /* Products */, + 5E2BB29E286765790098FB56 /* Frameworks */, + ); + sourceTree = ""; + }; + 5E8DCD212861D48400E6F704 /* Products */ = { + isa = PBXGroup; + children = ( + 5E8DCD202861D48400E6F704 /* LineSDKSample-SwiftUI.app */, + ); + name = Products; + sourceTree = ""; + }; + 5E8DCD222861D48500E6F704 /* LineSDKSample-SwiftUI */ = { + isa = PBXGroup; + children = ( + 5E2BB29A28675BEC0098FB56 /* Info.plist */, + 5E8DCD252861D48500E6F704 /* ContentView.swift */, + 5E8DCD232861D48500E6F704 /* LineSDKSampleApp.swift */, + 5E8DCD272861D48700E6F704 /* Assets.xcassets */, + 5EA44CC92865BEF300AF73FB /* Config.xcconfig */, + 5E2BB2A32867C5B20098FB56 /* API */, + 5E2BB29B2867627B0098FB56 /* Login */, + 5E8DCD292861D48800E6F704 /* Preview Content */, + 5E2BB2AF286949AC0098FB56 /* Utils */, + ); + path = "LineSDKSample-SwiftUI"; + sourceTree = ""; + }; + 5E8DCD292861D48800E6F704 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 5E8DCD2A2861D48800E6F704 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5E8DCD1F2861D48400E6F704 /* LineSDKSample-SwiftUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5E8DCD2E2861D48800E6F704 /* Build configuration list for PBXNativeTarget "LineSDKSample-SwiftUI" */; + buildPhases = ( + 5E8DCD1C2861D48400E6F704 /* Sources */, + 5E8DCD1D2861D48400E6F704 /* Frameworks */, + 5E8DCD1E2861D48400E6F704 /* Resources */, + 5E2BB2A2286765790098FB56 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "LineSDKSample-SwiftUI"; + productName = LineSDKSwiftUISample; + productReference = 5E8DCD202861D48400E6F704 /* LineSDKSample-SwiftUI.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 5E8DCD182861D48400E6F704 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1340; + LastUpgradeCheck = 1340; + TargetAttributes = { + 5E8DCD1F2861D48400E6F704 = { + CreatedOnToolsVersion = 13.4; + }; + }; + }; + buildConfigurationList = 5E8DCD1B2861D48400E6F704 /* Build configuration list for PBXProject "LineSDKSample-SwiftUI" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 5E8DCD172861D48400E6F704; + productRefGroup = 5E8DCD212861D48400E6F704 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5E8DCD1F2861D48400E6F704 /* LineSDKSample-SwiftUI */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5E8DCD1E2861D48400E6F704 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E8DCD2B2861D48800E6F704 /* Preview Assets.xcassets in Resources */, + 5E8DCD282861D48700E6F704 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5E8DCD1C2861D48400E6F704 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E2BB2B1286949C90098FB56 /* AlertItem.swift in Sources */, + 5E2BB2A72868349D0098FB56 /* LineLoginButton.swift in Sources */, + 5E8DCD262861D48500E6F704 /* ContentView.swift in Sources */, + 5E8DCD242861D48500E6F704 /* LineSDKSampleApp.swift in Sources */, + 5E2BB2A52867C5D30098FB56 /* API.swift in Sources */, + 5E9284CC28726EE2001EFE25 /* LoginManager+Extension.swift in Sources */, + 5E2BB2A9286898600098FB56 /* ProfileView.swift in Sources */, + 5E2BB2AD28689D6F0098FB56 /* LoginView.swift in Sources */, + 5E2BB2AB28689ADA0098FB56 /* AuthorizatonStore.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 5E8DCD2C2861D48800E6F704 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 5E8DCD2D2861D48800E6F704 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 5E8DCD2F2861D48800E6F704 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5EA44CC92865BEF300AF73FB /* Config.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"LineSDKSample-SwiftUI/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "LineSDKSample-SwiftUI/Info.plist"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.linecorp.linesdk.sample${SAMPLE_CODE_DISAMBIGUATOR}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 5E8DCD302861D48800E6F704 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5EA44CC92865BEF300AF73FB /* Config.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"LineSDKSample-SwiftUI/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "LineSDKSample-SwiftUI/Info.plist"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.linecorp.linesdk.sample${SAMPLE_CODE_DISAMBIGUATOR}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5E8DCD1B2861D48400E6F704 /* Build configuration list for PBXProject "LineSDKSample-SwiftUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5E8DCD2C2861D48800E6F704 /* Debug */, + 5E8DCD2D2861D48800E6F704 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5E8DCD2E2861D48800E6F704 /* Build configuration list for PBXNativeTarget "LineSDKSample-SwiftUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5E8DCD2F2861D48800E6F704 /* Debug */, + 5E8DCD302861D48800E6F704 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 5E8DCD182861D48400E6F704 /* Project object */; +} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/xcshareddata/xcschemes/LineSDKSample-SwiftUI.xcscheme b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/xcshareddata/xcschemes/LineSDKSample-SwiftUI.xcscheme new file mode 100644 index 00000000..7b400ad4 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI.xcodeproj/xcshareddata/xcschemes/LineSDKSample-SwiftUI.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/API/API.swift b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/API/API.swift new file mode 100644 index 00000000..1fa55042 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/API/API.swift @@ -0,0 +1,32 @@ +// +// API.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import LineSDK + +extension API { + static func getProfile() async -> Result { + await withCheckedContinuation { continuation in + getProfile { result in + continuation.resume(returning: result) + } + } + } +} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..dfa4259b --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "Icon-Notification@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-Notification@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "Icon-Small@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-Small@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "Icon-Small-40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-Small-40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "Icon-60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "Icon-60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "Icon-Notification.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "Icon-Notification@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-Small.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-Small@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-Small-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "Icon-Small-40@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "Icon-76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "Icon-83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 00000000..c773baa3 Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 00000000..32fff97b Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-76.png new file mode 100644 index 00000000..81c6fefe Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-76.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png new file mode 100644 index 00000000..8c1c6b9a Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png new file mode 100644 index 00000000..efa9fc95 Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png new file mode 100644 index 00000000..0320a9bb Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png new file mode 100644 index 00000000..f0cb41fb Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png new file mode 100644 index 00000000..d1a64dd2 Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png new file mode 100644 index 00000000..f0cb41fb Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png new file mode 100644 index 00000000..aa86ce66 Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png new file mode 100644 index 00000000..c773baa3 Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small.png new file mode 100644 index 00000000..6a877518 Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png new file mode 100644 index 00000000..f869e4b9 Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png new file mode 100644 index 00000000..cace3c97 Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/icon.png b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/icon.png new file mode 100644 index 00000000..fac0b195 Binary files /dev/null and b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/AppIcon.appiconset/icon.png differ diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/Contents.json b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Config.xcconfig b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Config.xcconfig new file mode 100644 index 00000000..b950cad6 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Config.xcconfig @@ -0,0 +1,29 @@ +// +// Config.xcconfig +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +LINE_CHANNEL_ID = 1620019587 +LINE_SDK_URL_SCHEME = line3rdp +LINE_AUTH_URL_SCHEME = lineauth2 + +SAMPLE_CODE_DISAMBIGUATOR = ${DEVELOPMENT_TEAM} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/ContentView.swift b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/ContentView.swift new file mode 100644 index 00000000..5b6672aa --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/ContentView.swift @@ -0,0 +1,29 @@ +// +// ContentView.swift +// LineSDKSwiftUISample +// +// Created by mrfour on 2022/6/21. +// + +import LineSDK +import SwiftUI + +struct ContentView: View { + @EnvironmentObject private var authorizationStore: AuthorizationStore + + var body: some View { + NavigationView { + if authorizationStore.isAuthorized { + ProfileView() + } else { + LoginView() + } + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Info.plist b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Info.plist new file mode 100644 index 00000000..528ddc0c --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Info.plist @@ -0,0 +1,23 @@ + + + + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + line3rdp.$(PRODUCT_BUNDLE_IDENTIFIER) + + + + LINE Channel ID + $(LINE_CHANNEL_ID) + LSApplicationQueriesSchemes + + $(LINE_AUTH_URL_SCHEME) + + + diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/LineSDKSampleApp.swift b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/LineSDKSampleApp.swift new file mode 100644 index 00000000..f6581008 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/LineSDKSampleApp.swift @@ -0,0 +1,58 @@ +// +// LineSDKSwiftUISampleApp.swift +// LineSDKSwiftUISample +// +// Created by mrfour on 2022/6/21. +// + +import LineSDK +import SwiftUI + +@main +struct LineSDKSampleApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(AuthorizationStore()) + .onOpenURL { url in + let _ = LoginManager.shared.application(.shared, open: url) + } + } + } +} + +class AppDelegate: NSObject, UIApplicationDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool + { + // Modify Config.xcconfig to setup your LINE channel ID. + if let channelID = Bundle.main.infoDictionary?["LINE Channel ID"] as? String, + let _ = Int(channelID) + { + LoginManager.shared.setup(channelID: channelID, universalLinkURL: nil) + } else { + fatalError("Please set correct channel ID in Config.xcconfig file.") + } + + return true + } + + func application( + _ application: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool + { + return LoginManager.shared.application(application, open: url, options: options) + } + + func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool + { + return LoginManager.shared.application(application, open: userActivity.webpageURL) + } +} diff --git a/LineSDK/LineSDK/Login/Model/OneTimePassword.swift b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/AuthorizatonStore.swift similarity index 67% rename from LineSDK/LineSDK/Login/Model/OneTimePassword.swift rename to LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/AuthorizatonStore.swift index dbd01775..5b2a328f 100644 --- a/LineSDK/LineSDK/Login/Model/OneTimePassword.swift +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/AuthorizatonStore.swift @@ -1,13 +1,13 @@ // -// OneTimePassword.swift +// AuthorizatonResult.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,9 +19,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import Foundation +import Combine +import LineSDK -struct OneTimePassword: Decodable { - let otpId: String - let otp: String +class AuthorizationStore: ObservableObject { + @Published var isAuthorized = LoginManager.shared.isAuthorized } diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/LineLoginButton.swift b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/LineLoginButton.swift new file mode 100644 index 00000000..10df1130 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/LineLoginButton.swift @@ -0,0 +1,97 @@ +// +// LineLoginButton.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import LineSDK +import SwiftUI + +struct LineLoginButton: UIViewRepresentable { + let permissions: Set + + fileprivate var onLoginSucceed: ((LoginResult) -> Void)? + fileprivate var onLoginFail: ((LineSDKError) -> Void)? + fileprivate var onLoginStart: (() -> Void)? + + init(permissions: Set = [.profile]) { + self.permissions = permissions + } + + // MARK: - Wrapping view + + func makeUIView(context: Context) -> LoginButton { + let button = LoginButton() + button.setContentHuggingPriority(.required, for: .horizontal) + button.setContentHuggingPriority(.required, for: .vertical) + button.delegate = context.coordinator + return button + } + + func updateUIView(_ uiView: LoginButton, context: Context) {} + + // MARK: - Coordinating + + func makeCoordinator() -> Coordinator { + Coordinator(parent: self) + } + + // MARK: - LoginButton Delegating + + func onLoginSuccess(perform: @escaping (LoginResult) -> Void) -> Self { + modified(\.onLoginSucceed, with: perform) + } + + func onLoginFail(perform: @escaping (LineSDKError) -> Void) -> Self { + modified(\.onLoginFail, with: perform) + } + + func onLoginStart(perform: @escaping () -> Void) -> Self { + modified(\.onLoginStart, with: perform) + } + + private func modified(_ keyPath: WritableKeyPath, with value: Value) -> Self { + var modified = self + modified[keyPath: keyPath] = value + return modified + } +} + +extension LineLoginButton { + class Coordinator: NSObject, LoginButtonDelegate { + private let parent: LineLoginButton + + init(parent: LineLoginButton) { + self.parent = parent + } + + // MARK: - LoginButtonDelegate + + func loginButtonDidStartLogin(_ button: LoginButton) { + parent.onLoginStart?() + } + + func loginButton(_ button: LoginButton, didSucceedLogin loginResult: LoginResult) { + parent.onLoginSucceed?(loginResult) + } + + func loginButton(_ button: LoginButton, didFailLogin error: LineSDKError) { + parent.onLoginFail?(error) + } + } +} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/LoginView.swift b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/LoginView.swift new file mode 100644 index 00000000..b680f49a --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/LoginView.swift @@ -0,0 +1,61 @@ +// +// LoginView.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import LineSDK +import SwiftUI + +struct LoginView: View { + @EnvironmentObject var authorizationStore: AuthorizationStore + + @AlertItem + var presentedLoginError: LineSDKError? + + @AlertItem + var presentedLoginResult: LoginResult? + + var body: some View { + ZStack { + LineLoginButton() + .onLoginStart {} + .onLoginSuccess { result in + presentedLoginResult = result + } + .onLoginFail { error in + presentedLoginError = error + } + } + .navigationBarTitle("Login", displayMode: .inline) + .alert( + "Success", isPresented: $presentedLoginResult, presenting: presentedLoginResult + ) { _ in + Button("OK") { authorizationStore.isAuthorized = true } + } message: { + Text(verbatim: "\($0)") + } + .alert( + "Error", isPresented: $presentedLoginError, presenting: presentedLoginError + ) { _ in + + } message: { + Text($0.localizedDescription) + } + } +} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/ProfileView.swift b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/ProfileView.swift new file mode 100644 index 00000000..54ebf321 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Login/ProfileView.swift @@ -0,0 +1,81 @@ +// +// ProfileView.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import LineSDK +import SwiftUI + +struct ProfileView: View { + @EnvironmentObject private var authorizationStore: AuthorizationStore + + @State var profile: UserProfile? + + @State var logoutResult: Result? + + @State var alertMessage: String? + + var body: some View { + VStack { + if let profile = profile { + profileContent(profile) + } + } + .navigationBarTitle("Profile", displayMode: .inline) + .navigationBarItems(leading: logoutButton) + .onAppear { + guard profile == nil else { return } + + Task { + let result = await API.getProfile() + switch result { + case .success(let profile): + self.profile = profile + case .failure(let error): + alertMessage = error.localizedDescription + } + } + } + } + + @ViewBuilder + private func profileContent(_ profile: UserProfile) -> some View { + AsyncImage(url: profile.pictureURL) { image in + image.resizable() + } placeholder: { + Color.gray.opacity(0.1) + } + .frame(width: 150, height: 150) + + Text(profile.displayName) + .font(.title2) + + Text("Status Message: \(profile.statusMessage ?? "N/A")") + .font(.subheadline) + } + + private var logoutButton: some View { + Button("Logout") { + Task { + logoutResult = await LoginManager.shared.logout() + authorizationStore.isAuthorized = false + } + } + } +} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Preview Content/Preview Assets.xcassets/Contents.json b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LineSDK/LineSDK/LineSDK.h b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Utils/AlertItem.swift similarity index 58% rename from LineSDK/LineSDK/LineSDK.h rename to LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Utils/AlertItem.swift index ec94b63d..b05873d1 100644 --- a/LineSDK/LineSDK/LineSDK.h +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Utils/AlertItem.swift @@ -1,13 +1,13 @@ // -// LineSDK.h +// AlertItem.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,14 +19,18 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#import +import SwiftUI -//! Project version number for LineSDK. -FOUNDATION_EXPORT double LineSDKVersionNumber; - -//! Project version string for LineSDK. -FOUNDATION_EXPORT const unsigned char LineSDKVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import +@propertyWrapper +struct AlertItem: DynamicProperty { + @State private var value: Item? + var wrappedValue: Item? { + get { value } + nonmutating set { value = newValue } + } + var projectedValue: Binding { + Binding(get: { value != nil }, set: { if !$0 { value = nil } }) + } +} diff --git a/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Utils/LoginManager+Extension.swift b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Utils/LoginManager+Extension.swift new file mode 100644 index 00000000..64010e9f --- /dev/null +++ b/LineSDKSample-SwiftUI/LineSDKSample-SwiftUI/Utils/LoginManager+Extension.swift @@ -0,0 +1,59 @@ +// +// LoginManager+Extension.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import LineSDK + +extension LoginManager { + /// Logs into the LINE platform. + /// + /// This is the async version of `LoginManager.login(permission:in:parameters:completionHandler:)`, for more + /// detail please refer to it. + /// + /// - Parameters: + /// - permissions: The set of permissions requested by your app. The default value is + /// `[.profile]`. + /// - parameters: The parameters used during the login process. For more information, + /// see `LoginManager.Parameters`. + /// - Returns: The login `Result`. + func login( + permissions: Set = [.profile], + parameters: LoginManager.Parameters = .init() + ) async -> Result { + await withCheckedContinuation { continuation in + login(permissions: permissions, parameters: parameters) { result in + continuation.resume(returning: result) + } + } + } + + /// Logs out the current user by revoking the refresh token and all its corresponding access tokens. + /// + /// This is the async version of `LoginManager.logout(completionHandler:)`, for more detail please refer to it. + /// + /// - Returns: The logout `Result`. + func logout() async -> Result { + await withCheckedContinuation { continuation in + logout { result in + continuation.resume(returning: result) + } + } + } +} diff --git a/LineSDKSample/LineSDKSample.xcodeproj/project.pbxproj b/LineSDKSample/LineSDKSample.xcodeproj/project.pbxproj index ccd5c3ab..9743e855 100644 --- a/LineSDKSample/LineSDKSample.xcodeproj/project.pbxproj +++ b/LineSDKSample/LineSDKSample.xcodeproj/project.pbxproj @@ -3,22 +3,24 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 53; objects = { /* Begin PBXBuildFile section */ 4B104808216ED51A00300C61 /* LineSDKSampleError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B104807216ED51A00300C61 /* LineSDKSampleError.swift */; }; + 4B121F132249DE4000473132 /* SampleUIHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B121F122249DE4000473132 /* SampleUIHomeViewController.swift */; }; 4B15EEF1211D322B00866E6C /* APIResultEntryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B15EEF0211D322B00866E6C /* APIResultEntryTests.swift */; }; 4B3687E2212AB62D00B6048A /* Placeholder.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4B3687E0212AB62D00B6048A /* Placeholder.strings */; }; - 4B63F46F2106D43D003D1BF1 /* LineSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B63F46E2106D43C003D1BF1 /* LineSDK.framework */; }; - 4B63F4702106D43D003D1BF1 /* LineSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B63F46E2106D43C003D1BF1 /* LineSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 4B38A3EF22FA70DB00A21F05 /* ShareMessageTemplateAddingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B38A3EE22FA70DB00A21F05 /* ShareMessageTemplateAddingViewController.swift */; }; + 4B38A3F122FA77A700A21F05 /* NotificationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B38A3F022FA77A700A21F05 /* NotificationToken.swift */; }; + 4B8D531B28C6D12600E9E3E3 /* LineSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B8D531A28C6D12600E9E3E3 /* LineSDK.framework */; }; + 4B8D531C28C6D12600E9E3E3 /* LineSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B8D531A28C6D12600E9E3E3 /* LineSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4B90589F21006E9C004D717F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B90589E21006E9C004D717F /* AppDelegate.swift */; }; 4B9058A121006E9C004D717F /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9058A021006E9C004D717F /* LoginViewController.swift */; }; 4B9058A421006E9C004D717F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B9058A221006E9C004D717F /* Main.storyboard */; }; 4B9058A621006E9C004D717F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B9058A521006E9C004D717F /* Assets.xcassets */; }; 4B9058A921006E9C004D717F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B9058A721006E9C004D717F /* LaunchScreen.storyboard */; }; 4B9058B421006E9C004D717F /* LineSDKSampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9058B321006E9C004D717F /* LineSDKSampleTests.swift */; }; - 4B9058BF21006E9C004D717F /* LineSDKSampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9058BE21006E9C004D717F /* LineSDKSampleUITests.swift */; }; 4BB8445D211BD41A005B60D7 /* APIHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB8445C211BD41A005B60D7 /* APIHomeViewController.swift */; }; 4BB8445F211BD4DF005B60D7 /* APIStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB8445E211BD4DF005B60D7 /* APIStore.swift */; }; 4BB84461211BE5E7005B60D7 /* APIResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB84460211BE5E7005B60D7 /* APIResultViewController.swift */; }; @@ -29,12 +31,15 @@ 4BCD27A32114033000B90D8F /* LoginNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD27A22114033000B90D8F /* LoginNavigationController.swift */; }; 4BCD27A521142A3400B90D8F /* UserDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD27A421142A3400B90D8F /* UserDetailViewController.swift */; }; 4BDADDD6211C35230044D827 /* APIResultEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDADDD5211C35230044D827 /* APIResultEntry.swift */; }; + 4BFE511822F7BA8200500B72 /* ShareMessagesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFE511722F7BA8200500B72 /* ShareMessagesTableViewController.swift */; }; + 4BFE511B22F7BAC600500B72 /* MessageStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFE511A22F7BAC600500B72 /* MessageStore.swift */; }; 65E6BDB92154D80A0024D7C1 /* LoginPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E6BDB82154D80A0024D7C1 /* LoginPage.swift */; }; - 65E6BDBB2154D8240024D7C1 /* APIHomePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E6BDBA2154D8240024D7C1 /* APIHomePage.swift */; }; - 65E6BDC02154D8670024D7C1 /* LineLoginButtonTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E6BDBF2154D8670024D7C1 /* LineLoginButtonTest.swift */; }; - 65E6BDC22154D9300024D7C1 /* GraphAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E6BDC12154D9300024D7C1 /* GraphAPITest.swift */; }; - 9712B432215C951500C367D1 /* MessageAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9712B431215C951500C367D1 /* MessageAPITest.swift */; }; - 9712B435215CC44600C367D1 /* LineSDKScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9712B434215CC44600C367D1 /* LineSDKScript.swift */; }; + 9735344E241BB60900D47AAA /* Page.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9735344D241BB60900D47AAA /* Page.swift */; }; + 97353450241BB67500D47AAA /* AuthenticationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9735344F241BB67500D47AAA /* AuthenticationTests.swift */; }; + D17A9F3724BD312A00D0FD0D /* LoginSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D17A9F3624BD312A00D0FD0D /* LoginSettingsViewController.swift */; }; + D17A9F3924BD34EE00D0FD0D /* LoginSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D17A9F3824BD34EE00D0FD0D /* LoginSettings.swift */; }; + D1F6B58D243C5ACF00024910 /* OpenChatRoomTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F6B58C243C5ACF00024910 /* OpenChatRoomTableViewController.swift */; }; + D1F6B58F243C5E0F00024910 /* OpenChatRoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F6B58E243C5E0F00024910 /* OpenChatRoom.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -61,7 +66,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 4B63F4702106D43D003D1BF1 /* LineSDK.framework in Embed Frameworks */, + 4B8D531C28C6D12600E9E3E3 /* LineSDK.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -70,6 +75,7 @@ /* Begin PBXFileReference section */ 4B104807216ED51A00300C61 /* LineSDKSampleError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKSampleError.swift; sourceTree = ""; }; + 4B121F122249DE4000473132 /* SampleUIHomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleUIHomeViewController.swift; sourceTree = ""; }; 4B15EEF0211D322B00866E6C /* APIResultEntryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIResultEntryTests.swift; sourceTree = ""; }; 4B3687E1212AB62D00B6048A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Placeholder.strings; sourceTree = ""; }; 4B3687E3212AB63B00B6048A /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Placeholder.strings"; sourceTree = ""; }; @@ -89,7 +95,10 @@ 4B3687F1212ABABE00B6048A /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Placeholder.strings; sourceTree = ""; }; 4B3687F2212ABAC500B6048A /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Placeholder.strings; sourceTree = ""; }; 4B3687F3212ABACB00B6048A /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Placeholder.strings; sourceTree = ""; }; - 4B63F46E2106D43C003D1BF1 /* LineSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LineSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B38A3EE22FA70DB00A21F05 /* ShareMessageTemplateAddingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareMessageTemplateAddingViewController.swift; sourceTree = ""; }; + 4B38A3F022FA77A700A21F05 /* NotificationToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationToken.swift; sourceTree = ""; }; + 4B8D531828C6D11200E9E3E3 /* LineSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LineSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B8D531A28C6D12600E9E3E3 /* LineSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LineSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4B90589B21006E9B004D717F /* LineSDKSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LineSDKSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4B90589E21006E9C004D717F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4B9058A021006E9C004D717F /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; @@ -101,9 +110,10 @@ 4B9058B321006E9C004D717F /* LineSDKSampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKSampleTests.swift; sourceTree = ""; }; 4B9058B521006E9C004D717F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4B9058BA21006E9C004D717F /* LineSDKSampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LineSDKSampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 4B9058BE21006E9C004D717F /* LineSDKSampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKSampleUITests.swift; sourceTree = ""; }; 4B9058C021006E9C004D717F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4B9AB20D2159EE1500B8C22C /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; + 4BAEEB8322C4B462007B53D8 /* LoginViewController+SPM.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LoginViewController+SPM.swift"; sourceTree = ""; }; + 4BAEEB8522C4B55D007B53D8 /* SampleUIHomeViewController+SPM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SampleUIHomeViewController+SPM.swift"; sourceTree = ""; }; 4BB8445C211BD41A005B60D7 /* APIHomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIHomeViewController.swift; sourceTree = ""; }; 4BB8445E211BD4DF005B60D7 /* APIStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIStore.swift; sourceTree = ""; }; 4BB84460211BE5E7005B60D7 /* APIResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIResultViewController.swift; sourceTree = ""; }; @@ -114,12 +124,16 @@ 4BCD27A22114033000B90D8F /* LoginNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginNavigationController.swift; sourceTree = ""; }; 4BCD27A421142A3400B90D8F /* UserDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailViewController.swift; sourceTree = ""; }; 4BDADDD5211C35230044D827 /* APIResultEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIResultEntry.swift; sourceTree = ""; }; + 4BFE511722F7BA8200500B72 /* ShareMessagesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareMessagesTableViewController.swift; sourceTree = ""; }; + 4BFE511A22F7BAC600500B72 /* MessageStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStore.swift; sourceTree = ""; }; 65E6BDB82154D80A0024D7C1 /* LoginPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPage.swift; sourceTree = ""; }; - 65E6BDBA2154D8240024D7C1 /* APIHomePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIHomePage.swift; sourceTree = ""; }; - 65E6BDBF2154D8670024D7C1 /* LineLoginButtonTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineLoginButtonTest.swift; sourceTree = ""; }; - 65E6BDC12154D9300024D7C1 /* GraphAPITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphAPITest.swift; sourceTree = ""; }; - 9712B431215C951500C367D1 /* MessageAPITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageAPITest.swift; sourceTree = ""; }; - 9712B434215CC44600C367D1 /* LineSDKScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineSDKScript.swift; sourceTree = ""; }; + 9735344D241BB60900D47AAA /* Page.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Page.swift; sourceTree = ""; }; + 9735344F241BB67500D47AAA /* AuthenticationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationTests.swift; sourceTree = ""; }; + D1467333238F6D5B004BF2B6 /* LineSDKSample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LineSDKSample.entitlements; sourceTree = ""; }; + D17A9F3624BD312A00D0FD0D /* LoginSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginSettingsViewController.swift; sourceTree = ""; }; + D17A9F3824BD34EE00D0FD0D /* LoginSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginSettings.swift; sourceTree = ""; }; + D1F6B58C243C5ACF00024910 /* OpenChatRoomTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatRoomTableViewController.swift; sourceTree = ""; }; + D1F6B58E243C5E0F00024910 /* OpenChatRoom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatRoom.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -127,7 +141,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4B63F46F2106D43D003D1BF1 /* LineSDK.framework in Frameworks */, + 4B8D531B28C6D12600E9E3E3 /* LineSDK.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -148,6 +162,18 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 4B121F112249DE2300473132 /* UI */ = { + isa = PBXGroup; + children = ( + 4B121F122249DE4000473132 /* SampleUIHomeViewController.swift */, + 4BAEEB8522C4B55D007B53D8 /* SampleUIHomeViewController+SPM.swift */, + 4BFE511722F7BA8200500B72 /* ShareMessagesTableViewController.swift */, + 4B38A3EE22FA70DB00A21F05 /* ShareMessageTemplateAddingViewController.swift */, + D1F6B58C243C5ACF00024910 /* OpenChatRoomTableViewController.swift */, + ); + path = UI; + sourceTree = ""; + }; 4B3687DF212AB62D00B6048A /* Localization */ = { isa = PBXGroup; children = ( @@ -156,14 +182,23 @@ path = Localization; sourceTree = ""; }; + 4B8D531728C6D11200E9E3E3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4B8D531A28C6D12600E9E3E3 /* LineSDK.framework */, + 4B8D531828C6D11200E9E3E3 /* LineSDK.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 4B90589221006E9B004D717F = { isa = PBXGroup; children = ( - 4B63F46E2106D43C003D1BF1 /* LineSDK.framework */, 4B90589D21006E9C004D717F /* LineSDKSample */, 4B9058B221006E9C004D717F /* LineSDKSampleTests */, 4B9058BD21006E9C004D717F /* LineSDKSampleUITests */, 4B90589C21006E9B004D717F /* Products */, + 4B8D531728C6D11200E9E3E3 /* Frameworks */, ); sourceTree = ""; }; @@ -180,8 +215,11 @@ 4B90589D21006E9C004D717F /* LineSDKSample */ = { isa = PBXGroup; children = ( + D1467333238F6D5B004BF2B6 /* LineSDKSample.entitlements */, + 4BFE511922F7BAB800500B72 /* Model */, 4B3687DF212AB62D00B6048A /* Localization */, 4BB8445B211BD2B6005B60D7 /* API */, + 4B121F112249DE2300473132 /* UI */, 4BCD27A12114031B00B90D8F /* Login */, 4BCD279C2113FE9000B90D8F /* Utils */, 4B90589E21006E9C004D717F /* AppDelegate.swift */, @@ -208,9 +246,7 @@ 4B9058BD21006E9C004D717F /* LineSDKSampleUITests */ = { isa = PBXGroup; children = ( - 4B9058BE21006E9C004D717F /* LineSDKSampleUITests.swift */, 4B9058C021006E9C004D717F /* Info.plist */, - 9712B433215CC3FD00C367D1 /* Script */, 65E6BDBE2154D83C0024D7C1 /* PageObject */, 65E6BDC32154D9640024D7C1 /* TestSuites */, ); @@ -231,6 +267,7 @@ 4BCD279C2113FE9000B90D8F /* Utils */ = { isa = PBXGroup; children = ( + 4B38A3F022FA77A700A21F05 /* NotificationToken.swift */, 4BCD279D2113FE9D00B90D8F /* IndicatorDisplay.swift */, 4BCD279F2114007500B90D8F /* UIAlertControllerHelpers.swift */, 4BB84462211BF48F005B60D7 /* UIViewControllerExtensions.swift */, @@ -242,6 +279,9 @@ isa = PBXGroup; children = ( 4B9058A021006E9C004D717F /* LoginViewController.swift */, + D17A9F3624BD312A00D0FD0D /* LoginSettingsViewController.swift */, + D17A9F3824BD34EE00D0FD0D /* LoginSettings.swift */, + 4BAEEB8322C4B462007B53D8 /* LoginViewController+SPM.swift */, 4BCD27972113F95700B90D8F /* UserProfileViewController.swift */, 4BCD27A22114033000B90D8F /* LoginNavigationController.swift */, 4BCD27A421142A3400B90D8F /* UserDetailViewController.swift */, @@ -249,31 +289,30 @@ path = Login; sourceTree = ""; }; - 65E6BDBE2154D83C0024D7C1 /* PageObject */ = { + 4BFE511922F7BAB800500B72 /* Model */ = { isa = PBXGroup; children = ( - 65E6BDB82154D80A0024D7C1 /* LoginPage.swift */, - 65E6BDBA2154D8240024D7C1 /* APIHomePage.swift */, + 4BFE511A22F7BAC600500B72 /* MessageStore.swift */, + D1F6B58E243C5E0F00024910 /* OpenChatRoom.swift */, ); - path = PageObject; + path = Model; sourceTree = ""; }; - 65E6BDC32154D9640024D7C1 /* TestSuites */ = { + 65E6BDBE2154D83C0024D7C1 /* PageObject */ = { isa = PBXGroup; children = ( - 65E6BDBF2154D8670024D7C1 /* LineLoginButtonTest.swift */, - 65E6BDC12154D9300024D7C1 /* GraphAPITest.swift */, - 9712B431215C951500C367D1 /* MessageAPITest.swift */, + 65E6BDB82154D80A0024D7C1 /* LoginPage.swift */, + 9735344D241BB60900D47AAA /* Page.swift */, ); - path = TestSuites; + path = PageObject; sourceTree = ""; }; - 9712B433215CC3FD00C367D1 /* Script */ = { + 65E6BDC32154D9640024D7C1 /* TestSuites */ = { isa = PBXGroup; children = ( - 9712B434215CC44600C367D1 /* LineSDKScript.swift */, + 9735344F241BB67500D47AAA /* AuthenticationTests.swift */, ); - path = Script; + path = TestSuites; sourceTree = ""; }; /* End PBXGroup section */ @@ -339,22 +378,23 @@ 4B90589321006E9B004D717F /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0940; - LastUpgradeCheck = 0940; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "LINE Corp"; TargetAttributes = { 4B90589A21006E9B004D717F = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1340; }; 4B9058AE21006E9C004D717F = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1340; TestTargetID = 4B90589A21006E9B004D717F; }; 4B9058B921006E9C004D717F = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1340; TestTargetID = 4B90589A21006E9B004D717F; }; }; @@ -385,6 +425,8 @@ vi, ); mainGroup = 4B90589221006E9B004D717F; + packageReferences = ( + ); productRefGroup = 4B90589C21006E9B004D717F /* Products */; projectDirPath = ""; projectRoot = ""; @@ -429,19 +471,28 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D1F6B58D243C5ACF00024910 /* OpenChatRoomTableViewController.swift in Sources */, 4BB8445D211BD41A005B60D7 /* APIHomeViewController.swift in Sources */, 4BB84463211BF48F005B60D7 /* UIViewControllerExtensions.swift in Sources */, 4BDADDD6211C35230044D827 /* APIResultEntry.swift in Sources */, + D1F6B58F243C5E0F00024910 /* OpenChatRoom.swift in Sources */, 4BCD27A521142A3400B90D8F /* UserDetailViewController.swift in Sources */, 4B104808216ED51A00300C61 /* LineSDKSampleError.swift in Sources */, + D17A9F3724BD312A00D0FD0D /* LoginSettingsViewController.swift in Sources */, 4BCD279E2113FE9D00B90D8F /* IndicatorDisplay.swift in Sources */, + 4BFE511822F7BA8200500B72 /* ShareMessagesTableViewController.swift in Sources */, 4BB8445F211BD4DF005B60D7 /* APIStore.swift in Sources */, 4BB84461211BE5E7005B60D7 /* APIResultViewController.swift in Sources */, + D17A9F3924BD34EE00D0FD0D /* LoginSettings.swift in Sources */, 4BCD27A32114033000B90D8F /* LoginNavigationController.swift in Sources */, 4BCD27A02114007500B90D8F /* UIAlertControllerHelpers.swift in Sources */, 4B9058A121006E9C004D717F /* LoginViewController.swift in Sources */, + 4B38A3F122FA77A700A21F05 /* NotificationToken.swift in Sources */, 4BCD27982113F95700B90D8F /* UserProfileViewController.swift in Sources */, 4B90589F21006E9C004D717F /* AppDelegate.swift in Sources */, + 4B38A3EF22FA70DB00A21F05 /* ShareMessageTemplateAddingViewController.swift in Sources */, + 4B121F132249DE4000473132 /* SampleUIHomeViewController.swift in Sources */, + 4BFE511B22F7BAC600500B72 /* MessageStore.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -458,13 +509,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9735344E241BB60900D47AAA /* Page.swift in Sources */, + 97353450241BB67500D47AAA /* AuthenticationTests.swift in Sources */, 65E6BDB92154D80A0024D7C1 /* LoginPage.swift in Sources */, - 9712B435215CC44600C367D1 /* LineSDKScript.swift in Sources */, - 65E6BDC02154D8670024D7C1 /* LineLoginButtonTest.swift in Sources */, - 9712B432215C951500C367D1 /* MessageAPITest.swift in Sources */, - 65E6BDBB2154D8240024D7C1 /* APIHomePage.swift in Sources */, - 4B9058BF21006E9C004D717F /* LineSDKSampleUITests.swift in Sources */, - 65E6BDC22154D9300024D7C1 /* GraphAPITest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -555,6 +602,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -580,7 +628,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -616,6 +664,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -635,7 +684,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; @@ -649,7 +698,12 @@ baseConfigurationReference = 4B9AB20D2159EE1500B8C22C /* Config.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = LineSDKSample/LineSDKSample.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = A4YJ9MRZ66; INFOPLIST_FILE = LineSDKSample/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -657,7 +711,10 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.linesdk.sample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SUPPORTS_MACCATALYST = YES; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -667,7 +724,12 @@ baseConfigurationReference = 4B9AB20D2159EE1500B8C22C /* Config.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = LineSDKSample/LineSDKSample.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = A4YJ9MRZ66; INFOPLIST_FILE = LineSDKSample/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -675,7 +737,10 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.linesdk.sample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SUPPORTS_MACCATALYST = YES; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -694,7 +759,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.LineSDKSampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LineSDKSample.app/LineSDKSample"; }; @@ -714,7 +779,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.LineSDKSampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LineSDKSample.app/LineSDKSample"; }; @@ -733,7 +798,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.LineSDKSampleUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = LineSDKSample; }; @@ -752,7 +817,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.linecorp.LineSDKSampleUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = LineSDKSample; }; diff --git a/LineSDKSample/LineSDKSample.xcodeproj/xcshareddata/xcschemes/LineSDKSample.xcscheme b/LineSDKSample/LineSDKSample.xcodeproj/xcshareddata/xcschemes/LineSDKSample.xcscheme new file mode 100644 index 00000000..f7746754 --- /dev/null +++ b/LineSDKSample/LineSDKSample.xcodeproj/xcshareddata/xcschemes/LineSDKSample.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LineSDKSample/LineSDKSample/API/APIHomeViewController.swift b/LineSDKSample/LineSDKSample/API/APIHomeViewController.swift index 825d558f..045927d4 100644 --- a/LineSDKSample/LineSDKSample/API/APIHomeViewController.swift +++ b/LineSDKSample/LineSDKSample/API/APIHomeViewController.swift @@ -1,13 +1,13 @@ // // APIHomeViewController.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -39,7 +39,7 @@ class APIHomeViewController: UITableViewController { let cell = tableView.dequeueReusableCell(withIdentifier: "APIHomeCell", for: indexPath) cell.textLabel?.text = api.title cell.detailTextLabel?.textColor = .gray - cell.detailTextLabel?.text = api.path + cell.detailTextLabel?.text = ":\(api.method.rawValue) \(api.path)" return cell } diff --git a/LineSDKSample/LineSDKSample/API/APIResultEntry.swift b/LineSDKSample/LineSDKSample/API/APIResultEntry.swift index 41d5570a..07921b0a 100644 --- a/LineSDKSample/LineSDKSample/API/APIResultEntry.swift +++ b/LineSDKSample/LineSDKSample/API/APIResultEntry.swift @@ -1,13 +1,13 @@ // // APIResultEntry.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -37,14 +37,17 @@ enum APIResultEntry: Comparable { // These will unwrap if an optional value contained in `value`. // Feel free to add additional types you need to unwrap before displaying. - case let v as String: self = .pair(key, v) - case let v as URL: self = .pair(key, "\(v)") - case let v as Int: self = .pair(key, "\(v)") - case let v as Double: self = .pair(key, "\(v)") - case let v as Date: self = .pair(key, "\(v)") - case let v as Bool: self = .pair(key, "\(v)") - case let v as LoginPermission: self = .pair(key, "\(v)") - case let v as MessageSendingStatus: self = .pair(key, "\(v)") + case let v as String: self = .pair(key, v) + case let v as URL: self = .pair(key, "\(v)") + case let v as Int: self = .pair(key, "\(v)") + case let v as Double: self = .pair(key, "\(v)") + case let v as Date: self = .pair(key, "\(v)") + case let v as Bool: self = .pair(key, "\(v)") + case let v as LoginPermission: self = .pair(key, "\(v)") + case let v as MessageSendingStatus: self = .pair(key, "\(v)") + case let v as GetOpenChatRoomStatusRequest.Status: self = .pair(key, "\(v.rawValue)") + case let v as GetOpenChatRoomMembershipStateRequest.State: self = .pair(key, "\(v.rawValue)") + case let v as GetOpenChatRoomJoinTypeRequest.RoomType: self = .pair(key, "\(v.rawValue)") case let v as [Any]: let entries = v.enumerated().map { offset, element in diff --git a/LineSDKSample/LineSDKSample/API/APIResultViewController.swift b/LineSDKSample/LineSDKSample/API/APIResultViewController.swift index e9ab824c..0e1ea950 100644 --- a/LineSDKSample/LineSDKSample/API/APIResultViewController.swift +++ b/LineSDKSample/LineSDKSample/API/APIResultViewController.swift @@ -1,13 +1,13 @@ // // APIResultViewController.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDKSample/LineSDKSample/API/APIStore.swift b/LineSDKSample/LineSDKSample/API/APIStore.swift index 409d1a23..4e1548d0 100644 --- a/LineSDKSample/LineSDKSample/API/APIStore.swift +++ b/LineSDKSample/LineSDKSample/API/APIStore.swift @@ -1,13 +1,13 @@ // // APIStore.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -20,6 +20,7 @@ // import Foundation +import UIKit import LineSDK enum ApplicationError: Error, LocalizedError { @@ -39,6 +40,7 @@ enum APICategory: Int, CaseIterable { case friendship case graph case messaging + case openChat } class APIStore { @@ -48,19 +50,47 @@ class APIStore { private(set) var friendshipAPIs: [APIItem] = [] private(set) var graphAPIs: [APIItem] = [] private(set) var messagingAPIs: [APIItem] = [] + private(set) var openChatAPIs: [APIItem] = [] + + private var tokenDidUpdateObserver: NotificationToken? + private var tokenDidRemoveObserver: NotificationToken? private init() { refresh() let center = NotificationCenter.default - center.addObserver(forName: .LineSDKAccessTokenDidUpdate , object: nil, queue: nil) { _ in + tokenDidUpdateObserver = center.addObserver(forName: .LineSDKAccessTokenDidUpdate , object: nil, queue: nil) { + _ in self.refresh() } - center.addObserver(forName: .LineSDKAccessTokenDidRemove , object: nil, queue: nil) { _ in + tokenDidRemoveObserver = center.addObserver(forName: .LineSDKAccessTokenDidRemove , object: nil, queue: nil) { + _ in self.refresh() } } - func refresh() { + func numberOfAPIs(in category: APICategory) -> Int { + switch category { + case .auth: return authAPIs.count + case .friendship: return friendshipAPIs.count + case .graph: return graphAPIs.count + case .messaging: return messagingAPIs.count + case .openChat: return openChatAPIs.count + } + } + + func api(in category: APICategory, at index: Int) -> APIItem { + switch category { + case .auth: return authAPIs[index] + case .friendship: return friendshipAPIs[index] + case .graph: return graphAPIs[index] + case .messaging: return messagingAPIs[index] + case .openChat: return openChatAPIs[index] + } + } +} + +extension APIStore { + private func refresh() { authAPIs = [ .init( title: "Get User Profile", @@ -70,23 +100,29 @@ class APIStore { title: "Verify Token", request: GetVerifyTokenRequest(accessToken: AccessTokenStore.shared.current?.value ?? ""), available: AccessTokenStore.shared.current != nil - ) + ), + .refreshToken, ] friendshipAPIs = [ - .init(title: "Get Bot Friendship Status", - request: GetBotFriendshipStatusRequest()) + .init( + title: "Get Official Account Friendship Status", + request: GetBotFriendshipStatusRequest() + ) ] graphAPIs = [ - .init(title: "Get Friends" , - request: GetFriendsRequest() + .init( + title: "Get Friends" , + request: GetFriendsRequest() ), - .init(title: "Get Approvers in Friends", - request: GetApproversInFriendsRequest() + .init( + title: "Get Approvers in Friends", + request: GetApproversInFriendsRequest() ), - .init(title: "Get Groups", - request: GetGroupsRequest() + .init( + title: "Get Groups", + request: GetGroupsRequest() ), .getApproversInGroup ] @@ -96,24 +132,18 @@ class APIStore { .multiSendTextMessage, .sendFlexMessage ] - } - - func numberOfAPIs(in category: APICategory) -> Int { - switch category { - case .auth: return authAPIs.count - case .friendship: return friendshipAPIs.count - case .graph: return graphAPIs.count - case .messaging: return messagingAPIs.count - } - } - - func api(in category: APICategory, at index: Int) -> APIItem { - switch category { - case .auth: return authAPIs[index] - case .friendship: return friendshipAPIs[index] - case .graph: return graphAPIs[index] - case .messaging: return messagingAPIs[index] - } + + openChatAPIs = [ + .init( + title: "Agreement Status", + request: GetOpenChatTermAgreementStatusRequest() + ), + .checkOpenChatRoomStatus, + .checkOpenChatRoomMembershipState, + .checkOpenChatRoomJoinType, + .createOpenChatRoom, + .joinOpenChatRoom + ] } } @@ -124,12 +154,14 @@ struct APIItem { let block: AnyResultBlock let path: String + let method: HTTPMethod let title: String let available: Bool init(title: String, request: T, available: Bool = true) { - self.init(title: title, path: request.path, available: available) { (controller, handler) in + self.init(title: title, path: request.path, method: request.method, available: available) { + (controller, handler) in Session.shared.send(request) { result in switch result { @@ -142,9 +174,14 @@ struct APIItem { } } - init(title: String, path: String, available: Bool = true, block: @escaping AnyResultBlock) { + init(title: String, mock: T, available: Bool = true, block: @escaping AnyResultBlock) { + self.init(title: title, path: mock.path, method: mock.method, available: available, block: block) + } + + init(title: String, path: String, method: HTTPMethod, available: Bool = true, block: @escaping AnyResultBlock) { self.title = title self.path = path + self.method = method self.block = block self.available = available @@ -156,6 +193,24 @@ struct APIItem { } extension APIItem { + + static var refreshToken: APIItem { + return APIItem( + title: "Refresh token", + path: "/oauth2/v2.1/token", + method: .post, + available: LoginManager.shared.isAuthorized) { viewController, handler in + API.Auth.refreshAccessToken { result in + switch result { + case .success(let token): + handler(.success(token)) + case .failure(let error): + handler(.failure(.sdkError(error))) + } + } + } + } + static var sendTextMessage: APIItem { let mock = PostSendMessagesRequest(chatID: "", messages: []) let block: AnyResultBlock = { arg in @@ -178,7 +233,13 @@ extension APIItem { } } - return APIItem(title: "Send text message to a friend", path: mock.path, available: true, block: block) + return APIItem( + title: "Send text message to a friend", + path: mock.path, + method: mock.method, + available: true, + block: block + ) } static var multiSendTextMessage: APIItem { @@ -213,7 +274,9 @@ extension APIItem { } } - return APIItem(title: "Multisend text message to first five friends", path: mock.path, available: true, block: block) + return APIItem( + title: "Multisend text message to first five friends", mock: mock, available: true, block: block + ) } static var sendFlexMessage: APIItem { @@ -244,20 +307,18 @@ extension APIItem { } } } - return APIItem(title: "Send flex message to a friend", path: mock.path, available: true, block: block) + return APIItem(title: "Send flex message to a friend", mock: mock, available: true, block: block) } static var getApproversInGroup: APIItem { - let path = GetApproversInGroupRequest(groupID: "[groupID]").path + let mock = try! GetApproversInGroupRequest(groupID: "groupID") let block: AnyResultBlock = { arg in let (controller, handler) = arg selectGroupFromGroupList(in: controller, handler: { result in switch result { case .success(let groupID): - let request = GetApproversInGroupRequest(groupID: groupID) - Session.shared.send(request) { - response in - switch response { + API.getApproversInGroup(groupID: groupID, pageToken: nil) { result in + switch result { case .success(let value): handler(.success(value)) case .failure(let error): handler(.failure(.sdkError(error))) } @@ -267,13 +328,97 @@ extension APIItem { } }) } - return APIItem(title: "Get Approvers in given Group", path: path, available: true, block: block) + return APIItem(title: "Get Approvers in given Group", mock: mock, available: true, block: block) + } + + static var checkOpenChatRoomStatus: APIItem { + let mock = try! GetOpenChatRoomStatusRequest(openChatId: "openChatId") + let block: AnyResultBlock = { arg in + let (controller, handler) = arg + collectOpenChatMid(in: controller) { result in + let text = try! result.get() + API.getOpenChatRoomStatus(openChatId: text) { result in + switch result { + case .success(let value): handler(.success(value)) + case .failure(let error): handler(.failure(.sdkError(error))) + } + } + } + } + return APIItem(title: "Check Open Chat Room Status", mock: mock, block: block) } + + static var checkOpenChatRoomJoinType: APIItem { + let mock = try! GetOpenChatRoomJoinTypeRequest(openChatId: "openChatId") + let block: AnyResultBlock = { arg in + let (controller, handler) = arg + collectOpenChatMid(in: controller) { result in + let text = try! result.get() + API.getOpenChatRoomJoinType(openChatId: text) { result in + switch result { + case .success(let value): handler(.success(value)) + case .failure(let error): handler(.failure(.sdkError(error))) + } + } + } + } + return APIItem(title: "Check Open Chat Room Join Type", mock: mock, block: block) + } + + static var checkOpenChatRoomMembershipState: APIItem { + let mock = try! GetOpenChatRoomMembershipStateRequest(openChatId: "openChatId") + let block: AnyResultBlock = { arg in + let (controller, handler) = arg + collectOpenChatMid(in: controller) { result in + let text = try! result.get() + API.getOpenChatRoomMembershipState(openChatId: text) { result in + switch result { + case .success(let value): handler(.success(value)) + case .failure(let error): handler(.failure(.sdkError(error))) + } + } + } + } + return APIItem(title: "Check Open Chat Room Membership State", mock: mock, block: block) + } + + static var createOpenChatRoom: APIItem { + let room = OpenChatRoomCreatingItem( + name: "Sample Room", + roomDescription: "This is just a sample open chat room", + creatorDisplayName: "onevcat", + category: OpenChatCategory.notSelected, + allowSearch: true + ) + return .init(title: "Create Open Chat Room", request: PostOpenChatCreateRequest(room: room)) + } + + static var joinOpenChatRoom: APIItem { + let mock = try! PostOpenChatRoomJoinRequest(openChatId: "openChatId", displayName: "displayName") + let block: AnyResultBlock = { arg in + let (controller, handler) = arg + collectionOpenChatIdAndUsername(in: controller) { result in + let text = try! result.get() + API.postOpenChatRoomJoin(openChatId: text.0, displayName: text.1) { result in + switch result { + case .success: handler(.success(APIStatus(code: 204))) + case .failure(let error): handler(.failure(.sdkError(error))) + } + } + } + } + return APIItem(title: "Join an Open Chat Room", mock: mock, block: block) + } +} + +struct APIStatus { + let code: Int } func selectUserFromFriendList( in viewController: UIViewController, - handler: @escaping (Result) -> Void) + handler: @escaping (Result) -> Void +) { let getFriends = GetFriendsRequest() Session.shared.send(getFriends) { res in @@ -308,7 +453,8 @@ func selectUserFromFriendList( func selectGroupFromGroupList( in viewController: UIViewController, - handler: @escaping (Result) -> Void) + handler: @escaping (Result) -> Void +) { let request = GetGroupsRequest() Session.shared.send(request) { res in @@ -343,7 +489,8 @@ func selectGroupFromGroupList( func selectFlexMessage( in viewController: UIViewController, - handler: @escaping (Result) -> Void) + handler: @escaping (Result) -> Void +) { let alert = UIAlertController(title: "Message", message: nil, preferredStyle: .actionSheet) @@ -379,6 +526,44 @@ func selectFlexMessage( viewController.present(alert, animated: true) } +func collectOpenChatMid( + in viewController: UIViewController, + handler: @escaping (Result) -> Void +) +{ + let alert = UIAlertController(title: "Open Chat Id", message: nil, preferredStyle: .alert) + alert.addTextField { $0.placeholder = "Input openChatId..." } + alert.addAction( + .init(title: "OK", style: .default) { + _ in + guard let text = alert.textFields?.first?.text else { return } + handler(.success(text)) + } + ) + alert.addAction(.init(title: "Cancel", style: .cancel)) + viewController.present(alert, animated: true) +} + +func collectionOpenChatIdAndUsername( + in viewController: UIViewController, + handler: @escaping (Result<(String,String), Never>) -> Void +) +{ + let alert = UIAlertController(title: "Open Chat Id", message: nil, preferredStyle: .alert) + alert.addTextField { $0.placeholder = "Open Chat Id..." } + alert.addTextField { $0.placeholder = "Display Name..." } + alert.addAction( + .init(title: "OK", style: .default) { + _ in + guard let openChatId = alert.textFields?[0].text else { return } + guard let displayName = alert.textFields?[1].text else { return } + handler(.success((openChatId, displayName))) + } + ) + alert.addAction(.init(title: "Cancel", style: .cancel)) + viewController.present(alert, animated: true) +} + extension UIAlertController { func setupPopover(in view: UIView) { guard let popoverController = popoverPresentationController else { diff --git a/LineSDKSample/LineSDKSample/AppDelegate.swift b/LineSDKSample/LineSDKSample/AppDelegate.swift index f0ac718e..fb871f7c 100644 --- a/LineSDKSample/LineSDKSample/AppDelegate.swift +++ b/LineSDKSample/LineSDKSample/AppDelegate.swift @@ -1,13 +1,13 @@ // // AppDelegate.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -45,6 +45,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { fatalError("Please set correct channel ID in Config.xcconfig file.") } + window!.tintColor = UIColor(red: 91 / 255, green: 192 / 255, blue: 110 / 255, alpha: 1.0) + return true } diff --git a/LineSDKSample/LineSDKSample/Assets.xcassets/ui.imageset/Contents.json b/LineSDKSample/LineSDKSample/Assets.xcassets/ui.imageset/Contents.json new file mode 100644 index 00000000..fb9c96bf --- /dev/null +++ b/LineSDKSample/LineSDKSample/Assets.xcassets/ui.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ui@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/LineSDKSample/LineSDKSample/Assets.xcassets/ui.imageset/ui@2x.png b/LineSDKSample/LineSDKSample/Assets.xcassets/ui.imageset/ui@2x.png new file mode 100644 index 00000000..7b2da5ec Binary files /dev/null and b/LineSDKSample/LineSDKSample/Assets.xcassets/ui.imageset/ui@2x.png differ diff --git a/LineSDKSample/LineSDKSample/Base.lproj/Main.storyboard b/LineSDKSample/LineSDKSample/Base.lproj/Main.storyboard index 5d19e76f..d28ab757 100644 --- a/LineSDKSample/LineSDKSample/Base.lproj/Main.storyboard +++ b/LineSDKSample/LineSDKSample/Base.lproj/Main.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -17,10 +15,16 @@ - + - + + + + + + + @@ -35,32 +39,32 @@ - + - + @@ -105,10 +109,10 @@ - + - + @@ -150,24 +154,24 @@ - + - + - + diff --git a/LineSDKSample/LineSDKSample/Config.xcconfig b/LineSDKSample/LineSDKSample/Config.xcconfig index a85e4a19..4e38889e 100644 --- a/LineSDKSample/LineSDKSample/Config.xcconfig +++ b/LineSDKSample/LineSDKSample/Config.xcconfig @@ -1,13 +1,13 @@ // // Config.xcconfig // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDKSample/LineSDKSample/Info.plist b/LineSDKSample/LineSDKSample/Info.plist index 7189fe29..5c88ea88 100644 --- a/LineSDKSample/LineSDKSample/Info.plist +++ b/LineSDKSample/LineSDKSample/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LINE Channel ID $(LINE_CHANNEL_ID) LSApplicationQueriesSchemes @@ -47,6 +47,8 @@ armv7 + UIStatusBarStyle + UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/LineSDKSample/LineSDKSample/LineSDKSample.entitlements b/LineSDKSample/LineSDKSample/LineSDKSample.entitlements new file mode 100644 index 00000000..225aa48b --- /dev/null +++ b/LineSDKSample/LineSDKSample/LineSDKSample.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + keychain-access-groups + + + diff --git a/LineSDKSample/LineSDKSample/LineSDKSampleError.swift b/LineSDKSample/LineSDKSample/LineSDKSampleError.swift index ffd87b5c..6772620c 100644 --- a/LineSDKSample/LineSDKSample/LineSDKSampleError.swift +++ b/LineSDKSample/LineSDKSample/LineSDKSampleError.swift @@ -1,13 +1,13 @@ // // LineSDKSampleError.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDKSample/LineSDKSample/Login/LoginNavigationController.swift b/LineSDKSample/LineSDKSample/Login/LoginNavigationController.swift index e806ba80..ef691127 100644 --- a/LineSDKSample/LineSDKSample/Login/LoginNavigationController.swift +++ b/LineSDKSample/LineSDKSample/Login/LoginNavigationController.swift @@ -1,13 +1,13 @@ // // LoginNavigationController.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDKSample/LineSDKSample/Login/LoginSettings.swift b/LineSDKSample/LineSDKSample/Login/LoginSettings.swift new file mode 100644 index 00000000..918aa751 --- /dev/null +++ b/LineSDKSample/LineSDKSample/Login/LoginSettings.swift @@ -0,0 +1,56 @@ +// +// LoginSettings.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation +import LineSDK + +class LoginSettings { + + static let normalPermissions: [LoginPermission] = [ + .profile, .friends, .groups, .messageWrite, .oneTimeShare, + .openChatTermStatus, .openChatInfo, .openChatRoomCreateAndJoin + ] + + static let openIDPermissions: [LoginPermission] = [ + .email, .address, .birthdate, .gender, .phone, .realName + ] + + private(set) var permissions: Set = [.profile] + var parameters = LoginManager.Parameters() + + func togglePermission(_ permission: LoginPermission) { + if permissions.contains(permission) { + permissions.remove(permission) + } else { + permissions.insert(permission) + } + + if permissions.intersection(LoginSettings.openIDPermissions).isEmpty { + permissions.remove(.openID) + } else { + permissions.insert(.openID) + } + } + + func permissionIsSelected(_ permission: LoginPermission) -> Bool { + return permissions.contains(permission) + } +} diff --git a/LineSDKSample/LineSDKSample/Login/LoginSettingsViewController.swift b/LineSDKSample/LineSDKSample/Login/LoginSettingsViewController.swift new file mode 100644 index 00000000..c089987f --- /dev/null +++ b/LineSDKSample/LineSDKSample/Login/LoginSettingsViewController.swift @@ -0,0 +1,182 @@ +// +// LoginSettingsViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import LineSDK + +protocol LoginSettingsViewControllerDelegate: AnyObject { + func loginSettingsViewControllerWillDisappear(_ viewController: LoginSettingsViewController) +} + +class LoginSettingsViewController: UITableViewController { + enum Section: Int, CaseIterable { + case permissions + case openID + case parameters + + var sectionTitle: String { + switch self { + case .permissions: + return "Permissions" + case .openID: + return "Open ID" + case .parameters: + return "Parameters" + } + } + } + + struct PermissionItem { + let title: String + let permission: LoginPermission + } + + struct ParameterItem { + let title: String + var text: (LoginManager.Parameters) -> String + let action: (inout LoginManager.Parameters) -> Void + } + + let permissions: [PermissionItem] = { + return LoginSettings.normalPermissions.map { PermissionItem(title: $0.rawValue, permission: $0) } + }() + + let openIDs: [PermissionItem] = { + return LoginSettings.openIDPermissions.map { PermissionItem(title: $0.rawValue, permission: $0) } + }() + + let parameters: [ParameterItem] = [ + ParameterItem( + title: "Only Web Login", + text: { p in + return p.onlyWebLogin ? "Yes" : "No" + }, + action: { p in + p.onlyWebLogin.toggle() + } + ), + ParameterItem( + title: "Bot (OA) Prompt", + text: { p in + switch p.botPromptStyle { + case .aggressive: return "Aggressive" + case .normal: return "Normal" + case .none: return "None" + } + }, action: { p in + switch p.botPromptStyle { + case .aggressive: p.botPromptStyle = .normal + case .normal: p.botPromptStyle = .none + case .none: p.botPromptStyle = .aggressive + } + } + ), + ParameterItem( + title: "Initial Auth Method", + text: { p in + switch p.initialWebAuthenticationMethod { + case .email: return "Email" + case .qrCode: return "QR Code" + } + }, action: { p in + switch p.initialWebAuthenticationMethod { + case .email: p.initialWebAuthenticationMethod = .qrCode + case .qrCode: p.initialWebAuthenticationMethod = .email + } + } + ) + ] + + var loginSettings: LoginSettings! + weak var delegate: LoginSettingsViewControllerDelegate? + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + delegate?.loginSettingsViewControllerWillDisappear(self) + } + + @IBAction func donePressed(_ sender: Any) { + dismiss(animated: true) + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return Section.allCases.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch Section(rawValue: section) { + case .permissions: return permissions.count + case .openID: return openIDs.count + case .parameters: return parameters.count + case .none: return 0 + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + + guard let section = Section(rawValue: indexPath.section) else { + preconditionFailure() + } + switch section { + case .permissions: + let p = permissions[indexPath.row] + cell.textLabel?.text = p.title + cell.detailTextLabel?.text = nil + cell.accessoryType = loginSettings.permissionIsSelected(p.permission) ? .checkmark : .none + case .openID: + let p = openIDs[indexPath.row] + cell.textLabel?.text = p.title + cell.detailTextLabel?.text = nil + cell.accessoryType = loginSettings.permissionIsSelected(p.permission) ? .checkmark : .none + case .parameters: + let p = parameters[indexPath.row] + cell.textLabel?.text = p.title + cell.detailTextLabel?.text = p.text(loginSettings.parameters) + cell.accessoryType = .none + } + + return cell + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return Section(rawValue: section)?.sectionTitle + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let section = Section(rawValue: indexPath.section) else { + return + } + switch section { + case .permissions: + let p = permissions[indexPath.row] + loginSettings.togglePermission(p.permission) + case .openID: + let p = openIDs[indexPath.row] + loginSettings.togglePermission(p.permission) + case .parameters: + let p = parameters[indexPath.row] + p.action(&loginSettings.parameters) + } + tableView.deselectRow(at: indexPath, animated: true) + tableView.reloadRows(at: [indexPath], with: .none) + } +} diff --git a/LineSDKSample/LineSDKSample/Login/LoginViewController+SPM.swift b/LineSDKSample/LineSDKSample/Login/LoginViewController+SPM.swift new file mode 100644 index 00000000..ddbdf66a --- /dev/null +++ b/LineSDKSample/LineSDKSample/Login/LoginViewController+SPM.swift @@ -0,0 +1,61 @@ +// +// LoginViewController+SPM.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import LineSDK + +extension Notification.Name { + static let userDidLogin = Notification.Name("com.linecorp.linesdk_sample.userDidLogin") +} + +class LoginViewController: UIViewController, IndicatorDisplay { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + let loginButton = UIButton(type: .system) + loginButton.addTarget(self, action: #selector(login), for: .touchUpInside) + loginButton.setTitle("Login with LINE", for: .normal) + + view.addSubview(loginButton) + + loginButton.translatesAutoresizingMaskIntoConstraints = false + loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true + loginButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true + } + + @objc func login() { + showIndicator() + LoginManager.shared.login(permissions: [.profile, .friends, .groups, .messageWrite, .openID], in: self) { + result in + self.hideIndicator() + switch result { + case .success(let loginResult): + UIAlertController.present(in: self, successResult: "\(loginResult)") { + NotificationCenter.default.post(name: .userDidLogin, object: loginResult) + } + case .failure(let error): + UIAlertController.present(in: self, error: error) + } + + } + } +} diff --git a/LineSDKSample/LineSDKSample/Login/LoginViewController.swift b/LineSDKSample/LineSDKSample/Login/LoginViewController.swift index 9ee8efbd..ea15f198 100644 --- a/LineSDKSample/LineSDKSample/Login/LoginViewController.swift +++ b/LineSDKSample/LineSDKSample/Login/LoginViewController.swift @@ -1,13 +1,13 @@ // // ViewController.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -27,24 +27,48 @@ extension Notification.Name { } class LoginViewController: UIViewController, IndicatorDisplay { - + + let loginSettings = LoginSettings() + var loginButton: LoginButton! + override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. - let loginButton = LoginButton() + loginButton = LoginButton() loginButton.delegate = self loginButton.presentingViewController = self - // You could set the permissions you need or use default permissions - loginButton.permissions = [.profile, .friends, .groups, .messageWrite, .openID] - view.addSubview(loginButton) loginButton.translatesAutoresizingMaskIntoConstraints = false loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true loginButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "ShowSettings" { + let viewController = (segue.destination as! UINavigationController).topViewController as! LoginSettingsViewController + viewController.loginSettings = loginSettings + viewController.delegate = self + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + updateButton() + } + + func updateButton() { + loginButton.permissions = loginSettings.permissions + loginButton.parameters = loginSettings.parameters + } +} + +extension LoginViewController: LoginSettingsViewControllerDelegate { + func loginSettingsViewControllerWillDisappear(_ viewController: LoginSettingsViewController) { + updateButton() + } } extension LoginViewController: LoginButtonDelegate { @@ -56,13 +80,23 @@ extension LoginViewController: LoginButtonDelegate { } } - func loginButton(_ button: LoginButton, didFailLogin error: Error) { + func loginButton(_ button: LoginButton, didFailLogin error: LineSDKError) { hideIndicator() + #if targetEnvironment(macCatalyst) + // For macCatalyst app, we allow process discarding so just ignore this error. + if case .generalError(reason: .processDiscarded(let p)) = error { + print("Process discarded: \(p)") + return + } + #endif + UIAlertController.present(in: self, error: error) } func loginButtonDidStartLogin(_ button: LoginButton) { + #if !targetEnvironment(macCatalyst) showIndicator() + #endif } } diff --git a/LineSDKSample/LineSDKSample/Login/UserDetailViewController.swift b/LineSDKSample/LineSDKSample/Login/UserDetailViewController.swift index cdc6da5e..62612c0d 100644 --- a/LineSDKSample/LineSDKSample/Login/UserDetailViewController.swift +++ b/LineSDKSample/LineSDKSample/Login/UserDetailViewController.swift @@ -1,13 +1,13 @@ // // UserDetailViewController.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -77,12 +77,10 @@ class UserDetailViewController: UITableViewController, CellCopyable { case 0: content = ("Access", token?.value ?? "N/A") case 1: - content = ("Refresh", token?.refreshToken ?? "N/A") - case 2: content = ("Created", token?.createdAt.description ?? "N/A") - case 3: + case 2: content = ("Expire", token?.expiresAt.description ?? "N/A") - case 4: + case 3: let permissions = token?.permissions ?? [] let text = permissions.map { $0.rawValue }.joined(separator: " ") content = ("Permissions", text) @@ -96,7 +94,7 @@ class UserDetailViewController: UITableViewController, CellCopyable { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch Section(rawValue: section)! { case .user: return 4 - case .token: return 5 + case .token: return 4 } } diff --git a/LineSDKSample/LineSDKSample/Login/UserProfileViewController.swift b/LineSDKSample/LineSDKSample/Login/UserProfileViewController.swift index dfd85e8f..19558a53 100644 --- a/LineSDKSample/LineSDKSample/Login/UserProfileViewController.swift +++ b/LineSDKSample/LineSDKSample/Login/UserProfileViewController.swift @@ -1,13 +1,13 @@ // // UserProfileViewController.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDKSample/LineSDKSample/Model/MessageStore.swift b/LineSDKSample/LineSDKSample/Model/MessageStore.swift new file mode 100644 index 00000000..13d0f133 --- /dev/null +++ b/LineSDKSample/LineSDKSample/Model/MessageStore.swift @@ -0,0 +1,310 @@ +// +// MessageStore.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation +import LineSDK + +let sdkTitleImageURL = + URL(string: "https://repository-images.githubusercontent.com/154433729/773e8080-9e83-11e9-8021-45a46d2d2e4c")! +let sdkLogoImageURL = + URL(string: "https://raw.githubusercontent.com/line/line-sdk-ios-swift/assets/assets/sdklogo.png")! +let starIconImageURL = + URL(string: "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png")! +let swiftLogoImageURL = + URL(string: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Swift_logo.svg/1138px-Swift_logo.svg.png")! + +extension MessageConvertible { + func named(_ name: String) -> MessageStore.StoredMessage { + return MessageStore.StoredMessage(name: name, message: self.message) + } +} + +extension Notification.Name { + static let messageStoreMessageInserted = + Notification.Name(rawValue: "com.linecorp.linesdk.sample.messageStoreMessageInserted") +} + +class MessageStore { + + struct StoredMessage: Codable { + let name: String + let message: Message + } + + static let shared = MessageStore() + + let url: URL = { + let fileManager = FileManager.default + let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask) + .first! + .appendingPathComponent("message_template.json") + return url + }() + + var messages: [StoredMessage] { + didSet { + save() + } + } + + private init() { + do { + let data = try Data(contentsOf: url) + messages = try JSONDecoder().decode([StoredMessage].self, from: data) + } catch { + messages = [ + MessageStore.SampleMessage.textMessage, + MessageStore.SampleMessage.imageMessage, + MessageStore.SampleMessage.videoMessage, + MessageStore.SampleMessage.audioMessage, + MessageStore.SampleMessage.locationMessage, + MessageStore.SampleMessage.templateButtonsMessage, + MessageStore.SampleMessage.templateConfirmMessage, + MessageStore.SampleMessage.flexBubbleMessage, + MessageStore.SampleMessage.flexCarouselMessage + ] + save() + } + } + + func save() { + let data = try! JSONEncoder().encode(messages) + try! data.write(to: url) + } + + func insert(_ message: Message, name: String, at index: Int? = nil) { + let storedMessage = StoredMessage(name: name, message: message) + if let index = index { + messages.insert(storedMessage, at: index) + } else { + messages.append(storedMessage) + } + NotificationCenter.default.post(name: .messageStoreMessageInserted, object: self) + } + + func remove(at index: Int) { + messages.remove(at: index) + } +} + +extension MessageStore { + enum SampleMessage { + static var textMessage: StoredMessage { + return TextMessage(text: "Hello From LINE SDK").named("Text Message") + } + + static var imageMessage: StoredMessage { + return try! ImageMessage( + originalContentURL: sdkTitleImageURL, + previewImageURL: sdkLogoImageURL) + .named("Image Message") + } + + static var videoMessage: StoredMessage { + return try! VideoMessage( + originalContentURL: + URL(string: "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4")!, + previewImageURL: + URL(string: "https://sample-videos.com/img/Sample-png-image-100kb.png")!) + .named("Video Message") + } + + static var audioMessage: StoredMessage { + return try! AudioMessage( + originalContentURL: URL(string: "https://sample-videos.com/audio/mp3/crowd-cheering.mp3")!, + duration: 28) + .named("Audio Message") + } + + static var locationMessage: StoredMessage { + return LocationMessage( + title: "Current Location", + address: "JR Shinjuku Miraina Tower 23F, 4-1-6 Shinjuku, Tokyo, Japan", + latitude: 35.69, + longitude: 139.70) + .named("Location Message") + } + + static var templateButtonsMessage: StoredMessage { + return TemplateMessage( + altText: "Template Message (Buttons)", + payload: TemplateButtonsPayload( + title: "Weather", + text: "It's a sunny day today!", + actions: [ + MessageURIAction( + label: "Yes", + uri: URL(string: "https://example.com?action_index=0")!), + MessageURIAction( + label: "Absolutely", + uri: URL(string: "https://example.com?action_index=1")!), + ] + ) + ) + .named("Template Message (Buttons)") + } + + static var templateConfirmMessage: StoredMessage { + return TemplateMessage( + altText: "Template Message (Buttons)", + payload: TemplateConfirmPayload( + text: "Eat outside?", + confirmAction: MessageURIAction( + label: "Yes", + uri: URL(string: "https://example.com?action_index=0")!), + cancelAction: MessageURIAction( + label: "No", + uri: URL(string: "https://example.com?action_index=1")!) + ) + ) + .named("Template Message (Confirm)") + } + + static var flexBubbleMessage: StoredMessage { + return FlexMessage( + altText: "Flex Message (Bubble)", + container:FlexBubbleItem.lineSDK.bubbleContainer + ).named("Flex Message (Bubble)") + } + + static var flexCarouselMessage: StoredMessage { + return FlexMessage( + altText: "Flex Message (Carousel)", + container: FlexCarouselContainer( + contents: [ + FlexBubbleItem.lineSDK.bubbleContainer, + FlexBubbleItem.armeria.bubbleContainer, + FlexBubbleItem.promgen.bubbleContainer + ] + ) + ).named("Flex Message (Carousel)") + } + + struct FlexBubbleItem { + let heroImageURL: URL + let repo: String + let title: String + let starCount: Int + let version: String + let coverage: Int + let swiftCompatible: Bool + + var repoURL: URL { + return URL(string: "https://github.com/\(repo)")! + } + + var bubbleContainer: FlexBubbleContainer { + return FlexBubbleContainer( + hero: try! FlexImageComponent( + url: heroImageURL, size: .full, + aspectRatio: .ratio_16x9, + aspectMode: .fill, + action: MessageURIAction(uri: repoURL).action), + body: FlexBoxComponent(layout: .vertical, spacing: .md) { + var components: [FlexMessageComponentConvertible] = [ + FlexTextComponent( + text: title, size: .xl, gravity: .center, weight: .bold), + FlexBoxComponent(layout: .baseline, margin: .md) { + return [ + try! FlexIconComponent(url: starIconImageURL, size: .md), + FlexTextComponent( + text: "\(starCount)", margin: .md, size: .sm, color: "#999999") + ] + }, + FlexBoxComponent(layout: .vertical, spacing: .sm, margin: .lg) { + return [ + FlexBoxComponent(layout: .baseline, spacing: .sm) { + return [ + FlexTextComponent( + text: "Repo", flex: 2, size: .sm, color: "#aaaaaa"), + FlexTextComponent( + text: repo, flex: 4, size: .sm, color: "#666666") + ] + }, + FlexBoxComponent(layout: .baseline, spacing: .sm) { + return [ + FlexTextComponent( + text: "Version", flex: 2, size: .sm, color: "#aaaaaa"), + FlexTextComponent( + text: version, flex: 4, size: .sm, color: "#666666") + ] + }, + FlexBoxComponent(layout: .baseline, spacing: .sm) { + return [ + FlexTextComponent( + text: "Coverage", flex: 2, size: .sm, color: "#aaaaaa"), + FlexTextComponent( + text: "\(coverage)%", flex: 4, size: .sm, color: "#666666") + ] + } + ] + } + ] + if swiftCompatible { + components.append( + FlexBoxComponent(layout: .horizontal, margin: .xxl) { + return [ + FlexSpacerComponent(size: nil), + try! FlexImageComponent(url: swiftLogoImageURL, size: .sm, aspectMode: .fit), + FlexTextComponent( + text: "Compatible with Swift " + + "(Swift and the Swift logo are trademarks of Apple Inc.)", + margin: .xxl, + size: .xs, + wrapping: true, + color: "#aaaaaa") + + ] + } + ) + } + return components + } + ) + } + + static let lineSDK = FlexBubbleItem( + heroImageURL: sdkTitleImageURL, + repo: "line/line-sdk-ios-swift", + title: "LINE SDK", + starCount: 607, + version: "5.3.0", + coverage: 82, + swiftCompatible: true) + static let armeria = FlexBubbleItem( + heroImageURL: URL(string: "https://pbs.twimg.com/profile_images/1115801670686871552/GdB0P9Dw.jpg")!, + repo: "line/armeria", + title: "Armeria", + starCount: 1997, + version: "0.89.0", + coverage: 77, + swiftCompatible: false) + static let promgen = FlexBubbleItem( + heroImageURL: URL(string: "https://line.github.io/centraldogma/_static/central_dogma.png")!, + repo: "line/centraldogma", + title: "Central Dogma", + starCount: 279, + version: "0.41.0", + coverage: 63, + swiftCompatible: false) + } + } +} diff --git a/LineSDKSample/LineSDKSample/Model/OpenChatRoom.swift b/LineSDKSample/LineSDKSample/Model/OpenChatRoom.swift new file mode 100644 index 00000000..56ff2c48 --- /dev/null +++ b/LineSDKSample/LineSDKSample/Model/OpenChatRoom.swift @@ -0,0 +1,70 @@ +// +// OpenChatRoom.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +class OpenChatRoom: Codable { + let chatRoomId: String + let url: URL + + init(chatRoomId: String, url: URL) { + self.chatRoomId = chatRoomId + self.url = url + } +} + +extension OpenChatRoom { + + static var onUpdated: (() -> Void)? + + static var all: [OpenChatRoom] { + return builtInRooms + createdRooms + } + + static let builtInRooms: [OpenChatRoom] = [ + ] + + static let url: URL = { + let fileManager = FileManager.default + let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask) + .first! + .appendingPathComponent("created_rooms.json") + return url + }() + + static var createdRooms: [OpenChatRoom] = { + do { + let data = try Data(contentsOf: url) + return try JSONDecoder().decode([OpenChatRoom].self, from: data) + } catch { + return [] + } + }() + { + didSet { + guard let data = try? JSONEncoder().encode(createdRooms) else { + return + } + try? data.write(to: url) + onUpdated?() + } + } +} diff --git a/LineSDKSample/LineSDKSample/UI/OpenChatRoomTableViewController.swift b/LineSDKSample/LineSDKSample/UI/OpenChatRoomTableViewController.swift new file mode 100644 index 00000000..b483bdd2 --- /dev/null +++ b/LineSDKSample/LineSDKSample/UI/OpenChatRoomTableViewController.swift @@ -0,0 +1,287 @@ +// +// OpenChatRoomTableViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import LineSDK + +class OpenChatRoomTableViewController: UITableViewController, IndicatorDisplay { + + enum Section: Int, CaseIterable { + case builtIn = 0 + case created = 1 + + var title: String { + switch self { + case .builtIn: return "Built-in Rooms" + case .created: return "Created Rooms" + } + } + } + + override func viewDidLoad() { + super.viewDidLoad() + OpenChatRoom.onUpdated = { [weak self] in + self?.tableView.reloadData() + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return Section.allCases.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch Section(rawValue: section) { + case .builtIn: + return OpenChatRoom.builtInRooms.count + default: + return OpenChatRoom.createdRooms.count + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "OpenChatRoomCell", for: indexPath) + let room = roomAtIndexPath(indexPath) + configure(cell: cell, room: room) + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let room = roomAtIndexPath(indexPath) + + var errors: [LineSDKError] = [] + + var roomStatus: GetOpenChatRoomStatusRequest.Status? + var membership: GetOpenChatRoomMembershipStateRequest.State? + var joinType: GetOpenChatRoomJoinTypeRequest.RoomType? + + showIndicatorOnWindow() + + let group = DispatchGroup() + + group.enter() + API.getOpenChatRoomStatus(openChatId: room.chatRoomId) { result in + switch result { + case .success(let response): + roomStatus = response.status + case .failure(let error): + errors.append(error) + } + group.leave() + } + + group.enter() + API.getOpenChatRoomMembershipState(openChatId: room.chatRoomId) { result in + switch result { + case .success(let response): + membership = response.state + case .failure(let error): + errors.append(error) + } + + group.leave() + } + + + group.enter() + API.getOpenChatRoomJoinType(openChatId: room.chatRoomId) { result in + switch result { + case .success(let response): + joinType = response.type + case .failure(let error): + errors.append(error) + } + group.leave() + } + + group.notify(queue: .main) { + self.hideIndicatorFromWindow() + guard errors.isEmpty else { + let errorMessages = errors.map { $0.errorDescription ?? "Unknown Error" }.joined(separator: "\n") + UIAlertController.present(in: self, error: errorMessages) + return + } + self.showRoomDetail(room: room, roomStatus: roomStatus!, membership: membership!, joinType: joinType!) + } + + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return Section(rawValue: section)?.title ?? "" + } + + private func configure(cell: UITableViewCell, room: OpenChatRoom) { + cell.textLabel?.text = room.chatRoomId + cell.detailTextLabel?.text = room.url.absoluteString + cell.accessoryType = .disclosureIndicator + } + + private func roomAtIndexPath(_ indexPath: IndexPath) -> OpenChatRoom { + switch Section(rawValue: indexPath.section) { + case .builtIn: + return OpenChatRoom.builtInRooms[indexPath.row] + case .created: + return OpenChatRoom.createdRooms[indexPath.row] + default: + preconditionFailure("Invalid section") + } + } + + private func showRoomDetail( + room: OpenChatRoom, + roomStatus: GetOpenChatRoomStatusRequest.Status, + membership: GetOpenChatRoomMembershipStateRequest.State, + joinType: GetOpenChatRoomJoinTypeRequest.RoomType + ) { + guard roomStatus == .alive else { + UIAlertController.present(in: self, error: "The room is not valid anymore. State: \(roomStatus.rawValue)") + return + } + + let alert = UIAlertController( + title: "Open Chat Room", + message: "Room Status: \(roomStatus)\nJoin State: \(membership)\nRoom Join Type:\(joinType)", + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + + switch (membership, joinType) { + case (.joined, _): + alert.addAction(UIAlertAction(title: "Open", style: .default) { _ in self.openRoom(room) }) + case (.notJoined, .default): + alert.addAction(UIAlertAction(title: "Join", style: .default) { _ in self.joinRoom(room) }) + case (.notJoined, .undefined): + alert.message?.append("\n\nUndefined join type. Upgrade your LINE SDK.") + case (.notJoined, _): + alert.message?.append("\n\nUnsupported room type. Cannot join.") + case (.undefined, _): + alert.message?.append("\n\nUndefined membership. Upgrade your LINE SDK.") + } + + present(alert, animated: true) + } + + private func openRoom(_ room: OpenChatRoom) { + UIApplication.shared.open(room.url, options: [:]) + } + + private func joinRoom(_ room: OpenChatRoom) { + + showIndicatorOnWindow() + API.getProfile { result in + switch result { + case .success(let profile): + API.postOpenChatRoomJoin(openChatId: room.chatRoomId, displayName: profile.displayName) { result in + self.hideIndicatorFromWindow() + switch result { + case .success: + UIAlertController.present(in: self, successResult: "Joined.") + case .failure(let error): + UIAlertController.present(in: self, error: error) + } + } + case .failure(let error): + self.hideIndicatorFromWindow() + UIAlertController.present(in: self, error: error) + } + } + } + + @IBAction func createRoom(_ sender: Any) { + let status = OpenChatCreatingController.localAuthorizationStatusForCreatingOpenChat() + switch status { + case .authorized: + let openChatCreatingController = OpenChatCreatingController() + openChatCreatingController.delegate = self + + showIndicatorOnWindow() + openChatCreatingController.loadAndPresent(in: self) { result in + self.hideIndicatorFromWindow() + switch result { + case .success: print("Presented without problem.") + case .failure(let error): + UIAlertController.present(in: self, error: error) + } + } + case .lackOfPermissions(let p): + UIAlertController.present( + in: self, + title: nil, + message: "Lack of permissions: \(p)", + actions: [.init(title: "OK", style: .cancel)] + ) + case .lackOfToken: + UIAlertController.present( + in: self, + title: nil, + message: "Please login first.", + actions: [.init(title: "OK", style: .cancel)] + ) + } + } +} + +extension OpenChatRoomTableViewController: OpenChatCreatingControllerDelegate { + func openChatCreatingController( + _ controller: OpenChatCreatingController, + didCreateChatRoom room: OpenChatRoomInfo, + withCreatingItem item: OpenChatRoomCreatingItem + ) + { + UIPasteboard.general.string = room.openChatId + let text = "Chat room created.\nURL: \(room.url)\nRoom ID: \(room.openChatId)" + UIAlertController.present(in: self, successResult: text) + + OpenChatRoom.createdRooms.append(.init(chatRoomId: room.openChatId, url: room.url)) + } + + func openChatCreatingController( + _ controller: OpenChatCreatingController, + didFailWithError error: LineSDKError, + withCreatingItem item: OpenChatRoomCreatingItem, + presentingViewController: UIViewController + ) + { + var errorText = error.errorDescription ?? "" + errorText.append("\nTried creating: \(item)") + UIAlertController.present(in: presentingViewController, error: errorText) + } + + func openChatCreatingController( + _ controller: OpenChatCreatingController, + shouldPreventUserTermAlertFrom presentingViewController: UIViewController + ) -> Bool + { + print("The term is not agreed yet. Asking user action...") + return false + } + + func openChatCreatingControllerDidCancelCreating(_ controller: OpenChatCreatingController) { + UIAlertController.present(in: self, error: "User cancelled.") + } + + func openChatCreatingController( + _ controller: OpenChatCreatingController, + willPresentCreatingNavigationController navigationController: OpenChatCreatingNavigationController + ) + { + print("willPresentCreatingNavigationController: \(navigationController)") + } +} diff --git a/LineSDKSample/LineSDKSample/UI/SampleUIHomeViewController+SPM.swift b/LineSDKSample/LineSDKSample/UI/SampleUIHomeViewController+SPM.swift new file mode 100644 index 00000000..7d768afb --- /dev/null +++ b/LineSDKSample/LineSDKSample/UI/SampleUIHomeViewController+SPM.swift @@ -0,0 +1,36 @@ +// +// SampleUIHomeViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import LineSDK + +class SampleUIHomeViewController: UITableViewController { + + enum Cell: Int { + case shareMessage + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.row == Cell.shareMessage.rawValue { + UIAlertController.present(in: self, error: "UI is not supported yet with Swift Package Manager integration") + } + } +} diff --git a/LineSDKSample/LineSDKSample/UI/SampleUIHomeViewController.swift b/LineSDKSample/LineSDKSample/UI/SampleUIHomeViewController.swift new file mode 100644 index 00000000..82c5a68f --- /dev/null +++ b/LineSDKSample/LineSDKSample/UI/SampleUIHomeViewController.swift @@ -0,0 +1,53 @@ +// +// SampleUIHomeViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import LineSDK + +class SampleUIHomeViewController: UITableViewController { + + override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { + if identifier == "showMessageChooser" { + let status = ShareViewController.localAuthorizationStatusForSendingMessage() + switch status { + case .authorized: + return true + case .lackOfPermissions(let p): + UIAlertController.present( + in: self, + title: nil, + message: "Lack of permissions: \(p)", + actions: [.init(title: "OK", style: .cancel)] + ) + return false + case .lackOfToken: + UIAlertController.present( + in: self, + title: nil, + message: "Please login first.", + actions: [.init(title: "OK", style: .cancel)] + ) + return false + } + } + return true + } +} diff --git a/LineSDKSample/LineSDKSample/UI/ShareMessageTemplateAddingViewController.swift b/LineSDKSample/LineSDKSample/UI/ShareMessageTemplateAddingViewController.swift new file mode 100644 index 00000000..0d59088c --- /dev/null +++ b/LineSDKSample/LineSDKSample/UI/ShareMessageTemplateAddingViewController.swift @@ -0,0 +1,59 @@ +// +// ShareMessageTemplateAddingViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import LineSDK + +class ShareMessageTemplateAddingViewController: UIViewController { + + @IBOutlet weak var textView: UITextView! + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + @IBAction func done(_ sender: Any) { + let data = textView.text.data(using: .utf8)! + do { + let container = try JSONDecoder().decode(FlexMessageContainer.self, from: data) + let nameAlert = UIAlertController( + title: "Name", message: "Specify a name for this message.", preferredStyle: .alert) + nameAlert.addTextField { t in + t.placeholder = "Untitled" + } + nameAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + nameAlert.addAction(UIAlertAction(title: "Done", style: .default, handler: { _ in + if let name = nameAlert.textFields?.first?.text, !name.isEmpty { + let message = FlexMessage(altText: name, container: container).message + MessageStore.shared.insert(message, name: name) + + self.dismiss(animated: true) + } + })) + present(nameAlert, animated: true) + } catch { + UIAlertController.present(in: self, error: error) + } + + } +} diff --git a/LineSDKSample/LineSDKSample/UI/ShareMessagesTableViewController.swift b/LineSDKSample/LineSDKSample/UI/ShareMessagesTableViewController.swift new file mode 100644 index 00000000..74c36a7e --- /dev/null +++ b/LineSDKSample/LineSDKSample/UI/ShareMessagesTableViewController.swift @@ -0,0 +1,156 @@ +// +// ShareMessagesTableViewController.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import UIKit +import LineSDK + +class ShareMessagesTableViewController: UITableViewController { + + private var observerToken: NotificationToken? + + override func viewDidLoad() { + super.viewDidLoad() + + observerToken = NotificationCenter.default.addObserver( + forName: .messageStoreMessageInserted, + object: MessageStore.shared, + queue: .main, + using: { [weak self] _ in + self?.tableView.reloadData() + }) + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add(_:))) + tableView.allowsMultipleSelection = true + } + + var addButton: UIBarButtonItem { + return UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add(_:))) + } + + var sendButton: UIBarButtonItem { + return UIBarButtonItem(title: "Send", style: .plain, target: self, action: #selector(sendMessages(_:))) + } + + // MARK: - Table view data source + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return MessageStore.shared.messages.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) + cell.textLabel?.text = MessageStore.shared.messages[indexPath.row].name + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + navigationItem.rightBarButtonItem = sendButton + } + + override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + if tableView.indexPathsForSelectedRows == nil { + navigationItem.rightBarButtonItem = addButton + } + } + + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + return true + } + + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + MessageStore.shared.remove(at: indexPath.row) + tableView.deleteRows(at: [indexPath], with: .fade) + } + } + + @IBAction func unwindFromAdding(segue: UIStoryboardSegue) { } + + @objc func add(_ sender: Any) { + performSegue(withIdentifier: "showMessageAdding", sender: self) + } + + @objc func sendMessages(_ sender: Any) { + guard let indexes = tableView.indexPathsForSelectedRows else { + return + } + + let viewController = ShareViewController() + + let messages = indexes.map { MessageStore.shared.messages[$0.row].message } + viewController.messages = messages + + viewController.shareDelegate = self + present(viewController, animated: true) + + } +} + +extension ShareMessagesTableViewController: ShareViewControllerDelegate { + func shareViewController( + _ controller: ShareViewController, + didFailLoadingListType shareType: MessageShareTargetType, + withError error: LineSDKError) + { + print("Sharing list did not finish loading. Error: \(error)") + dismiss(animated: true) { + UIAlertController.present(in: self, error: error) + } + } + + func shareViewControllerDidCancelSharing(_ controller: ShareViewController) { + UIAlertController.present( + in: self, title: nil, message: "User Cancelled", actions: [.init(title: "OK", style: .cancel)]) + } + + func shareViewController( + _ controller: ShareViewController, + messagesForSendingToTargets targets: [ShareTarget]) -> [MessageConvertible] + { + print("LineSDK will send message \(controller.messages!) to \(targets).") + return controller.messages! + } + + func shareViewController( + _ controller: ShareViewController, + didSendMessages messages: [MessageConvertible], + toTargets targets: [ShareTarget]) + { + print("Sharing is done.") + dismiss(animated: true) { + UIAlertController.present(in: self, successResult: "Share done.") + } + } + + func shareViewController( + _ controller: ShareViewController, + didFailSendingMessages messages: [MessageConvertible], + toTargets targets: [ShareTarget], + withError error: LineSDKError) + { + print("Sharing finished with error: \(error)") + dismiss(animated: true) { + UIAlertController.present(in: self, error: error) + } + } + + func shareViewControllerShouldDismissAfterSending(_ controller: ShareViewController) -> Bool { + return false + } +} diff --git a/LineSDKSample/LineSDKSample/Utils/IndicatorDisplay.swift b/LineSDKSample/LineSDKSample/Utils/IndicatorDisplay.swift index 2b6f4bcd..c484c109 100644 --- a/LineSDKSample/LineSDKSample/Utils/IndicatorDisplay.swift +++ b/LineSDKSample/LineSDKSample/Utils/IndicatorDisplay.swift @@ -1,13 +1,13 @@ // // IndicatorDisplay.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -32,16 +32,26 @@ extension IndicatorDisplay where Self: UIViewController { func showIndicator() { showIndicator(in: view) } + + func showIndicatorOnWindow() { + let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first + showIndicator(in: keyWindow ?? view) + } func hideIndicator() { hideIndicator(from: view) } + + func hideIndicatorFromWindow() { + let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first + hideIndicator(from: keyWindow ?? view) + } func showIndicator(in view: UIView) { let holderView = IndicatorHolderView() - let indicator = UIActivityIndicatorView(style: .whiteLarge) + let indicator = UIActivityIndicatorView(style: .large) indicator.color = .gray holderView.addSubview(indicator) diff --git a/LineSDKSample/LineSDKSample/Utils/NotificationToken.swift b/LineSDKSample/LineSDKSample/Utils/NotificationToken.swift new file mode 100644 index 00000000..3da50101 --- /dev/null +++ b/LineSDKSample/LineSDKSample/Utils/NotificationToken.swift @@ -0,0 +1,51 @@ +// +// NotificationToken.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation + +/// Wraps normal `Notification` observing method, to provide a behavior of releasing `token` automatically when +/// observer gets deinit. +class NotificationToken { + let token: NSObjectProtocol + let center: NotificationCenter + + init(token: NSObjectProtocol, in center: NotificationCenter) { + self.token = token + self.center = center + } + + deinit { + center.removeObserver(token) + } +} + +extension NotificationCenter { + func addObserver( + forName name: Notification.Name?, + object obj: Any?, + queue: OperationQueue?, + using block: @escaping (Notification) -> Swift.Void) -> NotificationToken + { + let token: NSObjectProtocol = addObserver(forName: name, object: obj, queue: queue, using: block) + return NotificationToken(token: token, in: self) + } + +} diff --git a/LineSDKSample/LineSDKSample/Utils/UIAlertControllerHelpers.swift b/LineSDKSample/LineSDKSample/Utils/UIAlertControllerHelpers.swift index c2e53967..c3e41d35 100644 --- a/LineSDKSample/LineSDKSample/Utils/UIAlertControllerHelpers.swift +++ b/LineSDKSample/LineSDKSample/Utils/UIAlertControllerHelpers.swift @@ -1,13 +1,13 @@ // // UIAlertControllerHelpers.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -53,9 +53,7 @@ extension UIAlertController { viewController.present(alert, animated: true, completion: nil) return true } - - - + @discardableResult static func present( in viewController: UIViewController, @@ -71,6 +69,22 @@ extension UIAlertController { ] ) } + + @discardableResult + static func present( + in viewController: UIViewController, + error: String, + done: (() -> Void)? = nil) -> Bool + { + return present( + in: viewController, + title: "Error", + message: error, + actions: [ + .init(title: "OK", style: .cancel) { _ in done?() } + ] + ) + } @discardableResult static func present( diff --git a/LineSDKSample/LineSDKSample/Utils/UIViewControllerExtensions.swift b/LineSDKSample/LineSDKSample/Utils/UIViewControllerExtensions.swift index 28d06328..2ad198cf 100644 --- a/LineSDKSample/LineSDKSample/Utils/UIViewControllerExtensions.swift +++ b/LineSDKSample/LineSDKSample/Utils/UIViewControllerExtensions.swift @@ -1,13 +1,13 @@ // // UIViewControllerExtensions.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDKSample/LineSDKSampleTests/APIResultEntryTests.swift b/LineSDKSample/LineSDKSampleTests/APIResultEntryTests.swift index 7feae792..5a5c6788 100644 --- a/LineSDKSample/LineSDKSampleTests/APIResultEntryTests.swift +++ b/LineSDKSample/LineSDKSampleTests/APIResultEntryTests.swift @@ -1,13 +1,13 @@ // // APIResultEntryTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDKSample/LineSDKSampleTests/LineSDKSampleTests.swift b/LineSDKSample/LineSDKSampleTests/LineSDKSampleTests.swift index 73290e1c..3c7d67b1 100644 --- a/LineSDKSample/LineSDKSampleTests/LineSDKSampleTests.swift +++ b/LineSDKSample/LineSDKSampleTests/LineSDKSampleTests.swift @@ -1,13 +1,13 @@ // // LineSDKSampleTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // diff --git a/LineSDKSample/LineSDKSampleUITests/LineSDKSampleUITests.swift b/LineSDKSample/LineSDKSampleUITests/LineSDKSampleUITests.swift deleted file mode 100644 index 8d7e7ce0..00000000 --- a/LineSDKSample/LineSDKSampleUITests/LineSDKSampleUITests.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// LineSDKSampleUITests.swift -// -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. -// -// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, -// copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. -// -// As with any software that integrates with the LINE Corporation platform, your use of this software -// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. -// This copyright notice shall be included in all copies or substantial portions of the software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import XCTest - -class LineSDKSampleUITests: XCTestCase { - - override func setUp() { - super.setUp() - - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. - XCUIApplication().launch() - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } -} diff --git a/LineSDKSample/LineSDKSampleUITests/PageObject/APIHomePage.swift b/LineSDKSample/LineSDKSampleUITests/PageObject/APIHomePage.swift deleted file mode 100644 index 7ad9539f..00000000 --- a/LineSDKSample/LineSDKSampleUITests/PageObject/APIHomePage.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// APIHomePage.swift -// -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. -// -// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, -// copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. -// -// As with any software that integrates with the LINE Corporation platform, your use of this software -// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. -// This copyright notice shall be included in all copies or substantial portions of the software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import XCTest - -class APIHomePage { - - let app = XCUIApplication() - var table: XCUIElement - - init() { - table = app.tables.firstMatch - } - - func navigateToAPIHomePage() { - app.tabBars.buttons["API"].tap() - } - - func tapGetFriends() { - table.cells.staticTexts["Get Friends"].tap() - } - - func tapGetApproversInFriends() { - table.cells.staticTexts["Get Approvers in Friends"].tap() - } - - func tapGetGroups() { - table.cells.staticTexts["Get Groups"].tap() - } - - func tapGetApproversInGivenGroup() { - table.cells.staticTexts["Get Approvers in given Group"].tap() - } - - func tapSendTextMessage() { - table.cells.staticTexts["Send text message to a friend"].tap() - } - - func tapMultisendTextMessage() { - table.cells.staticTexts["Multisend text message to first five friends"].tap() - } - - func tapSendFlexMessage() { - table.cells.staticTexts["Send flex message to a friend"].tap() - } -} diff --git a/LineSDKSample/LineSDKSampleUITests/PageObject/LoginPage.swift b/LineSDKSample/LineSDKSampleUITests/PageObject/LoginPage.swift index 60e97417..9f093906 100644 --- a/LineSDKSample/LineSDKSampleUITests/PageObject/LoginPage.swift +++ b/LineSDKSample/LineSDKSampleUITests/PageObject/LoginPage.swift @@ -1,13 +1,13 @@ // // LoginPage.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -21,30 +21,55 @@ import XCTest -class LoginPage { - - let app = XCUIApplication() - let lineLoginButton: XCUIElement - let lineLogoutButton: XCUIElement - - init() { - lineLoginButton = app.buttons["login.button"] - lineLogoutButton = app.navigationBars.buttons["Logout"] - } - - func tapLoginButton() { - lineLoginButton.tap() +class LoginPage: Page { + + lazy var okButton = app.buttons["OK"] + lazy var lineLoginButton = app.buttons["login.button"] + lazy var lineLogoutButton = app.navigationBars.buttons["Logout"] + + @discardableResult + func tapLoginButton() -> Self { + tap(element: lineLoginButton) + return self } - + func tapLogoutButton() { - lineLogoutButton.tap() + tap(element: lineLogoutButton) } + @discardableResult func isLineLoginButtonExists() -> Bool { return lineLoginButton.exists } + @discardableResult func isLineLogoutButtonExists() -> Bool{ return lineLogoutButton.exists } + + func checkLineLoginButtonExists() { + expect(element: lineLoginButton, status: .exist) + } + + func checkLineLogoutButtonExists() { + expect(element: lineLogoutButton, status: .exist) + } + + func logout() { + tapLogoutButton() + tap(element: app.alerts.buttons["Logout"]) + tap(element: app.alerts.buttons["OK"]) + } + + func login() { + tapLoginButton() + // Open LINE + let line = XCUIApplication(bundleIdentifier: "jp.naver.line") + tap(element: line.buttons["許可する"]) + tap(element: line.staticTexts["確認"]) + tap(element: line.buttons["開く"]) + + // click ok button in App + tap(element: okButton) + } } diff --git a/LineSDKSample/LineSDKSampleUITests/PageObject/Page.swift b/LineSDKSample/LineSDKSampleUITests/PageObject/Page.swift new file mode 100644 index 00000000..7bd941e3 --- /dev/null +++ b/LineSDKSample/LineSDKSampleUITests/PageObject/Page.swift @@ -0,0 +1,57 @@ +// +// Page.swift +// +// Copyright (c) 2016-present, LY Corporation. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by LY Corporation. +// +// As with any software that integrates with the LY Corporation platform, your use of this software +// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. +// This copyright notice shall be included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +import XCTest + +class Page { + var app: XCUIApplication + + required init(_ app: XCUIApplication) { + self.app = app + } + + func on(page: T.Type) -> T { + return page.init(app) + } + + enum UIStatus: String { + case exist = "exists == true" + case notExist = "exist == false" + case selected = "selected == true" + case notSelected = "selected == false" + case hittable = "isHittable == true" + case notHittable = "hittable == false" + } + + func expect(element: XCUIElement, status: UIStatus, withIn timeout: TimeInterval = 20) { + let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: status.rawValue), object: element) + let result = XCTWaiter.wait(for: [expectation], timeout: timeout) + + if result == .timedOut { + XCTFail(expectation.description) + } + } + + func tap(element: XCUIElement) { + expect(element: element, status: .exist) + element.tap() + } +} diff --git a/LineSDKSample/LineSDKSampleUITests/TestSuites/LineLoginButtonTest.swift b/LineSDKSample/LineSDKSampleUITests/TestSuites/AuthenticationTests.swift similarity index 63% rename from LineSDKSample/LineSDKSampleUITests/TestSuites/LineLoginButtonTest.swift rename to LineSDKSample/LineSDKSampleUITests/TestSuites/AuthenticationTests.swift index e8d39e9a..eac436e4 100644 --- a/LineSDKSample/LineSDKSampleUITests/TestSuites/LineLoginButtonTest.swift +++ b/LineSDKSample/LineSDKSampleUITests/TestSuites/AuthenticationTests.swift @@ -1,13 +1,13 @@ // -// LineLoginButtonTest.swift +// AuthenticationTests.swift // -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. +// Copyright (c) 2016-present, LY Corporation. All rights reserved. // // You are hereby granted a non-exclusive, worldwide, royalty-free license to use, // copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. +// in connection with the web services and APIs provided by LY Corporation. // -// As with any software that integrates with the LINE Corporation platform, your use of this software +// As with any software that integrates with the LY Corporation platform, your use of this software // is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. // This copyright notice shall be included in all copies or substantial portions of the software. // @@ -19,25 +19,28 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import Foundation import XCTest -class LineLoginButtonTest: XCTestCase { +class AuthenticationTests: XCTestCase { let app = XCUIApplication() - let loginPage = LoginPage() override func setUp() { super.setUp() continueAfterFailure = false app.launch() + let loginPage = LoginPage(app) if loginPage.isLineLogoutButtonExists() { - LineSDKScript.logout(app: app, loginPage: loginPage) + loginPage.logout() } } - func testLineLoginButton() { - XCTAssert(loginPage.isLineLoginButtonExists()) + func testLineLogin() { + // A login script for LINE installed and did not set universal link + let loginPage = LoginPage(app) + loginPage.checkLineLoginButtonExists() + loginPage.login() + loginPage.checkLineLogoutButtonExists() } } diff --git a/LineSDKSample/LineSDKSampleUITests/TestSuites/GraphAPITest.swift b/LineSDKSample/LineSDKSampleUITests/TestSuites/GraphAPITest.swift deleted file mode 100644 index ed85264e..00000000 --- a/LineSDKSample/LineSDKSampleUITests/TestSuites/GraphAPITest.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// GraphAPITest.swift -// -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. -// -// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, -// copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. -// -// As with any software that integrates with the LINE Corporation platform, your use of this software -// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. -// This copyright notice shall be included in all copies or substantial portions of the software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import Foundation -import XCTest - -class GraphAPITest: XCTestCase{ - - let app = XCUIApplication() - let apiHomePage = APIHomePage() - - override func setUp() { - super.setUp() - continueAfterFailure = false - app.launch() - - let loginPage = LoginPage() - if loginPage.isLineLogoutButtonExists() { - LineSDKScript.logout(app: app, loginPage: loginPage) - } - } - - func testGetFriends() { - apiHomePage.navigateToAPIHomePage() - apiHomePage.tapGetFriends() - tapOKButtonInErrorAlertView() - } - - func testGetApproversInFriends() { - apiHomePage.navigateToAPIHomePage() - apiHomePage.tapGetApproversInFriends() - tapOKButtonInErrorAlertView() - } - - func testGetGroups() { - apiHomePage.navigateToAPIHomePage() - apiHomePage.tapGetGroups() - tapOKButtonInErrorAlertView() - } - - func testGetApproversInGivenGroup() { - apiHomePage.navigateToAPIHomePage() - apiHomePage.tapGetApproversInGivenGroup() - tapOKButtonInErrorAlertView() - } - - func tapOKButtonInErrorAlertView() { - addUIInterruptionMonitor(withDescription: "No access token error of using graph API") { (alert) -> Bool in - - XCTAssert(alert.staticTexts["Error"].exists) - - if alert.buttons["OK"].exists { - alert.buttons["OK"].tap() - return true - } - return false - } - app.navigationBars.firstMatch.tap() - } -} diff --git a/LineSDKSample/LineSDKSampleUITests/TestSuites/MessageAPITest.swift b/LineSDKSample/LineSDKSampleUITests/TestSuites/MessageAPITest.swift deleted file mode 100644 index 15b3e008..00000000 --- a/LineSDKSample/LineSDKSampleUITests/TestSuites/MessageAPITest.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// MessageAPITest.swift -// -// Copyright (c) 2016-present, LINE Corporation. All rights reserved. -// -// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, -// copy and distribute this software in source code or binary form for use -// in connection with the web services and APIs provided by LINE Corporation. -// -// As with any software that integrates with the LINE Corporation platform, your use of this software -// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement]. -// This copyright notice shall be included in all copies or substantial portions of the software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import Foundation -import XCTest - -class MessageAPITest: XCTestCase{ - - let app = XCUIApplication() - let apiHomePage = APIHomePage() - - override func setUp() { - super.setUp() - continueAfterFailure = false - app.launch() - - let loginPage = LoginPage() - if loginPage.isLineLogoutButtonExists() { - LineSDKScript.logout(app: app, loginPage: loginPage) - } - } - - func testSendTextMessage() { - apiHomePage.navigateToAPIHomePage() - apiHomePage.tapSendTextMessage() - tapOKButtonInErrorAlertView() - } - - func testMultiSendTextMessage() { - apiHomePage.navigateToAPIHomePage() - apiHomePage.tapMultisendTextMessage() - tapOKButtonInErrorAlertView() - } - - func testSendFlexMessage() { - apiHomePage.navigateToAPIHomePage() - apiHomePage.tapSendFlexMessage() - tapOKButtonInErrorAlertView() - } - - func tapOKButtonInErrorAlertView() { - addUIInterruptionMonitor(withDescription: "No access token error of using message API") { (alert) -> Bool in - - XCTAssert(alert.staticTexts["Error"].exists) - - if alert.buttons["OK"].exists { - alert.buttons["OK"].tap() - return true - } - return false - } - app.navigationBars.firstMatch.tap() - } -} diff --git a/LineSDKSwift.podspec b/LineSDKSwift.podspec index 29030088..f5b2e372 100644 --- a/LineSDKSwift.podspec +++ b/LineSDKSwift.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "LineSDKSwift" - s.version = "5.0.0" + s.version = "5.11.2" s.summary = "The LINE SDK for iOS Swift provides a modern way of implementing LINE APIs." s.description = <<-DESC @@ -10,22 +10,23 @@ Pod::Spec.new do |s| engaging and personalized user experience. DESC - s.homepage = "https://developers.line.me/" + s.homepage = "https://developers.line.biz/" s.license = "Apache License, Version 2.0" s.author = "LINE" - s.platform = :ios, "10.0" + s.platform = :ios, "13.0" s.module_name = "LineSDK" - s.swift_version = "4.2" + s.swift_version = "5.0" + s.swift_versions = ["4.2", "5.0"] s.source = { :git => "https://github.com/line/line-sdk-ios-swift.git", :tag => "#{s.version}" } s.default_subspecs = "Core" s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-DLineSDKCocoaPods' } s.subspec "Core" do |sp| - sp.source_files = ["LineSDK/LineSDK/**/*.swift", "LineSDK/LineSDK/LineSDK.h"] - sp.resources = ["LineSDK/LineSDK/Assets.xcassets", "LineSDK/LineSDK/Resource.bundle"] + sp.source_files = ["LineSDK/LineSDK/**/*.swift", "LineSDK/LineSDK/LineSDK.h"] + sp.resource_bundles = { 'LineSDK' => ["LineSDK/LineSDK/Assets.xcassets", "LineSDK/LineSDK/Resource.bundle"] } end s.subspec "ObjC" do |sp| diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..b99c2887 --- /dev/null +++ b/Package.swift @@ -0,0 +1,25 @@ +// swift-tools-version:5.0 +import PackageDescription + +let package = Package( + name: "LineSDK", + + platforms: [.iOS(.v13)], + products: [ + .library(name: "LineSDK", targets: ["LineSDK"]), + .library(name: "LineSDKObjC", targets: ["LineSDKObjC"]) + ], + targets: [ + .target( + name: "LineSDK", + path: "LineSDK/LineSDK", + exclude: ["LineSDKUI"] + ), + .target( + name: "LineSDKObjC", + dependencies: ["LineSDK"], + path: "LineSDK/LineSDKObjC", + exclude: ["LineSDKUI"] + ) + ] +) diff --git a/Package@swift-5.3.swift b/Package@swift-5.3.swift new file mode 100644 index 00000000..b2f5d26b --- /dev/null +++ b/Package@swift-5.3.swift @@ -0,0 +1,32 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "LineSDK", + defaultLocalization: "en", + platforms: [.iOS(.v13)], + products: [ + .library(name: "LineSDK", targets: ["LineSDK"]), + .library(name: "LineSDKObjC", targets: ["LineSDKObjC"]) + ], + targets: [ + .target( + name: "LineSDK", + path: "LineSDK/LineSDK", + exclude: [ + "Info.plist" + ], + resources: [ + .process("Resource.bundle") + ] + ), + .target( + name: "LineSDKObjC", + dependencies: ["LineSDK"], + path: "LineSDK/LineSDKObjC", + exclude: [ + "Info.plist" + ] + ) + ] +) diff --git a/README.md b/README.md index 777c02b4..ae9733ba 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ +[![LINE SDK CI](https://github.com/line/line-sdk-ios-swift/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/line/line-sdk-ios-swift/actions/workflows/ci.yaml) +[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/LineSDKSwift.svg)](https://cocoapods.org/pods/LineSDKSwift) +[![Swift Package Manager Compatible](https://img.shields.io/badge/SPM-supported-DE5C43.svg?style=flat)](https://swift.org/package-manager/) +[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) + # LINE SDK for iOS Swift ## Overview @@ -16,19 +21,24 @@ This feature allows users to log in to your service with their LINE accounts. Wi ### Utilizing user data with OpenID support -Once the user is authorized, you can get the user’s LINE profile. You can utilize the user's information registered in LINE without building your user system. +Once the user authorizes, you can get the user’s LINE profile. You can utilize the user's information registered in LINE without building your user system. The LINE SDK supports the OpenID Connect 1.0 specification. You can get ID tokens that contain the user’s LINE profile when you retrieve the access token. ## Using the SDK +### Prerequisites + +* iOS 13.0 or later as the deployment target. +* Xcode 14.1 or later. + To use the LINE SDK with your iOS app, follow the steps below. * Create a channel. * Integrate LINE Login into your iOS app using the SDK. -* Make API calls from your app using the SDK or from server-side through the Social API. +* Make API calls from your app using the SDK or from the server side through the Social API. -For more information, refer to the [LINE SDK for iOS Swift guide](https://developers.line.biz/en/docs/ios-sdk/) on the [LINE Developers site](https://developers.line.biz). +For more information, refer to the [LINE SDK for iOS Swift guide](https://developers.line.biz/en/docs/line-login-sdks/ios-sdk/swift/overview/) on the [LINE Developers site](https://developers.line.biz). ### Trying the starter app diff --git a/REFERENCETOP.md b/REFERENCETOP.md index 6bd0ed33..1fa26ef3 100644 --- a/REFERENCETOP.md +++ b/REFERENCETOP.md @@ -1,3 +1,3 @@ -# LINE SDK v5.0 for iOS Swift +# LINE SDK v5.11 for iOS Swift -Developed in Swift, the LINE SDK for iOS Swift provides a modern way of implementing LINE APIs. The features included in this SDK will help you develop an iOS app with engaging and personalized user experience. \ No newline at end of file +Developed in Swift, the LINE SDK for iOS Swift provides a modern way of implementing LINE APIs. The features included in this SDK will help you develop an iOS app with engaging and personalized user experience. diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 693f53a6..71fc486c 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -3,7 +3,11 @@ default_platform(:ios) platform :ios do before_all do - xcversion(version: "10.1") + begin + xcodes(version: "16.0", select_for_current_build_only: true) + rescue => ex + UI.error("Failed when switching to Xcode version: #{ex}") + end end desc "Switch to Beta environment" @@ -13,28 +17,56 @@ platform :ios do desc "Run tests." lane :tests do + sdk_tests + end + + lane :sdk_tests do + swift_version = ENV["SWIFT_VERSION"] || "4.2" run_tests( workspace: "LineSDK.xcworkspace", - devices: ["iPhone 6s"], - scheme: "LineSDK" + devices: ["iPhone 16"], + scheme: "LineSDK", + xcargs: "SWIFT_VERSION=#{swift_version}" ) + run_tests( workspace: "LineSDK.xcworkspace", - devices: ["iPhone 6s"], - scheme: "LineSDKObjC" + devices: ["iPhone 16"], + scheme: "LineSDKObjC", + xcargs: "SWIFT_VERSION=#{swift_version}" ) + end + + lane :sample_tests do run_tests( workspace: "LineSDK.xcworkspace", - devices: ["iPhone 6s"], + devices: ["iPhone 16"], scheme: "LineSDKSample" ) end - desc "Lint to check Carthage and CocoaPods compatibility." + desc "Lint to check dependency manager compatibility." lane :lint do - ensure_latest_carthage - carthage(command: "build", no_skip_current: true) - Action.sh("bundle exec pod lib lint ../LineSDKSwift.podspec") + lint_spm + lint_pod + end + + lane :lint_pod do + swift_version = ENV["SWIFT_VERSION"] || "4.2" + Action.sh("bundle exec pod lib lint ../LineSDKSwift.podspec --swift-version=#{swift_version}") + end + + lane :lint_spm do + sdk_path = `xcrun --sdk iphonesimulator --show-sdk-path`.strip + swiftc_flags = [ + "-sdk", sdk_path, + "-target", "arm64-apple-ios18.0-simulator" + ].map { |flag| "-Xswiftc #{flag}" } + cc_flags = [ + "-isysroot", sdk_path + ].map { |flag| "-Xcc #{flag}" } + + sh("swift build #{swiftc_flags.join(' ')} #{cc_flags.join(' ')}") end desc "Release a new version." @@ -56,27 +88,26 @@ platform :ios do version_number: target_version, xcodeproj: "LineSDK/LineSDK.xcodeproj" ) - - # Increase version numbers in resource info plist. - plistbuddy = "/usr/libexec/PlistBuddy" - Action.sh("#{plistbuddy} -c 'Set :CFBundleShortVersionString #{target_version}' ../LineSDK/LineSDK/Resource.bundle/Info.plist") - Action.sh("#{plistbuddy} -c 'Set :CFBundleVersion #{number_of_commits}' ../LineSDK/LineSDK/Resource.bundle/Info.plist") + bump_constant_version(version: target_version) + bump_reference_top_version(version: target_version) version_bump_podspec(path: "LineSDKSwift.podspec", version_number: target_version) release_content = read_changelog(excluded_markdown_elements: []) + change_log(version: target_version) - # change_log(version: target_version) + git_commit_all(message: "Bump version to #{target_version}") + Actions.sh("git tag -u #{ENV["GPG_KEY_ID"]} #{target_version} -m ''") + + binary_frameworks = xcframework(version: target_version) - git_commit_all(message: "Bump version to #{target_version}") - add_git_tag(tag: target_version, sign: true) push_to_git_remote - set_github_release( repository_name: "line/line-sdk-ios-swift", - api_token: ENV['GITHUB_TOKEN'], + api_token: ENV["GITHUB_TOKEN"], name: target_version, tag_name: target_version, + upload_assets: binary_frameworks, description: release_content ) @@ -85,7 +116,8 @@ platform :ios do desc "Generate documentation" lane :doc do - jazzy + copyright = "jazzy --copyright '© " + Time.new.year.to_s + " [LY Corporation.](https://line.me) All rights reserved.'" + Actions.sh(copyright) end desc "Generate documentation for internal usage" @@ -96,11 +128,66 @@ platform :ios do lane :change_log do |options| target_version = options[:version] raise "The version is missed. You need to specify a version parameter." if target_version.nil? - stamp_changelog(section_identifier: target_version) + stamp_changelog(section_identifier: target_version, git_tag: target_version, stamp_datetime_format: "%F") end - lane :ensure_latest_carthage do - Actions.sh("brew update || brew update") - Actions.sh("brew outdated carthage || brew upgrade carthage") + lane :bump_constant_version do |options| + target_version = options[:version] + replacing = "public static let SDKVersion = \"#{target_version}\"" + regex = "public static let SDKVersion = .*" + constant_file = "../LineSDK/LineSDK/Utils/Constant.swift" + Action.sh("sed -e 's/#{regex}/#{replacing}/g' #{constant_file} | tee #{constant_file}") + end + + lane :bump_reference_top_version do |options| + target_version = options[:version] + target_version_to_minor = target_version.split(".")[0...-1].join(".") + lines = File.readlines("../REFERENCETOP.md") + lines[0] = "# LINE SDK v#{target_version_to_minor} for iOS Swift\n" + File.open("../REFERENCETOP.md", "w") { |f| f.write(lines.join) } + end + + desc "Create binary frameworks with the `xcframework` format under the `build/` folder." + lane :xcframework do |options| + target_version = options[:version] + output_path = "build" + + FileUtils.rm_rf "../#{output_path}" + + artifact_paths = [] + + create_xcframework( + workspace: "LineSDK.xcworkspace", + scheme: "LineSDK", + destinations: ['iOS'], + enable_bitcode: false, + include_debug_symbols: true, + xcframework_output_directory: output_path + ) + unless is_ci + Actions.sh("codesign --timestamp -v --sign 'Apple Distribution: LINE Corporation (VUTU7AKEUR)' ../#{output_path}/LineSDK.xcframework") + end + artifact_paths.push(ENV["XCFRAMEWORK_OUTPUT_PATH"]) + + create_xcframework( + workspace: "LineSDK.xcworkspace", + scheme: "LineSDKObjCBinary", + product_name: "LineSDKObjC", + destinations: ['iOS'], + enable_bitcode: false, + include_debug_symbols: true, + xcframework_output_directory: output_path + ) + unless is_ci + Actions.sh("codesign --timestamp -v --sign 'Apple Distribution: LINE Corporation (VUTU7AKEUR)' ../#{output_path}/LineSDKObjC.xcframework") + end + artifact_paths.push(ENV["XCFRAMEWORK_OUTPUT_PATH"]) + + zipped_files = [] + artifact_paths.each do |path| + basename = File.basename(path, ".xcframework") + zipped_files << zip(path: path, output_path: "#{output_path}/#{basename}-#{target_version}.zip") + end + zipped_files end end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile index 8f896e85..999d45ed 100644 --- a/fastlane/Pluginfile +++ b/fastlane/Pluginfile @@ -3,3 +3,4 @@ # Ensure this file is checked in to source control! gem 'fastlane-plugin-changelog' +gem 'fastlane-plugin-create_xcframework' diff --git a/fastlane/README.md b/fastlane/README.md index 4edc5a1b..dd4f0ed3 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -1,64 +1,144 @@ fastlane documentation -================ +---- + # Installation Make sure you have the latest version of the Xcode command line tools installed: -``` +```sh xcode-select --install ``` -Install _fastlane_ using -``` -[sudo] gem install fastlane -NV -``` -or alternatively using `brew cask install fastlane` +For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) # Available Actions + ## iOS + ### ios to_beta + +```sh +[bundle exec] fastlane ios to_beta ``` -fastlane ios to_beta -``` + Switch to Beta environment + ### ios tests + +```sh +[bundle exec] fastlane ios tests ``` -fastlane ios tests -``` + Run tests. + +### ios sdk_tests + +```sh +[bundle exec] fastlane ios sdk_tests +``` + + + +### ios sample_tests + +```sh +[bundle exec] fastlane ios sample_tests +``` + + + ### ios lint + +```sh +[bundle exec] fastlane ios lint ``` -fastlane ios lint + +Lint to check dependency manager compatibility. + +### ios lint_pod + +```sh +[bundle exec] fastlane ios lint_pod ``` -Lint to check Carthage and CocoaPods compatibility. -### ios release + + + +### ios lint_spm + +```sh +[bundle exec] fastlane ios lint_spm ``` -fastlane ios release + + + +### ios release + +```sh +[bundle exec] fastlane ios release ``` + Release a new version. + ### ios doc + +```sh +[bundle exec] fastlane ios doc ``` -fastlane ios doc -``` + Generate documentation + ### ios doc_internal + +```sh +[bundle exec] fastlane ios doc_internal ``` -fastlane ios doc_internal -``` + Generate documentation for internal usage + ### ios change_log + +```sh +[bundle exec] fastlane ios change_log ``` -fastlane ios change_log -``` + + ### ios ensure_latest_carthage + +```sh +[bundle exec] fastlane ios ensure_latest_carthage ``` -fastlane ios ensure_latest_carthage + + + +### ios bump_constant_version + +```sh +[bundle exec] fastlane ios bump_constant_version ``` + +### ios bump_reference_top_version + +```sh +[bundle exec] fastlane ios bump_reference_top_version +``` + + + +### ios xcframework + +```sh +[bundle exec] fastlane ios xcframework +``` + +Create binary frameworks with the `xcframework` format under the `build/` folder. + ---- -This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. -More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). -The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. + +More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). + +The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/jazzy-templates/linedevdocs/assets/css/custom.css.scss b/jazzy-templates/linedevdocs/assets/css/custom.css.scss new file mode 100644 index 00000000..a7232651 --- /dev/null +++ b/jazzy-templates/linedevdocs/assets/css/custom.css.scss @@ -0,0 +1,143 @@ +@font-face { + font-family: icon; + src: url(../fonts/icon-font/icon-font.eot); + src: url(../fonts/icon-font/icon-font.eot?#iefix) format("eot"),url(../fonts/icon-font/icon-font.woff) format("woff"),url(../fonts/icon-font/icon-font.ttf) format("truetype"); +} + +body { + font-size: 14px; + font-family: Avenir Next W01,"ヒラギノ角ゴ Pro",Hiragino Kaku Gothic Pro,"游ゴシック体",Yu Gothic,YuGothic,"メイリオ",Meiryo,Arial Unicode MS,Tahoma,Helvetica,"MS Pゴシック",MS PGothic,sans-serif; + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; +} + +/* Util */ +.d-flex { + display: flex; +} +.align-items-center { + align-items: center; +} + +// 外部リンク +.Link-blank { + display:inline-block; + position:relative; + padding-right:16px; +} +.Link-blank:after { + width: 1em; + height: 1em; + display: inline-block; + vertical-align: middle; + font-family: icon; + font-style: normal; + font-weight: 400; + -webkit-font-feature-settings: normal; + font-feature-settings: normal; + font-variant: normal; + line-height: 1; + letter-spacing: 0; + text-transform: none; + speak: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content:"\EA33"; + position:absolute; + line-height:inherit; + top: 7px; + right:0; + -webkit-transform:translateY(-50%); + transform:translateY(-50%); + color:#bec8d2; +} + +/* ヘッダー */ +.header { + height: 60px; + display: flex; + align-items: center; + padding: 0 20px; +} + +.header-left { + display: flex; + align-items: center; + flex-grow: 1; + letter-spacing: normal; +} + + +form[role=search] input { + border-radius: 6px; +} +.header-title { + font-weight: bold; + font-size: 18px; + margin-right: 35px; + white-space: nowrap; + font-family: Avenir Next W01,"ヒラギノ角ゴ Pro",Hiragino Kaku Gothic Pro,"游ゴシック体",Yu Gothic,YuGothic,"メイリオ",Meiryo,Arial Unicode MS,Tahoma,Helvetica,"MS Pゴシック",MS PGothic,sans-serif; + +} + +.GlobalHeaderNav > ul { + display: flex; + flex-wrap: nowrap; + list-style-type: none; + margin: 0; + padding: 0; + > li { + padding: 0 10px ; + line-height: 1em; + a { + color: #fff; + font-family: Avenir Next W01,"ヒラギノ角ゴ Pro",Hiragino Kaku Gothic Pro,"游ゴシック体",Yu Gothic,YuGothic,"メイリオ",Meiryo,Arial Unicode MS,Tahoma,Helvetica,"MS Pゴシック",MS PGothic,sans-serif; + font-size: 13px; + } + } +} + +.header-col { + overflow: hidden; +} +.header-link { + white-space: nowrap; +} + +/* サイドナビ */ +.navigation { + background-color: #fff; +} +.nav-group-name-link { + font-size: 14px; + color: #444; +} +.nav-group-task-link { + color: #444; +} +.nav-group-task { + font-size: 14px; + line-height: 32px; +} + +/* メインコンテンツ部 */ +.main-content h1 { + font-size: 24px; +} +h4 { + font-size: 14px; +} +.item .token { + font-size: 14px; +} +.language { + border-left: 5px solid #16C464; +} +.language .aside-title { + color: #16C464; +} + +.declaration .highlight { + background-color: #F7F7F7; + padding-left: 16px; +} diff --git a/jazzy-templates/linedevdocs/assets/fonts/global-icon-font/global-icon-font.eot b/jazzy-templates/linedevdocs/assets/fonts/global-icon-font/global-icon-font.eot new file mode 100644 index 00000000..b4cbaa2b Binary files /dev/null and b/jazzy-templates/linedevdocs/assets/fonts/global-icon-font/global-icon-font.eot differ diff --git a/jazzy-templates/linedevdocs/assets/fonts/global-icon-font/global-icon-font.ttf b/jazzy-templates/linedevdocs/assets/fonts/global-icon-font/global-icon-font.ttf new file mode 100644 index 00000000..a9cd049c Binary files /dev/null and b/jazzy-templates/linedevdocs/assets/fonts/global-icon-font/global-icon-font.ttf differ diff --git a/jazzy-templates/linedevdocs/assets/fonts/global-icon-font/global-icon-font.woff b/jazzy-templates/linedevdocs/assets/fonts/global-icon-font/global-icon-font.woff new file mode 100644 index 00000000..172ae62c Binary files /dev/null and b/jazzy-templates/linedevdocs/assets/fonts/global-icon-font/global-icon-font.woff differ diff --git a/jazzy-templates/linedevdocs/assets/fonts/icon-font/icon-font.eot b/jazzy-templates/linedevdocs/assets/fonts/icon-font/icon-font.eot new file mode 100644 index 00000000..8cce7e28 Binary files /dev/null and b/jazzy-templates/linedevdocs/assets/fonts/icon-font/icon-font.eot differ diff --git a/jazzy-templates/linedevdocs/assets/fonts/icon-font/icon-font.ttf b/jazzy-templates/linedevdocs/assets/fonts/icon-font/icon-font.ttf new file mode 100644 index 00000000..270826e9 Binary files /dev/null and b/jazzy-templates/linedevdocs/assets/fonts/icon-font/icon-font.ttf differ diff --git a/jazzy-templates/linedevdocs/assets/fonts/icon-font/icon-font.woff b/jazzy-templates/linedevdocs/assets/fonts/icon-font/icon-font.woff new file mode 100644 index 00000000..cf3097e1 Binary files /dev/null and b/jazzy-templates/linedevdocs/assets/fonts/icon-font/icon-font.woff differ diff --git a/jazzy-templates/linedevdocs/assets/js/jazzy.js b/jazzy-templates/linedevdocs/assets/js/jazzy.js index 009c80d3..6a604412 100755 --- a/jazzy-templates/linedevdocs/assets/js/jazzy.js +++ b/jazzy-templates/linedevdocs/assets/js/jazzy.js @@ -8,12 +8,31 @@ if (navigator.userAgent.match(/xcode/i)) { window.jazzy.docset = true } -// On doc load, toggle the URL hash discussion if present +// On doc load, toggle the URL hash discussion if present and remove "last updated" from pages other than index.html $(document).ready(function() { if (!window.jazzy.docset) { var linkToHash = $('a[href="' + window.location.hash +'"]'); linkToHash.trigger("click"); } + + // If the file path includes index.html, add last updated to prevent git file updates + var filePath = location.pathname; + if (filePath.includes("index.html") || filePath.endsWith("/ios-sdk-swift/")) { + const footerElement = document.getElementsByClassName("footer")[0]; + + // Get the last updated date and format it + let lastUpdatedTimeStamp = new Date(document.lastModified); + const lastModifiedYear = lastUpdatedTimeStamp.getFullYear(); + const lastModifiedMonth = lastUpdatedTimeStamp.getMonth() + 1; + const lastModifiedDate = lastUpdatedTimeStamp.getDate(); + lastUpdatedTimeStamp = lastModifiedYear + "-" + lastModifiedMonth + "-" + lastModifiedDate; + + // Append the formatted timestamp to the copyright text + const lastUpdated = " (Last updated: " + lastUpdatedTimeStamp + ")"; + const dateSpan = document.createElement('span'); + dateSpan.innerHTML = lastUpdated; + footerElement.childNodes[1].appendChild(dateSpan); + } }); // On token click, toggle its discussion and animate token.marginLeft diff --git a/jazzy-templates/linedevdocs/templates/doc.mustache b/jazzy-templates/linedevdocs/templates/doc.mustache index 6af90fba..ae695ba3 100644 --- a/jazzy-templates/linedevdocs/templates/doc.mustache +++ b/jazzy-templates/linedevdocs/templates/doc.mustache @@ -4,9 +4,22 @@ {{name}} {{kind}} Reference + + + + {{{custom_head}}} {{^disable_search}} diff --git a/jazzy-templates/linedevdocs/templates/header.mustache b/jazzy-templates/linedevdocs/templates/header.mustache index 37cb4cc1..95dda1e2 100644 --- a/jazzy-templates/linedevdocs/templates/header.mustache +++ b/jazzy-templates/linedevdocs/templates/header.mustache @@ -1,10 +1,18 @@
-

- - {{module_name}} Docs - - {{#doc_coverage}} ({{doc_coverage}}% documented){{/doc_coverage}} -

+
+
+ + LINE SDK Docs + + {{#doc_coverage}} ({{doc_coverage}}% documented){{/doc_coverage}} +
+ +
{{^disable_search}}

diff --git a/script/lint.sh b/script/lint.sh index 726b1b3d..4226ef53 100755 --- a/script/lint.sh +++ b/script/lint.sh @@ -3,4 +3,5 @@ BASE="$(cd "$(dirname "$0")"; pwd)" source "$BASE/common.sh" +bundle exec ensure_latest_carthage bundle exec fastlane lint \ No newline at end of file