├── .gitignore ├── .idea ├── CTFeedbackSwift.iml ├── encodings.xml ├── modules.xml ├── runConfigurations │ ├── CTFeedbackSwift.xml │ ├── CTFeedbackSwiftTests.xml │ └── Demo.xml ├── vcs.xml ├── workspace 2.xml └── xcode.xml ├── .swift-version ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── CTFeedbackDemo ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── MainViewController.swift ├── ar.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── bs.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── cs-CZ.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── da.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── de.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── fa-IR.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── fr.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── hr-HR.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── it.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── ja.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── ko.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── nl.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── ru.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── sk.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── sr.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── sv-SE.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── tr.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── ur.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── zh-Hans.lproj │ ├── LaunchScreen.strings │ └── Main.strings └── zh-Hant.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── CTFeedbackSwift.png ├── CTFeedbackSwift.podspec ├── CTFeedbackSwift.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── xcshareddata │ └── xcschemes │ │ ├── CTFeedbackSwift.xcscheme │ │ ├── CTFeedbackSwiftTests.xcscheme │ │ └── Demo.xcscheme └── xcuserdata │ └── rizumita.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── CTFeedbackSwift ├── Bundle+Extensions.swift ├── CTFeedbackSwift.h ├── CellFactory.swift ├── DrawUpPresentationController.swift ├── Feedback.swift ├── FeedbackConfiguration.swift ├── FeedbackEditing │ ├── CTFeedbackError.swift │ ├── FeedbackEditingItemsRepositoryProtocol.swift │ ├── FeedbackEditingService.swift │ └── FeedbackGenerator.swift ├── FeedbackItemProtocol.swift ├── FeedbackItemsDataSource.swift ├── FeedbackViewController.swift ├── FeedbackWireframe.swift ├── Functions.swift ├── Info.plist ├── Items │ ├── AppBuild │ │ ├── AppBuildCell.swift │ │ └── AppBuildItem.swift │ ├── AppName │ │ ├── AppNameCell.swift │ │ └── AppNameItem.swift │ ├── AppVersion │ │ ├── AppVersionCell.swift │ │ └── AppVersionItem.swift │ ├── Attachment │ │ ├── AttachmentCell.swift │ │ ├── AttachmentItem.swift │ │ └── Media.swift │ ├── Body │ │ ├── BodyCell.swift │ │ └── BodyItem.swift │ ├── DeviceName │ │ ├── DeviceNameCell.swift │ │ └── DeviceNameItem.swift │ ├── SystemVersion │ │ ├── SystemVersionCell.swift │ │ └── SystemVersionItem.swift │ ├── Topic │ │ ├── Topic.swift │ │ ├── TopicCell.swift │ │ └── TopicItem.swift │ └── UserEmail │ │ ├── UserEmailCell.swift │ │ └── UserEmailItem.swift ├── Resources │ ├── PlatformNames.plist │ ├── ar.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── bs.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── cs-CZ.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── da.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── de.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── en.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── fa-IR.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── fr.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── hr-HR.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── it.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── ja.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── ko.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── nl.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── ru.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── sk.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── sr.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── sv-SE.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── tr.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── ur.lproj │ │ └── CTFeedbackLocalizable.strings │ ├── zh-Hans.lproj │ │ └── CTFeedbackLocalizable.strings │ └── zh-Hant.lproj │ │ └── CTFeedbackLocalizable.strings └── TopicsViewController.swift ├── CTFeedbackSwiftTests ├── Bundle+ExtensionsTests.swift ├── CellFactoryTests.swift ├── FeedbackEditing │ ├── FeedbackEditingServiceTests.swift │ └── FeedbackGeneratorTests.swift ├── FeedbackItemsDataSourceTests.swift ├── FunctionsTests.swift ├── Info.plist └── Items │ ├── AppBuild │ └── AppBuildCellTests.swift │ ├── AppName │ └── AppNameCellTests.swift │ ├── AppVersion │ └── AppVersionCellTests.swift │ ├── Attachment │ ├── AttachmentCellTests.swift │ └── AttachmentItemTests.swift │ ├── Body │ └── BodyCellTests.swift │ ├── DeviceName │ ├── DeviceNameCellTests.swift │ └── DeviceNameItemTests.swift │ ├── SystemVersion │ ├── SystemVersionCellTests.swift │ └── SystemVersionItemTests.swift │ ├── Topic │ ├── TopicCellTests.swift │ └── TopicItemTests.swift │ └── UserEmail │ └── UserEmailCellTests.swift ├── LICENSE ├── Package.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/055194192edb9ace9bedf5e6bf6e49f6ab824c6e/Global/macOS.gitignore 2 | 3 | # General 4 | *.DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | 31 | ### https://raw.github.com/github/gitignore/055194192edb9ace9bedf5e6bf6e49f6ab824c6e/Swift.gitignore 32 | 33 | # Xcode 34 | # 35 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 36 | 37 | ## Build generated 38 | build/ 39 | DerivedData/ 40 | 41 | ## Various settings 42 | *.pbxuser 43 | !default.pbxuser 44 | *.mode1v3 45 | !default.mode1v3 46 | *.mode2v3 47 | !default.mode2v3 48 | *.perspectivev3 49 | !default.perspectivev3 50 | xcuserdata/ 51 | 52 | ## Other 53 | *.moved-aside 54 | *.xccheckout 55 | *.xcscmblueprint 56 | 57 | ## Obj-C/Swift specific 58 | *.hmap 59 | *.ipa 60 | *.dSYM.zip 61 | *.dSYM 62 | 63 | ## Playgrounds 64 | timeline.xctimeline 65 | playground.xcworkspace 66 | 67 | # Swift Package Manager 68 | # 69 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 70 | # Packages/ 71 | # Package.pins 72 | # Package.resolved 73 | .build/ 74 | 75 | # CocoaPods 76 | # 77 | # We recommend against adding the Pods directory to your .gitignore. However 78 | # you should judge for yourself, the pros and cons are mentioned at: 79 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 80 | # 81 | # Pods/ 82 | 83 | # Carthage 84 | # 85 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 86 | # Carthage/Checkouts 87 | 88 | Carthage/Build 89 | 90 | # fastlane 91 | # 92 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 93 | # screenshots whenever they are needed. 94 | # For more information about the recommended setup visit: 95 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 96 | 97 | fastlane/report.xml 98 | fastlane/Preview.html 99 | fastlane/screenshots 100 | fastlane/test_output 101 | 102 | 103 | ### https://raw.github.com/github/gitignore/055194192edb9ace9bedf5e6bf6e49f6ab824c6e/Global/JetBrains.gitignore 104 | 105 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 106 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 107 | 108 | # User-specific stuff: 109 | .idea/**/workspace.xml 110 | .idea/**/tasks.xml 111 | .idea/dictionaries 112 | 113 | # Sensitive or high-churn files: 114 | .idea/**/dataSources/ 115 | .idea/**/dataSources.ids 116 | .idea/**/dataSources.xml 117 | .idea/**/dataSources.local.xml 118 | .idea/**/sqlDataSources.xml 119 | .idea/**/dynamic.xml 120 | .idea/**/uiDesigner.xml 121 | 122 | # Gradle: 123 | .idea/**/gradle.xml 124 | .idea/**/libraries 125 | 126 | # CMake 127 | cmake-build-debug/ 128 | 129 | # Mongo Explorer plugin: 130 | .idea/**/mongoSettings.xml 131 | 132 | ## File-based project format: 133 | *.iws 134 | 135 | ## Plugin-specific files: 136 | 137 | # IntelliJ 138 | out/ 139 | 140 | # mpeltonen/sbt-idea plugin 141 | .idea_modules/ 142 | 143 | # JIRA plugin 144 | atlassian-ide-plugin.xml 145 | 146 | # Cursive Clojure plugin 147 | .idea/replstate.xml 148 | 149 | # Crashlytics plugin (for Android Studio and IntelliJ) 150 | com_crashlytics_export_strings.xml 151 | crashlytics.properties 152 | crashlytics-build.properties 153 | fabric.properties 154 | 155 | 156 | -------------------------------------------------------------------------------- /.idea/CTFeedbackSwift.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/CTFeedbackSwift.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations/CTFeedbackSwiftTests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/xcode.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CTFeedbackDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CTFeedbackDemo 4 | // 5 | // Created by 和泉田 領一 on 2017/09/07. 6 | // Copyright © 2017年 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /CTFeedbackDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /CTFeedbackDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CTFeedbackDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /CTFeedbackDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | Demo app uses camera 25 | NSMicrophoneUsageDescription 26 | Demo app uses microphone 27 | NSPhotoLibraryUsageDescription 28 | Demo app uses photo library 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationPortraitUpsideDown 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /CTFeedbackDemo/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // CTFeedbackDemo 4 | // 5 | // Created by 和泉田 領一 on 2017/09/07. 6 | // Copyright © 2017年 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CTFeedbackSwift 11 | 12 | class MainViewController: UITableViewController { 13 | enum PresentationType: String { 14 | case feedback = "Feedback" 15 | case feedbackCustom = "Feedback Custom" 16 | case feedbackSimple = "Feedback Simple" 17 | case feedbackModal = "Feedback Modal" 18 | } 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | title = "Main" 24 | } 25 | 26 | override func didReceiveMemoryWarning() { 27 | super.didReceiveMemoryWarning() 28 | // Dispose of any resources that can be recreated. 29 | } 30 | 31 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 32 | guard let cell = tableView.cellForRow(at: indexPath), 33 | let identifier = cell.reuseIdentifier, 34 | let type = PresentationType(rawValue: identifier) 35 | else { fatalError() } 36 | 37 | switch type { 38 | case .feedback: showFeedback() 39 | case .feedbackCustom: () 40 | case .feedbackSimple: () 41 | case .feedbackModal: showFeedbackByModal() 42 | } 43 | } 44 | 45 | private func showFeedback() { 46 | let configuration = FeedbackConfiguration(toRecipients: ["test@example.com"], 47 | usesHTML: true) 48 | let controller = FeedbackViewController(configuration: configuration) 49 | navigationController?.pushViewController(controller, animated: true) 50 | } 51 | 52 | private func showFeedbackByModal() { 53 | let configuration = FeedbackConfiguration(toRecipients: ["test@example.com"], 54 | usesHTML: true) 55 | let controller = FeedbackViewController(configuration: configuration) 56 | let nav = UINavigationController(rootViewController: controller) 57 | navigationController?.present(nav, animated: true) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /CTFeedbackDemo/ar.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/ar.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/bs.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/bs.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/cs-CZ.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/cs-CZ.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/da.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/da.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/de.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/de.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/fa-IR.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/fa-IR.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/fr.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/fr.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/hr-HR.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/hr-HR.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/it.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/it.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/ja.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/ja.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/ko.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/ko.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/nl.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/nl.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/ru.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/ru.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/sk.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/sk.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/sr.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/sr.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/sv-SE.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/sv-SE.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/tr.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/tr.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/ur.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/ur.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/zh-Hans.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/zh-Hans.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackDemo/zh-Hant.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CTFeedbackDemo/zh-Hant.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Feedback - Simple"; ObjectID = "9zk-o1-xhH"; */ 3 | "9zk-o1-xhH.text" = "Feedback - Simple"; 4 | 5 | /* Class = "UINavigationItem"; title = "Root View Controller"; ObjectID = "Pgf-xs-Oef"; */ 6 | "Pgf-xs-Oef.title" = "Root View Controller"; 7 | 8 | /* Class = "UILabel"; text = "Feedback - Modal"; ObjectID = "ZOH-G7-6NW"; */ 9 | "ZOH-G7-6NW.text" = "Feedback - Modal"; 10 | 11 | /* Class = "UILabel"; text = "Feedback"; ObjectID = "aYu-TA-cZQ"; */ 12 | "aYu-TA-cZQ.text" = "Feedback"; 13 | 14 | /* Class = "UILabel"; text = "Feedback - Custom"; ObjectID = "y1T-Yo-hKa"; */ 15 | "y1T-Yo-hKa.text" = "Feedback - Custom"; 16 | -------------------------------------------------------------------------------- /CTFeedbackSwift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rizumita/CTFeedbackSwift/0f6f64c674555a47b65a4e87c078651dac7ea0b6/CTFeedbackSwift.png -------------------------------------------------------------------------------- /CTFeedbackSwift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CTFeedbackSwift" 3 | s.version = "0.1.9" 4 | s.summary = "Feedback composer for iOS" 5 | s.homepage = "https://github.com/rizumita/CTFeedbackSwift" 6 | s.screenshots = "https://github.com/rizumita/CTFeedbackSwift/raw/master/CTFeedbackSwift.png" 7 | s.license = "MIT" 8 | s.author = { "Ryoichi Izumita" => "r.izumita@caph.jp" } 9 | s.social_media_url = "http://twitter.com/rizumita" 10 | s.platform = :ios, "9.0" 11 | s.source = { :git => "https://github.com/rizumita/CTFeedbackSwift.git", :tag => "v#{s.version}" } 12 | s.source_files = "CTFeedbackSwift", "CTFeedbackSwift/**/*.{h,m,swift}" 13 | s.resources = ["CTFeedbackSwift/Resources/*.lproj", "CTFeedbackSwift/Resources/PlatformNames.plist"] 14 | s.framework = 'MessageUI' 15 | end 16 | -------------------------------------------------------------------------------- /CTFeedbackSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CTFeedbackSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CTFeedbackSwift.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CTFeedbackSwift.xcodeproj/xcshareddata/xcschemes/CTFeedbackSwift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /CTFeedbackSwift.xcodeproj/xcshareddata/xcschemes/CTFeedbackSwiftTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /CTFeedbackSwift.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /CTFeedbackSwift.xcodeproj/xcuserdata/rizumita.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CTFeedbackDemo.xcscheme 8 | 9 | orderHint 10 | 2 11 | 12 | CTFeedbackSwift.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | CTFeedbackSwiftTests.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 3 21 | 22 | Demo.xcscheme_^#shared#^_ 23 | 24 | orderHint 25 | 1 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Bundle+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/07. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import Dispatch 8 | 9 | var _feedbackBundle: Bundle? 10 | 11 | extension Bundle { 12 | static var feedbackBundle: Bundle { 13 | if let bundle = _feedbackBundle { return bundle } 14 | 15 | let bundle = Bundle(for: FeedbackViewController.self) 16 | _feedbackBundle = bundle 17 | return bundle 18 | } 19 | 20 | static var platformNamesPlistPath: String? { 21 | #if SWIFT_PACKAGE && swift(>=5.3) 22 | let bundles: [Bundle] = [Bundle.main, Bundle.feedbackBundle, Bundle.module] 23 | #else 24 | let bundles: [Bundle] = [Bundle.main, Bundle.feedbackBundle] 25 | #endif 26 | 27 | for bundle in bundles { 28 | guard let path = bundle.path(forResource: "PlatformNames", ofType: "plist") 29 | else { continue } 30 | return path 31 | } 32 | if let path = Bundle.main.path(forResource: "PlatformNames", ofType: "plist") { 33 | return path 34 | } 35 | return .none 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CTFeedbackSwift/CTFeedbackSwift.h: -------------------------------------------------------------------------------- 1 | // 2 | // CTFeedbackSwift.h 3 | // CTFeedbackSwift 4 | // 5 | // Created by 和泉田 領一 on 2017/09/07. 6 | // Copyright © 2017年 CAPH TECH. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for CTFeedbackSwift. 12 | FOUNDATION_EXPORT double CTFeedbackSwiftVersionNumber; 13 | 14 | //! Project version string for CTFeedbackSwift. 15 | FOUNDATION_EXPORT const unsigned char CTFeedbackSwiftVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /CTFeedbackSwift/CellFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/07. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | protocol CellFactoryProtocol { 9 | associatedtype Item 10 | associatedtype Cell: UITableViewCell 11 | associatedtype EventHandler 12 | 13 | static var reuseIdentifier: String { get } 14 | 15 | static func configure(_ cell: Cell, 16 | with item: Item, 17 | for indexPath: IndexPath, 18 | eventHandler: EventHandler) 19 | } 20 | 21 | extension CellFactoryProtocol { 22 | static var reuseIdentifier: String { return String(describing: self) } 23 | 24 | static func suitable(for item: Any) -> Bool { return item is Item } 25 | 26 | static func configure(_ cell: UITableViewCell, 27 | with item: Any, 28 | for indexPath: IndexPath, 29 | eventHandler: Any?) -> UITableViewCell? { 30 | guard let cell = cell as? Cell, 31 | let item = item as? Item, 32 | let eventHandler = eventHandler as? EventHandler 33 | else { return .none } 34 | configure(cell, with: item, for: indexPath, eventHandler: eventHandler) 35 | return cell 36 | } 37 | } 38 | 39 | public class AnyCellFactory { 40 | let cellType: AnyClass 41 | let reuseIdentifier: String 42 | private let suitableClosure: (Any) -> Bool 43 | private let configureCellClosure: (UITableViewCell, Any, IndexPath, Any?) -> UITableViewCell? 44 | 45 | init(_ cellFactory: Factory.Type) { 46 | cellType = Factory.Cell.self 47 | reuseIdentifier = cellFactory.reuseIdentifier 48 | suitableClosure = cellFactory.suitable(for:) 49 | configureCellClosure = cellFactory.configure(_:with:for:eventHandler:) 50 | } 51 | 52 | func suitable(for item: Any) -> Bool { return suitableClosure(item) } 53 | 54 | func configure(_ cell: UITableViewCell, 55 | with item: Any, 56 | for indexPath: IndexPath, 57 | eventHandler: Any?) -> UITableViewCell? { 58 | return configureCellClosure(cell, item, indexPath, eventHandler) 59 | } 60 | 61 | static func cellFactoryFilter() -> (Any, [AnyCellFactory]) -> AnyCellFactory? { 62 | var cache = [String : AnyCellFactory]() 63 | return { item, factories in 64 | let itemType = String(describing: type(of: item)) 65 | if let factory = cache[itemType] { 66 | return factory 67 | } else if let factory = _cellFactoryFilter(item, factories) { 68 | cache[itemType] = factory 69 | return factory 70 | } else { 71 | return .none 72 | } 73 | } 74 | } 75 | } 76 | 77 | extension UITableView { 78 | func register(with cellFactory: AnyCellFactory) { 79 | register(cellFactory.cellType, forCellReuseIdentifier: cellFactory.reuseIdentifier) 80 | } 81 | 82 | func dequeueCell(to item: Any, 83 | from cellFactories: [AnyCellFactory], 84 | for indexPath: IndexPath, 85 | filter: (Any, [AnyCellFactory]) -> AnyCellFactory? = _cellFactoryFilter, 86 | eventHandler: Any?) -> UITableViewCell { 87 | guard let cellFactory = filter(item, cellFactories) else { fatalError() } 88 | let cell = dequeueReusableCell(withIdentifier: cellFactory.reuseIdentifier, for: indexPath) 89 | guard let configured = cellFactory.configure(cell, 90 | with: item, 91 | for: indexPath, 92 | eventHandler: eventHandler) 93 | else { fatalError() } 94 | return configured 95 | } 96 | } 97 | 98 | let _cellFactoryFilter: (Any, [AnyCellFactory]) -> AnyCellFactory? = { item, factories in 99 | return factories.first { $0.suitable(for: item) } 100 | } 101 | -------------------------------------------------------------------------------- /CTFeedbackSwift/DrawUpPresentationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/17. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | class DrawUpPresentationController: UIPresentationController { 9 | var overlayView: UIView! 10 | 11 | func createOverlayView(withFrame frame: CGRect) -> UIView { 12 | let view = UIView(frame: frame) 13 | let gestureRecognizer = UITapGestureRecognizer(target: self, 14 | action: #selector(overlayTouched(_:))) 15 | view.addGestureRecognizer(gestureRecognizer) 16 | view.backgroundColor = .black 17 | view.alpha = 0.0 18 | return view 19 | } 20 | 21 | override func presentationTransitionWillBegin() { 22 | guard let containerView = self.containerView else { return } 23 | overlayView = createOverlayView(withFrame: containerView.bounds) 24 | containerView.insertSubview(overlayView, at: 0) 25 | presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [unowned self] 26 | context in 27 | self.overlayView.alpha = 0.5 28 | }) 29 | } 30 | 31 | override func dismissalTransitionWillBegin() { 32 | presentedViewController.transitionCoordinator?.animate(alongsideTransition: { context in 33 | self.overlayView.alpha = 0.0 34 | }) 35 | } 36 | 37 | override func dismissalTransitionDidEnd(_ completed: Bool) { 38 | guard completed else { return } 39 | overlayView.removeFromSuperview() 40 | overlayView = .none 41 | } 42 | 43 | override func size(forChildContentContainer container: UIContentContainer, 44 | withParentContainerSize parentSize: CGSize) -> CGSize { 45 | return CGSize(width: parentSize.width, height: parentSize.height / 2.0) 46 | } 47 | 48 | override var frameOfPresentedViewInContainerView: CGRect { 49 | guard let containerBounds = containerView?.bounds else { return CGRect.zero } 50 | var result = CGRect.zero 51 | result.size = size(forChildContentContainer: presentedViewController, 52 | withParentContainerSize: containerBounds.size) 53 | result.origin.y = containerBounds.height - result.height 54 | return result 55 | } 56 | 57 | override func containerViewWillLayoutSubviews() { 58 | guard let containerBounds = containerView?.bounds else { return } 59 | overlayView.frame = containerBounds 60 | presentedView?.frame = frameOfPresentedViewInContainerView 61 | } 62 | 63 | @objc func overlayTouched(_ sender: Any) { presentedViewController.dismiss(animated: true) } 64 | } 65 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Feedback.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/25. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public struct Feedback { 9 | public let email: String? 10 | public let to: [String] 11 | public let cc: [String] 12 | public let bcc: [String] 13 | public let subject: String 14 | public let body: String 15 | public let isHTML: Bool 16 | public let jpeg: Data? 17 | public let mp4: Data? 18 | } 19 | -------------------------------------------------------------------------------- /CTFeedbackSwift/FeedbackConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/07. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public class FeedbackConfiguration { 9 | public var subject: String? 10 | public var additionalDiagnosticContent: String? 11 | public var toRecipients: [String] 12 | public var ccRecipients: [String] 13 | public var bccRecipients: [String] 14 | public var usesHTML: Bool 15 | public var dataSource: FeedbackItemsDataSource 16 | 17 | /* 18 | If topics array contains no topics, topics cell is hidden. 19 | */ 20 | public init(subject: String? = .none, 21 | additionalDiagnosticContent: String? = .none, 22 | topics: [TopicProtocol] = TopicItem.defaultTopics, 23 | toRecipients: [String], 24 | ccRecipients: [String] = [], 25 | bccRecipients: [String] = [], 26 | hidesUserEmailCell: Bool = true, 27 | hidesAttachmentCell: Bool = false, 28 | hidesAppInfoSection: Bool = false, 29 | usesHTML: Bool = false, 30 | appName: String? = nil) { 31 | self.subject = subject 32 | self.additionalDiagnosticContent = additionalDiagnosticContent 33 | self.toRecipients = toRecipients 34 | self.ccRecipients = ccRecipients 35 | self.bccRecipients = bccRecipients 36 | self.usesHTML = usesHTML 37 | self.dataSource = FeedbackItemsDataSource(topics: topics, 38 | hidesUserEmailCell: hidesUserEmailCell, 39 | hidesAttachmentCell: hidesAttachmentCell, 40 | hidesAppInfoSection: hidesAppInfoSection, 41 | appName: appName) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CTFeedbackSwift/FeedbackEditing/CTFeedbackError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/25. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | enum CTFeedbackError: Error { 9 | case unknown 10 | } 11 | -------------------------------------------------------------------------------- /CTFeedbackSwift/FeedbackEditing/FeedbackEditingItemsRepositoryProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/09. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public protocol FeedbackEditingItemsRepositoryProtocol { 9 | func item(of type: Item.Type) -> Item? 10 | 11 | @discardableResult 12 | func set(item: Item) -> IndexPath? 13 | } 14 | -------------------------------------------------------------------------------- /CTFeedbackSwift/FeedbackEditing/FeedbackEditingService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/09. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public protocol FeedbackEditingEventProtocol { 9 | func updated(at indexPath: IndexPath) 10 | } 11 | 12 | public protocol FeedbackEditingServiceProtocol { 13 | var topics: [TopicProtocol] { get } 14 | var hasAttachedMedia: Bool { get } 15 | func update(userEmailText: String?) 16 | func update(bodyText: String?) 17 | func update(selectedTopic: TopicProtocol) 18 | func update(attachmentMedia: Media?) 19 | func generateFeedback(configuration: FeedbackConfiguration) throws -> Feedback 20 | } 21 | 22 | public class FeedbackEditingService { 23 | var editingItemsRepository: FeedbackEditingItemsRepositoryProtocol 24 | let feedbackEditingEventHandler: FeedbackEditingEventProtocol 25 | 26 | public init(editingItemsRepository: FeedbackEditingItemsRepositoryProtocol, 27 | feedbackEditingEventHandler: FeedbackEditingEventProtocol) { 28 | self.editingItemsRepository = editingItemsRepository 29 | self.feedbackEditingEventHandler = feedbackEditingEventHandler 30 | } 31 | } 32 | 33 | extension FeedbackEditingService: FeedbackEditingServiceProtocol { 34 | public var topics: [TopicProtocol] { 35 | guard let item = editingItemsRepository.item(of: TopicItem.self) else { return [] } 36 | return item.topics 37 | } 38 | 39 | public var hasAttachedMedia: Bool { 40 | guard let item = editingItemsRepository.item(of: AttachmentItem.self) else { return false } 41 | return item.media != .none 42 | } 43 | 44 | public func update(userEmailText: String?) { 45 | guard var item = editingItemsRepository.item(of: UserEmailItem.self) else { return } 46 | item.email = userEmailText 47 | editingItemsRepository.set(item: item) 48 | } 49 | 50 | public func update(bodyText: String?) { 51 | guard var item = editingItemsRepository.item(of: BodyItem.self) else { return } 52 | item.bodyText = bodyText 53 | editingItemsRepository.set(item: item) 54 | } 55 | 56 | public func update(selectedTopic: TopicProtocol) { 57 | guard var item = editingItemsRepository.item(of: TopicItem.self) else { return } 58 | item.selected = selectedTopic 59 | guard let indexPath = editingItemsRepository.set(item: item) else { return } 60 | feedbackEditingEventHandler.updated(at: indexPath) 61 | } 62 | 63 | public func update(attachmentMedia: Media?) { 64 | guard var item = editingItemsRepository.item(of: AttachmentItem.self) else { return } 65 | item.media = attachmentMedia 66 | guard let indexPath = editingItemsRepository.set(item: item) else { return } 67 | feedbackEditingEventHandler.updated(at: indexPath) 68 | } 69 | 70 | public func generateFeedback(configuration: FeedbackConfiguration) throws -> Feedback { 71 | return try FeedbackGenerator.generate(configuration: configuration, 72 | repository: editingItemsRepository) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /CTFeedbackSwift/FeedbackEditing/FeedbackGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/25. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct FeedbackGenerator { 9 | static func generate(configuration: FeedbackConfiguration, 10 | repository: FeedbackEditingItemsRepositoryProtocol) throws -> Feedback { 11 | guard let deviceName = repository.item(of: DeviceNameItem.self)?.deviceName, 12 | let systemVersion = repository.item(of: SystemVersionItem.self)?.version 13 | else { throw CTFeedbackError.unknown } 14 | let appName = repository.item(of: AppNameItem.self)?.name ?? "" 15 | let appVersion = repository.item(of: AppVersionItem.self)?.version ?? "" 16 | let appBuild = repository.item(of: AppBuildItem.self)?.buildString ?? "" 17 | let email = repository.item(of: UserEmailItem.self)?.email 18 | let topic = repository.item(of: TopicItem.self)?.selected 19 | let attachment = repository.item(of: AttachmentItem.self)?.media 20 | let body = repository.item(of: BodyItem.self)?.bodyText ?? "" 21 | 22 | let subject = configuration.subject ?? generateSubject(appName: appName, topic: topic) 23 | 24 | let format = configuration.usesHTML ? generateHTML : generateString 25 | let formattedBody = format(body, 26 | deviceName, 27 | systemVersion, 28 | appName, 29 | appVersion, 30 | appBuild, 31 | configuration.additionalDiagnosticContent) 32 | 33 | return Feedback(email: email, 34 | to: configuration.toRecipients, 35 | cc: configuration.ccRecipients, 36 | bcc: configuration.bccRecipients, 37 | subject: subject, 38 | body: formattedBody, 39 | isHTML: configuration.usesHTML, 40 | jpeg: attachment?.jpegData, 41 | mp4: attachment?.videoData) 42 | } 43 | 44 | private static func generateSubject(appName: String, topic: TopicProtocol?) -> String { 45 | return String(format: "%@: %@", appName, topic?.title ?? "") 46 | } 47 | 48 | private static func generateHTML(body: String, 49 | deviceName: String, 50 | systemVersion: String, 51 | appName: String, 52 | appVersion: String, 53 | appBuild: String, 54 | additionalDiagnosticContent: String?) -> String { 55 | var platform = "iOS" 56 | #if targetEnvironment(macCatalyst) 57 | platform="macOS" 58 | #endif 59 | let format = """ 60 | 61 |

%@


62 | 63 | 64 | 65 | 66 | 67 | 68 |
Device:%@
%@:%@
App:%@
Version:%@
Build:%@
69 | """ 70 | var content: String = String(format: format, 71 | body.replacingOccurrences(of: "\n", with: "
"), 72 | deviceName, 73 | platform, 74 | systemVersion, 75 | appName, 76 | appVersion, 77 | appBuild) 78 | if let additional = additionalDiagnosticContent { content.append(additional) } 79 | return content 80 | } 81 | 82 | private static func generateString(body: String, 83 | deviceName: String, 84 | systemVersion: String, 85 | appName: String, 86 | appVersion: String, 87 | appBuild: String, 88 | additionalDiagnosticContent: String?) -> String { 89 | var platform = "iOS" 90 | #if targetEnvironment(macCatalyst) 91 | platform="macOS" 92 | #endif 93 | var content: String 94 | = String(format: "%@\n\n\nDevice: %@\n%@: %@\nApp: %@\nVersion: %@\nBuild: %@", 95 | body, 96 | deviceName, 97 | platform, 98 | systemVersion, 99 | appName, 100 | appVersion, 101 | appBuild) 102 | if let additional = additionalDiagnosticContent { content.append(additional) } 103 | return content 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /CTFeedbackSwift/FeedbackItemProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/24. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public protocol FeedbackItemProtocol { 9 | var isHidden: Bool { get } 10 | } 11 | -------------------------------------------------------------------------------- /CTFeedbackSwift/FeedbackItemsDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/07. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | public class FeedbackItemsDataSource { 9 | var sections: [FeedbackItemsSection] = [] 10 | 11 | var numberOfSections: Int { 12 | return filteredSections.count 13 | } 14 | 15 | public init(topics: [TopicProtocol], 16 | hidesUserEmailCell: Bool = true, 17 | hidesAttachmentCell: Bool = false, 18 | hidesAppInfoSection: Bool = false, 19 | appName: String? = nil) { 20 | sections.append(FeedbackItemsSection(title: CTLocalizedString("CTFeedback.UserDetail"), 21 | items: [UserEmailItem(isHidden: hidesUserEmailCell)])) 22 | sections.append(FeedbackItemsSection(items: [TopicItem(topics), BodyItem()])) 23 | sections.append(FeedbackItemsSection(title: CTLocalizedString("CTFeedback.AdditionalInfo"), 24 | items: [AttachmentItem(isHidden: hidesAttachmentCell)])) 25 | sections.append(FeedbackItemsSection(title: CTLocalizedString("CTFeedback.DeviceInfo"), 26 | items: [DeviceNameItem(), 27 | SystemVersionItem()])) 28 | sections.append(FeedbackItemsSection(title: CTLocalizedString("CTFeedback.AppInfo"), 29 | items: [AppNameItem(isHidden: hidesAppInfoSection, name: appName), 30 | AppVersionItem(isHidden: hidesAppInfoSection), 31 | AppBuildItem(isHidden: hidesAppInfoSection)])) 32 | } 33 | 34 | func section(at section: Int) -> FeedbackItemsSection { 35 | return filteredSections[section] 36 | } 37 | } 38 | 39 | extension FeedbackItemsDataSource { 40 | private var filteredSections: [FeedbackItemsSection] { 41 | return sections.filter { section in 42 | section.items.filter { !$0.isHidden }.isEmpty == false 43 | } 44 | } 45 | 46 | private subscript(indexPath: IndexPath) -> FeedbackItemProtocol { 47 | get { return filteredSections[indexPath.section][indexPath.item] } 48 | set { filteredSections[indexPath.section][indexPath.item] = newValue } 49 | } 50 | 51 | private func indexPath(of type: Item.Type) -> IndexPath? { 52 | let filtered = filteredSections 53 | for section in filtered { 54 | guard let index = filtered.firstIndex(where: { $0 === section }), 55 | let subIndex = section.items.firstIndex(where: { $0 is Item }) 56 | else { continue } 57 | return IndexPath(item: subIndex, section: index) 58 | } 59 | return .none 60 | } 61 | } 62 | 63 | extension FeedbackItemsDataSource: FeedbackEditingItemsRepositoryProtocol { 64 | public func item(of type: Item.Type) -> Item? { 65 | guard let indexPath = indexPath(of: type) else { return .none } 66 | return self[indexPath] as? Item 67 | } 68 | 69 | @discardableResult 70 | public func set(item: Item) -> IndexPath? { 71 | guard let indexPath = indexPath(of: Item.self) else { return .none } 72 | self[indexPath] = item 73 | return indexPath 74 | } 75 | } 76 | 77 | class FeedbackItemsSection { 78 | let title: String? 79 | var items: [FeedbackItemProtocol] 80 | 81 | init(title: String? = .none, items: [FeedbackItemProtocol]) { 82 | self.title = title 83 | self.items = items 84 | } 85 | } 86 | 87 | extension FeedbackItemsSection: Collection { 88 | var startIndex: Int { return items.startIndex } 89 | var endIndex: Int { return items.endIndex } 90 | 91 | subscript(position: Int) -> FeedbackItemProtocol { 92 | get { return items[position] } 93 | set { items[position] = newValue } 94 | } 95 | 96 | func index(after i: Int) -> Int { return items.index(after: i) } 97 | } 98 | -------------------------------------------------------------------------------- /CTFeedbackSwift/FeedbackWireframe.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/25. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import MobileCoreServices 8 | import MessageUI 9 | 10 | protocol FeedbackWireframeProtocol { 11 | func showTopicsView(with service: FeedbackEditingServiceProtocol) 12 | func showMailComposer(with feedback: Feedback) 13 | func showAttachmentActionSheet(cellRect: CGRect, 14 | authorizePhotoLibrary: @escaping (@escaping (Bool) -> ()) -> (), 15 | authorizeCamera: @escaping (@escaping (Bool) -> ()) -> (), 16 | deleteAction: (() -> ())?) 17 | func showFeedbackGenerationError() 18 | func showUnknownErrorAlert() 19 | func showMailComposingError(_ error: NSError) 20 | func dismiss(completion: (() -> ())?) 21 | func pop() 22 | } 23 | 24 | final class FeedbackWireframe { 25 | private weak var viewController: UIViewController? 26 | private weak var transitioningDelegate: UIViewControllerTransitioningDelegate? 27 | private weak var imagePickerDelegate: (UIImagePickerControllerDelegate & UINavigationControllerDelegate)? 28 | private weak var mailComposerDelegate: MFMailComposeViewControllerDelegate? 29 | 30 | init(viewController: UIViewController, 31 | transitioningDelegate: UIViewControllerTransitioningDelegate, 32 | imagePickerDelegate: UIImagePickerControllerDelegate & UINavigationControllerDelegate, 33 | mailComposerDelegate: MFMailComposeViewControllerDelegate) { 34 | self.viewController = viewController 35 | self.transitioningDelegate = transitioningDelegate 36 | self.imagePickerDelegate = imagePickerDelegate 37 | self.mailComposerDelegate = mailComposerDelegate 38 | } 39 | } 40 | 41 | extension FeedbackWireframe: FeedbackWireframeProtocol { 42 | func showTopicsView(with service: FeedbackEditingServiceProtocol) { 43 | let controller = TopicsViewController(service: service) 44 | controller.modalPresentationStyle = .custom 45 | controller.transitioningDelegate = transitioningDelegate 46 | 47 | DispatchQueue.main.async { self.viewController?.present(controller, animated: true) } 48 | } 49 | 50 | func showMailComposer(with feedback: Feedback) { 51 | guard MFMailComposeViewController.canSendMail() else { return showMailConfigurationError() } 52 | let controller: MFMailComposeViewController = MFMailComposeViewController() 53 | controller.mailComposeDelegate = mailComposerDelegate 54 | controller.setToRecipients(feedback.to) 55 | controller.setCcRecipients(feedback.cc) 56 | controller.setBccRecipients(feedback.bcc) 57 | controller.setSubject(feedback.subject) 58 | controller.setMessageBody(feedback.body, isHTML: feedback.isHTML) 59 | if let jpeg = feedback.jpeg { 60 | controller.addAttachmentData(jpeg, mimeType: "image/jpeg", fileName: "screenshot.jpg") 61 | } else if let mp4 = feedback.mp4 { 62 | controller.addAttachmentData(mp4, mimeType: "video/mp4", fileName: "screenshot.mp4") 63 | } 64 | viewController?.present(controller, animated: true) 65 | } 66 | 67 | func showAttachmentActionSheet(cellRect: CGRect, 68 | authorizePhotoLibrary: @escaping (@escaping (Bool) -> ()) -> (), 69 | authorizeCamera: @escaping (@escaping (Bool) -> ()) -> (), 70 | deleteAction: (() -> ())?) { 71 | let alertController = UIAlertController(title: .none, 72 | message: .none, 73 | preferredStyle: .actionSheet) 74 | if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) { 75 | alertController.addAction( 76 | UIAlertAction(title: CTLocalizedString("CTFeedback.PhotoLibrary"), 77 | style: .default) { _ in 78 | authorizePhotoLibrary { granted in 79 | if granted { 80 | self.showImagePicker(sourceType: .photoLibrary) 81 | } else { 82 | self.showPhotoLibraryAuthorizingAlert() 83 | } 84 | } 85 | }) 86 | } 87 | if UIImagePickerController.isSourceTypeAvailable(.camera) { 88 | alertController.addAction( 89 | UIAlertAction(title: CTLocalizedString("CTFeedback.Camera"), 90 | style: .default) { _ in 91 | authorizeCamera { granted in 92 | if granted { 93 | self.showImagePicker(sourceType: .camera) 94 | } else { 95 | self.showCameraAuthorizingAlert() 96 | } 97 | } 98 | }) 99 | } 100 | 101 | if let delete = deleteAction { 102 | alertController.addAction( 103 | UIAlertAction(title: CTLocalizedString("CTFeedback.Delete"), 104 | style: .destructive) { _ in delete() }) 105 | } 106 | 107 | alertController.addAction(UIAlertAction(title: CTLocalizedString("CTFeedback.Cancel"), 108 | style: .cancel)) 109 | let screenSize = UIScreen.main.bounds 110 | alertController.popoverPresentationController?.sourceView = viewController?.view 111 | alertController.popoverPresentationController?.sourceRect = cellRect 112 | alertController.popoverPresentationController?.permittedArrowDirections = .any 113 | 114 | viewController?.present(alertController, animated: true) 115 | } 116 | 117 | func showFeedbackGenerationError() { 118 | let alertController 119 | = UIAlertController(title: CTLocalizedString("CTFeedback.Error"), 120 | message: 121 | CTLocalizedString("CTFeedback.FeedbackGenerationErrorMessage"), 122 | preferredStyle: .alert) 123 | alertController.addAction(UIAlertAction(title: CTLocalizedString("CTFeedback.Dismiss"), 124 | style: .cancel)) 125 | viewController?.present(alertController, animated: true) 126 | } 127 | 128 | func showUnknownErrorAlert() { 129 | let title = CTLocalizedString("CTFeedback.UnknownError") 130 | let alertController = UIAlertController(title: title, 131 | message: .none, 132 | preferredStyle: .alert) 133 | alertController.addAction(UIAlertAction(title: CTLocalizedString("CTFeedback.Dismiss"), 134 | style: .default)) 135 | viewController?.present(alertController, animated: true) 136 | } 137 | 138 | func showMailComposingError(_ error: NSError) { 139 | let alertController = UIAlertController(title: CTLocalizedString("CTFeedback.Error"), 140 | message: error.localizedDescription, 141 | preferredStyle: .alert) 142 | alertController.addAction(UIAlertAction(title: CTLocalizedString("CTFeedback.Dismiss"), 143 | style: .cancel)) 144 | viewController?.present(alertController, animated: true) 145 | } 146 | 147 | func dismiss(completion: (() -> ())?) { 148 | viewController?.dismiss(animated: true, completion: completion) 149 | } 150 | 151 | func pop() { viewController?.navigationController?.popViewController(animated: true) } 152 | } 153 | 154 | private extension FeedbackWireframe { 155 | func showMailConfigurationError() { 156 | let alertController 157 | = UIAlertController(title: CTLocalizedString("CTFeedback.Error"), 158 | message: 159 | CTLocalizedString("CTFeedback.MailConfigurationErrorMessage"), 160 | preferredStyle: .alert) 161 | alertController.addAction(UIAlertAction(title: CTLocalizedString("CTFeedback.Dismiss"), 162 | style: .cancel)) 163 | viewController?.present(alertController, animated: true) 164 | } 165 | 166 | func showImagePicker(sourceType: UIImagePickerController.SourceType) { 167 | let imagePicker = UIImagePickerController() 168 | imagePicker.sourceType = sourceType 169 | imagePicker.mediaTypes = [kUTTypeImage as String, kUTTypeMovie as String] 170 | imagePicker.allowsEditing = false 171 | imagePicker.delegate = imagePickerDelegate 172 | imagePicker.modalPresentationStyle = .formSheet 173 | let presentation = imagePicker.popoverPresentationController 174 | presentation?.permittedArrowDirections = .any 175 | presentation?.sourceView = viewController?.view 176 | presentation?.sourceRect = viewController?.view.frame ?? CGRect.zero 177 | viewController?.present(imagePicker, animated: true) 178 | } 179 | 180 | func showPhotoLibraryAuthorizingAlert() { 181 | let alert = UIAlertController(title: .none, 182 | message: CTLocalizedString("CTFeedback.requiredLibraryAccess"), 183 | preferredStyle: .alert) 184 | alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in 185 | if let url = URL(string: UIApplication.openSettingsURLString) { 186 | UIApplication.shared.openURL(url) 187 | } 188 | })) 189 | viewController?.present(alert, animated: true) 190 | } 191 | 192 | func showCameraAuthorizingAlert() { 193 | let alert = UIAlertController(title: .none, 194 | message: CTLocalizedString("CTFeedback.requiredCameraAccess"), 195 | preferredStyle: .alert) 196 | alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in 197 | if let url = URL(string: UIApplication.openSettingsURLString) { 198 | UIApplication.shared.openURL(url) 199 | } 200 | })) 201 | viewController?.present(alert, animated: true) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Functions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/07. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import MobileCoreServices 8 | import AVFoundation 9 | 10 | func CTLocalizedString(_ key: String) -> String { 11 | #if SWIFT_PACKAGE && swift(>=5.3) 12 | let bundles: [Bundle] = [Bundle.main, Bundle.feedbackBundle, Bundle.module] 13 | #else 14 | let bundles: [Bundle] = [Bundle.main, Bundle.feedbackBundle] 15 | #endif 16 | 17 | for bundle in bundles { 18 | let string = NSLocalizedString(key, 19 | tableName: "CTFeedbackLocalizable", 20 | bundle: bundle, 21 | comment: "") 22 | if key != string { return string } 23 | } 24 | return key 25 | } 26 | 27 | func getMediaFromImagePickerInfo(_ info: [String : Any]) -> Media? { 28 | let imageType = kUTTypeImage as String 29 | let movieType = kUTTypeMovie as String 30 | 31 | switch info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaType)] as? String { 32 | case imageType?: 33 | guard let image = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.originalImage)] as? UIImage 34 | else { return .none } 35 | return .image(image) 36 | case movieType?: 37 | guard let url = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaURL)] as? URL else { return .none } 38 | return getMediaFromURL(url) 39 | default: 40 | return .none 41 | } 42 | } 43 | 44 | func getMediaFromURL(_ url: URL) -> Media? { 45 | let asset = AVURLAsset(url: url) 46 | let generator = AVAssetImageGenerator(asset: asset) 47 | generator.appliesPreferredTrackTransform = true 48 | let time = CMTimeMake(value: 1, timescale: 1) 49 | guard let cgImage = try? generator.copyCGImage(at: time, actualTime: .none) 50 | else { return .none } 51 | return .video(UIImage(cgImage: cgImage), url) 52 | } 53 | 54 | func push(_ item: Item?) -> (((Item) -> ()) -> ())? { 55 | guard let item = item else { return .none } 56 | return { closure in closure(item) } 57 | } 58 | 59 | // Helper function inserted by Swift 4.2 migrator. 60 | fileprivate func convertFromUIImagePickerControllerInfoKey(_ input: UIImagePickerController.InfoKey) -> String { 61 | return input.rawValue 62 | } 63 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/AppBuild/AppBuildCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppBuildCell.swift 3 | // CTFeedbackSwift 4 | // 5 | // Created by 和泉田 領一 on 2017/09/24. 6 | // Copyright © 2017 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AppBuildCell: UITableViewCell { 12 | override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 13 | super.init(style: .value1, reuseIdentifier: reuseIdentifier) 14 | } 15 | 16 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 17 | } 18 | 19 | extension AppBuildCell: CellFactoryProtocol { 20 | class func configure(_ cell: AppBuildCell, 21 | with item: AppBuildItem, 22 | for indexPath: IndexPath, 23 | eventHandler: Any?) { 24 | cell.textLabel?.text = CTLocalizedString("CTFeedback.Build") 25 | cell.detailTextLabel?.text = item.buildString 26 | cell.selectionStyle = .none 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/AppBuild/AppBuildItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/24. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct AppBuildItem: FeedbackItemProtocol { 9 | var buildString: String { 10 | guard let build = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String 11 | else { return "" } 12 | return build 13 | } 14 | 15 | let isHidden: Bool 16 | 17 | init(isHidden: Bool) { self.isHidden = isHidden } 18 | } 19 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/AppName/AppNameCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppNameCell.swift 3 | // CTFeedbackSwift 4 | // 5 | // Created by 和泉田 領一 on 2017/09/24. 6 | // Copyright © 2017 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AppNameCell: UITableViewCell { 12 | override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 13 | super.init(style: .value1, reuseIdentifier: reuseIdentifier) 14 | } 15 | 16 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 17 | } 18 | 19 | extension AppNameCell: CellFactoryProtocol { 20 | class func configure(_ cell: AppNameCell, 21 | with item: AppNameItem, 22 | for indexPath: IndexPath, 23 | eventHandler: Any?) { 24 | cell.textLabel?.text = CTLocalizedString("CTFeedback.Name") 25 | cell.detailTextLabel?.text = item.name 26 | cell.selectionStyle = .none 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/AppName/AppNameItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/24. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct AppNameItem: FeedbackItemProtocol { 9 | var name: String { 10 | if let result = _name { 11 | return result 12 | } 13 | if let displayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String { 14 | return displayName 15 | } 16 | if let bundleName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String { 17 | return bundleName 18 | } 19 | return "" 20 | } 21 | 22 | private var _name: String? 23 | 24 | let isHidden: Bool 25 | 26 | init(isHidden: Bool, name: String? = nil) { 27 | self.isHidden = isHidden 28 | self._name = name 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/AppVersion/AppVersionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppVersionCell.swift 3 | // CTFeedbackSwift 4 | // 5 | // Created by 和泉田 領一 on 2017/09/24. 6 | // Copyright © 2017 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AppVersionCell: UITableViewCell { 12 | override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 13 | super.init(style: .value1, reuseIdentifier: reuseIdentifier) 14 | } 15 | 16 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 17 | } 18 | 19 | extension AppVersionCell: CellFactoryProtocol { 20 | class func configure(_ cell: AppVersionCell, 21 | with item: AppVersionItem, 22 | for indexPath: IndexPath, 23 | eventHandler: Any?) { 24 | cell.textLabel?.text = CTLocalizedString("CTFeedback.Version") 25 | cell.detailTextLabel?.text = item.version 26 | cell.selectionStyle = .none 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/AppVersion/AppVersionItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/24. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct AppVersionItem: FeedbackItemProtocol { 9 | var version: String { 10 | guard let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String 11 | else { return "" } 12 | return shortVersion 13 | } 14 | 15 | let isHidden: Bool 16 | 17 | init(isHidden: Bool) { self.isHidden = isHidden } 18 | } 19 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/Attachment/AttachmentCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/18. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | protocol AttachmentCellEventProtocol { 9 | func showImage(of item: AttachmentItem) 10 | } 11 | 12 | class AttachmentCell: UITableViewCell { 13 | private struct Const { 14 | static let NoAttachedCellHeight: CGFloat = 44.0 15 | static let AttachedCellHeight: CGFloat = 65.0 16 | static let Margin: CGFloat = 15.0 17 | } 18 | 19 | private var eventHandler: AttachmentCellEventProtocol! 20 | private var item: AttachmentItem? 21 | 22 | private let attachmentImageView = UIImageView() 23 | private var attachmentImageViewHeightConstraint: NSLayoutConstraint? 24 | private var attachmentImageViewWidthConstraint: NSLayoutConstraint? 25 | 26 | private let attachmentLabel = UILabel() 27 | private var attachmentLabelLeadingWithImageViewConstraint: NSLayoutConstraint? 28 | private var attachmentLabelLeadingWithContentViewViewConstraint: NSLayoutConstraint? 29 | 30 | private let tapImageViewGestureRecognizer = UITapGestureRecognizer() 31 | 32 | override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 33 | super.init(style: style, reuseIdentifier: reuseIdentifier) 34 | 35 | attachmentImageView.translatesAutoresizingMaskIntoConstraints = false 36 | contentView.addSubview(attachmentImageView) 37 | attachmentImageView.isUserInteractionEnabled = true 38 | attachmentImageViewHeightConstraint = attachmentImageView.heightAnchor 39 | .constraint(equalToConstant: Const.NoAttachedCellHeight) 40 | attachmentImageViewHeightConstraint?.isActive = true 41 | contentView.topAnchor.constraint(equalTo: attachmentImageView.topAnchor).isActive = true 42 | contentView.bottomAnchor.constraint(equalTo: attachmentImageView.bottomAnchor).isActive = true 43 | attachmentImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, 44 | constant: Const.Margin).isActive = true 45 | attachmentImageViewWidthConstraint = attachmentImageView.widthAnchor 46 | .constraint(equalToConstant: 0.0) 47 | attachmentImageViewWidthConstraint?.isActive = true 48 | attachmentImageView.addGestureRecognizer(tapImageViewGestureRecognizer) 49 | tapImageViewGestureRecognizer.addTarget(self, action: #selector(imageViewTapped(_:))) 50 | 51 | attachmentLabel.translatesAutoresizingMaskIntoConstraints = false 52 | attachmentLabel.numberOfLines = 0 53 | contentView.addSubview(attachmentLabel) 54 | attachmentLabel.adjustsFontSizeToFitWidth = true 55 | attachmentLabelLeadingWithImageViewConstraint 56 | = attachmentLabel.leadingAnchor.constraint(equalTo: attachmentImageView.trailingAnchor, 57 | constant: Const.Margin) 58 | attachmentLabelLeadingWithContentViewViewConstraint 59 | = attachmentLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, 60 | constant: Const.Margin) 61 | contentView.trailingAnchor.constraint(equalTo: attachmentLabel.trailingAnchor, 62 | constant: 0.0).isActive = true 63 | contentView.centerYAnchor.constraint(equalTo: attachmentLabel.centerYAnchor).isActive = true 64 | attachmentLabel.text = CTLocalizedString("CTFeedback.AttachImageOrVideo") 65 | 66 | accessoryType = .disclosureIndicator 67 | } 68 | 69 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 70 | } 71 | 72 | extension AttachmentCell { 73 | @objc func imageViewTapped(_ gestureRecognizer: UITapGestureRecognizer) { 74 | guard let item = item, item.image != .none else { return } 75 | eventHandler.showImage(of: item) 76 | } 77 | } 78 | 79 | extension AttachmentCell: CellFactoryProtocol { 80 | class func configure(_ cell: AttachmentCell, 81 | with item: AttachmentItem, 82 | for indexPath: IndexPath, 83 | eventHandler: AttachmentCellEventProtocol) { 84 | cell.item = item 85 | 86 | cell.imageView?.image = item.image 87 | if let heightConstraint = cell.attachmentImageViewHeightConstraint { 88 | heightConstraint.constant = item.attached ? Const.AttachedCellHeight : Const.NoAttachedCellHeight 89 | 90 | if let image = cell.imageView?.image { 91 | cell.attachmentImageViewWidthConstraint?.constant = image.size.width 92 | * heightConstraint.constant 93 | / image.size.height 94 | cell.attachmentLabelLeadingWithContentViewViewConstraint?.isActive = false 95 | cell.attachmentLabelLeadingWithImageViewConstraint?.isActive = true 96 | } else { 97 | cell.attachmentImageViewWidthConstraint?.constant = 0.0 98 | cell.attachmentLabelLeadingWithImageViewConstraint?.isActive = false 99 | cell.attachmentLabelLeadingWithContentViewViewConstraint?.isActive = true 100 | } 101 | } 102 | cell.eventHandler = eventHandler 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/Attachment/AttachmentItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/18. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | struct AttachmentItem: FeedbackItemProtocol { 9 | var attached: Bool { return media != .none } 10 | var media: Media? 11 | var image: UIImage? { 12 | switch media { 13 | case .image(let i)?: return i 14 | case .video(let i, _)?: return i 15 | default: return .none 16 | } 17 | } 18 | let isHidden: Bool 19 | 20 | init(isHidden: Bool) { self.isHidden = isHidden } 21 | } 22 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/Attachment/Media.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/22. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | public enum Media: Equatable { 9 | var jpegData: Data? { 10 | guard case let .image(image) = self else { return .none } 11 | return image.jpegData(compressionQuality: 0.5) 12 | } 13 | var videoData: Data? { 14 | guard case let .video(_, url) = self else { return .none } 15 | return try? Data(contentsOf: url) 16 | } 17 | case image(UIImage) 18 | case video(UIImage, URL) 19 | 20 | public static func ==(lhs: Media, rhs: Media) -> Bool { 21 | switch (lhs, rhs) { 22 | case (.image(let lImage), .image(let rImage)): 23 | return lImage == rImage 24 | case (.video(_, let lUrl), .video(_, let rUrl)): 25 | return lUrl == rUrl 26 | default: 27 | return false 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/Body/BodyCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BodyCell.swift 3 | // CTFeedbackSwift 4 | // 5 | // Created by 和泉田 領一 on 2017/09/16. 6 | // Copyright © 2017年 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol BodyCellEventProtocol { 12 | func bodyCellHeightChanged() 13 | func bodyTextDidChange(_ text: String?) 14 | } 15 | 16 | class BodyCell: UITableViewCell { 17 | var eventHandler: BodyCellEventProtocol? 18 | 19 | let textView: UITextView = { 20 | let result = UITextView(frame: CGRect.zero) 21 | result.translatesAutoresizingMaskIntoConstraints = false 22 | result.isScrollEnabled = false 23 | result.font = .systemFont(ofSize: 14.0) 24 | result.backgroundColor = .clear 25 | return result 26 | }() 27 | 28 | let placeholederLabel: UILabel = { 29 | let result = UILabel() 30 | result.text = CTLocalizedString("CTFeedback.BodyPlaceholder") 31 | result.font = .systemFont(ofSize: 14.0) 32 | result.numberOfLines = 1 33 | result.isUserInteractionEnabled = false 34 | result.translatesAutoresizingMaskIntoConstraints = false 35 | return result 36 | }() 37 | 38 | var heightConstraint: NSLayoutConstraint? 39 | 40 | var height: CGFloat { 41 | let size = textView.sizeThatFits(CGSize(width: textView.frame.width, 42 | height: CGFloat.greatestFiniteMagnitude)) 43 | return max(100.0, size.height) 44 | } 45 | 46 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 47 | super.init(style: style, reuseIdentifier: reuseIdentifier) 48 | 49 | textView.delegate = self 50 | contentView.addSubview(textView) 51 | contentView.addSubview(placeholederLabel) 52 | contentView.topAnchor.constraint(equalTo: textView.topAnchor).isActive = true 53 | contentView.bottomAnchor.constraint(equalTo: textView.bottomAnchor).isActive = true 54 | contentView.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: -12.0).isActive = true 55 | contentView.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: -12.0).isActive = true 56 | heightConstraint = contentView.heightAnchor.constraint(equalToConstant: height) 57 | heightConstraint?.priority = .defaultHigh 58 | heightConstraint?.isActive = true 59 | 60 | contentView.topAnchor.constraint( 61 | equalTo: placeholederLabel.topAnchor, 62 | constant: -8.0 63 | ).isActive = true 64 | 65 | contentView.leadingAnchor.constraint( 66 | equalTo: placeholederLabel.leadingAnchor, 67 | constant: -16.0 68 | ).isActive = true 69 | 70 | contentView.trailingAnchor.constraint( 71 | equalTo: placeholederLabel.trailingAnchor, 72 | constant: -16.0 73 | ).isActive = true 74 | } 75 | 76 | required init?(coder aDecoder: NSCoder) { fatalError() } 77 | } 78 | 79 | extension BodyCell: UITextViewDelegate { 80 | func textViewDidChange(_ textView: UITextView) { 81 | placeholederLabel.isHidden = !textView.text.isEmpty 82 | heightConstraint?.constant = height 83 | eventHandler?.bodyCellHeightChanged() 84 | eventHandler?.bodyTextDidChange(textView.text) 85 | } 86 | } 87 | 88 | extension BodyCell: CellFactoryProtocol { 89 | static func configure(_ cell: BodyCell, 90 | with item: BodyItem, 91 | for indexPath: IndexPath, 92 | eventHandler: BodyCellEventProtocol) { 93 | cell.textView.text = item.bodyText 94 | cell.selectionStyle = .none 95 | cell.eventHandler = eventHandler 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/Body/BodyItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/07. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct BodyItem: FeedbackItemProtocol { 9 | var bodyText: String? 10 | let isHidden: Bool = false 11 | } 12 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/DeviceName/DeviceNameCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceNameCell.swift 3 | // CTFeedbackSwift 4 | // 5 | // Created by 和泉田 領一 on 2017/09/24. 6 | // Copyright © 2017 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DeviceNameCell: UITableViewCell { 12 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 13 | super.init(style: .value1, reuseIdentifier: reuseIdentifier) 14 | } 15 | 16 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 17 | } 18 | 19 | extension DeviceNameCell: CellFactoryProtocol { 20 | static let reuseIdentifier: String = "DeviceNameCell" 21 | 22 | static func configure(_ cell: DeviceNameCell, 23 | with item: DeviceNameItem, 24 | for indexPath: IndexPath, 25 | eventHandler: Any?) { 26 | cell.textLabel?.text = CTLocalizedString("CTFeedback.Device") 27 | cell.detailTextLabel?.text = item.deviceName 28 | cell.selectionStyle = .none 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/DeviceName/DeviceNameItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/24. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public struct DeviceNameItem: FeedbackItemProtocol { 9 | var deviceName: String { 10 | guard let path = Bundle.platformNamesPlistPath, 11 | let dictionary = NSDictionary(contentsOfFile: path) as? [String: String] 12 | else { return "" } 13 | 14 | let rawPlatform = platform 15 | return dictionary[rawPlatform] ?? rawPlatform 16 | } 17 | 18 | private var platform: String { 19 | var systemInfo = utsname() 20 | uname(&systemInfo) 21 | guard let machine = withUnsafePointer(to: &systemInfo.machine, { 22 | $0.withMemoryRebound(to: CChar.self, capacity: 1) { 23 | ptr in 24 | String.init(validatingUTF8: ptr) 25 | } 26 | }) else { return "Unknown" } 27 | return String(validatingUTF8: machine) ?? "Unknown" 28 | } 29 | 30 | public let isHidden: Bool = false 31 | } 32 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/SystemVersion/SystemVersionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemVersionCell.swift 3 | // CTFeedbackSwift 4 | // 5 | // Created by 和泉田 領一 on 2017/09/24. 6 | // Copyright © 2017 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SystemVersionCell: UITableViewCell { 12 | override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 13 | super.init(style: .value1, reuseIdentifier: reuseIdentifier) 14 | } 15 | 16 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 17 | } 18 | 19 | extension SystemVersionCell: CellFactoryProtocol { 20 | class func configure(_ cell: SystemVersionCell, 21 | with item: SystemVersionItem, 22 | for indexPath: IndexPath, 23 | eventHandler: Any?) { 24 | #if targetEnvironment(macCatalyst) 25 | cell.textLabel?.text = "macOS" 26 | #else 27 | cell.textLabel?.text = CTLocalizedString("CTFeedback.iOS") 28 | #endif 29 | cell.detailTextLabel?.text = item.version 30 | cell.selectionStyle = .none 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/SystemVersion/SystemVersionItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/24. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | struct SystemVersionItem: FeedbackItemProtocol { 9 | var version: String { return UIDevice.current.systemVersion } 10 | let isHidden: Bool = false 11 | } 12 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/Topic/Topic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/07. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public protocol TopicProtocol { 9 | var title: String { get } 10 | var localizedTitle: String { get } 11 | } 12 | 13 | enum Topic: String { 14 | case question = "Question" 15 | case request = "Request" 16 | case bugReport = "Bug Report" 17 | case other = "Other" 18 | } 19 | 20 | extension Topic: TopicProtocol { 21 | var title: String { return rawValue } 22 | var localizedTitle: String { return CTLocalizedString(title) } 23 | } 24 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/Topic/TopicCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/07. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | final public class TopicCell: UITableViewCell { 9 | public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 10 | super.init(style: .value1, reuseIdentifier: TopicCell.reuseIdentifier) 11 | } 12 | 13 | public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 14 | } 15 | 16 | extension TopicCell: CellFactoryProtocol { 17 | static func configure(_ cell: TopicCell, 18 | with item: TopicItem, 19 | for indexPath: IndexPath, 20 | eventHandler: Any?) { 21 | cell.textLabel?.text = CTLocalizedString("CTFeedback.Topic") 22 | cell.detailTextLabel?.text = item.topicTitle 23 | cell.accessoryType = .disclosureIndicator 24 | cell.selectionStyle = .none 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/Topic/TopicItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/07. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public struct TopicItem: FeedbackItemProtocol { 9 | public static var defaultTopics: [TopicProtocol] { 10 | return [Topic.question, 11 | Topic.request, 12 | Topic.bugReport, 13 | Topic.other] 14 | } 15 | 16 | var topicTitle: String { return selected?.localizedTitle ?? topics.first?.localizedTitle ?? "" } 17 | var topics: [TopicProtocol] = [] 18 | var selected: TopicProtocol? { 19 | get { return _selected ?? topics.first } 20 | set { _selected = newValue } 21 | } 22 | private var _selected: TopicProtocol? 23 | public let isHidden: Bool 24 | 25 | init(_ topics: [TopicProtocol]) { 26 | self.topics = topics 27 | self.isHidden = topics.isEmpty 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/UserEmail/UserEmailCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserEmailCell.swift 3 | // CTFeedbackSwift 4 | // 5 | // Created by 和泉田 領一 on 2017/09/24. 6 | // Copyright © 2017 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol UserEmailCellEventProtocol { 12 | func userEmailTextDidChange(_ text: String?) 13 | } 14 | 15 | class UserEmailCell: UITableViewCell { 16 | private struct Const { 17 | static let FontSize: CGFloat = 14.0 18 | static let Margin: CGFloat = 15.0 19 | static let Height: CGFloat = 44.0 20 | } 21 | 22 | private var eventHandler: UserEmailCellEventProtocol! 23 | 24 | let textField = UITextField() 25 | 26 | override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 27 | super.init(style: .value1, reuseIdentifier: reuseIdentifier) 28 | 29 | textField.backgroundColor = .clear 30 | textField.delegate = self 31 | textField.placeholder = CTLocalizedString("CTFeedback.Mail") 32 | textField.keyboardType = .emailAddress 33 | contentView.addSubview(textField) 34 | textField.translatesAutoresizingMaskIntoConstraints = false 35 | textField.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true 36 | textField.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true 37 | textField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, 38 | constant: Const.Margin).isActive = true 39 | textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, 40 | constant: Const.Margin).isActive = true 41 | textField.heightAnchor.constraint(equalToConstant: Const.Height).isActive = true 42 | } 43 | 44 | required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } 45 | } 46 | 47 | extension UserEmailCell: UITextFieldDelegate { 48 | func textField(_ textField: UITextField, 49 | shouldChangeCharactersIn range: NSRange, 50 | replacementString string: String) -> Bool { 51 | eventHandler.userEmailTextDidChange(textField.text) 52 | return true 53 | } 54 | } 55 | 56 | extension UserEmailCell: CellFactoryProtocol { 57 | class func configure(_ cell: UserEmailCell, 58 | with item: UserEmailItem, 59 | for indexPath: IndexPath, 60 | eventHandler: UserEmailCellEventProtocol) { 61 | cell.eventHandler = eventHandler 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Items/UserEmail/UserEmailItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 和泉田 領一 on 2017/09/24. 3 | // Copyright (c) 2017 CAPH TECH. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct UserEmailItem: FeedbackItemProtocol { 9 | let isHidden: Bool 10 | var email: String? = .none 11 | 12 | init(isHidden: Bool) { self.isHidden = isHidden } 13 | } 14 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/PlatformNames.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | iPhone1,1 6 | iPhone 1G 7 | iPhone1,2 8 | iPhone 3G 9 | iPhone2,1 10 | iPhone 3GS 11 | iPhone3,1 12 | iPhone 4 13 | iPhone3,3 14 | Verizon iPhone 4 15 | iPhone4,1 16 | iPhone 4S 17 | iPhone5,1 18 | iPhone 5 (GSM) 19 | iPhone5,2 20 | iPhone 5 (GSM+CDMA) 21 | iPhone5,3 22 | iPhone 5c (GSM) 23 | iPhone5,4 24 | iPhone 5c (Global) 25 | iPhone6,1 26 | iPhone 5s (GSM) 27 | iPhone6,2 28 | iPhone 5s (Global) 29 | iPhone7,2 30 | iPhone 6 31 | iPhone7,1 32 | iPhone 6 Plus 33 | iPhone8,1 34 | iPhone 6s 35 | iPhone8,2 36 | iPhone 6s Plus 37 | iPhone8,4 38 | iPhone SE 39 | iPhone9,1 40 | iPhone 7 (Global) 41 | iPhone9,2 42 | iPhone 7 Plus (Global) 43 | iPhone9,3 44 | iPhone 7 (GSM) 45 | iPhone9,4 46 | iPhone 7 Plus (GSM) 47 | iPhone10,1 48 | iPhone 8 (Global) 49 | iPhone10,4 50 | iPhone 8 (GSM) 51 | iPhone10,2 52 | iPhone 8 Plus (Global) 53 | iPhone10,5 54 | iPhone 8 Plus (GSM) 55 | iPhone10,3 56 | iPhone X (Global) 57 | iPhone10,6 58 | iPhone X (GSM) 59 | iPhone11,2 60 | iPhone XS 61 | iPhone11,4 62 | iPhone XS Max 63 | iPhone11,6 64 | iPhone XS Max China 65 | iPhone11,8 66 | iPhone XR 67 | iPhone12,1 68 | iPhone 11 69 | iPhone12,3 70 | iPhone 11 Pro 71 | iPhone12,5 72 | iPhone 11 Pro Max 73 | iPhone12,8 74 | iPhone SE (2nd Gen) 75 | iPhone13,1 76 | iPhone 12 Mini 77 | iPhone13,2 78 | iPhone 12 79 | iPhone13,3 80 | iPhone 12 Pro 81 | iPhone13,4 82 | iPhone 12 Pro Max 83 | iPhone14,2 84 | iPhone 13 Pro 85 | iPhone14,3 86 | iPhone 13 Pro Max 87 | iPhone14,4 88 | iPhone 13 Mini 89 | iPhone14,5 90 | iPhone 13 91 | iPod1,1 92 | iPod Touch 1G 93 | iPod2,1 94 | iPod Touch 2G 95 | iPod3,1 96 | iPod Touch 3G 97 | iPod4,1 98 | iPod Touch 4G 99 | iPod5,1 100 | iPod Touch 5G 101 | iPod7,1 102 | iPod Touch 6G 103 | iPod9,1 104 | iPod Touch 7G 105 | iPad1,1 106 | iPad 107 | iPad2,1 108 | iPad 2 (W i) 109 | iPad2,2 110 | iPad 2 (GSM) 111 | iPad2,3 112 | iPad 2 (CDMA) 113 | iPad2,4 114 | iPad 2 115 | iPad3,1 116 | iPad-3G (W i) 117 | iPad3,2 118 | iPad-3G (4G) 119 | iPad3,3 120 | iPad-3G (4G) 121 | iPad3,4 122 | iPad-4G (W i) 123 | iPad3,5 124 | iPad-4G (GSM) 125 | iPad3,6 126 | iPad-4G (GSM+CDMA) 127 | iPad4,1 128 | iPad Air (W i) 129 | iPad4,2 130 | iPad Air (Cellular) 131 | iPad4,3 132 | iPad Air 133 | iPad5,3 134 | iPad Air 2 135 | iPad5,4 136 | iPad Air 2 137 | iPad2,5 138 | iPad mini-1G (W i) 139 | iPad2,6 140 | iPad mini-1G (GSM) 141 | iPad2,7 142 | iPad mini-1G (GSM+CDMA) 143 | iPad4,4 144 | iPad mini-2G (W i) 145 | iPad4,5 146 | iPad mini-2G (Cellular) 147 | iPad4,6 148 | iPad mini 2 149 | iPad4,7 150 | iPad mini 3 151 | iPad4,8 152 | iPad mini 3 153 | iPad4,9 154 | iPad mini 3 155 | iPad5,1 156 | iPad mini 4 157 | iPad5,2 158 | iPad mini 4 159 | iPad6,7 160 | iPad Pro (12.9) 161 | iPad6,8 162 | iPad Pro (12.9) 163 | iPad6,3 164 | iPad Pro (9.7) 165 | iPad6,4 166 | iPad Pro (9.7) 167 | iPad6,11 168 | iPad (5th generation) 169 | iPad6,12 170 | iPad (5th generation) 171 | iPad7,1 172 | iPad Pro (12.9, 2nd generation) 173 | iPad7,2 174 | iPad Pro (12.9, 2nd generation) 175 | iPad7,3 176 | iPad Pro (10.5) 177 | iPad7,4 178 | iPad Pro (10.5) 179 | iPad7,5 180 | iPad (6th generation) 181 | iPad7,6 182 | iPad (6th generation) 183 | iPad8,1 184 | iPad Pro (11 WiFi) 185 | iPad8,2 186 | iPad Pro (11 1TB WiFi) 187 | iPad8,3 188 | iPad Pro (11 Cellular) 189 | iPad8,4 190 | iPad Pro (11 1TB Cellular) 191 | iPad8,5 192 | iPad Pro (12.9 3rd generation WiFi) 193 | iPad8,6 194 | iPad Pro (12.9 3rd generation 1TB WiFi) 195 | iPad8,7 196 | iPad Pro (12.9 3rd generation Cellular) 197 | iPad8,8 198 | iPad Pro (12.9 3rd generation 1TB Cellular) 199 | iPad8,9 200 | iPad Pro (11 2nd generation WiFi) 201 | iPad8,10 202 | iPad Pro (11 2nd generation 1TB WiFi) 203 | iPad8,11 204 | iPad Pro (12.9 4th generation WiFi) 205 | iPad8,12 206 | iPad Pro (12.9 4th generation Cellular) 207 | iPad11,1 208 | iPad mini (5th generation WiFi) 209 | iPad11,2 210 | iPad mini (5th generation Cellular) 211 | iPad11,3 212 | iPad Air (3rd generation WiFi) 213 | iPad11,4 214 | iPad Air (3rd generation Cellular) 215 | 216 | 217 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/ar.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ehab Abdou on 2016/05/10. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "سؤال"; 8 | 9 | "Request" = "طلب"; 10 | 11 | "Bug Report" = "الإبلاغ عن خطأ"; 12 | 13 | "Other" = "آخر"; 14 | 15 | "CTFeedback.Device" = "الجهاز"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "الاسم"; 20 | 21 | "CTFeedback.Version" = "الإصدار"; 22 | 23 | "CTFeedback.Build" = "البنية"; 24 | 25 | "CTFeedback.DeviceInfo" = "معلومات الجهاز"; 26 | 27 | "CTFeedback.AppInfo" = "معلومات التطبيق"; 28 | 29 | "CTFeedback.Feedback" = "رأيك"; 30 | 31 | "CTFeedback.Topic" = "الموضوع"; 32 | 33 | "CTFeedback.Topics" = "المواضيع"; 34 | 35 | "CTFeedback.Error" = "خطأ"; 36 | 37 | "CTFeedback.Mail" = "إرسال"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="لم يكن لديك حساب بريد إلكتروني صالح"; 40 | 41 | "CTFeedback.Dismiss" ="صرف"; 42 | 43 | "CTFeedback.AdditionalInfo"="معلومات إضافية"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="اختر مرفق رأيك (صورة)"; 46 | 47 | "CTFeedback.Cancel" ="إلغاء"; 48 | 49 | "CTFeedback.Camera" ="كاميرا"; 50 | 51 | "CTFeedback.PhotoLibrary" ="اختر من الألبوم"; 52 | 53 | "CTFeedback.UserDetail"="معلومات المستخدم"; 54 | 55 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 56 | "CTFeedback.UnknownError" = "Unknown Error"; 57 | "CTFeedback.Delete" = "Delete"; 58 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 59 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 60 | 61 | "CTFeedback.BodyPlaceholder" = "أدخل النص هنا"; 62 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/bs.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "Pitanje"; 8 | 9 | "Request" = "Zahtjev"; 10 | 11 | "Bug Report" = "Greška"; 12 | 13 | "Other" = "Drugo"; 14 | 15 | "CTFeedback.Device" = "Uređaj"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "Ime"; 20 | 21 | "CTFeedback.Version" = "Verzija"; 22 | 23 | "CTFeedback.Build" = "Podverzija"; 24 | 25 | "CTFeedback.DeviceInfo" = "Informacije o uređaju"; 26 | 27 | "CTFeedback.AppInfo" = "Informacije o aplikaciji"; 28 | 29 | "CTFeedback.Feedback" = "Povratna informacija"; 30 | 31 | "CTFeedback.Topic" = "Tema"; 32 | 33 | "CTFeedback.Topics" = "Teme"; 34 | 35 | "CTFeedback.Error" = "Greška"; 36 | 37 | "CTFeedback.Mail" = "E-mail"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="Nemate podešen e-mail račun"; 40 | 41 | "CTFeedback.Dismiss" ="Zatvori"; 42 | 43 | "CTFeedback.AdditionalInfo"="Dodatne informacije"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="Izaberite prilog (sliku)"; 46 | 47 | "CTFeedback.Cancel" ="Otkaži"; 48 | 49 | "CTFeedback.Camera" ="Kamera"; 50 | 51 | "CTFeedback.PhotoLibrary" ="Izaberite iz albuma"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "Ovdje unesite tekst ..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/cs-CZ.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CTFeedbackSwiftLanguages 4 | 5 | Created by Sebastian Szöcs on 9/28/21. 6 | 7 | */ 8 | "Question" = "Otázka"; 9 | 10 | "Request" = "Žádost"; 11 | 12 | "Bug Report" = "Hlášení chyby"; 13 | 14 | "Other" = "Jiný"; 15 | 16 | "CTFeedback.Device" = "Zařízení"; 17 | 18 | "CTFeedback.iOS" = "iOS"; 19 | 20 | "CTFeedback.Name" = "Aplikace"; 21 | 22 | "CTFeedback.Version" = "Verze"; 23 | 24 | "CTFeedback.Build" = "Build"; 25 | 26 | "CTFeedback.DeviceInfo" = "Informace o zařízení"; 27 | 28 | "CTFeedback.AppInfo" = "Informace o aplikaci"; 29 | 30 | "CTFeedback.Feedback" = "Zpětná vazba"; 31 | 32 | "CTFeedback.Topic" = "Téma"; 33 | 34 | "CTFeedback.Topics" = "Témata"; 35 | 36 | "CTFeedback.Error" = "Chyba"; 37 | 38 | "CTFeedback.Mail" = "Mail"; 39 | 40 | "CTFeedback.MailConfigurationErrorMessage" ="Nemáš platný e-mailový účet"; 41 | 42 | "CTFeedback.Dismiss" ="Zrušit"; 43 | 44 | "CTFeedback.AdditionalInfo"="Doplňující informace"; 45 | 46 | "CTFeedback.AttachImageOrVideo" ="Vyber přílohu pro zpětnou vazbu (obrázek)"; 47 | 48 | "CTFeedback.Cancel" ="Zrušit"; 49 | 50 | "CTFeedback.Camera" ="Kamera"; 51 | 52 | "CTFeedback.PhotoLibrary" ="Vybrat fotku z knihovny"; 53 | "CTFeedback.UserDetail" = "Detail uživatele"; 54 | "CTFeedback.FeedbackGenerationErrorMessage" = "Vyskytla se chyba při generování zpětné vazby"; 55 | "CTFeedback.UnknownError" = "Neznámá chyba"; 56 | "CTFeedback.Delete" = "Vymazat"; 57 | "CTFeedback.requiredCameraAccess" = "Aplikace vyžaduje přístup k fotoaparátu"; 58 | "CTFeedback.requiredLibraryAccess" = "Aplikace vyžaduje přístup ke knihovne fotek pro vybrání snímku obrazovky"; 59 | 60 | "CTFeedback.BodyPlaceholder" = "Sem zadej text..."; 61 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/da.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "Spørgsmål"; 8 | 9 | "Request" = "Forespørgsel"; 10 | 11 | "Bug Report" = "Fejlmelding"; 12 | 13 | "Other" = "Andet"; 14 | 15 | "CTFeedback.Device" = "Enhed"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "Navn"; 20 | 21 | "CTFeedback.Version" = "Version"; 22 | 23 | "CTFeedback.Build" = "Build"; 24 | 25 | "CTFeedback.DeviceInfo" = "Information om enheden"; 26 | 27 | "CTFeedback.AppInfo" = "Information om appen"; 28 | 29 | "CTFeedback.Feedback" = "Feedback"; 30 | 31 | "CTFeedback.Topic" = "Emne"; 32 | 33 | "CTFeedback.Topics" = "Emner"; 34 | 35 | "CTFeedback.Error" = "Fejl"; 36 | 37 | "CTFeedback.Mail" = "Send"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="Du har ikke en gyldig email konto"; 40 | 41 | "CTFeedback.Dismiss" ="Annuller"; 42 | 43 | "CTFeedback.AdditionalInfo"="Yderligere information"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="Vælg et billede"; 46 | 47 | "CTFeedback.Cancel" ="Annuller"; 48 | 49 | "CTFeedback.Camera" ="Kamera"; 50 | 51 | "CTFeedback.PhotoLibrary" ="Vælg fra album"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "Indtast tekst her ..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/de.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "Frage"; 8 | 9 | "Request" = "Anfrage"; 10 | 11 | "Bug Report" = "Fehlerbericht"; 12 | 13 | "Other" = "Sonstiges"; 14 | 15 | "CTFeedback.Device" = "Gerät"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "Name"; 20 | 21 | "CTFeedback.Version" = "Version"; 22 | 23 | "CTFeedback.Build" = "Build"; 24 | 25 | "CTFeedback.DeviceInfo" = "Geräte-Angaben"; 26 | 27 | "CTFeedback.AppInfo" = "App-Angaben"; 28 | 29 | "CTFeedback.Feedback" = "Feedback"; 30 | 31 | "CTFeedback.Topic" = "Thema"; 32 | 33 | "CTFeedback.Topics" = "Themen"; 34 | 35 | "CTFeedback.Error" = "Fehler"; 36 | 37 | "CTFeedback.Mail" = "E-Mail"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" = "Es ist kein gültiger E-Mail-Account eingerichtet."; 40 | 41 | "CTFeedback.Dismiss" = "Okay"; 42 | 43 | "CTFeedback.AdditionalInfo" = "Zusätzliche Angaben"; 44 | 45 | "CTFeedback.AttachImageOrVideo" = "Anhang für Feedback wählen (Foto)"; 46 | 47 | "CTFeedback.Cancel" = "Abbrechen"; 48 | 49 | "CTFeedback.Camera" = "Kamera"; 50 | 51 | "CTFeedback.PhotoLibrary" = "Aus Album auswählen"; 52 | 53 | 54 | "CTFeedback.UserDetail" = "User Detail"; 55 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 56 | "CTFeedback.UnknownError" = "Unknown Error"; 57 | "CTFeedback.Delete" = "Delete"; 58 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 59 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 60 | 61 | "CTFeedback.BodyPlaceholder" = "Hier Text eingeben..."; 62 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/en.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "Question"; 8 | 9 | "Request" = "Request"; 10 | 11 | "Bug Report" = "Bug Report"; 12 | 13 | "Other" = "Other"; 14 | 15 | "CTFeedback.Device" = "Device"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "Name"; 20 | 21 | "CTFeedback.Version" = "Version"; 22 | 23 | "CTFeedback.Build" = "Build"; 24 | 25 | "CTFeedback.DeviceInfo" = "Device Info"; 26 | 27 | "CTFeedback.AppInfo" = "App Info"; 28 | 29 | "CTFeedback.Feedback" = "Feedback"; 30 | 31 | "CTFeedback.Topic" = "Topic"; 32 | 33 | "CTFeedback.Topics" = "Topics"; 34 | 35 | "CTFeedback.Error" = "Error"; 36 | 37 | "CTFeedback.Mail" = "Mail"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="You do not have a valid e-mail account"; 40 | 41 | "CTFeedback.Dismiss" ="Dismiss"; 42 | 43 | "CTFeedback.AdditionalInfo"="Additional Info"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="Select the feedback attachment (picture)"; 46 | 47 | "CTFeedback.Cancel" ="Cancel"; 48 | 49 | "CTFeedback.Camera" ="Camera"; 50 | 51 | "CTFeedback.PhotoLibrary" ="Select from the album"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "Enter text here..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/fa-IR.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "سوال"; 8 | 9 | "Request" = "درخواست"; 10 | 11 | "Bug Report" = "گزارش مشکل"; 12 | 13 | "Other" = "دیگر"; 14 | 15 | "CTFeedback.Device" = "دستگاه"; 16 | 17 | "CTFeedback.iOS" = "آی‌او‌اس"; 18 | 19 | "CTFeedback.Name" = "نام"; 20 | 21 | "CTFeedback.Version" = "نسخه"; 22 | 23 | "CTFeedback.Build" = "ساخت"; 24 | 25 | "CTFeedback.DeviceInfo" = "اطلاعات دستگاه"; 26 | 27 | "CTFeedback.AppInfo" = "اطلاعات برنامه"; 28 | 29 | "CTFeedback.Feedback" = "نظر"; 30 | 31 | "CTFeedback.Topic" = "موضوع"; 32 | 33 | "CTFeedback.Topics" = "موضوع‌ها"; 34 | 35 | "CTFeedback.Error" = "خطا"; 36 | 37 | "CTFeedback.Mail" = "ارسال پیام"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="شما ایمیل دستگاه خود را تنظیم نکرده‌اید"; 40 | 41 | "CTFeedback.Dismiss" ="بستن"; 42 | 43 | "CTFeedback.AdditionalInfo"="اطلاعات اضافه"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="پیوست خود را انتخاب کنید (تصویر)"; 46 | 47 | "CTFeedback.Cancel" ="لغو"; 48 | 49 | "CTFeedback.Camera" ="دوربین"; 50 | 51 | "CTFeedback.PhotoLibrary" ="از آلبوم خود انتخاب کنید"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "متن را اینجا وارد کنید"; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/fr.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "Question"; 8 | 9 | "Request" = "Requête"; 10 | 11 | "Bug Report" = "Rapport de bug"; 12 | 13 | "Other" = "Autre"; 14 | 15 | "CTFeedback.Device" = "Appareil"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "Nom"; 20 | 21 | "CTFeedback.Version" = "Version"; 22 | 23 | "CTFeedback.Build" = "Build"; 24 | 25 | "CTFeedback.DeviceInfo" = "Informations sur l'appareil"; 26 | 27 | "CTFeedback.AppInfo" = "Informations sur l'application"; 28 | 29 | "CTFeedback.Feedback" = "Support"; 30 | 31 | "CTFeedback.Topic" = "Sujet"; 32 | 33 | "CTFeedback.Topics" = "Sujets"; 34 | 35 | "CTFeedback.Error" = "Error"; 36 | 37 | "CTFeedback.Mail" = "Mail"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" = "Vous n'avez pas configuré de compte mail"; 40 | 41 | "CTFeedback.Dismiss" = "Fermer"; 42 | 43 | "CTFeedback.AdditionalInfo" = "Informations complémentaires"; 44 | 45 | "CTFeedback.AttachImageOrVideo" = "Sélectionner une pièce-jointe (image)"; 46 | 47 | "CTFeedback.Cancel" = "Annuler"; 48 | 49 | "CTFeedback.Camera" = "Appareil photo"; 50 | 51 | "CTFeedback.PhotoLibrary" = "Sélectionner depuis l'album"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.Delete" = "Delete"; 54 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 55 | "CTFeedback.UnknownError" = "Unknown Error"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "Entrez le texte ici..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/hr-HR.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "Pitanje"; 8 | 9 | "Request" = "Zahtjev"; 10 | 11 | "Bug Report" = "Greška"; 12 | 13 | "Other" = "Drugo"; 14 | 15 | "CTFeedback.Device" = "Uređaj"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "Ime"; 20 | 21 | "CTFeedback.Version" = "Verzija"; 22 | 23 | "CTFeedback.Build" = "Podverzija"; 24 | 25 | "CTFeedback.DeviceInfo" = "Informacije o uređaju"; 26 | 27 | "CTFeedback.AppInfo" = "Informacije o aplikaciji"; 28 | 29 | "CTFeedback.Feedback" = "Povratna informacija"; 30 | 31 | "CTFeedback.Topic" = "Tema"; 32 | 33 | "CTFeedback.Topics" = "Teme"; 34 | 35 | "CTFeedback.Error" = "Greška"; 36 | 37 | "CTFeedback.Mail" = "E-mail"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="Nemate podešen e-mail račun"; 40 | 41 | "CTFeedback.Dismiss" ="Zatvori"; 42 | 43 | "CTFeedback.AdditionalInfo"="Dodatne informacije"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="Izaberite prilog (sliku)"; 46 | 47 | "CTFeedback.Cancel" ="Otkaži"; 48 | 49 | "CTFeedback.Camera" ="Kamera"; 50 | 51 | "CTFeedback.PhotoLibrary" ="Izaberite iz albuma"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "Ovdje unesite tekst ..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/it.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "Domanda"; 8 | 9 | "Request" = "Richiesta"; 10 | 11 | "Bug Report" = "Segnalazione Bug"; 12 | 13 | "Other" = "Altro"; 14 | 15 | "CTFeedback.Device" = "Dispositivo"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "Nome"; 20 | 21 | "CTFeedback.Version" = "Versione"; 22 | 23 | "CTFeedback.Build" = "Build"; 24 | 25 | "CTFeedback.DeviceInfo" = "Informazioni sul Dispositivo"; 26 | 27 | "CTFeedback.AppInfo" = "Informazioni sull'App"; 28 | 29 | "CTFeedback.Feedback" = "Feedback"; 30 | 31 | "CTFeedback.Topic" = "Argomento"; 32 | 33 | "CTFeedback.Topics" = "Argomenti"; 34 | 35 | "CTFeedback.Error" = "Errore"; 36 | 37 | "CTFeedback.Mail" = "Invia"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="Account di posta non configurato"; 40 | 41 | "CTFeedback.Dismiss" ="Chiudi"; 42 | 43 | "CTFeedback.AdditionalInfo"="Informazioni Aggiuntive"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="Seleziona un allegato (immagine)"; 46 | 47 | "CTFeedback.Cancel" ="Annulla"; 48 | 49 | "CTFeedback.Camera" ="Fotocamera"; 50 | 51 | "CTFeedback.PhotoLibrary" ="Seleziona dalla libreria"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "Inserisci qui il testo ..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/ja.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "質問"; 8 | 9 | "Request" = "リクエスト"; 10 | 11 | "Bug Report" = "バグレポート"; 12 | 13 | "Other" = "その他"; 14 | 15 | "CTFeedback.Device" = "デバイス"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "アプリ名"; 20 | 21 | "CTFeedback.Version" = "バージョン"; 22 | 23 | "CTFeedback.Build" = "ビルド"; 24 | 25 | "CTFeedback.DeviceInfo" = "デバイス情報"; 26 | 27 | "CTFeedback.AppInfo" = "アプリ情報"; 28 | 29 | "CTFeedback.Feedback" = "フィードバック"; 30 | 31 | "CTFeedback.Topic" = "トピック"; 32 | 33 | "CTFeedback.Topics" = "トピック"; 34 | 35 | "CTFeedback.Error" = "エラー"; 36 | 37 | "CTFeedback.Mail" = "メール"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="有効な電子メールアカウントを持っていません"; 40 | 41 | "CTFeedback.Dismiss" ="閉じる"; 42 | 43 | "CTFeedback.AdditionalInfo"="追加情報"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="フィードバック添付画像を選択"; 46 | 47 | "CTFeedback.Cancel" ="キャンセル"; 48 | 49 | "CTFeedback.Camera" ="カメラ"; 50 | 51 | "CTFeedback.PhotoLibrary" ="アルバムから選択"; 52 | "CTFeedback.UserDetail" = "ユーザ情報"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "削除"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "ここにテキストを入力してください..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/ko.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "질문"; 8 | 9 | "Request" = "요청"; 10 | 11 | "Bug Report" = "버그 리포트"; 12 | 13 | "Other" = "기타"; 14 | 15 | "CTFeedback.Device" = "디바이스"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "이름"; 20 | 21 | "CTFeedback.Version" = "버젼"; 22 | 23 | "CTFeedback.Build" = "빌드"; 24 | 25 | "CTFeedback.DeviceInfo" = "디바이스 정보"; 26 | 27 | "CTFeedback.AppInfo" = "앱 정보"; 28 | 29 | "CTFeedback.Feedback" = "피드백"; 30 | 31 | "CTFeedback.Topic" = "주제"; 32 | 33 | "CTFeedback.Topics" = "주제들"; 34 | 35 | "CTFeedback.Error" = "오류"; 36 | 37 | "CTFeedback.Mail" = "메일"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="정상적인 이메일 계정을 갖고 있지 않습니다"; 40 | 41 | "CTFeedback.Dismiss" ="닫기"; 42 | 43 | "CTFeedback.AdditionalInfo"="추가 정보"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="같이 전달할 첨부사진 고르기"; 46 | 47 | "CTFeedback.Cancel" ="취소"; 48 | 49 | "CTFeedback.Camera" ="카메라에서 선택"; 50 | 51 | "CTFeedback.PhotoLibrary" ="앨범에서 선택"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "여기에 텍스트를 입력하세요 ..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/nl.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "Vraag"; 8 | 9 | "Request" = "Verzoek"; 10 | 11 | "Bug Report" = "Bug verslag"; 12 | 13 | "Other" = "Overig"; 14 | 15 | "CTFeedback.Device" = "Toestel"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "Naam"; 20 | 21 | "CTFeedback.Version" = "Versie"; 22 | 23 | "CTFeedback.Build" = "Build"; 24 | 25 | "CTFeedback.DeviceInfo" = "Toestel informatie"; 26 | 27 | "CTFeedback.AppInfo" = "App informatie"; 28 | 29 | "CTFeedback.Feedback" = "Feedback"; 30 | 31 | "CTFeedback.Topic" = "Onderwerp"; 32 | 33 | "CTFeedback.Topics" = "Topics"; 34 | 35 | "CTFeedback.Error" = "Error"; 36 | 37 | "CTFeedback.Mail" = "Mail"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" = "Je hebt geen geldige e-mail account"; 40 | 41 | "CTFeedback.Dismiss" = "Sluiten"; 42 | 43 | "CTFeedback.AdditionalInfo" = "Extra informatie"; 44 | 45 | "CTFeedback.AttachImageOrVideo" = "Selecteer een afbeelding"; 46 | 47 | "CTFeedback.Cancel" = "Annuleren"; 48 | 49 | "CTFeedback.Camera" = "Camera"; 50 | 51 | "CTFeedback.PhotoLibrary" = "Selecteer uit album"; 52 | 53 | "CTFeedback.UserDetail" = "Gebruiker detail"; 54 | 55 | "CTFeedback.UserDetail" = "User Detail"; 56 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 57 | "CTFeedback.UnknownError" = "Unknown Error"; 58 | "CTFeedback.Delete" = "Delete"; 59 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 60 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 61 | 62 | "CTFeedback.BodyPlaceholder" = "Voeg hier tekst in..."; 63 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/ru.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | // Edited by Dennis Kutlubaev (alwawee@gmail.com) 8 | 9 | "Question" = "Вопрос"; 10 | 11 | "Request" = "Предложение"; 12 | 13 | "Bug Report" = "Уведомление об ошибке"; 14 | 15 | "Other" = "Другое"; 16 | 17 | "CTFeedback.Device" = "Устройство"; 18 | 19 | "CTFeedback.iOS" = "iOS"; 20 | 21 | "CTFeedback.Name" = "Название"; 22 | 23 | "CTFeedback.Version" = "Версия"; 24 | 25 | "CTFeedback.Build" = "Сборка"; 26 | 27 | "CTFeedback.DeviceInfo" = "Информация об устройстве"; 28 | 29 | "CTFeedback.AppInfo" = "Информация о приложении"; 30 | 31 | "CTFeedback.Feedback" = "Обратная связь"; 32 | 33 | "CTFeedback.Topic" = "Тема"; 34 | 35 | "CTFeedback.Topics" = "Темы"; 36 | 37 | "CTFeedback.Error" = "Ошибка"; 38 | 39 | "CTFeedback.Mail" = "Отправить"; 40 | 41 | "CTFeedback.MailConfigurationErrorMessage" ="Отсутствует учётная запись электронной почты."; 42 | 43 | "CTFeedback.Dismiss" ="Dismiss"; 44 | 45 | "CTFeedback.AdditionalInfo"="Дополнительная информация"; 46 | 47 | "CTFeedback.AttachImageOrVideo" ="Прикрепить"; 48 | 49 | "CTFeedback.Cancel" ="Отмена"; 50 | 51 | "CTFeedback.Camera" ="Камера"; 52 | 53 | "CTFeedback.PhotoLibrary" ="Фотоальбом"; 54 | 55 | "CTFeedback.UserDetail"="Информация о пользователе"; 56 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 57 | "CTFeedback.UnknownError" = "Unknown Error"; 58 | "CTFeedback.Delete" = "Delete"; 59 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 60 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 61 | 62 | "CTFeedback.BodyPlaceholder" = "Введите текст..."; 63 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/sk.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CTFeedbackSwiftLanguages 4 | 5 | Created by Sebastian Szöcs on 9/28/21. 6 | 7 | */ 8 | "Question" = "Otázka"; 9 | 10 | "Request" = "Požiadavka"; 11 | 12 | "Bug Report" = "Hlásenie chyby"; 13 | 14 | "Other" = "Ostatné"; 15 | 16 | "CTFeedback.Device" = "Zariadenie"; 17 | 18 | "CTFeedback.iOS" = "iOS"; 19 | 20 | "CTFeedback.Name" = "Apka"; 21 | 22 | "CTFeedback.Version" = "Verzia"; 23 | 24 | "CTFeedback.Build" = "Build"; 25 | 26 | "CTFeedback.DeviceInfo" = "Informácie o zariadení"; 27 | 28 | "CTFeedback.AppInfo" = "Informácie o apke"; 29 | 30 | "CTFeedback.Feedback" = "Spätná väzba"; 31 | 32 | "CTFeedback.Topic" = "Téma"; 33 | 34 | "CTFeedback.Topics" = "Témy"; 35 | 36 | "CTFeedback.Error" = "Chyba"; 37 | 38 | "CTFeedback.Mail" = "Mail"; 39 | 40 | "CTFeedback.MailConfigurationErrorMessage" ="Nemáš platné e-mailové konto"; 41 | 42 | "CTFeedback.Dismiss" ="Zrušiť"; 43 | 44 | "CTFeedback.AdditionalInfo"="Dodatočné informácie"; 45 | 46 | "CTFeedback.AttachImageOrVideo" ="Vyber prílohu pre spätnú väzbu (obrázok)"; 47 | 48 | "CTFeedback.Cancel" ="Zrušiť"; 49 | 50 | "CTFeedback.Camera" ="Kamera"; 51 | 52 | "CTFeedback.PhotoLibrary" ="Vybrať z albumu"; 53 | "CTFeedback.UserDetail" = "Detail užívateľa"; 54 | "CTFeedback.FeedbackGenerationErrorMessage" = "Vyskytla sa chyba pri generovaní spätnej väzby"; 55 | "CTFeedback.UnknownError" = "Neznáma chyba"; 56 | "CTFeedback.Delete" = "Vymazať"; 57 | "CTFeedback.requiredCameraAccess" = "Apka vyžaduje prístup ku kamere pre pridanie obrázku"; 58 | "CTFeedback.requiredLibraryAccess" = "Apka vyžaduje prístup ku knižnici fotiek pre vybranie snímku obrazovky"; 59 | 60 | "CTFeedback.BodyPlaceholder" = "Sem zadaj text..."; 61 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/sr.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "Питање"; 8 | 9 | "Request" = "Захтјев"; 10 | 11 | "Bug Report" = "Грешка"; 12 | 13 | "Other" = "Друго"; 14 | 15 | "CTFeedback.Device" = "Уређај"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "Име"; 20 | 21 | "CTFeedback.Version" = "Верзија"; 22 | 23 | "CTFeedback.Build" = "Подверзија"; 24 | 25 | "CTFeedback.DeviceInfo" = "Информације о иређају"; 26 | 27 | "CTFeedback.AppInfo" = "Информације о програму"; 28 | 29 | "CTFeedback.Feedback" = "Повратна информација"; 30 | 31 | "CTFeedback.Topic" = "Тема"; 32 | 33 | "CTFeedback.Topics" = "Теме"; 34 | 35 | "CTFeedback.Error" = "Грешка"; 36 | 37 | "CTFeedback.Mail" = "Е-маил"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="Немате подешен е-маил рачун"; 40 | 41 | "CTFeedback.Dismiss" ="Затвори"; 42 | 43 | "CTFeedback.AdditionalInfo"="Додатне информације"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="Изаберите прилог (слику)"; 46 | 47 | "CTFeedback.Cancel" ="Откажи"; 48 | 49 | "CTFeedback.Camera" ="Камера"; 50 | 51 | "CTFeedback.PhotoLibrary" ="Изаберите из албума"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "Овде унесите текст ..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/sv-SE.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "Förfrågan"; 8 | 9 | "Request" = "Förslag"; 10 | 11 | "Bug Report" = "Bugg"; 12 | 13 | "Other" = "Annan"; 14 | 15 | "CTFeedback.Device" = "Enhet"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "Namn"; 20 | 21 | "CTFeedback.Version" = "Version"; 22 | 23 | "CTFeedback.Build" = "Build"; 24 | 25 | "CTFeedback.DeviceInfo" = "Enhetsinformation "; 26 | 27 | "CTFeedback.AppInfo" = "App Info"; 28 | 29 | "CTFeedback.Feedback" = "Feedback"; 30 | 31 | "CTFeedback.Topic" = "Ämne"; 32 | 33 | "CTFeedback.Topics" = "Ämnen"; 34 | 35 | "CTFeedback.Error" = "Felmeddelande"; 36 | 37 | "CTFeedback.Mail" = "Skicka"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="Det går inte att verifiera ett giltligt epost-konto"; 40 | 41 | "CTFeedback.Dismiss" ="Avfärda"; 42 | 43 | "CTFeedback.AdditionalInfo"="Ytterligare information"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="Välj bifogad fil(bild) "; 46 | 47 | "CTFeedback.Cancel" ="Ångra"; 48 | 49 | "CTFeedback.Camera" ="Kamera"; 50 | 51 | "CTFeedback.PhotoLibrary" ="Välj från album"; 52 | 53 | "CTFeedback.UserDetail"="Användardetaljer"; 54 | 55 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 56 | "CTFeedback.UnknownError" = "Unknown Error"; 57 | "CTFeedback.Delete" = "Delete"; 58 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 59 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 60 | 61 | "CTFeedback.BodyPlaceholder" = "Ange text här ..."; 62 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/tr.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | // Edited by Gürhan Yerlikaya (gurhanyerlikaya@gmail.com) 8 | 9 | "Question" = "Soru"; 10 | 11 | "Request" = "İstek"; 12 | 13 | "Bug Report" = "Hata bildirimi"; 14 | 15 | "Other" = "Diğer"; 16 | 17 | "CTFeedback.Device" = "Cihaz"; 18 | 19 | "CTFeedback.iOS" = "iOS"; 20 | 21 | "CTFeedback.Name" = "İsim"; 22 | 23 | "CTFeedback.Version" = "Sürüm"; 24 | 25 | "CTFeedback.Build" = "Build"; 26 | 27 | "CTFeedback.DeviceInfo" = "Cihaz Bilgisi"; 28 | 29 | "CTFeedback.AppInfo" = "Uygulama bilgisi"; 30 | 31 | "CTFeedback.Feedback" = "Geri Bildirim"; 32 | 33 | "CTFeedback.Topic" = "Konu"; 34 | 35 | "CTFeedback.Topics" = "Konular"; 36 | 37 | "CTFeedback.Error" = "Hata"; 38 | 39 | "CTFeedback.Mail" = "Gönder"; 40 | 41 | "CTFeedback.MailConfigurationErrorMessage" ="Cihazda tanımlı e-posta bulunamadı."; 42 | 43 | "CTFeedback.Dismiss" ="Son ver"; 44 | 45 | "CTFeedback.AdditionalInfo"="Ek bilgi"; 46 | 47 | "CTFeedback.AttachImageOrVideo" ="Detay"; 48 | 49 | "CTFeedback.Cancel" ="İptal"; 50 | 51 | "CTFeedback.Camera" ="Kamera"; 52 | 53 | "CTFeedback.PhotoLibrary" ="Cihaz’dan ekle"; 54 | 55 | "CTFeedback.UserDetail"="Kullanıcı detayı"; 56 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 57 | "CTFeedback.UnknownError" = "Unknown Error"; 58 | "CTFeedback.Delete" = "Delete"; 59 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 60 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 61 | 62 | "CTFeedback.BodyPlaceholder" = "Yazıyı buraya girin..."; 63 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/ur.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "سوال"; 8 | 9 | "Request" = "درخواست"; 10 | 11 | "Bug Report" = "بَگ رپورٹ"; 12 | 13 | "Other" = "دیگر"; 14 | 15 | "CTFeedback.Device" = "آلہ"; 16 | 17 | "CTFeedback.iOS" = "آئی او ایس"; 18 | 19 | "CTFeedback.Name" = "نام"; 20 | 21 | "CTFeedback.Version" = "ورژن"; 22 | 23 | "CTFeedback.Build" = "ساخت"; 24 | 25 | "CTFeedback.DeviceInfo" = "معلوماتِ آلہ"; 26 | 27 | "CTFeedback.AppInfo" = "معلوماتِ اطلاق"; 28 | 29 | "CTFeedback.Feedback" = "رائے"; 30 | 31 | "CTFeedback.Topic" = "موضوع"; 32 | 33 | "CTFeedback.Topics" = "موضوعات"; 34 | 35 | "CTFeedback.Error" = "غلطی"; 36 | 37 | "CTFeedback.Mail" = "میل"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="آپ کے پاس جائز ای میل کھاتہ نہیں ہے"; 40 | 41 | "CTFeedback.Dismiss" ="منسوخ"; 42 | 43 | "CTFeedback.AdditionalInfo"="اضافی معلومات"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="رائے کا منسلکہ(تصویر) منتخب کیجیے"; 46 | 47 | "CTFeedback.Cancel" ="منسوخ"; 48 | 49 | "CTFeedback.Camera" ="کیمرہ"; 50 | 51 | "CTFeedback.PhotoLibrary" ="البم سے منتخب کیجیے"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "یہاں متن درج کریں"; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/zh-Hans.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "问题"; 8 | 9 | "Request" = "需求"; 10 | 11 | "Bug Report" = "程序错误报告"; 12 | 13 | "Other" = "其他"; 14 | 15 | "CTFeedback.Device" = "设备"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "名字"; 20 | 21 | "CTFeedback.Version" = "版本"; 22 | 23 | "CTFeedback.Build" = "Build"; 24 | 25 | "CTFeedback.DeviceInfo" = "设备信息"; 26 | 27 | "CTFeedback.AppInfo" = "应用信息"; 28 | 29 | "CTFeedback.Feedback" = "反馈"; 30 | 31 | "CTFeedback.Topic" = "主题"; 32 | 33 | "CTFeedback.Topics" = "主题"; 34 | 35 | "CTFeedback.Error" = "错误"; 36 | 37 | "CTFeedback.Mail" = "邮件"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="您没有一个有效的邮件账户"; 40 | 41 | "CTFeedback.Dismiss" ="Dismiss"; 42 | 43 | "CTFeedback.AdditionalInfo"="附加信息"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="选择反馈附件(图片)"; 46 | 47 | "CTFeedback.Cancel" ="取消"; 48 | 49 | "CTFeedback.Camera" ="拍照"; 50 | 51 | "CTFeedback.PhotoLibrary" ="从相册中选取"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "在此输入文字..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/Resources/zh-Hant.lproj/CTFeedbackLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* Localizable.strings 2 | CTFeedbackDemo 3 | 4 | Created by Ryoichi Izumita on 2013/10/31. 5 | Copyright (c) 2013 CAPH. All rights reserved. */ 6 | 7 | "Question" = "問題"; 8 | 9 | "Request" = "需求"; 10 | 11 | "Bug Report" = "程式錯誤回報"; 12 | 13 | "Other" = "其他"; 14 | 15 | "CTFeedback.Device" = "設備"; 16 | 17 | "CTFeedback.iOS" = "iOS"; 18 | 19 | "CTFeedback.Name" = "名字"; 20 | 21 | "CTFeedback.Version" = "版本"; 22 | 23 | "CTFeedback.Build" = "Build"; 24 | 25 | "CTFeedback.DeviceInfo" = "設備資訊"; 26 | 27 | "CTFeedback.AppInfo" = "軟體資訊"; 28 | 29 | "CTFeedback.Feedback" = "反饋"; 30 | 31 | "CTFeedback.Topic" = "主題"; 32 | 33 | "CTFeedback.Topics" = "主題"; 34 | 35 | "CTFeedback.Error" = "錯誤"; 36 | 37 | "CTFeedback.Mail" = "郵件"; 38 | 39 | "CTFeedback.MailConfigurationErrorMessage" ="您沒有一個有效的郵件賬戶"; 40 | 41 | "CTFeedback.Dismiss" ="Dismiss"; 42 | 43 | "CTFeedback.AdditionalInfo"="附加信息"; 44 | 45 | "CTFeedback.AttachImageOrVideo" ="選擇反饋附件(圖片)"; 46 | 47 | "CTFeedback.Cancel" ="取消"; 48 | 49 | "CTFeedback.Camera" ="拍照"; 50 | 51 | "CTFeedback.PhotoLibrary" ="從相冊中選取"; 52 | "CTFeedback.UserDetail" = "User Detail"; 53 | "CTFeedback.FeedbackGenerationErrorMessage" = "Feedback Generation Error"; 54 | "CTFeedback.UnknownError" = "Unknown Error"; 55 | "CTFeedback.Delete" = "Delete"; 56 | "CTFeedback.requiredCameraAccess" = "Camera access is absolutely necessary to use this app"; 57 | "CTFeedback.requiredLibraryAccess" = "Library access is absolutely necessary to use this app"; 58 | 59 | "CTFeedback.BodyPlaceholder" = "在此輸入文字..."; 60 | -------------------------------------------------------------------------------- /CTFeedbackSwift/TopicsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopicsViewController.swift 3 | // CTFeedbackSwift 4 | // 5 | // Created by 和泉田 領一 on 2017/09/08. 6 | // Copyright © 2017 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TopicsViewController: UITableViewController { 12 | let feedbackEditingService: FeedbackEditingServiceProtocol 13 | let topics: [TopicProtocol] 14 | 15 | init(service: FeedbackEditingServiceProtocol) { 16 | self.feedbackEditingService = service 17 | self.topics = self.feedbackEditingService.topics 18 | super.init(style: .plain) 19 | } 20 | 21 | required init?(coder aDecoder: NSCoder) { fatalError() } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | title = CTLocalizedString("CTFeedback.Topics") 27 | } 28 | 29 | override func didReceiveMemoryWarning() { 30 | super.didReceiveMemoryWarning() 31 | // Dispose of any resources that can be recreated. 32 | } 33 | } 34 | 35 | extension TopicsViewController { 36 | // MARK: - Table view data source 37 | 38 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 39 | // #warning Incomplete implementation, return the number of rows 40 | return topics.count 41 | } 42 | 43 | override func tableView(_ tableView: UITableView, 44 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 45 | let identifier = "Cell" 46 | let cell = tableView.dequeueReusableCell(withIdentifier: identifier) 47 | ?? UITableViewCell(style: .default, reuseIdentifier: identifier) 48 | let topic = topics[indexPath.row] 49 | cell.textLabel?.text = topic.localizedTitle 50 | return cell 51 | } 52 | } 53 | 54 | extension TopicsViewController { 55 | // MARK: - Table view delegate 56 | 57 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 58 | let topic = topics[indexPath.row] 59 | feedbackEditingService.update(selectedTopic: topic) 60 | dismiss(animated: true) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Bundle+ExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class Bundle_ExtensionsTests:XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testPlatformNamesPlistPath() { 16 | XCTAssertNotNil(Bundle.platformNamesPlistPath) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/CellFactoryTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class CellFactoryTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testReuseIdentifier() { 16 | XCTAssertEqual(TestCellFactory.reuseIdentifier, "TestCellFactory") 17 | } 18 | } 19 | 20 | class AnyCellFactoryTests: XCTestCase { 21 | func testSuitable() { 22 | let concreteFactory = TestCellFactory.self 23 | let factory = AnyCellFactory(concreteFactory) 24 | XCTAssertTrue(factory.suitable(for: "")) 25 | } 26 | 27 | func testConfigure() { 28 | let concreteFactory = TestCellFactory.self 29 | let factory = AnyCellFactory(concreteFactory) 30 | let cell = UITableViewCell() 31 | let indexPath = IndexPath(row: 0, section: 0) 32 | _ = factory.configure(cell, 33 | with: "test", 34 | for: indexPath, 35 | eventHandler: "Handler") 36 | XCTAssertTrue(concreteFactory.cell === cell) 37 | XCTAssertEqual(concreteFactory.item, "test") 38 | XCTAssertEqual(concreteFactory.indexPath, indexPath) 39 | XCTAssertEqual(concreteFactory.eventHandler, "Handler") 40 | } 41 | } 42 | 43 | class UITableView_ExtensionsTests: XCTestCase { 44 | func testDequeueCell() { 45 | let concreteFactory = TestCellFactory.self 46 | let factory = AnyCellFactory(concreteFactory) 47 | let tableView = UITableView() 48 | tableView.register(with: factory) 49 | let indexPath = IndexPath(row: 0, section: 0) 50 | let cell: UITableViewCell = tableView.dequeueCell(to: "Item", 51 | from: [factory], 52 | for: indexPath, 53 | eventHandler: "EventHandler") 54 | XCTAssertTrue(concreteFactory.cell === cell) 55 | XCTAssertEqual(concreteFactory.item, "Item") 56 | XCTAssertEqual(concreteFactory.indexPath, indexPath) 57 | XCTAssertEqual(concreteFactory.eventHandler, "EventHandler") 58 | } 59 | } 60 | 61 | class TestCellFactory: CellFactoryProtocol { 62 | static var cell: UITableViewCell? 63 | static var item: String? 64 | static var indexPath: IndexPath? 65 | static var eventHandler: String? 66 | 67 | static func configure(_ cell: UITableViewCell, 68 | with item: String, 69 | for indexPath: IndexPath, 70 | eventHandler: String?) { 71 | self.cell = cell 72 | self.item = item 73 | self.indexPath = indexPath 74 | self.eventHandler = eventHandler 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/FeedbackEditing/FeedbackEditingServiceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeedbackEditingServiceTests.swift 3 | // CTFeedbackSwift 4 | // 5 | // Created by 和泉田 領一 on 2017/09/17. 6 | // Copyright © 2017 CAPH TECH. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import CTFeedbackSwift 11 | 12 | class FeedbackEditingServiceTests: XCTestCase { 13 | var itemsRepository: MockFeedbackEditingItemsRepository! 14 | var eventHandler: MockFeedbackEditingEventHandler! 15 | var service: FeedbackEditingService! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | ready() 21 | } 22 | 23 | override func tearDown() { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | super.tearDown() 26 | } 27 | 28 | private func ready() { 29 | itemsRepository = MockFeedbackEditingItemsRepository() 30 | eventHandler = MockFeedbackEditingEventHandler() 31 | service = FeedbackEditingService(editingItemsRepository: itemsRepository, 32 | feedbackEditingEventHandler: eventHandler) 33 | } 34 | 35 | func testTopics() { 36 | itemsRepository.set(item: TopicItem(TopicItem.defaultTopics)) 37 | let topics = service.topics 38 | XCTAssertEqual(topics.count, 4) 39 | } 40 | 41 | func testHasAttachedMediaWithNoMedia() { 42 | let item = AttachmentItem(isHidden: false) 43 | itemsRepository.set(item: item) 44 | XCTAssertFalse(service.hasAttachedMedia) 45 | } 46 | 47 | func testHasAttachedMediaWithImage() { 48 | var item = AttachmentItem(isHidden: false) 49 | item.media = .image(UIImage()) 50 | itemsRepository.set(item: item) 51 | XCTAssertTrue(service.hasAttachedMedia) 52 | } 53 | 54 | func testUpdateUserEmailText() { 55 | itemsRepository.set(item: UserEmailItem(isHidden: false)) 56 | XCTAssertNil(itemsRepository.item(of: UserEmailItem.self)?.email) 57 | service.update(userEmailText: "test") 58 | XCTAssertEqual(itemsRepository.item(of: UserEmailItem.self)?.email, "test") 59 | } 60 | 61 | func testUpdateBodyText() { 62 | itemsRepository.set(item: BodyItem(bodyText: .none)) 63 | XCTAssertNil(itemsRepository.item(of: BodyItem.self)?.bodyText) 64 | service.update(bodyText: "test") 65 | XCTAssertEqual(itemsRepository.item(of: BodyItem.self)?.bodyText, "test") 66 | } 67 | 68 | func testUpdateSelectedTopic() { 69 | itemsRepository.set(item: TopicItem(TopicItem.defaultTopics)) 70 | XCTAssertNil(eventHandler.invokedUpdatedParameters) 71 | service.update(selectedTopic: TopicItem.defaultTopics[1]) 72 | XCTAssertEqual(eventHandler.invokedUpdatedParameters?.indexPath, 73 | IndexPath(row: 0, section: 0)) 74 | } 75 | 76 | func testUpdateAttachmentMedia() { 77 | itemsRepository.set(item: AttachmentItem(isHidden: false)) 78 | service.update(attachmentMedia: .image(UIImage())) 79 | XCTAssertEqual(eventHandler.invokedUpdatedParameters?.indexPath, 80 | IndexPath(row: 0, section: 0)) 81 | } 82 | 83 | func testGenerateFeedback() { 84 | let dataSource = FeedbackItemsDataSource(topics: TopicItem.defaultTopics) 85 | service = FeedbackEditingService(editingItemsRepository: dataSource, 86 | feedbackEditingEventHandler: eventHandler) 87 | do { 88 | let configuration = FeedbackConfiguration(subject: "String", 89 | additionalDiagnosticContent: "additional", 90 | topics: TopicItem.defaultTopics, 91 | toRecipients: ["test@example.com"], 92 | ccRecipients: ["cc@example.com"], 93 | bccRecipients: ["bcc@example.com"]) 94 | let feedback = try service.generateFeedback(configuration: configuration) 95 | XCTAssertEqual(feedback.subject, "String") 96 | XCTAssertEqual(feedback.to, ["test@example.com"]) 97 | XCTAssertEqual(feedback.cc, ["cc@example.com"]) 98 | XCTAssertEqual(feedback.bcc, ["bcc@example.com"]) 99 | XCTAssertFalse(feedback.isHTML) 100 | } catch { 101 | XCTFail() 102 | } 103 | } 104 | } 105 | 106 | class MockFeedbackEditingItemsRepository: FeedbackEditingItemsRepositoryProtocol { 107 | var stubbedItems: [Any] = [] 108 | 109 | func item(of type: Item.Type) -> Item? { 110 | return stubbedItems.filter { item in item is Item }.first as? Item 111 | } 112 | 113 | @discardableResult 114 | func set(item: Item) -> IndexPath? { 115 | if let index = stubbedItems.index(where: { stored in stored is Item }) { 116 | stubbedItems.remove(at: index) 117 | } 118 | stubbedItems.append(item) 119 | return IndexPath(row: 0, section: 0) 120 | } 121 | } 122 | 123 | class MockFeedbackEditingEventHandler: FeedbackEditingEventProtocol { 124 | var invokedUpdated = false 125 | var invokedUpdatedCount = 0 126 | var invokedUpdatedParameters: (indexPath: IndexPath, Void)? 127 | var invokedUpdatedParametersList = [(indexPath: IndexPath, Void)]() 128 | 129 | func updated(at indexPath: IndexPath) { 130 | invokedUpdated = true 131 | invokedUpdatedCount += 1 132 | invokedUpdatedParameters = (indexPath, ()) 133 | invokedUpdatedParametersList.append((indexPath, ())) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/FeedbackEditing/FeedbackGeneratorTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class FeedbackGeneratorTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testGenerateNoHTML() { 16 | let configuration = FeedbackConfiguration(subject: "Subject", 17 | additionalDiagnosticContent: "Additional", 18 | topics: TopicItem.defaultTopics, 19 | toRecipients: ["to@example.com"], 20 | ccRecipients: ["cc@example.com"], 21 | bccRecipients: ["bcc@example.com"], 22 | usesHTML: false) 23 | do { 24 | let feedback = try FeedbackGenerator.generate(configuration: configuration, 25 | repository: configuration.dataSource) 26 | XCTAssertEqual(feedback.subject, "Subject") 27 | XCTAssertTrue(feedback.body.contains("Additional")) 28 | XCTAssertFalse(feedback.isHTML) 29 | XCTAssertEqual(feedback.to, ["to@example.com"]) 30 | XCTAssertEqual(feedback.cc, ["cc@example.com"]) 31 | XCTAssertEqual(feedback.bcc, ["bcc@example.com"]) 32 | } catch { 33 | XCTFail() 34 | } 35 | } 36 | 37 | func testGenerateNoHTMLWithHidesAppInfoSection() { 38 | let configuration = FeedbackConfiguration(subject: "Subject", 39 | additionalDiagnosticContent: "Additional", 40 | topics: TopicItem.defaultTopics, 41 | toRecipients: ["to@example.com"], 42 | ccRecipients: ["cc@example.com"], 43 | bccRecipients: ["bcc@example.com"], 44 | hidesAppInfoSection: true, 45 | usesHTML: false) 46 | do { 47 | let feedback = try FeedbackGenerator.generate(configuration: configuration, 48 | repository: configuration.dataSource) 49 | XCTAssertEqual(feedback.subject, "Subject") 50 | XCTAssertTrue(feedback.body.contains("Additional")) 51 | XCTAssertFalse(feedback.isHTML) 52 | XCTAssertEqual(feedback.to, ["to@example.com"]) 53 | XCTAssertEqual(feedback.cc, ["cc@example.com"]) 54 | XCTAssertEqual(feedback.bcc, ["bcc@example.com"]) 55 | } catch { 56 | XCTFail() 57 | } 58 | } 59 | 60 | func testGenerateHTML() { 61 | let configuration = FeedbackConfiguration(subject: "Subject", 62 | additionalDiagnosticContent: "Additional", 63 | topics: TopicItem.defaultTopics, 64 | toRecipients: ["to@example.com"], 65 | ccRecipients: ["cc@example.com"], 66 | bccRecipients: ["bcc@example.com"], 67 | usesHTML: false) 68 | do { 69 | let feedback = try FeedbackGenerator.generate(configuration: configuration, 70 | repository: configuration.dataSource) 71 | XCTAssertEqual(feedback.subject, "Subject") 72 | XCTAssertTrue(feedback.body.contains("Additional")) 73 | XCTAssertFalse(feedback.isHTML) 74 | XCTAssertEqual(feedback.to, ["to@example.com"]) 75 | XCTAssertEqual(feedback.cc, ["cc@example.com"]) 76 | XCTAssertEqual(feedback.bcc, ["bcc@example.com"]) 77 | } catch { 78 | XCTFail() 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/FeedbackItemsDataSourceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class FeedbackItemsDataSourceTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testNumberOfSections() { 16 | let dataSource = FeedbackItemsDataSource(topics: TopicItem.defaultTopics, 17 | hidesUserEmailCell: true, 18 | hidesAttachmentCell: false, 19 | hidesAppInfoSection: true) 20 | XCTAssertEqual(dataSource.numberOfSections, 3) 21 | } 22 | 23 | func testSection() { 24 | let dataSource = FeedbackItemsDataSource(topics: TopicItem.defaultTopics, 25 | hidesUserEmailCell: false, 26 | hidesAttachmentCell: false, 27 | hidesAppInfoSection: true) 28 | XCTAssertEqual(dataSource.section(at: 0).title, "User Detail") 29 | } 30 | 31 | func testItem() { 32 | let dataSource = FeedbackItemsDataSource(topics: TopicItem.defaultTopics, 33 | hidesUserEmailCell: true, 34 | hidesAttachmentCell: false, 35 | hidesAppInfoSection: true) 36 | let item: TopicItem? = dataSource.item(of: TopicItem.self) 37 | XCTAssertNotNil(item) 38 | } 39 | 40 | func testSetItem() { 41 | let dataSource = FeedbackItemsDataSource(topics: TopicItem.defaultTopics, 42 | hidesUserEmailCell: true, 43 | hidesAttachmentCell: false, 44 | hidesAppInfoSection: true) 45 | guard var item = dataSource.item(of: BodyItem.self) else { 46 | XCTFail() 47 | return 48 | } 49 | 50 | item.bodyText = "body" 51 | let indexPath: IndexPath? = dataSource.set(item: item) 52 | XCTAssertNotNil(indexPath) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/FunctionsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import MobileCoreServices 3 | @testable import CTFeedbackSwift 4 | 5 | class FunctionsTests: XCTestCase { 6 | override func setUp() { 7 | super.setUp() 8 | // Put setup code here. This method is called before the invocation of each test method in the class. 9 | } 10 | 11 | override func tearDown() { 12 | // Put teardown code here. This method is called after the invocation of each test method in the class. 13 | super.tearDown() 14 | } 15 | 16 | // func testGetURLFromImagePickerInfo() { 17 | // let url = URL(string: "https://example.com") 18 | // let validImageInfo: [String : Any] = [UIImagePickerControllerMediaType : kUTTypeImage, 19 | // UIImagePickerControllerImageURL : url] 20 | // XCTAssertEqual(getURLFromImagePickerInfo(validInfo), url) 21 | // 22 | // let validMovieInfo: [String : Any] = [UIImagePickerControllerMediaType : kUTTypeMovie, 23 | // UIImagePickerControllerMediaURL : url] 24 | // XCTAssertEqual(getURLFromImagePickerInfo(validMovieInfo), url) 25 | // 26 | // let invalidImageInfo: [String : Any] = [UIImagePickerControllerMediaType : kUTTypeImage, 27 | // UIImagePickerControllerMediaURL : url] 28 | // XCTAssertNil(getURLFromImagePickerInfo(invalidImageInfo)) 29 | // 30 | // let invalidMovieInfo: [String : Any] = [UIImagePickerControllerMediaType : kUTTypeMovie, 31 | // UIImagePickerControllerImageURL : url] 32 | // XCTAssertNil(getURLFromImagePickerInfo(invalidMovieInfo)) 33 | // 34 | // let invalidInfo: [String : Any] = ["invalid" : url] 35 | // XCTAssertNil(getURLFromImagePickerInfo(invalidInfo)) 36 | // } 37 | } 38 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/AppBuild/AppBuildCellTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class AppBuildCellTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testConfigure() { 16 | let cell = AppBuildCell(style: .value1, reuseIdentifier: AppBuildCell.reuseIdentifier) 17 | let item = AppBuildItem(isHidden: false) 18 | AppBuildCell.configure(cell, 19 | with: item, 20 | for: IndexPath(row: 0, section: 0), 21 | eventHandler: .none) 22 | XCTAssertEqual(cell.textLabel?.text, "Build") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/AppName/AppNameCellTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class AppNameCellTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testConfigure() { 16 | let cell = AppNameCell(style: .value1, reuseIdentifier: AppNameCell.reuseIdentifier) 17 | let item = AppNameItem(isHidden: false) 18 | AppNameCell.configure(cell, 19 | with: item, 20 | for: IndexPath(row: 0, section: 0), 21 | eventHandler: .none) 22 | XCTAssertEqual(cell.textLabel?.text, "Name") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/AppVersion/AppVersionCellTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class AppVersionCellTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testConfigure() { 16 | let cell = AppVersionCell(style: .value1, reuseIdentifier: AppVersionCell.reuseIdentifier) 17 | let item = AppVersionItem(isHidden: false) 18 | AppVersionCell.configure(cell, 19 | with: item, 20 | for: IndexPath(row: 0, section: 0), 21 | eventHandler: .none) 22 | XCTAssertEqual(cell.textLabel?.text, "Version") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/Attachment/AttachmentCellTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class AttachmentCellTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testConfigure() { 16 | let cell = AttachmentCell(style: .default, 17 | reuseIdentifier: AttachmentCell.reuseIdentifier) 18 | var item = AttachmentItem(isHidden: false) 19 | let image = UIImage(color: .red)! 20 | item.media = .image(image) 21 | let handler = MockAttachmentCellEventHandler() 22 | AttachmentCell.configure(cell, 23 | with: item, 24 | for: IndexPath(row: 0, section: 0), 25 | eventHandler: handler) 26 | XCTAssertTrue(cell.imageView?.image === image) 27 | } 28 | 29 | func testTapImageView() { 30 | let cell = AttachmentCell(style: .default, 31 | reuseIdentifier: AttachmentCell.reuseIdentifier) 32 | var item = AttachmentItem(isHidden: false) 33 | let image = UIImage(color: .red)! 34 | item.media = .image(image) 35 | let handler = MockAttachmentCellEventHandler() 36 | AttachmentCell.configure(cell, 37 | with: item, 38 | for: IndexPath(row: 0, section: 0), 39 | eventHandler: handler) 40 | cell.imageViewTapped(UITapGestureRecognizer()) 41 | XCTAssertTrue(handler.invokedShowImage) 42 | } 43 | } 44 | 45 | class MockAttachmentCellEventHandler: AttachmentCellEventProtocol { 46 | var invokedShowImage = false 47 | var invokedShowImageCount = 0 48 | var invokedShowImageParameters: (item: AttachmentItem, Void)? 49 | var invokedShowImageParametersList = [(item: AttachmentItem, Void)]() 50 | 51 | func showImage(of item: AttachmentItem) { 52 | invokedShowImage = true 53 | invokedShowImageCount += 1 54 | invokedShowImageParameters = (item, ()) 55 | invokedShowImageParametersList.append((item, ())) 56 | } 57 | } 58 | 59 | extension UIImage { 60 | convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) { 61 | let rect = CGRect(origin: .zero, size: size) 62 | UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) 63 | color.setFill() 64 | UIRectFill(rect) 65 | let image = UIGraphicsGetImageFromCurrentImageContext() 66 | UIGraphicsEndImageContext() 67 | 68 | guard let cgImage = image?.cgImage else { return nil } 69 | self.init(cgImage: cgImage) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/Attachment/AttachmentItemTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class AttachmentItemTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testAttached() { 16 | var item = AttachmentItem(isHidden: false) 17 | XCTAssertFalse(item.attached) 18 | 19 | item.media = .image(UIImage(color: .red)!) 20 | XCTAssertTrue(item.attached) 21 | 22 | item.media = .video(UIImage(color: .red)!, URL(string: "https://example.com")!) 23 | XCTAssertTrue(item.attached) 24 | } 25 | 26 | func testImage() { 27 | var item = AttachmentItem(isHidden: false) 28 | XCTAssertNil(item.image) 29 | 30 | let image = UIImage(color: .red)! 31 | item.media = .image(image) 32 | XCTAssertNotNil(item.image) 33 | XCTAssertTrue(item.image === image) 34 | 35 | item.media = .video(image, URL(string: "https://example.com")!) 36 | XCTAssertNotNil(item.image) 37 | XCTAssertTrue(item.image === image) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/Body/BodyCellTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class BodyCellTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testEventHandling() { 16 | let cell = BodyCell(style: .default, reuseIdentifier: BodyCell.reuseIdentifier) 17 | let item = BodyItem(bodyText: "test") 18 | let handler = MockBodyCellEventHandler() 19 | let indexPath = IndexPath(row: 0, section: 0) 20 | BodyCell.configure(cell, with: item, for: indexPath, eventHandler: handler) 21 | cell.textViewDidChange(cell.textView) 22 | XCTAssertTrue(handler.invokedBodyCellHeightChanged) 23 | XCTAssertEqual(handler.invokedBodyTextDidChangeParameters?.text, "test") 24 | } 25 | } 26 | 27 | class MockBodyCellEventHandler: BodyCellEventProtocol { 28 | var invokedBodyCellHeightChanged = false 29 | var invokedBodyCellHeightChangedCount = 0 30 | 31 | func bodyCellHeightChanged() { 32 | invokedBodyCellHeightChanged = true 33 | invokedBodyCellHeightChangedCount += 1 34 | } 35 | 36 | var invokedBodyTextDidChange = false 37 | var invokedBodyTextDidChangeCount = 0 38 | var invokedBodyTextDidChangeParameters: (text: String?, Void)? 39 | var invokedBodyTextDidChangeParametersList = [(text: String?, Void)]() 40 | 41 | func bodyTextDidChange(_ text: String?) { 42 | invokedBodyTextDidChange = true 43 | invokedBodyTextDidChangeCount += 1 44 | invokedBodyTextDidChangeParameters = (text, ()) 45 | invokedBodyTextDidChangeParametersList.append((text, ())) 46 | } 47 | } -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/DeviceName/DeviceNameCellTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class DeviceNameCellTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testConfigure() { 16 | let cell = DeviceNameCell(style: .default, 17 | reuseIdentifier: DeviceNameCell.reuseIdentifier) 18 | let item = DeviceNameItem() 19 | let indexPath = IndexPath(row: 0, section: 0) 20 | DeviceNameCell.configure(cell, with: item, for: indexPath, eventHandler: .none) 21 | XCTAssertEqual(cell.textLabel?.text, "Device") 22 | 23 | switch cell.detailTextLabel?.text { 24 | case "Simulator", "arm64", "x86_64": 25 | break 26 | default: 27 | XCTFail(cell.detailTextLabel?.text ?? "") 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/DeviceName/DeviceNameItemTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class DeviceNameItemTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testDeviceName() { 16 | let item = DeviceNameItem() 17 | switch item.deviceName { 18 | case "Simulator", "arm64", "x86_64": 19 | break 20 | default: 21 | XCTFail(item.deviceName) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/SystemVersion/SystemVersionCellTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class SystemVersionCellTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testConfigure() { 16 | let cell = SystemVersionCell(style: .default, 17 | reuseIdentifier: SystemVersionCell.reuseIdentifier) 18 | let item = SystemVersionItem() 19 | let indexPath = IndexPath(row: 0, section: 0) 20 | SystemVersionCell.configure(cell, with: item, for: indexPath, eventHandler: .none) 21 | XCTAssertEqual(cell.textLabel?.text, "iOS") 22 | XCTAssertEqual(cell.detailTextLabel?.text, item.version) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/SystemVersion/SystemVersionItemTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class SystemVersionItemTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testVersion() { 16 | let item: SystemVersionItem = SystemVersionItem() 17 | XCTAssertEqual(item.version, UIDevice.current.systemVersion) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/Topic/TopicCellTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class TopicCellTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testConfigure() { 16 | let cell = TopicCell(style: .default, reuseIdentifier: TopicCell.reuseIdentifier) 17 | let item = TopicItem(TopicItem.defaultTopics) 18 | let indexPath = IndexPath(row: 0, section: 0) 19 | TopicCell.configure(cell, with: item, for: indexPath, eventHandler: .none) 20 | XCTAssertEqual(cell.textLabel?.text, "Topic") 21 | XCTAssertEqual(cell.detailTextLabel?.text, item.selected?.localizedTitle) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/Topic/TopicItemTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class TopicItemTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testSelected() { 16 | var item = TopicItem([]) 17 | let topics = TopicItem.defaultTopics 18 | 19 | XCTAssertNil(item.selected) 20 | 21 | item.topics = topics 22 | XCTAssertEqual(item.selected?.title, topics.first?.title) 23 | 24 | item.selected = topics[1] 25 | XCTAssertEqual(item.selected?.title, topics[1].title) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CTFeedbackSwiftTests/Items/UserEmail/UserEmailCellTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CTFeedbackSwift 3 | 4 | class UserEmailCellTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testHandleTextDidChange() { 16 | let cell = UserEmailCell(style: .default, 17 | reuseIdentifier: UserEmailCell.reuseIdentifier) 18 | let item = UserEmailItem(isHidden: false) 19 | let indexPath = IndexPath(row: 0, section: 0) 20 | let handler = MockUserEmailCellEventHandler() 21 | UserEmailCell.configure(cell, with: item, for: indexPath, eventHandler: handler) 22 | cell.textField.text = "test" 23 | _ = cell.textField(cell.textField, 24 | shouldChangeCharactersIn: NSRange(location: 0, length: 4), 25 | replacementString: "") 26 | XCTAssertEqual(handler.invokedUserEmailTextDidChangeParameters?.text, "test") 27 | } 28 | } 29 | 30 | class MockUserEmailCellEventHandler: UserEmailCellEventProtocol { 31 | var invokedUserEmailTextDidChange = false 32 | var invokedUserEmailTextDidChangeCount = 0 33 | var invokedUserEmailTextDidChangeParameters: (text: String?, Void)? 34 | var invokedUserEmailTextDidChangeParametersList = [(text: String?, Void)]() 35 | 36 | func userEmailTextDidChange(_ text: String?) { 37 | invokedUserEmailTextDidChange = true 38 | invokedUserEmailTextDidChangeCount += 1 39 | invokedUserEmailTextDidChangeParameters = (text, ()) 40 | invokedUserEmailTextDidChangeParametersList.append((text, ())) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ryoichi Izumita 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "CTFeedbackSwift", 7 | defaultLocalization: "en", 8 | platforms: [ 9 | // Some platform where run yours library 10 | .iOS(.v10), .tvOS(.v10), .watchOS(.v4) 11 | ], 12 | products: [ 13 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 14 | .library(name: "CTFeedbackSwift", targets: ["CTFeedbackSwift"]) 15 | ], 16 | targets: [ 17 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 18 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 19 | .target(name: "CTFeedbackSwift", path: "CTFeedbackSwift") 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTFeedbackSwift 2 | CTFeedbackSwift is a framework to compose a feedback for iOS 9.0+ 3 | 4 | [CTFeedback](https://github.com/rizumita/CTFeedback) is written in Objective-C. CTFeedbackSwift is rebooted with Swift. 5 | 6 | ![Screenshot](https://github.com/rizumita/CTFeedbackSwift/raw/master/CTFeedbackSwift.png) 7 | 8 | ## Install 9 | 10 | CTFeedbackSwift is now support Carthage. 11 | 12 | Cartfile 13 | 14 | ``` 15 | github "rizumita/CTFeedbackSwift" 16 | ``` 17 | 18 | And do along Carthage documents. 19 | 20 | ## How to use 21 | 22 | ```Swift 23 | let configuration = FeedbackConfiguration(toRecipients: ["test@example.com"], usesHTML: true) 24 | let controller = FeedbackViewController(configuration: configuration) 25 | navigationController?.pushViewController(controller, animated: true) 26 | ``` 27 | 28 | ## License 29 | 30 | MIT License 31 | --------------------------------------------------------------------------------