├── .gitignore
├── 12306ForMac.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ ├── dream.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
│ │ └── lindahai.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
└── xcuserdata
│ ├── dream.xcuserdatad
│ └── xcschemes
│ │ ├── 12306ForMac.xcscheme
│ │ └── xcschememanagement.plist
│ └── lindahai.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ ├── 12306ForMac.xcscheme
│ └── xcschememanagement.plist
├── 12306ForMac
├── 12306ForMac-Bridging-Header.h
├── 12306ForMac.entitlements
├── AppDelegate.swift
├── BaseWindowController.swift
├── Images.xcassets
│ ├── Add.imageset
│ │ ├── Add.png
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── icon_128x128.png
│ │ ├── icon_128x128@2x.png
│ │ ├── icon_16x16.png
│ │ ├── icon_16x16@2x.png
│ │ ├── icon_256x256.png
│ │ ├── icon_256x256@2x.png
│ │ ├── icon_32x32.png
│ │ ├── icon_32x32@2x.png
│ │ ├── icon_512x512.png
│ │ └── icon_512x512@2x.png
│ ├── Contents.json
│ ├── DefineFilter.imageset
│ │ ├── Contents.json
│ │ └── DefineFilter.png
│ ├── Direction.imageset
│ │ ├── Contents.json
│ │ └── Direction.png
│ ├── Filter.imageset
│ │ ├── Contents.json
│ │ └── Filter.png
│ ├── Reminder.imageset
│ │ ├── Contents.json
│ │ └── Reminder.png
│ ├── Reverse.imageset
│ │ ├── Contents.json
│ │ └── Reverse.png
│ └── User.imageset
│ │ ├── Contents.json
│ │ └── User.png
├── Info.plist
├── MainWindowController.swift
├── MainWindowController.xib
├── Model
│ ├── AutoSubmitParams.swift
│ ├── CaptchaImageParam.swift
│ ├── ConfirmSingleForQueueAsysParam.swift
│ ├── ConfirmSingleForQueueParam.swift
│ ├── GetQueueCountAsysParam.swift
│ ├── GetQueueCountParam.swift
│ ├── LeftTicketParam.swift
│ ├── MainModel.swift
│ ├── OrderDTO.swift
│ ├── PassengerDTO.swift
│ ├── PaycheckNewParam.swift
│ ├── QueryByTrainCodeParam.swift
│ ├── QueryLeftNewDTO.swift
│ ├── QueryOrderParam.swift
│ ├── QueryOrderWaitTimeResult.swift
│ ├── QueryTrainPriceParam.swift
│ ├── SubmitOrderParams.swift
│ ├── TicketConstants.swift
│ ├── TicketQueueCountResult.swift
│ └── TrainCodeDetail.swift
├── Notifications.swift
├── OrderViewControllers
│ ├── OrderViewController.swift
│ ├── OrderViewController.xib
│ ├── PayWindowController.swift
│ └── PayWindowController.xib
├── Preferences
│ ├── AdvancedPreferenceManager.swift
│ ├── AdvancedPreferenceViewController.swift
│ ├── AdvancedPreferenceViewController.xib
│ ├── FilterPreferenceViewController.swift
│ ├── FilterPreferenceViewController.xib
│ ├── GeneralPreferenceManager.swift
│ ├── GeneralPreferenceViewController.swift
│ ├── GeneralPreferenceViewController.xib
│ ├── ReminderPreferenceViewController.swift
│ └── ReminderPreferenceViewController.xib
├── RealmModel
│ ├── DataManager.swift
│ └── User.swift
├── Resources
│ └── Credits.rtf
├── Service
│ ├── Dama.swift
│ ├── DamaError.swift
│ ├── Service+Login.swift
│ ├── Service+Order.swift
│ ├── Service+QueryOrder.swift
│ ├── Service+QueryTicket.swift
│ ├── Service+Utilities.swift
│ ├── Service.swift
│ └── ServiceError.swift
├── Sheets
│ ├── LoginWindowController.swift
│ ├── LoginWindowController.xib
│ ├── SubmitWindowController.swift
│ └── SubmitWindowController.xib
├── StationData.swift
├── TicketViewControllers
│ ├── PassengerSelectViewController.swift
│ ├── PassengerSelectViewController.xib
│ ├── PassengerViewController.swift
│ ├── PassengerViewController.xib
│ ├── TicketQueryViewController.swift
│ ├── TicketQueryViewController.xib
│ ├── TicketTaskManagerWindowController.swift
│ ├── TicketTaskManagerWindowController.xib
│ ├── TrainCodeDetailViewController.swift
│ ├── TrainCodeDetailViewController.xib
│ ├── TrainFilterWindowController.swift
│ └── TrainFilterWindowController.xib
├── UserControls
│ ├── AutoCompleteTextField.swift
│ ├── ClickableDatePicker.swift
│ ├── ContentBackgroundView.swift
│ ├── FilterTrainCodeTransformer.swift
│ ├── GlassView.swift
│ ├── InfoButton.swift
│ ├── LoginButton.swift
│ ├── LunarCalendar
│ │ ├── LunarCalendarView.swift
│ │ ├── LunarCalendarView.xib
│ │ └── LunarSolarConverter.swift
│ ├── NSTableView+ContextMenu.swift
│ ├── RandCodeImageView.swift
│ ├── RandCodeImageView2.swift
│ ├── Theme.swift
│ ├── TrainCodeDetailHeaderCell.swift
│ ├── TrainInfoTableCellView.swift
│ ├── TrainStationTableCellView.swift
│ ├── TrainTableRowView.swift
│ ├── URLButton.swift
│ └── UrlLabel.swift
├── Utilities
│ ├── CalendarManager.swift
│ ├── FSPreventSystemSleep.h
│ ├── FSPreventSystemSleep.m
│ ├── Notifications.swift
│ ├── NotifySpeaker.swift
│ ├── QueryDefaultManager.swift
│ ├── ReminderManager.swift
│ └── SwiftyRegex.swift
├── station_name.js
└── zh-Hans.lproj
│ └── MainMenu.xib
├── Cartfile
├── Cartfile.resolved
├── DJProgressHUD
├── DJActivityIndicator.h
├── DJActivityIndicator.m
├── DJLayerView.h
├── DJLayerView.m
├── DJTipHUD.h
└── DJTipHUD.m
├── LICENSE.txt
├── README.md
└── screenshot
├── 12306ForMac.jpg
└── donate.png
/.gitignore:
--------------------------------------------------------------------------------
1 | 12306ForMac.xcworkspace/xcuserdata/
2 | 12306ForMac.xcworkspace/xcuserdata/*
3 |
4 | ## Build generated
5 | build/
6 | DerivedData/
7 | Build/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 |
21 | ## Other
22 | *.moved-aside
23 | *.xccheckout
24 | *.xcscmblueprint
25 |
26 | ## Obj-C/Swift specific
27 | *.hmap
28 | *.ipa
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | Carthage/Checkouts
52 |
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/screenshots
64 |
65 | Pods/
66 | Pods/*
67 |
--------------------------------------------------------------------------------
/12306ForMac.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/12306ForMac.xcodeproj/project.xcworkspace/xcuserdata/dream.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac.xcodeproj/project.xcworkspace/xcuserdata/dream.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/12306ForMac.xcodeproj/project.xcworkspace/xcuserdata/lindahai.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac.xcodeproj/project.xcworkspace/xcuserdata/lindahai.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/12306ForMac.xcodeproj/xcuserdata/dream.xcuserdatad/xcschemes/12306ForMac.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/12306ForMac.xcodeproj/xcuserdata/dream.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | 12306ForMac.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | F11EB4591B6A368E000063E4
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/12306ForMac.xcodeproj/xcuserdata/lindahai.xcuserdatad/xcschemes/12306ForMac.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
78 |
80 |
86 |
87 |
88 |
89 |
90 |
91 |
97 |
99 |
105 |
106 |
107 |
108 |
110 |
111 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/12306ForMac.xcodeproj/xcuserdata/lindahai.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | 12306ForMac.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | F11EB4591B6A368E000063E4
16 |
17 | primary
18 |
19 |
20 | F11EB4691B6A368E000063E4
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/12306ForMac/12306ForMac-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Train12306-Swift.h
3 | //
4 | //
5 | // Created by fancymax on 15/7/31.
6 | //
7 | //
8 |
9 | #import "DJTipHUD.h"
10 | #import "DJLayerView.h"
11 | #import
12 |
13 | #import "FSPreventSystemSleep.h"
14 |
15 |
--------------------------------------------------------------------------------
/12306ForMac/12306ForMac.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 | com.apple.security.personal-information.calendars
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/12306ForMac/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 15/7/30.
6 | // Copyright (c) 2015年 fancy. All rights reserved.
7 | //
8 | import Cocoa
9 | import XCGLogger
10 |
11 | var APP_LOG_PATH = ""
12 | var APP_LOG_DIRECTORY = ""
13 |
14 | let logger: XCGLogger = {
15 | // Setup XCGLogger
16 | let log = XCGLogger(identifier: "advancedLogger", includeDefaultDestinations: false)
17 |
18 | let bundleId = Bundle.main.bundleIdentifier!
19 | let fileName = "\(bundleId).txt"
20 |
21 | APP_LOG_DIRECTORY = "\(NSHomeDirectory())/Library/Logs/\(bundleId)/"
22 | APP_LOG_PATH = "\(APP_LOG_DIRECTORY)/\(fileName)"
23 |
24 | let isExistDirectory:Bool = FileManager.default.fileExists(atPath: APP_LOG_DIRECTORY, isDirectory: nil)
25 | if !isExistDirectory {
26 | do{
27 | try FileManager.default.createDirectory(atPath: APP_LOG_DIRECTORY, withIntermediateDirectories: true, attributes: nil)
28 | }
29 | catch {
30 | print("createDirectoryAtPath fail,can't log")
31 | }
32 | }
33 |
34 | // Create a destination for the system console log (via NSLog)
35 | let systemDestination = AppleSystemLogDestination(identifier: "advancedLogger.systemDestination")
36 |
37 | // Optionally set some configuration options
38 | systemDestination.outputLevel = .info
39 | systemDestination.showLogIdentifier = false
40 | systemDestination.showFunctionName = false
41 | systemDestination.showLevel = false
42 | systemDestination.showFileName = false
43 | systemDestination.showLineNumber = false
44 | systemDestination.showDate = false
45 | log.add(destination: systemDestination)
46 |
47 | let autoRotatingFileDestination = AutoRotatingFileDestination(writeToFile:APP_LOG_PATH)
48 | autoRotatingFileDestination.targetMaxLogFiles = 10
49 |
50 | autoRotatingFileDestination.outputLevel = .info
51 | autoRotatingFileDestination.showLogIdentifier = false
52 | autoRotatingFileDestination.showFunctionName = false
53 | autoRotatingFileDestination.showLevel = false
54 | autoRotatingFileDestination.showFileName = false
55 | autoRotatingFileDestination.showLineNumber = false
56 | autoRotatingFileDestination.showDate = true
57 | log.add(destination:autoRotatingFileDestination)
58 |
59 | // Add basic app info, version info etc, to the start of the logs
60 | log.logAppDetails()
61 |
62 |
63 | return log
64 | }()
65 |
66 | @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate {
67 |
68 | var mainController:MainWindowController?
69 |
70 | func applicationDidFinishLaunching(_ aNotification: Notification) {
71 |
72 | let mainController = MainWindowController(windowNibName: "MainWindowController")
73 | mainController.showWindow(self)
74 |
75 | self.mainController = mainController
76 | logger.info("Application start")
77 | logger.info("dama = \(AdvancedPreferenceManager.sharedInstance.isUseDama)")
78 | }
79 |
80 | @IBAction func openDebugFile(_ sender:AnyObject) {
81 | NSWorkspace.shared().openFile(APP_LOG_PATH)
82 | }
83 |
84 | @IBAction func openDebugDirectory(_ sender:AnyObject) {
85 | NSWorkspace.shared().openFile(APP_LOG_DIRECTORY)
86 | }
87 |
88 | func applicationWillTerminate(_ aNotification: Notification) {
89 | // Insert code here to tear down your application
90 | }
91 |
92 | func applicationShouldTerminateAfterLastWindowClosed(_ sender:NSApplication)->Bool {
93 | return true
94 | }
95 |
96 | // func applicationWillResignActive(_ notification: Notification) {
97 | // print("applicationWillResignActive")
98 | // }
99 | //
100 | // func applicationWillBecomeActive(_ notification: Notification) {
101 | // print("applicationWillBecomeActive")
102 | // }
103 |
104 | }
105 |
106 |
--------------------------------------------------------------------------------
/12306ForMac/BaseWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseWindowController.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/12/1.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class BaseWindowController: NSWindowController {
12 |
13 | func showTip(_ tip:String) {
14 | DJTipHUD.showStatus(tip, from: self.window?.contentView)
15 | }
16 |
17 | func startLoadingTip(_ tip:String)
18 | {
19 | DJLayerView.showStatus(tip, from: self.window?.contentView)
20 | }
21 |
22 | func stopLoadingTip(){
23 | DJLayerView.dismiss()
24 | }
25 |
26 | func dismissWithModalResponse(_ response:NSModalResponse)
27 | {
28 | if window != nil {
29 | if window!.sheetParent != nil {
30 | window!.sheetParent!.endSheet(window!,returnCode: response)
31 | }
32 | }
33 | }
34 | }
35 |
36 | class BaseViewController: NSViewController{
37 | func showTip(_ tip:String){
38 | DJTipHUD.showStatus(tip, from: self.view)
39 | }
40 |
41 | func startLoadingTip(_ tip:String)
42 | {
43 | DJLayerView.showStatus(tip, from: self.view)
44 | }
45 |
46 | func stopLoadingTip(){
47 | DJLayerView.dismiss()
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Add.imageset/Add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/Add.imageset/Add.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Add.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Add.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "icon_16x16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "icon_16x16@2x.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "icon_32x32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "icon_32x32@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "icon_128x128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "icon_128x128@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "icon_256x256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "icon_256x256@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "icon_512x512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "icon_512x512@2x.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_128x128.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_16x16.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_256x256.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_32x32.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_512x512.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/DefineFilter.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "DefineFilter.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/DefineFilter.imageset/DefineFilter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/DefineFilter.imageset/DefineFilter.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Direction.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Direction.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Direction.imageset/Direction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/Direction.imageset/Direction.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Filter.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Filter.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Filter.imageset/Filter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/Filter.imageset/Filter.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Reminder.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Reminder.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Reminder.imageset/Reminder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/Reminder.imageset/Reminder.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Reverse.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Reverse.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/Reverse.imageset/Reverse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/Reverse.imageset/Reverse.png
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/User.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "User.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/12306ForMac/Images.xcassets/User.imageset/User.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/User.imageset/User.png
--------------------------------------------------------------------------------
/12306ForMac/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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.6.8
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 35
23 | LSApplicationCategoryType
24 | public.app-category.utilities
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSAppTransportSecurity
28 |
29 | NSExceptionDomains
30 |
31 | 12306.cn
32 |
33 | NSIncludesSubdomains
34 |
35 | NSThirdPartyExceptionAllowsInsecureHTTPLoads
36 |
37 | NSThirdPartyExceptionRequiresForwardSecrecy
38 |
39 |
40 | api.dama2.com
41 |
42 | NSIncludesSubdomains
43 |
44 | NSThirdPartyExceptionAllowsInsecureHTTPLoads
45 |
46 | NSThirdPartyExceptionRequiresForwardSecrecy
47 |
48 |
49 | kyfw.12306.cn
50 |
51 | NSIncludesSubdomains
52 |
53 | NSThirdPartyExceptionAllowsInsecureHTTPLoads
54 |
55 | NSThirdPartyExceptionRequiresForwardSecrecy
56 |
57 |
58 |
59 |
60 | NSHumanReadableCopyright
61 | Copyright © 2015-2017年 Fancy Apps. All rights reserved.
62 | NSMainNibFile
63 | MainMenu
64 | NSPrincipalClass
65 | NSApplication
66 |
67 |
68 |
--------------------------------------------------------------------------------
/12306ForMac/Model/AutoSubmitParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // autoSubmitParams.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/12/13.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct AutoSubmitParams{
12 |
13 | init(with ticket:QueryLeftNewDTO, purposeCode:String,passengerTicket:String,oldPassenger:String) {
14 | secretStr = ticket.SecretStr!
15 | train_date = ticket.trainDateStr
16 | purpose_codes = purposeCode
17 | query_from_station_name = ticket.FromStationName!
18 | query_to_station_name = ticket.ToStationName!
19 |
20 | passengerTicketStr = passengerTicket
21 | oldPassengerStr = oldPassenger
22 | }
23 |
24 | var secretStr = ""
25 |
26 | var train_date = ""
27 |
28 | let tour_flag = "dc"
29 |
30 | var purpose_codes = "ADULT"
31 |
32 | var query_from_station_name = ""
33 |
34 | var query_to_station_name = ""
35 |
36 | let cancel_flag = "2"
37 |
38 | let bed_level_order_num = "000000000000000000000000000000"
39 |
40 | var passengerTicketStr = ""
41 | var oldPassengerStr = ""
42 |
43 | func ToPostParams()->[String:String]{
44 | return [
45 | "secretStr":secretStr,
46 | "train_date":train_date,//2015-11-17
47 | "tour_flag":tour_flag,
48 | "purpose_codes":purpose_codes,
49 | "query_from_station_name":query_from_station_name,
50 | "query_to_station_name":query_to_station_name,
51 | "cancel_flag":cancel_flag,
52 | "bed_level_order_num":bed_level_order_num,
53 | "passengerTicketStr":passengerTicketStr,
54 | "oldPassengerStr":oldPassengerStr]
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/12306ForMac/Model/CaptchaImageParam.swift:
--------------------------------------------------------------------------------
1 | //
2 | // captchaImageParam.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2017/9/28.
6 | // Copyright © 2017年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct CaptchaImageParam {
12 | var login_site:String = "E"
13 | var module:String = "login"
14 | var rand:String = "sjrand"
15 |
16 | func ToGetParams()->String{
17 | let random = CGFloat(Float(arc4random()) / Float(UINT32_MAX))//0~1
18 | return "login_site=\(login_site)&module=\(module)&rand=\(rand)&" + random.description
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/12306ForMac/Model/ConfirmSingleForQueueAsysParam.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfirmSingleForQueueAsysParam.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/12/14.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct ConfirmSingleForQueueParamAsys{
12 |
13 | init(randCodeStr:String,passengerTicket:String,oldPassenger:String) {
14 | passengerTicketStr = passengerTicket
15 | oldPassengerStr = oldPassenger
16 | randCode = randCodeStr
17 |
18 | key_check_isChange = MainModel.key_check_isChange ?? ""
19 | leftTicketStr = MainModel.selectedTicket?.yp_info ?? ""
20 | train_location = MainModel.train_location ?? ""
21 | }
22 |
23 | var passengerTicketStr = ""
24 | var oldPassengerStr = ""
25 | var randCode = ""
26 | var purpose_codes = "ADULT"
27 | var key_check_isChange = ""
28 | var leftTicketStr = ""
29 | var train_location = ""
30 |
31 | var choose_seats = ""
32 | var seatDetailType = ""
33 |
34 | func ToPostParams()->[String:String]{
35 | return [
36 | "passengerTicketStr":passengerTicketStr,
37 | "oldPassengerStr":oldPassengerStr,
38 | "randCode":randCode,
39 | "purpose_codes":purpose_codes,
40 | "key_check_isChange":key_check_isChange,
41 | "leftTicketStr":leftTicketStr,
42 | "train_location":train_location,
43 | "choose_seats":choose_seats,
44 | "seatDetailType":seatDetailType,
45 | "_json_att":""]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/12306ForMac/Model/ConfirmSingleForQueueParam.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfirmSingleForQueueParam.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/12/14.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct ConfirmSingleForQueueParam{
12 |
13 | init(randCodeStr:String,passengerTicket:String,oldPassenger:String) {
14 | passengerTicketStr = passengerTicket
15 | oldPassengerStr = oldPassenger
16 | randCode = randCodeStr
17 |
18 | key_check_isChange = MainModel.key_check_isChange ?? ""
19 | leftTicketStr = MainModel.selectedTicket?.yp_info ?? ""
20 | train_location = MainModel.train_location ?? ""
21 |
22 | REPEAT_SUBMIT_TOKEN = MainModel.globalRepeatSubmitToken
23 | }
24 |
25 | var passengerTicketStr = ""
26 | var oldPassengerStr = ""
27 | var randCode = ""
28 | var purpose_codes = "00"
29 | var key_check_isChange = ""
30 | var leftTicketStr = ""
31 | var train_location = ""
32 |
33 | var choose_seats = ""
34 | var seatDetailType = "000"
35 | var roomType = "00"
36 | var dwAll = "N"
37 |
38 | var REPEAT_SUBMIT_TOKEN = ""
39 |
40 | func ToPostParams()->[String:String]{
41 | return [
42 | "passengerTicketStr":passengerTicketStr,
43 | "oldPassengerStr":oldPassengerStr,
44 | "randCode":randCode,
45 | "purpose_codes":purpose_codes,
46 | "key_check_isChange":key_check_isChange,
47 | "leftTicketStr":leftTicketStr,
48 | "train_location":train_location,
49 | "choose_seats":choose_seats,
50 | "seatDetailType":seatDetailType,
51 | "roomType":roomType,
52 | "dwAll":dwAll,
53 | "_json_att":"",
54 | "REPEAT_SUBMIT_TOKEN":REPEAT_SUBMIT_TOKEN]
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/12306ForMac/Model/GetQueueCountAsysParam.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GetQueueCountAsysParam.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/12/14.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct GetQueueCountParamAsys{
12 |
13 | init(with selectedTicket:QueryLeftNewDTO, seatCode:String) {
14 | train_date = selectedTicket.jsStartTrainDateStr!
15 | train_no = selectedTicket.train_no!
16 | stationTrainCode = selectedTicket.TrainCode!
17 | seatType = seatCode
18 | fromStationTelecode = selectedTicket.FromStationCode!
19 | toStationTelecode = selectedTicket.ToStationCode!
20 | leftTicket = selectedTicket.yp_info!
21 | }
22 |
23 | var train_date = ""
24 | var train_no = ""
25 | var stationTrainCode = ""
26 | var seatType = ""
27 | var fromStationTelecode = ""
28 | var toStationTelecode = ""
29 | var leftTicket = ""
30 | var purpose_codes = "ADULT"
31 |
32 |
33 | func ToPostParams()->[String:String]{
34 | return [
35 | "train_date":train_date,
36 | "train_no":train_date,//2015-11-17
37 | "tour_flag":train_no,
38 | "stationTrainCode":stationTrainCode,
39 | "seatType":seatType,
40 | "fromStationTelecode":fromStationTelecode,
41 | "toStationTelecode":toStationTelecode,
42 | "leftTicket":leftTicket,
43 | "purpose_codes":purpose_codes,
44 | "_json_att":""]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/12306ForMac/Model/GetQueueCountParam.swift:
--------------------------------------------------------------------------------
1 | //
2 | // getQueueCountParam.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/12/14.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct GetQueueCountParam{
12 |
13 | init(with selectedTicket:QueryLeftNewDTO, seatCode:String, trainLocation:String, globalSubmitToken:String) {
14 | train_date = selectedTicket.jsStartTrainDateStr!
15 | train_no = selectedTicket.train_no!
16 | stationTrainCode = selectedTicket.TrainCode!
17 | seatType = seatCode
18 | fromStationTelecode = selectedTicket.FromStationCode!
19 | toStationTelecode = selectedTicket.ToStationCode!
20 | leftTicket = selectedTicket.yp_info!
21 | train_location = trainLocation
22 | REPEAT_SUBMIT_TOKEN = globalSubmitToken
23 | }
24 |
25 | var train_date = ""
26 | var train_no = ""
27 | var stationTrainCode = ""
28 | var seatType = ""
29 | var fromStationTelecode = ""
30 | var toStationTelecode = ""
31 | var leftTicket = ""
32 | var purpose_codes = "00"
33 | var train_location = ""
34 |
35 | var REPEAT_SUBMIT_TOKEN = ""
36 |
37 | func ToPostParams()->[String:String]{
38 | return [
39 | "train_date":train_date,
40 | "train_no":train_date,//2015-11-17
41 | "tour_flag":train_no,
42 | "stationTrainCode":stationTrainCode,
43 | "seatType":seatType,
44 | "fromStationTelecode":fromStationTelecode,
45 | "toStationTelecode":toStationTelecode,
46 | "leftTicket":leftTicket,
47 | "purpose_codes":purpose_codes,
48 | "train_location":train_location,
49 | "_json_att":"",
50 | "REPEAT_SUBMIT_TOKEN":REPEAT_SUBMIT_TOKEN]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/12306ForMac/Model/LeftTicketParam.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LeftTicketDTO.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 15/10/24.
6 | // Copyright © 2015年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct LeftTicketParam{
12 | //2015-09-23
13 | var train_date = "2015-09-23"
14 | //SZQ -> 深圳
15 | var from_stationCode = "SZQ"
16 | //SHH -> 上海
17 | var to_stationCode = "SHH"
18 | //ADULT
19 | var purpose_codes = "ADULT"
20 |
21 | func ToGetParams()->String{
22 | return "leftTicketDTO.train_date=\(train_date)&leftTicketDTO.from_station=\(from_stationCode)&leftTicketDTO.to_station=\(to_stationCode)&purpose_codes=\(purpose_codes)"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/12306ForMac/Model/MainModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainModel.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 15/10/6.
6 | // Copyright © 2015年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class MainModel{
12 | static var realName = ""
13 | static var userName = ""
14 | static var isGetUserInfo = false
15 |
16 | static var passengers = [PassengerDTO]()
17 | static var selectPassengers = [PassengerDTO]()
18 | static var isGetPassengersInfo = false
19 |
20 | static var selectedTicket:QueryLeftNewDTO?
21 |
22 | static var orderId:String?
23 |
24 | static var globalRepeatSubmitToken:String = ""
25 | static var key_check_isChange:String?
26 | static var train_location:String?
27 | static var trainDate:String?
28 |
29 | static var historyOrderList:[OrderDTO] = []
30 | static var noCompleteOrderList:[OrderDTO] = []
31 |
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/12306ForMac/Model/OrderDTO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrderDTO.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/2/16.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class OrderDTO:NSObject{
13 | var sequence_no: String?
14 |
15 | var start_train_date_page: String?
16 | // var train_code_page: String?
17 | var ticket_total_price_page: String?
18 | var return_flag: String?
19 | var resign_flag: String?
20 | var arrive_time_page: String?
21 |
22 | //tickets[x]
23 | var coach_no: String? //车厢号 05
24 | var seat_name: String? //席位号 01A号
25 | var str_ticket_price_page: String? //票价 603.5
26 | var ticket_type_name: String? //票种 成人票
27 | var seat_type_name: String? //席别 一等座
28 | var ticket_status_code: String? //状态 i
29 | var ticket_status_name: String? //状态 待支付
30 | var pay_limit_time: String? //限定支付时间 2016-06-14 10:36:28
31 |
32 | // stationTrainDTO
33 | var station_train_code: String? //G6012
34 | var from_station_name: String? //深圳北
35 | var to_station_name: String? //长沙南
36 |
37 | //passengerDTO
38 | var passenger_name :String? //小明
39 |
40 | //modified
41 | //05车厢
42 | var coachName:String{
43 | var name = ""
44 | if let no = coach_no {
45 | name = no + "车厢"
46 | }
47 | return name
48 | }
49 |
50 | var startTrainDate:Date? {
51 | if start_train_date_page == nil {
52 | return nil
53 | }
54 | let dateFormatter = DateFormatter()
55 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
56 | if let date = dateFormatter.date(from: start_train_date_page!) {
57 | return date
58 | }
59 | else {
60 | logger.error("trainDateStr2Date dateStr = \(self.start_train_date_page)")
61 | return nil
62 | }
63 | }
64 |
65 | var startEndStation:String {
66 | return "\(from_station_name!)->\(to_station_name!)"
67 | }
68 |
69 | var whereToSeat:String {
70 | return "\(coachName) \(seat_name!)"
71 | }
72 |
73 | //¥603.5
74 | var ticketPrice:String{
75 | var name = ""
76 | if let price = str_ticket_price_page {
77 | name = "¥" + price
78 | }
79 | return name
80 | }
81 |
82 | var payStatus:String{
83 | var name = ""
84 | if let limitTime = pay_limit_time, let status = ticket_status_name, let code = ticket_status_code {
85 | if code == "i" {
86 | name = "\(status)(请在 \(limitTime) 前支付)"
87 | }
88 | else {
89 | name = "\(status)"
90 | }
91 | }
92 | return name
93 | }
94 |
95 | init(json:JSON,ticketIdx:Int)
96 | {
97 | sequence_no = json["sequence_no"].string
98 | arrive_time_page = json["arrive_time_page"].string
99 |
100 | start_train_date_page = json["tickets"][ticketIdx]["start_train_date_page"].string
101 |
102 | coach_no = json["tickets"][ticketIdx]["coach_no"].string
103 | seat_name = json["tickets"][ticketIdx]["seat_name"].string
104 | str_ticket_price_page = json["tickets"][ticketIdx]["str_ticket_price_page"].string
105 | ticket_type_name = json["tickets"][ticketIdx]["ticket_type_name"].string
106 | seat_type_name = json["tickets"][ticketIdx]["seat_type_name"].string
107 | ticket_status_name = json["tickets"][ticketIdx]["ticket_status_name"].string
108 | ticket_status_code = json["tickets"][ticketIdx]["ticket_status_code"].string
109 | pay_limit_time = json["tickets"][ticketIdx]["pay_limit_time"].string
110 |
111 | station_train_code = json["tickets"][ticketIdx]["stationTrainDTO"]["station_train_code"].string
112 | from_station_name = json["tickets"][ticketIdx]["stationTrainDTO"]["from_station_name"].string
113 | to_station_name = json["tickets"][ticketIdx]["stationTrainDTO"]["to_station_name"].string
114 |
115 | passenger_name = json["tickets"][ticketIdx]["passengerDTO"]["passenger_name"].string
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/12306ForMac/Model/PassengerDTO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PassengerDTO.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 15/8/24.
6 | // Copyright (c) 2015年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | enum TicketType:String,CustomStringConvertible {
13 | case Student = "0X00"
14 | case Normal = "ADULT"
15 |
16 | var description: String {
17 | switch self {
18 | case .Student:
19 | return "学生"
20 | case .Normal:
21 | return "成人"
22 | }
23 | }
24 |
25 | var id_type_code:String {
26 | switch self {
27 | case .Student:
28 | return "3"
29 | case .Normal:
30 | return "1"
31 | }
32 | }
33 | }
34 |
35 | class PassengerDTO:NSObject {
36 |
37 | let code :String
38 | let passenger_name :String
39 | var sex_code :String?
40 | var sex_name :String?
41 | var born_date :String?
42 | var country_code :String?
43 | var passenger_id_type_code :String
44 | var passenger_id_type_name :String
45 | let passenger_id_no :String
46 | var passenger_type :String
47 | var passenger_type_name :String?
48 | var passenger_flag :String?
49 | var mobile_no :String?
50 | var phone_no :String?
51 | var email :String?
52 | var address :String?
53 | var postalcode :String?
54 | var first_letter :String?
55 | var recordCount :String?
56 | var total_times :String?
57 | var index_id :String?
58 |
59 | var isChecked: Bool
60 | var seatCode = "O";
61 | var seatCodeName = "二等座";
62 | var canSelectTicketType = false
63 |
64 | let passenger_type_name_Dic = ["成人","学生"]
65 | var passenger_id_type_select_index = 0 {
66 | willSet {
67 | if passenger_type_name_Dic[newValue] == TicketType.Student.description {
68 | passenger_type = TicketType.Student.id_type_code
69 | }
70 | else {
71 | passenger_type = TicketType.Normal.id_type_code
72 | }
73 | }
74 | }
75 |
76 |
77 | init(json:JSON)
78 | {
79 | isChecked = false
80 |
81 | code = json["code"].string!
82 | passenger_name = json["passenger_name"].string!
83 | sex_code = json["sex_code"].string
84 | sex_name = json["sex_name"].string
85 | born_date = json["born_date"].string
86 | country_code = json["country_code"].string
87 | passenger_id_type_code = json["passenger_id_type_code"].string!
88 | passenger_id_type_name = json["passenger_id_type_name"].string!
89 | passenger_id_no = json["passenger_id_no"].string!
90 | passenger_type = json["passenger_type"].string!
91 | passenger_flag = json["passenger_flag"].string
92 | passenger_type_name = json["passenger_type_name"].string
93 | mobile_no = json["mobile_no"].string
94 | phone_no = json["phone_no"].string
95 | email = json["email"].string
96 | address = json["address"].string
97 | postalcode = json["postalcode"].string
98 | first_letter = json["first_letter"].string
99 | recordCount = json["recordCount"].string
100 | total_times = json["total_times"].string
101 | index_id = json["index_id"].string
102 |
103 | }
104 |
105 | func setDefaultTicketType(date:Date){
106 | let sysDate = LunarCalendarView.toUTCDateComponent(date)
107 | let availableMonth = [6,7,8,9,12,1,2,3]
108 | let isDateAvailable:Bool
109 | if (availableMonth.contains(sysDate.month!)) {
110 | isDateAvailable = true
111 | }
112 | else {
113 | isDateAvailable = false
114 | }
115 |
116 | let isSeatAvailable:Bool
117 | if ((seatCode == "O") || (seatCode == "1") || (seatCode == "3")) {
118 | isSeatAvailable = true
119 | }
120 | else {
121 | isSeatAvailable = false
122 | }
123 |
124 | let isStudent:Bool
125 | if passenger_type_name == "学生" {
126 | isStudent = true
127 | }
128 | else {
129 | isStudent = false
130 | }
131 |
132 | if isStudent {
133 | if isDateAvailable && isSeatAvailable {
134 | passenger_id_type_select_index = 1
135 | canSelectTicketType = true
136 | }
137 | else {
138 | passenger_id_type_select_index = 0
139 | canSelectTicketType = false
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/12306ForMac/Model/PaycheckNewParam.swift:
--------------------------------------------------------------------------------
1 | //
2 | // paycheckNewParam.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/11/30.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct PaycheckNewParam {
12 |
13 | func ToPostParams()->[String:String]{
14 | return [
15 | "batch_nos":"",
16 | "coach_nos":"",
17 | "seat_nos":"",
18 | "passenger_id_types":"",
19 | "passenger_id_nos":"",
20 | "passenger_names":"",
21 | "insure_price_all":"",
22 | "insure_types":"",
23 | "if_buy_insure_only":"N",
24 | "hasBoughtIns":"",
25 | "json_att":""
26 | ]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/12306ForMac/Model/QueryByTrainCodeParam.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QueryByTrainCodeParam.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/06/12.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct QueryTrainCodeParam {
12 | let _train_no:String //6i000D232806"
13 | let _from_station_telecode:String // "IOQ"
14 | let _to_station_telecode:String //"FYS"
15 | let _depart_date:String //2016-09-10 !!列车出发时间!!
16 |
17 | init(_ ticket:QueryLeftNewDTO) {
18 | self.init(train_no:ticket.train_no,from_station_telecode:ticket.FromStationCode!,to_station_telecode:ticket.ToStationCode!,depart_date:ticket.start_train_date_formatStr!)
19 | }
20 |
21 | init(train_no:String,from_station_telecode:String,to_station_telecode:String,depart_date:String) {
22 | _train_no = train_no
23 | _from_station_telecode = from_station_telecode
24 | _to_station_telecode = to_station_telecode
25 | _depart_date = depart_date
26 | }
27 |
28 | //train_no=6i000D232806&from_station_telecode=IOQ&to_station_telecode=FYS&depart_date=2016-06-12
29 | func ToGetParams()->String{
30 | return "train_no=\(_train_no)&from_station_telecode=\(_from_station_telecode)&to_station_telecode=\(_to_station_telecode)&depart_date=\(_depart_date)"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/12306ForMac/Model/QueryOrderParam.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QueryOrderParam.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 16/2/16.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct QueryOrderParam{
12 | var queryType = "1"
13 | var queryStartDate = "2016-06-01"
14 | var queryEndDate = "2020-02-15"
15 | var come_from_flag = "my_order"
16 | var pageSize = 8
17 | var pageIndex = 0
18 | var query_where = "G"
19 | var sequeue_train_name = ""
20 |
21 | func ToPostParams()->[String:String]{
22 | return ["queryType":queryType,
23 | "queryStartDate":queryStartDate,
24 | "queryEndDate":queryEndDate,
25 | "come_from_flag":come_from_flag,
26 | "pageSize":String(pageSize),
27 | "pageIndex":String(pageIndex),
28 | "query_where":query_where,
29 | "sequeue_train_name":sequeue_train_name,
30 | ]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/12306ForMac/Model/QueryOrderWaitTimeResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QueryOrderWaitTimeResult.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/3/3.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class QueryOrderWaitTimeResult{
13 | var queryOrderWaitTimeStatus: Bool?
14 | var count:Int?
15 | var waitTime:Int?
16 | // var requestId:
17 | var waitCount:Int?
18 | var tourFlag:String?
19 | var orderId:String?
20 | var msg:String?
21 |
22 | init(json:JSON)
23 | {
24 | queryOrderWaitTimeStatus = json["queryOrderWaitTimeStatus"].boolValue
25 | count = json["count"].intValue
26 | waitTime = json["waitTime"].intValue
27 | waitCount = json["waitCount"].intValue
28 | tourFlag = json["tourFlag"].string
29 | orderId = json["orderId"].string
30 | msg = json["msg"].string
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/12306ForMac/Model/QueryTrainPriceParam.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QueryTrainPriceParam.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/11/8.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct QueryTrainPriceParam {
12 | let _train_no:String // "6i000D232806"
13 | let _from_station_no:String // "01"
14 | let _to_station_no:String // "14"
15 | let _seat_types:String // "OM9"
16 | let _train_date:String // "2016-12-29"
17 |
18 | init(_ ticket:QueryLeftNewDTO) {
19 | self.init(train_no:ticket.train_no,from_station_no:ticket.from_station_no!,to_station_no:ticket.to_station_no!,seat_types:ticket.seat_types!,train_date:ticket.trainDateStr)
20 | }
21 |
22 |
23 | init(train_no:String,from_station_no:String,to_station_no:String,seat_types:String,train_date:String) {
24 | _train_no = train_no
25 | _from_station_no = from_station_no
26 | _to_station_no = to_station_no
27 | _seat_types = seat_types
28 | _train_date = train_date
29 | }
30 |
31 | //?train_no=6i000G160202&from_station_no=01&to_station_no=14&seat_types=OM9&train_date=2016-12-2
32 | func ToGetParams()->String{
33 | return "train_no=\(_train_no)&from_station_no=\(_from_station_no)&to_station_no=\(_to_station_no)&seat_types=\(_seat_types)&train_date=\(_train_date)"
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/12306ForMac/Model/SubmitOrderParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SubmitOrderParams.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/10/31.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct SubmitOrderParams{
12 |
13 | init(with ticket:QueryLeftNewDTO, purposeCode:String) {
14 | secretStr = ticket.SecretStr!
15 | train_date = ticket.trainDateStr
16 | back_train_date = ticket.trainDateStr
17 | //tour_flag dc
18 | purpose_codes = purposeCode
19 | query_from_station_name = ticket.FromStationName!
20 | query_to_station_name = ticket.ToStationName!
21 |
22 | }
23 |
24 | var secretStr = ""
25 |
26 | var train_date = ""
27 |
28 | var back_train_date = ""
29 |
30 | let tour_flag = "dc"
31 |
32 | var purpose_codes = "ADULT"
33 |
34 | var query_from_station_name = ""
35 |
36 | var query_to_station_name = ""
37 |
38 | func ToPostParams()->[String:String]{
39 | return [
40 | "secretStr":secretStr,
41 | "train_date":train_date,//2015-11-17
42 | "back_train_date":back_train_date,//2015-11-03
43 | "tour_flag":tour_flag,
44 | "purpose_codes":purpose_codes,
45 | "query_from_station_name":query_from_station_name,
46 | "query_to_station_name":query_to_station_name,
47 | "undefined":""]
48 | }
49 | }
--------------------------------------------------------------------------------
/12306ForMac/Model/TicketConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TicketConstants.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/9/10.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //let cardTypeNameDic = ["二代身份证": "1", "一代身份证": "2", "港澳通行证": "C", "台湾通行证": "G", "护照": "B"]
12 |
13 | let SEAT_TYPE_ARRAY = ["商务座", "特等座", "一等座", "二等座", "高级软卧", "软卧", "硬卧", "软座", "硬座", "无座"]
14 |
15 | //动车
16 | let D_SEAT_TYPE_NAME_DIC = ["高级软卧":"A","商务座": "9", "特等座": "P", "一等座": "M", "二等座": "O","软卧": "F", "无座": "O"]
17 | let D_SEAT_TYPE_KEYPATH_DIC = ["高级软卧": "Gr_Num","商务座": "Swz_Num", "特等座": "Tz_Num", "一等座": "Zy_Num", "二等座": "Ze_Num","软卧": "Rw_Num", "无座": "Wz_Num"]
18 |
19 | //普通车
20 | let K_SEAT_TYPE_NAME_DIC = ["高级软卧": "6","软卧": "4", "硬卧": "3", "软座": "2", "硬座": "1", "无座": "1"]
21 | let K_SEAT_TYPE_KEYPATH_DIC = ["高级软卧": "Gr_Num","软卧": "Rw_Num", "硬卧": "Yw_Num", "软座": "Rz_Num", "硬座": "Yz_Num", "无座": "Wz_Num"]
22 |
23 | func G_QuerySeatTypeNameDicBy(_ trainCode:String)->[String:String] {
24 | if (trainCode.contains("G"))||(trainCode.contains("D")||(trainCode.contains("C"))) {
25 | return D_SEAT_TYPE_NAME_DIC;
26 | }
27 | else {
28 | return K_SEAT_TYPE_NAME_DIC;
29 | }
30 | }
31 |
32 | func G_QuerySeatTypeKeyPathDicBy(_ trainCode:String)->[String:String] {
33 | if (trainCode.contains("G"))||(trainCode.contains("D")||(trainCode.contains("C"))) {
34 | return D_SEAT_TYPE_KEYPATH_DIC;
35 | }
36 | else {
37 | return K_SEAT_TYPE_KEYPATH_DIC;
38 | }
39 | }
40 |
41 | //20160502->2016-05-02
42 | func G_Convert2StartTrainDateStr(_ dateStr: String)->String{
43 | var formateStr = dateStr
44 | var index = dateStr.characters.index(dateStr.startIndex, offsetBy: 4)
45 | formateStr.insert("-", at: index)
46 | index = dateStr.characters.index(dateStr.startIndex, offsetBy: 7)
47 | formateStr.insert("-", at: index)
48 |
49 | return formateStr
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/12306ForMac/Model/TicketQueueCountResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TicketQueueCountResult.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/3/3.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | class TicketQueueCountResult{
13 | var count:String?
14 | var countT:String?
15 | var op_2:String?
16 | var op_1:String?
17 | var ticket:String?
18 | var isRelogin:String?
19 |
20 |
21 | init(json:JSON)
22 | {
23 | count = json["count"].string
24 | countT = json["countT"].string
25 | op_1 = json["op_1"].string
26 | op_2 = json["op_2"].string
27 | ticket = json["ticket"].string
28 | isRelogin = json["isRelogin"].string
29 | }
30 |
31 | func shouldRelogin()->Bool {
32 | if let reloginStr = isRelogin , reloginStr == "Y" {
33 | return true;
34 | }
35 | else {
36 | return false;
37 | }
38 | }
39 |
40 | func isTicketSoldOut() -> Bool {
41 | if let status = op_2 , status == "true" {
42 | return true;
43 | }
44 | else {
45 | return false;
46 | }
47 | }
48 |
49 | func getWarningInfoBy(_ seatCodeName:String) -> String {
50 | var warningStr = ""
51 | if let leftTicket = ticket {
52 | if leftTicket.contains(",") {
53 | let nums = leftTicket.components(separatedBy: ",")
54 | assert(nums.count == 2)
55 | warningStr += "本次列车 剩余\(seatCodeName) \(nums[0]) 张, 无座 \(nums[1])张"
56 | }
57 | else {
58 | warningStr += "本次列车 剩余\(seatCodeName) \(leftTicket) 张"
59 | }
60 | }
61 | if let status = op_2 , status == "true" {
62 | warningStr += ",目前排队人数已经超过余票张数,请您选择其他席别或车次"
63 | }
64 | else {
65 | if let queueNum = countT , queueNum != "0" {
66 | warningStr += ",目前排队人数 \(queueNum)"
67 | }
68 | }
69 | return warningStr
70 | }
71 |
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/12306ForMac/Model/TrainCodeDetail.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrainCodeDetail.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/06/12.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 | import SwiftyJSON
12 |
13 | class TrainCodeDetail: NSObject {
14 | var station_no: String! // = "1"
15 | var station_name: String! // = "深圳北"
16 | var arrive_time: String! // = "-----"
17 | var start_time: String! // = "16:07"
18 | var stopover_time: String! // = "----"
19 | var isEnable: Bool! // = True
20 | var textColor:NSColor!
21 |
22 | init(json:JSON) {
23 | station_no = json["station_no"].string
24 | station_name = json["station_name"].string
25 | arrive_time = json["arrive_time"].string
26 | start_time = json["start_time"].string
27 | stopover_time = json["stopover_time"].string
28 | isEnable = json["isEnabled"].boolValue
29 | if isEnable! {
30 | textColor = NSColor.black
31 | }
32 | else{
33 | textColor = NSColor.gray
34 | }
35 | }
36 |
37 | }
38 |
39 | class TrainCodeDetails: NSObject {
40 | var start_station_name: String! // = "深圳北"
41 | var station_train_code: String! // = "D2306"
42 | var train_class_name: String! // = "动车"
43 | var service_type: String! // = "1"
44 | var end_station_name: String! // = "福州南"
45 |
46 | var trainNos: [TrainCodeDetail]!
47 |
48 | init(json:JSON) {
49 | if json.count <= 0 {
50 | return
51 | }
52 |
53 | start_station_name = json[0]["start_station_name"].string
54 | station_train_code = json[0]["station_train_code"].string
55 | train_class_name = json[0]["train_class_name"].string
56 | service_type = json[0]["service_type"].string
57 | end_station_name = json[0]["end_station_name"].string
58 |
59 | self.trainNos = [TrainCodeDetail]()
60 | for i in 0.. () in
33 | self.stopLoadingTip()
34 | self.showTip(translate(error))
35 | }
36 |
37 | self.startLoadingTip("正在加载...")
38 |
39 | Service.sharedInstance.payFlow(success: successHandler, failure: failureHandler)
40 | }
41 |
42 | @IBAction func clickRefreshPay(_ button:NSButton) {
43 | self.refreshPay()
44 | }
45 |
46 | @IBAction func clickCancel(_ button:NSButton){
47 | dismissWithModalResponse(NSModalResponseOK)
48 | }
49 |
50 | @IBAction func open12306(_ sender: NSButton) {
51 | NSWorkspace.shared().open(URL(string: "https://kyfw.12306.cn/otn/login/init")!)
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/12306ForMac/Preferences/AdvancedPreferenceManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdvancedPrefereceManager.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/8/10.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class AdvancedPreferenceManager {
12 |
13 | static let sharedInstance = AdvancedPreferenceManager()
14 |
15 | fileprivate let isUseDamaKey = "isUseDama"
16 | fileprivate let isUseDamaLoginKey = "isUseDamaLogin"
17 | fileprivate let isStopDamaWhenOperateKey = "isStopDamaWhenOperate"
18 | fileprivate let isStopDamaWhenFail5Key = "isStopDamaWhenFail5"
19 |
20 | fileprivate let damaUserKey = "damaUser"
21 | fileprivate let damaPasswordKey = "damaPassword"
22 |
23 | fileprivate let userDefaults = UserDefaults.standard
24 | fileprivate init()
25 | {
26 | registerUserDefault()
27 | }
28 |
29 | fileprivate func registerUserDefault()
30 | {
31 | let firstDefault = [isUseDamaKey: false,isUseDamaLoginKey: true,isStopDamaWhenOperateKey:true,isStopDamaWhenFail5Key: true,damaUserKey:"",damaPasswordKey:""] as [String : Any]
32 | userDefaults.register(defaults: firstDefault)
33 | }
34 |
35 | var isUseDama:Bool {
36 | get{
37 | return userDefaults.object(forKey: isUseDamaKey) as! Bool
38 | }
39 | set{
40 | userDefaults.set(newValue, forKey: isUseDamaKey)
41 | }
42 | }
43 |
44 | var isUseDamaLogin:Bool {
45 | get{
46 | return userDefaults.object(forKey: isUseDamaLoginKey) as! Bool
47 | }
48 | set{
49 | userDefaults.set(newValue, forKey: isUseDamaLoginKey)
50 | }
51 | }
52 |
53 | var isStopDamaWhenOperate:Bool {
54 | get{
55 | return userDefaults.object(forKey: isStopDamaWhenOperateKey) as! Bool
56 | }
57 | set{
58 | userDefaults.set(newValue, forKey: isStopDamaWhenOperateKey)
59 | }
60 | }
61 |
62 | var isStopDamaWhenFail5:Bool {
63 | get{
64 | return userDefaults.object(forKey: isStopDamaWhenFail5Key) as! Bool
65 | }
66 | set{
67 | userDefaults.set(newValue, forKey: isStopDamaWhenFail5Key)
68 |
69 | }
70 | }
71 |
72 | var damaUser:String {
73 | get{
74 | return userDefaults.object(forKey: damaUserKey) as! String
75 | }
76 | set{
77 | userDefaults.set(newValue, forKey: damaUserKey)
78 | }
79 | }
80 |
81 | var damaPassword:String{
82 | get{
83 | return userDefaults.object(forKey: damaPasswordKey) as! String
84 | }
85 | set{
86 | userDefaults.set(newValue, forKey: damaPasswordKey)
87 | }
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/12306ForMac/Preferences/AdvancedPreferenceViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdvancePreferenceViewController.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/8/9.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import MASPreferences
11 |
12 | class AdvancedPreferenceViewController: NSViewController,MASPreferencesViewController {
13 |
14 | override func viewDidLoad() {
15 | super.viewDidLoad()
16 | // Do view setup here.
17 | if !isUseDama {
18 | logoutDama()
19 | }
20 | else {
21 | getBalance()
22 | }
23 | }
24 |
25 | override var nibName: String? {
26 | return "AdvancedPreferenceViewController"
27 | }
28 |
29 | // MARK: - MASPreferencesViewController
30 | override var identifier: String!{
31 | get {
32 | return "AdvancedPreferences"
33 | }
34 | set{}
35 | }
36 |
37 | var toolbarItemImage: NSImage! {
38 | return NSImage(named: NSImageNameUser)
39 | }
40 |
41 | var toolbarItemLabel: String! {
42 | return NSLocalizedString("打码兔", comment: "")
43 | }
44 |
45 | // MARK: - UserDefault
46 | var isUseDama:Bool {
47 | get {
48 | return AdvancedPreferenceManager.sharedInstance.isUseDama
49 | }
50 | set {
51 | AdvancedPreferenceManager.sharedInstance.isUseDama = newValue
52 | logger.info("dama = \(newValue)")
53 | if newValue {
54 | getBalance()
55 | NotificationCenter.default.post(name: Notification.Name.App.DidDamaGetBalance, object:true)
56 | }
57 | else {
58 | NotificationCenter.default.post(name: Notification.Name.App.DidDamaGetBalance, object:false)
59 | logoutDama()
60 | }
61 | }
62 | }
63 |
64 | var isUseDamaLogin:Bool {
65 | get{
66 | return AdvancedPreferenceManager.sharedInstance.isUseDamaLogin
67 | }
68 | set{
69 | AdvancedPreferenceManager.sharedInstance.isUseDamaLogin = newValue
70 | }
71 | }
72 |
73 | var isStopDamaWhenOperate:Bool {
74 | get{
75 | return AdvancedPreferenceManager.sharedInstance.isStopDamaWhenOperate
76 | }
77 | set{
78 | AdvancedPreferenceManager.sharedInstance.isStopDamaWhenOperate = newValue
79 | }
80 | }
81 |
82 | var isStopDamaWhenFail5:Bool {
83 | get{
84 | return AdvancedPreferenceManager.sharedInstance.isStopDamaWhenFail5
85 | }
86 | set{
87 | AdvancedPreferenceManager.sharedInstance.isStopDamaWhenFail5 = newValue
88 |
89 | }
90 | }
91 |
92 | var damaUser:String {
93 | get{
94 | return AdvancedPreferenceManager.sharedInstance.damaUser
95 | }
96 | set{
97 | AdvancedPreferenceManager.sharedInstance.damaUser = newValue
98 | }
99 | }
100 |
101 | var damaPassword:String{
102 | get{
103 | return AdvancedPreferenceManager.sharedInstance.damaPassword
104 | }
105 | set{
106 | AdvancedPreferenceManager.sharedInstance.damaPassword = newValue
107 | }
108 | }
109 | // MARK: - Control and action
110 | @IBOutlet weak var damaUserlbl: NSTextField!
111 | @IBOutlet weak var damaPasswordlbl: NSSecureTextField!
112 | @IBOutlet weak var balancelbl: NSTextField!
113 | @IBAction func clickGetBalanceOfDama(_ sender: AnyObject) {
114 | getBalance()
115 | }
116 |
117 | func getBalance() {
118 | let successHandler = { (balance:String) ->() in
119 | self.balancelbl.stringValue = "已登录:当前题分 \(balance)"
120 | }
121 |
122 | let failureHandler = { (error:NSError)->() in
123 | self.balancelbl.stringValue = "登录失败: \(translate(error))"
124 | }
125 |
126 | Dama.sharedInstance.getBalance(damaUser, password: damaPassword, success: successHandler, failure: failureHandler)
127 | }
128 |
129 | func logoutDama() {
130 | self.balancelbl.stringValue = "未登录"
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/12306ForMac/Preferences/FilterPreferenceViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // filterPreferenceViewController.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2017/4/2.
6 | // Copyright © 2017年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import MASPreferences
11 |
12 | class FilterTimeSpan:NSObject {
13 | var start:String = ""
14 | var end:String = ""
15 | var isCheck = true
16 |
17 | override var debugDescription: String {
18 | return "\(start)~\(end) \(isCheck)"
19 | }
20 | }
21 |
22 | class FilterPreferenceViewController: NSViewController {
23 |
24 | override var nibName: String? {
25 | return "FilterPreferenceViewController"
26 | }
27 |
28 | override var identifier: String!{
29 | get {
30 | return "FilterPreferences"
31 | }
32 | set {
33 | super.identifier = newValue
34 | }
35 | }
36 |
37 | var toolbarItemImage: NSImage! {
38 | return NSImage(named: "DefineFilter")
39 | }
40 |
41 | var toolbarItemLabel: String! {
42 | return NSLocalizedString("筛选", comment: "Filter")
43 | }
44 |
45 | @IBOutlet weak var startFilterTimeSpanTable: NSTableView!
46 | @IBOutlet weak var startTimeSpanComboBox: NSComboBox!
47 |
48 | @IBOutlet weak var endFilterTimeSpanTable: NSTableView!
49 | @IBOutlet weak var endTimeSpanComboBox: NSComboBox!
50 |
51 | override func viewDidLoad() {
52 | super.viewDidLoad()
53 | self.initStartFilterTimeSpans()
54 | self.initEndFilterTimeSpans()
55 | }
56 |
57 | var startFilterTimeSpans:[FilterTimeSpan] = [FilterTimeSpan]()
58 | private func initStartFilterTimeSpans() {
59 | var timeSpan = GeneralPreferenceManager.sharedInstance.userDefindStartFilterTimeSpan
60 | var timeStatus = GeneralPreferenceManager.sharedInstance.userDefindStartFilterTimeStatus
61 |
62 | startFilterTimeSpans = [FilterTimeSpan]()
63 |
64 | let count = min(timeSpan.count, timeStatus.count)
65 |
66 | for i in 0.. Int {
165 | if tableView == startFilterTimeSpanTable {
166 | return startFilterTimeSpans.count
167 | }
168 | else {
169 | return endFilterTimeSpans.count
170 | }
171 | }
172 |
173 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
174 | if tableView == startFilterTimeSpanTable {
175 | return startFilterTimeSpans[row]
176 | }
177 | else {
178 | return endFilterTimeSpans[row]
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/12306ForMac/Preferences/GeneralPreferenceManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GeneralPreferenceManager.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/8/10.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class GeneralPreferenceManager {
12 |
13 | static let sharedInstance = GeneralPreferenceManager()
14 |
15 | fileprivate let autoQuerySecondsKey = "autoQuerySeconds"
16 | fileprivate let isShowInvalidTicketKey = "isShowInvalidTicket"
17 | fileprivate let isShowNoTrainTicketKey = "isShowNoTrainTicket"
18 | fileprivate let isNotifyTicketKey = "isNotifyTicket"
19 | fileprivate let isNotifyLoginKey = "isNotifyLogin"
20 | fileprivate let notifyStrKey = "notifyStr"
21 | fileprivate let notifyLoginStrKey = "notifyLoginStr"
22 | fileprivate let isAutoQueryAfterFilterKey = "isAutoQueryAfterFilter"
23 | fileprivate let userDefindStartFilterTimeSpanKey = "userDefindStartFilterTimeSpan"
24 | fileprivate let userDefindEndFilterTimeSpanKey = "userDefindEndFilterTimeSpan"
25 | fileprivate let userDefindStartFilterTimeStatusKey = "userDefindStartFilterTimeStatus"
26 | fileprivate let userDefindEndFilterTimeStatusKey = "userDefindEndFilterTimeStatus"
27 |
28 | fileprivate let userDefaults = UserDefaults.standard
29 |
30 | fileprivate init()
31 | {
32 | registerUserDefault()
33 | }
34 |
35 | fileprivate func registerUserDefault()
36 | {
37 | let firstDefault = [autoQuerySecondsKey: 5,
38 | isShowInvalidTicketKey: true,
39 | isShowNoTrainTicketKey:true,
40 | isNotifyTicketKey:true,
41 | notifyStrKey:"订到票啦",
42 | isNotifyLoginKey:true,
43 | notifyLoginStrKey:"要登录啦",
44 | isAutoQueryAfterFilterKey:true,
45 | userDefindStartFilterTimeSpanKey:["00:00~06:00","06:00~12:00","12:00~18:00","18:00~24:00"],
46 | userDefindEndFilterTimeSpanKey:["00:00~06:00","06:00~12:00","12:00~18:00","18:00~24:00"],
47 | userDefindStartFilterTimeStatusKey: [true,true,true,true],
48 | userDefindEndFilterTimeStatusKey: [true,true,true,true],
49 | ]
50 | as [String : Any]
51 | userDefaults.register(defaults: firstDefault)
52 | }
53 |
54 | var autoQuerySeconds:Int {
55 | get{
56 | return userDefaults.object(forKey: autoQuerySecondsKey) as! Int
57 | }
58 | set{
59 | userDefaults.set(newValue, forKey: autoQuerySecondsKey)
60 | }
61 | }
62 |
63 | var isShowInvalidTicket:Bool {
64 | get{
65 | return userDefaults.object(forKey: isShowInvalidTicketKey) as! Bool
66 | }
67 | set{
68 | userDefaults.set(newValue, forKey: isShowInvalidTicketKey)
69 | }
70 | }
71 |
72 | var isShowNoTrainTicket:Bool {
73 | get{
74 | return userDefaults.object(forKey: isShowNoTrainTicketKey) as! Bool
75 | }
76 | set{
77 | userDefaults.set(newValue, forKey: isShowNoTrainTicketKey)
78 | }
79 | }
80 |
81 | var isNotifyTicket:Bool {
82 | get{
83 | return userDefaults.object(forKey: isNotifyTicketKey) as! Bool
84 | }
85 | set{
86 | userDefaults.set(newValue, forKey: isNotifyTicketKey)
87 | }
88 | }
89 |
90 | var notifyStr:String {
91 | get{
92 | return userDefaults.object(forKey: notifyStrKey) as! String
93 | }
94 | set{
95 | userDefaults.set(newValue, forKey: notifyStrKey)
96 | }
97 | }
98 |
99 | var isNotifyLogin:Bool {
100 | get{
101 | return userDefaults.object(forKey: isNotifyLoginKey) as! Bool
102 | }
103 | set{
104 | userDefaults.set(newValue, forKey: isNotifyLoginKey)
105 | }
106 | }
107 |
108 | var notifyLoginStr:String {
109 | get{
110 | return userDefaults.object(forKey: notifyLoginStrKey) as! String
111 | }
112 | set{
113 | userDefaults.set(newValue, forKey: notifyLoginStrKey)
114 | }
115 | }
116 |
117 | var isAutoQueryAfterFilter:Bool {
118 | get{
119 | return userDefaults.object(forKey: isAutoQueryAfterFilterKey) as! Bool
120 | }
121 | set{
122 | userDefaults.set(newValue, forKey: isAutoQueryAfterFilterKey)
123 | }
124 | }
125 |
126 | var userDefindStartFilterTimeSpan:[String] {
127 | get{
128 | return userDefaults.array(forKey: userDefindStartFilterTimeSpanKey) as! [String]
129 | }
130 | set{
131 | userDefaults.set(newValue, forKey: userDefindStartFilterTimeSpanKey)
132 | }
133 | }
134 |
135 | var userDefindEndFilterTimeSpan:[String] {
136 | get{
137 | return userDefaults.array(forKey: userDefindEndFilterTimeSpanKey) as! [String]
138 | }
139 | set{
140 | userDefaults.set(newValue, forKey: userDefindEndFilterTimeSpanKey)
141 | }
142 | }
143 |
144 | var userDefindStartFilterTimeStatus:[Bool] {
145 | get{
146 | return userDefaults.array(forKey: userDefindStartFilterTimeStatusKey) as! [Bool]
147 | }
148 | set{
149 | userDefaults.set(newValue, forKey: userDefindStartFilterTimeStatusKey)
150 | }
151 | }
152 |
153 | var userDefindEndFilterTimeStatus:[Bool] {
154 | get{
155 | return userDefaults.array(forKey: userDefindEndFilterTimeStatusKey) as! [Bool]
156 | }
157 | set{
158 | userDefaults.set(newValue, forKey: userDefindEndFilterTimeStatusKey)
159 | }
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/12306ForMac/Preferences/GeneralPreferenceViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GeneralPreferenceViewController.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/8/9.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import MASPreferences
11 |
12 | class GeneralPreferenceViewController: NSViewController, MASPreferencesViewController {
13 |
14 | override func viewDidLoad() {
15 | super.viewDidLoad()
16 | }
17 |
18 | override var nibName: String? {
19 | return "GeneralPreferenceViewController"
20 | }
21 |
22 | override var identifier: String!{
23 | get {
24 | return "GeneralPreferences"
25 | }
26 | set {
27 | super.identifier = newValue
28 | }
29 | }
30 |
31 | var toolbarItemImage: NSImage! {
32 | return NSImage(named: NSImageNamePreferencesGeneral)
33 | }
34 |
35 | var toolbarItemLabel: String! {
36 | return NSLocalizedString("通用", comment: "General")
37 | }
38 |
39 | var autoQuerySeconds: Int {
40 | get{
41 | return GeneralPreferenceManager.sharedInstance.autoQuerySeconds
42 | }
43 | set{
44 | GeneralPreferenceManager.sharedInstance.autoQuerySeconds = newValue
45 | }
46 | }
47 |
48 | var isShowNoTrainTicket: Bool {
49 | get{
50 | return GeneralPreferenceManager.sharedInstance.isShowNoTrainTicket
51 | }
52 | set{
53 | GeneralPreferenceManager.sharedInstance.isShowNoTrainTicket = newValue
54 | if !newValue {
55 | willChangeValue(forKey: "isShowInvalidTicket")
56 | isShowInvalidTicket = false
57 | didChangeValue(forKey: "isShowInvalidTicket")
58 | }
59 | else {
60 | NotificationCenter.default.post(name: Notification.Name.App.DidRefilterQueryTicket, object:nil)
61 | }
62 | }
63 | }
64 |
65 | var isShowInvalidTicket: Bool {
66 | get{
67 | return GeneralPreferenceManager.sharedInstance.isShowInvalidTicket
68 | }
69 | set{
70 | GeneralPreferenceManager.sharedInstance.isShowInvalidTicket = newValue
71 | if newValue {
72 | willChangeValue(forKey: "isShowNoTrainTicket")
73 | isShowNoTrainTicket = true
74 | didChangeValue(forKey: "isShowNoTrainTicket")
75 | }
76 | else {
77 | NotificationCenter.default.post(name: Notification.Name.App.DidRefilterQueryTicket, object:nil)
78 | }
79 | }
80 | }
81 |
82 | var isAutoQueryAfterFilter: Bool {
83 | get{
84 | return GeneralPreferenceManager.sharedInstance.isAutoQueryAfterFilter
85 | }
86 | set{
87 | GeneralPreferenceManager.sharedInstance.isAutoQueryAfterFilter = newValue
88 | }
89 | }
90 |
91 | var isNotifyTicket: Bool {
92 | get{
93 | return GeneralPreferenceManager.sharedInstance.isNotifyTicket
94 | }
95 | set{
96 | GeneralPreferenceManager.sharedInstance.isNotifyTicket = newValue
97 | NotifySpeaker.sharedInstance.notify()
98 | }
99 | }
100 |
101 | var notifyStr: String {
102 | get{
103 | return GeneralPreferenceManager.sharedInstance.notifyStr
104 | }
105 | set{
106 | GeneralPreferenceManager.sharedInstance.notifyStr = newValue
107 | }
108 | }
109 |
110 | var isNotifyLogin: Bool {
111 | get{
112 | return GeneralPreferenceManager.sharedInstance.isNotifyLogin
113 | }
114 | set{
115 | GeneralPreferenceManager.sharedInstance.isNotifyLogin = newValue
116 | NotifySpeaker.sharedInstance.notifyLogin()
117 | }
118 | }
119 |
120 | var notifyLoginStr: String {
121 | get{
122 | return GeneralPreferenceManager.sharedInstance.notifyLoginStr
123 | }
124 | set{
125 | GeneralPreferenceManager.sharedInstance.notifyLoginStr = newValue
126 | }
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/12306ForMac/RealmModel/DataManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataManager.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/10/9.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import FMDB
11 |
12 | let DATA_DIRECTORY = "\(NSHomeDirectory())/Library/Application Support/\(Bundle.main.bundleIdentifier!)"
13 | let DATA_PATH = "\(DATA_DIRECTORY)/12306ForMac.db"
14 |
15 | class DataManger {
16 | static let sharedInstance = DataManger()
17 |
18 | fileprivate init() {
19 | let isExistDirectory:Bool = FileManager.default.fileExists(atPath: DATA_DIRECTORY, isDirectory: nil)
20 | if !isExistDirectory {
21 | do{
22 | try FileManager.default.createDirectory(atPath: DATA_DIRECTORY, withIntermediateDirectories: true, attributes: nil)
23 | }
24 | catch {
25 | logger.error("Creat \(DATA_DIRECTORY) fail")
26 | }
27 | }
28 | }
29 |
30 | func queryAllUsers() -> [UserX] {
31 | var users=[UserX]()
32 | let db = FMDatabase(path: DATA_PATH)
33 |
34 | if !(db?.open())! {
35 | logger.error("Could not open db")
36 | return users
37 | }
38 |
39 | db?.executeStatements("create table if not exists 'User' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,user varchar, password varchar)")
40 |
41 | let rs = try! db?.executeQuery("select * from User", values: nil)
42 | //if rs.counto
43 | while(rs?.next())!{
44 | let newUser = UserX()
45 | newUser.id = (rs?.long(forColumn: "id"))!
46 | newUser.name = (rs?.string(forColumn: "user"))!
47 | newUser.password = (rs?.string(forColumn: "password"))!
48 |
49 | users.append(newUser)
50 | }
51 | db?.close()
52 |
53 | return users
54 | }
55 |
56 | func updateUser(_ newUser:UserX) {
57 | let db = FMDatabase(path: DATA_PATH)
58 |
59 | if !(db?.open())! {
60 | logger.error("Could not open db")
61 | return
62 | }
63 |
64 | do {
65 | try db?.executeUpdate("update User set password=? where id=?", values: [newUser.password, newUser.id])
66 | }
67 | catch{
68 | logger.error("updateUser error:\(error)")
69 | }
70 |
71 | defer{
72 | db?.close()
73 | }
74 | }
75 |
76 | func inserUser(_ newUser:UserX) {
77 | let db = FMDatabase(path: DATA_PATH)
78 |
79 | if !(db?.open())! {
80 | logger.error("Could not open db")
81 | return
82 | }
83 |
84 | do {
85 | try db?.executeUpdate("insert into User (user,password) values (?, ?)", values: [newUser.name, newUser.password])
86 | }
87 | catch {
88 | logger.error("insertUser error:\(error)")
89 | }
90 |
91 | defer{
92 | print("db close")
93 | db?.close()
94 | }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/12306ForMac/RealmModel/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Person.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 16/2/19.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | class UserX {
10 | var id:Int = -1
11 | var name = ""
12 | var password = ""
13 | }
14 |
--------------------------------------------------------------------------------
/12306ForMac/Resources/Credits.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg936\cocoartf1404\cocoasubrtf130
2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;}
3 | {\colortbl;\red255\green255\blue255;\red0\green2\blue255;}
4 | \paperw11900\paperh16840\vieww17700\viewh10020\viewkind0
5 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0
6 |
7 | \f0\b\fs24 \cf0 Engineering & Design\
8 |
9 | \b0 fancymax\
10 | {\field{\*\fldinst{HYPERLINK "http://fancywt.cn"}}{\fldrslt http://fancywt.cn}}\
11 | }
--------------------------------------------------------------------------------
/12306ForMac/Service/Dama.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dama.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/8/10.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 | import SwiftyJSON
12 |
13 | fileprivate func md5(_ string: String) -> String {
14 | let context = UnsafeMutablePointer.allocate(capacity: 1)
15 | var digest = Array(repeating:0, count:Int(CC_MD5_DIGEST_LENGTH))
16 | CC_MD5_Init(context)
17 | CC_MD5_Update(context, string, CC_LONG(string.lengthOfBytes(using: String.Encoding.utf8)))
18 | CC_MD5_Final(&digest, context)
19 | context.deallocate(capacity: 1)
20 | var hexString = ""
21 | for byte in digest {
22 | hexString += String(format:"%02x", byte)
23 | }
24 |
25 | return hexString
26 | }
27 |
28 | extension Data {
29 | func hexedString() -> String {
30 | return reduce("") {$0 + String(format: "%02x", $1)}
31 | }
32 |
33 | func MD5() -> Data {
34 | var result = Data(count: Int(CC_MD5_DIGEST_LENGTH))
35 | _ = result.withUnsafeMutableBytes {resultPtr in
36 | self.withUnsafeBytes {(bytes: UnsafePointer) in
37 | CC_MD5(bytes, CC_LONG(count), resultPtr)
38 | }
39 | }
40 | return result
41 | }
42 | }
43 |
44 | class Dama: NSObject {
45 |
46 | static let sharedInstance = Dama()
47 |
48 | let AppId:String = "43327"
49 | let AppKey:String = "36f3ff0b2f66f2b3f1cd9b5953095858"
50 |
51 | private override init() {
52 | super.init()
53 | }
54 |
55 | private func getCurrentFileHex(ofImage image:NSImage)->String {
56 | let originData = image.tiffRepresentation
57 | let imageRep = NSBitmapImageRep(data: originData!)
58 | let imageData = imageRep!.representation(using: .PNG, properties: ["NSImageCompressionFactor":1.0])!
59 |
60 | let result = imageData.hexedString()
61 | return result
62 | }
63 |
64 | private func getpwd(_ user:String,password:String) -> String{
65 | let nameMD5 = md5(user)
66 | let passwordMD5 = md5(password)
67 | let x1MD5 = md5(nameMD5 + passwordMD5)
68 | let x2MD5 = md5(AppKey + x1MD5)
69 | return x2MD5
70 | }
71 |
72 | private func getsign(ofUser user:String) ->String {
73 | let key = AppKey + user
74 | let x1MD5 = md5(key)
75 | let x2 = x1MD5[x1MD5.startIndex...x1MD5.index(x1MD5.startIndex, offsetBy: 7)]
76 | return x2
77 | }
78 |
79 | private func getFileDataSign2(ofImage image:NSImage,user:String)->String{
80 | let originData = image.tiffRepresentation
81 | let imageRep = NSBitmapImageRep(data: originData!)
82 | let imageData = imageRep!.representation(using: .PNG, properties: ["NSImageCompressionFactor":1.0])!
83 |
84 | let AppKeyData = AppKey.data(using: String.Encoding.utf8)
85 | let AppUserData = user.data(using: String.Encoding.utf8)
86 |
87 | var finalData = Data()
88 | finalData.append(AppKeyData!)
89 | finalData.append(AppUserData!)
90 | finalData.append(imageData)
91 |
92 | let x1MD5 = finalData.MD5().hexedString()
93 | let startIndex = x1MD5.startIndex
94 | return x1MD5[startIndex...x1MD5.index(startIndex, offsetBy: 7)]
95 | }
96 |
97 | func getBalance(_ user:String,password:String,success:@escaping (String)->Void,failure:@escaping (NSError)->Void){
98 |
99 | let url = "http://api.dama2.com:7766/app/d2Balance"
100 | let pwd = getpwd(user,password: password)
101 | let sign = getsign(ofUser: user)
102 |
103 | let urlX = "\(url)?appID=\(AppId)&user=\(user)&pwd=\(pwd)&sign=\(sign)"
104 | Alamofire.request(urlX).responseJSON { response in
105 | switch(response.result){
106 | case .failure(let error):
107 | failure(error as NSError)
108 | case .success(let data):
109 | if let balanceVal = JSON(data)["balance"].string {
110 | success(balanceVal)
111 | }
112 | else {
113 | if let errorCode = JSON(data)["ret"].int {
114 | failure(DamaError.errorWithCode(errorCode))
115 | }
116 | }
117 | }
118 | }
119 | }
120 |
121 | func dama(_ user:String,password:String,ofImage image:NSImage,success:@escaping (String)->Void,failure:@escaping (NSError)->Void){
122 |
123 | let pwd = getpwd(user,password: password)
124 | let sign = getFileDataSign2(ofImage: image,user: user)
125 | let type = "287"
126 | let fileData = getCurrentFileHex(ofImage: image)
127 | let url = "http://api.dama2.com:7766/app/d2File"
128 |
129 | let params = ["appID":AppId,
130 | "user":user,
131 | "pwd":pwd,
132 | "type":type,
133 | "fileData":fileData,
134 | "sign":sign]
135 | Alamofire.request(url, method:.post, parameters: params).responseJSON { response in
136 | switch(response.result) {
137 | case .failure(let error):
138 | failure(error as NSError)
139 | case .success(let data):
140 | if let imageCode = JSON(data)["result"].string {
141 | success(imageCode)
142 | }
143 | else {
144 | if let errorCode = JSON(data)["ret"].int {
145 | failure(DamaError.errorWithCode(errorCode))
146 | }
147 | }
148 | }
149 | }
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/12306ForMac/Service/DamaError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DamaError.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/8/13.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct DamaError {
12 | static let Domain = "com.Dama2.error"
13 |
14 | static let errorDic = [
15 | -1: "系统错误",
16 | -100: "无效软件KEY",
17 | -101: "黑名单机器信息",
18 | -102: "黑名单IP",
19 | -103: "用户不存在",
20 | -104: "用户密码错",
21 | -105: "用户被停用",
22 | -106: "无效服务器类型",
23 | -107: "无效用户类型",
24 | -108: "动态码校验失败,需要发送动态码",
25 | -109: "无效的黑名单类型",
26 | -110: "参数为空",
27 | -111: "验证码字符类型校验错误",
28 | -112: "APPCODETYPEID不存在",
29 | -113: "CODETYPEID不存在",
30 | -114: "无效充值卡密",
31 | -115: "无效的验证码结果状态",
32 | -116: "无效的提现模式",
33 | -117: "无效软件类型",
34 | -118: "无效动态验证码发送类型",
35 | -119: "打码工人选择了错误的字符类型",
36 | -200: "无负载均衡服务器",
37 | -201: "均衡服务器未配置",
38 | -202: "没有配置全客户端类型的版本",
39 | -203: "没有可用验证码",
40 | -204: "提交验证码结果时,没有指定的验证码",
41 | -205: "没有打码工人统计数据",
42 | -206: "没有指定的验证码ID记录",
43 | -300: "COOKIE长度溢出",
44 | -301: "用户名重复",
45 | -302: "报告验证码结果状态失败(没更新到数据)",
46 | -303: "验证码尚未打码完成",
47 | -304: "余额不足,题分不足",
48 | -305: "验证码状态不允许",
49 | -306: "用户动态码发送配置不正确",
50 | -400: "动态验证码发送超限",
51 | -401: "动态码超时",
52 | -402: "功能不支持",
53 | -403: "发送动态验证码失败",
54 | -404: "参数非法",
55 | -406: "用户无权使用该APP",
56 | -9990: "decrypt password error",
57 | -9991: "appID error",
58 | -9992: "用户不存在",
59 | -9993: "用户密码错",
60 | -9994: "no file",
61 | -9995: "id error",
62 | -9996: "email error",
63 | -9997: "tel error",
64 | -9998: "db name error",
65 | -9999: "encrypt key error",
66 | -10000: "busy",
67 | -10020: "program error",
68 | -10021: "db error",
69 | -10022: "db execute error",
70 | -10023: "URL错误",
71 | -10025: "POST数据中包含了多个文件",
72 | -10027: "sign error",
73 | -10028: "extName error",
74 | -10029: "fileData error/fileDataBase64 error"]
75 |
76 | static func errorWithCode(_ code:Int)->NSError{
77 | if errorDic.keys.contains(code) {
78 | return errorWithCode(code, failureReason: "打码兔错误: \(errorDic[code]!)")
79 | }
80 | else {
81 | return errorWithCode(code, failureReason: "未知错误, 错误码 = \(code)")
82 | }
83 | }
84 |
85 | static func errorWithCode(_ code: Int, failureReason: String) -> NSError {
86 | let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
87 | return NSError(domain: Domain, code: code, userInfo: userInfo)
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/12306ForMac/Service/Service+Utilities.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Service+Utilities.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/3/19.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | extension Service {
13 | func getWanIP(success:(wanIP:String)->()){
14 | let url = "http://ifconfig.me/ip"
15 | Service.Manager.request(.GET, url).responseString(completionHandler:{ response in
16 | switch (response.result){
17 | case .Failure(let error):
18 | print(error)
19 | case .Success(let content):
20 | print(content)
21 | success(wanIP: content)
22 | }})}
23 | }
--------------------------------------------------------------------------------
/12306ForMac/Service/Service.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ServiceBase.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 15/8/4.
6 | // Copyright (c) 2015年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 | import PromiseKit
12 |
13 | class Service {
14 |
15 | static let sharedInstance = Service()
16 |
17 | private init(){ }
18 |
19 | static var Manager : Alamofire.SessionManager = {
20 | // Create the server trust policies
21 | let serverTrustPolicies: [String: ServerTrustPolicy] = ["kyfw.12306.cn": ServerTrustPolicy.performDefaultEvaluation(validateHost: true)]
22 |
23 | // Create custom manager
24 | let headers = [
25 | "refer": "https://kyfw.12306.cn/otn/leftTicket/init",
26 | "Host": "kyfw.12306.cn",
27 | "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Safari/604.1.38",
28 | "Connection" : "keep-alive"]
29 | let configuration = URLSessionConfiguration.default
30 |
31 | configuration.httpCookieAcceptPolicy = .always
32 | configuration.httpAdditionalHeaders = headers
33 | configuration.timeoutIntervalForRequest = 10
34 |
35 | let manager = Alamofire.SessionManager(
36 | configuration: configuration,
37 | serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
38 | )
39 | return manager
40 | }()
41 |
42 | @objc var passport_appId = "otn"
43 | @objc var passport_login = "https://kyfw.12306.cn/passport/web/login"
44 | @objc var passport_captcha = "https://kyfw.12306.cn/passport/captcha/captcha-image"
45 | @objc var passport_authuam = "https://kyfw.12306.cn/passport/web/auth/uamtk"
46 | @objc var passport_captcha_check = "https://kyfw.12306.cn/passport/captcha/captcha-check"
47 | @objc var passport_authclient = "uamauthclient"
48 | @objc var passport_loginPage = "login/init"
49 | @objc var passport_okPage = "index/initMy12306"
50 |
51 | func getConfigFromInitContent(_ content:String) {
52 | func getMatchByKey(_ key:String, matchVar:inout String) {
53 | if let matches = Regex("var \(key) = '([^']+)'").getMatches(content){
54 | matchVar = matches[0][0]
55 | }
56 | else{
57 | logger.error("fail to get \(key)")
58 | }
59 | }
60 |
61 | getMatchByKey(#keyPath(passport_appId),matchVar: &passport_appId)
62 | getMatchByKey(#keyPath(passport_login),matchVar: &passport_login)
63 | getMatchByKey(#keyPath(passport_captcha),matchVar: &passport_captcha)
64 | getMatchByKey(#keyPath(passport_authuam),matchVar: &passport_authuam)
65 | getMatchByKey(#keyPath(passport_captcha_check),matchVar: &passport_captcha_check)
66 | getMatchByKey(#keyPath(passport_authclient),matchVar: &passport_authclient)
67 | }
68 |
69 | func jc_getcookie(key:String) -> String? {
70 | return nil
71 | }
72 |
73 | let referKey = "refer"
74 | let referValueForLoginInit = "https://kyfw.12306.cn/otn/login/init"
75 |
76 | func requestDynamicJs(_ jsName:String,referHeader:[String:String])->Promise{
77 | return Promise{ fulfill, reject in
78 | let url = "https://kyfw.12306.cn/otn/dynamicJs/" + jsName
79 | Service.Manager.request(url, headers:referHeader).response(completionHandler:{ _ in
80 | fulfill()
81 | })
82 | }
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/12306ForMac/Service/ServiceError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ServiceError.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/3/9.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | func translate(_ error:NSError)->String{
12 | logger.error(error)
13 |
14 | if error.domain == "NSURLErrorDomain"{
15 | if error.code == -1009 {
16 | return "网络连接失败,请检查连接或稍后再试"
17 | }
18 | }
19 | if let err = error.localizedFailureReason{
20 | return err
21 | }
22 | else{
23 | return error.localizedDescription
24 | }
25 | }
26 |
27 | struct ServiceError {
28 | static let Domain = "com.12306Service.error"
29 |
30 | enum Code: Int {
31 | case loginFailed = -7000
32 | case queryTicketFailed = -7001
33 | case getRandCodeFailed = -7003
34 | case checkRandCodeFailed = -7004
35 | case checkUserFailed = -7005
36 | case submitOrderFailed = -7006
37 | case checkOrderInfoFailed = -7007
38 | case confirmSingleForQueueFailed = -7008
39 | case cancelOrderFailed = -7009
40 | case zeroOrderFailed = -7010
41 | case queryTicketNoFailed = -7011
42 | case queryTicketPriceFailed = -7012
43 | case getPassengerFailed = -7013
44 | case loginUserFailed = -7014
45 | case autoSumbitOrderFailed = -7015
46 | case queryOrderWaitTimeFailed = -7016
47 | case getQueueCountFailed = -7017
48 | }
49 |
50 | static let errorDic = [
51 | Code.loginFailed:"登录失败",
52 | Code.queryTicketFailed:"未能查到任何车次,请检查查询设置",
53 | Code.autoSumbitOrderFailed: "自动提交订单失败",
54 | Code.getRandCodeFailed: "获取验证码失败",
55 | Code.checkRandCodeFailed: "验证码错误",
56 | Code.checkUserFailed: "非登录状态,需要重新登录",
57 | Code.submitOrderFailed: "提交订单失败",
58 | Code.checkOrderInfoFailed: "订单信息错误",
59 | Code.confirmSingleForQueueFailed: "锁定订单失败",
60 | Code.getQueueCountFailed: "获取排队信息失败",
61 | Code.queryOrderWaitTimeFailed: "查询订单剩余时间失败",
62 | Code.cancelOrderFailed: "取消订单失败",
63 | Code.zeroOrderFailed:"您没有历史订单",
64 | Code.queryTicketNoFailed:"查询车次详细信息失败",
65 | Code.getPassengerFailed:"查询乘客信息失败",
66 | Code.queryTicketPriceFailed:"查询票价失败",
67 | Code.loginUserFailed:"登录用户失败"]
68 |
69 | static func errorWithCode(_ code:Code)->NSError{
70 | if errorDic.keys.contains(code) {
71 | return errorWithCode(code, failureReason: errorDic[code]!)
72 | }
73 | else {
74 | return errorWithCode(code, failureReason: "未知错误, 错误码 = \(code.rawValue)")
75 | }
76 | }
77 |
78 | static func errorWithCode(_ code: Code, failureReason: String) -> NSError {
79 | let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
80 | return NSError(domain: Domain, code: code.rawValue, userInfo: userInfo)
81 | }
82 |
83 | static func isCheckRandCodeError(_ error:NSError)->Bool {
84 | if error.code == Code.checkRandCodeFailed.rawValue {
85 | return true
86 | }
87 | else {
88 | return false
89 | }
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/12306ForMac/Sheets/LoginWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginWindowController.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 15/8/10.
6 | // Copyright (c) 2015年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class LoginWindowController: BaseWindowController{
12 |
13 | @IBOutlet weak var passWord: NSSecureTextField!
14 | @IBOutlet weak var userName: AutoCompleteTextField!
15 | @IBOutlet weak var loginImage: RandCodeImageView2!
16 | @IBOutlet weak var nextImageBtn: NSButton!
17 |
18 | var users = [UserX]()
19 | var isAutoLogin = false
20 |
21 | private var isLogin = false
22 | private var spaceKeyboardMonitor:Any!
23 |
24 | override var windowNibName: String{
25 | return "LoginWindowController"
26 | }
27 |
28 | override func windowDidLoad() {
29 | if let lastName = QueryDefaultManager.sharedInstance.lastUserName,
30 | let lastPassword = QueryDefaultManager.sharedInstance.lastUserPassword{
31 | userName.stringValue = lastName
32 | passWord.stringValue = lastPassword
33 | }
34 |
35 | userName.tableViewDelegate = self
36 |
37 | users = DataManger.sharedInstance.queryAllUsers()
38 |
39 | loadImage()
40 |
41 | //增加对Space按键的支持
42 | spaceKeyboardMonitor = NSEvent.addLocalMonitorForEvents(matching: NSKeyDownMask) { [weak self] (theEvent) -> NSEvent? in
43 | //Space Key
44 | if (theEvent.keyCode == 49){
45 | if let weakSelf = self {
46 | weakSelf.clickOK(nil)
47 | }
48 | return nil
49 | }
50 | return theEvent
51 | }
52 | }
53 |
54 | private func handlerAfterSuccess(){
55 | QueryDefaultManager.sharedInstance.lastUserName = userName.stringValue
56 | QueryDefaultManager.sharedInstance.lastUserPassword = passWord.stringValue
57 |
58 | //save user
59 | let updateUser = users.filter({$0.name == userName.stringValue})
60 | if updateUser.count > 0 {
61 | for user in updateUser where user.password != passWord.stringValue {
62 | user.password = passWord.stringValue
63 | DataManger.sharedInstance.updateUser(user)
64 | }
65 | }
66 | else {
67 | let user = UserX()
68 | user.name = userName.stringValue
69 | user.password = passWord.stringValue
70 | DataManger.sharedInstance.inserUser(user)
71 | }
72 |
73 | self.dismissWithModalResponse(NSModalResponseOK)
74 | }
75 |
76 | func loadImage(){
77 | self.loginImage.clearRandCodes()
78 | self.startLoadingTip("正在加载...")
79 | self.nextImageBtn.isEnabled = false
80 |
81 | let autoLoginHandler = {(image:NSImage)->() in
82 | self.startLoadingTip("自动打码...")
83 |
84 | Dama.sharedInstance.dama(AdvancedPreferenceManager.sharedInstance.damaUser,
85 | password: AdvancedPreferenceManager.sharedInstance.damaPassword,
86 | ofImage: image,
87 | success: {imageCode in
88 | self.stopLoadingTip()
89 | self.loginImage.drawDamaCodes(imageCode)
90 | self.clickOK(nil)
91 | },
92 | failure: {error in
93 | self.stopLoadingTip()
94 | self.showTip(translate(error))
95 | })
96 | }
97 |
98 | let successHandler = {(image:NSImage) -> () in
99 | self.loginImage.image = image
100 | self.stopLoadingTip()
101 | self.nextImageBtn.isEnabled = true
102 |
103 | if ((self.isAutoLogin)&&(AdvancedPreferenceManager.sharedInstance.isUseDama)) {
104 | autoLoginHandler(image)
105 | }
106 | }
107 | let failureHandler = {(error:NSError) -> () in
108 | self.stopLoadingTip()
109 | self.nextImageBtn.isEnabled = true
110 | self.showTip(translate(error))
111 | }
112 | Service.sharedInstance.preLoginFlow(success: successHandler,failure: failureHandler)
113 | }
114 |
115 | override func dismissWithModalResponse(_ response:NSModalResponse)
116 | {
117 | if window != nil {
118 | if window!.sheetParent != nil {
119 | window!.sheetParent!.endSheet(window!,returnCode: response)
120 | NSEvent.removeMonitor(spaceKeyboardMonitor!)
121 | }
122 | }
123 | }
124 |
125 | @IBAction func clickNextImage(_ sender: NSButton)
126 | {
127 | loadImage()
128 | }
129 |
130 | @IBAction func clickOK(_ sender:AnyObject?){
131 | if userName.stringValue == "" || passWord.stringValue == "" {
132 | self.showTip("请先输入用户名和密码")
133 | return
134 | }
135 | if loginImage.randCodeStr == nil {
136 | self.showTip("请先选择验证码")
137 | return
138 | }
139 |
140 | if isLogin {
141 | return
142 | }
143 |
144 | isLogin = true
145 | self.startLoadingTip("正在登录...")
146 |
147 | let failureHandler = {(error:NSError) -> () in
148 | self.isLogin = false
149 | self.stopLoadingTip()
150 | self.showTip(translate(error))
151 |
152 | if ServiceError.isCheckRandCodeError(error) {
153 | DispatchQueue.main.asyncAfter(deadline: .now() + Double(2)) {
154 | self.loadImage()
155 | }
156 | }
157 | else {
158 | //刷新太快,请稍后重试
159 | }
160 | }
161 |
162 | let successHandler = {
163 | self.stopLoadingTip()
164 | self.handlerAfterSuccess()
165 | }
166 |
167 | Service.sharedInstance.loginFlow(user: userName.stringValue, passWord: passWord.stringValue, randCodeStr: loginImage.randCodeStr!, success: successHandler, failure: failureHandler)
168 | }
169 |
170 | @IBAction func clickCancel(_ button:NSButton){
171 | dismissWithModalResponse(NSModalResponseCancel)
172 | }
173 | }
174 |
175 | // MARK: - AutoCompleteTableViewDelegate
176 | extension LoginWindowController: AutoCompleteTableViewDelegate{
177 | func textField(_ textField: NSTextField, completions words: [String], forPartialWordRange charRange: NSRange, indexOfSelectedItem index: Int) -> [String] {
178 | var matches = [String]()
179 | for user in self.users {
180 | if let _ = user.name.range(of: textField.stringValue, options: NSString.CompareOptions.anchored)
181 | {
182 | matches.append(user.name)
183 | }
184 | }
185 | return matches
186 | }
187 |
188 | func textField(_ textField:NSTextField,didSelectItem item: String){
189 | for user in self.users where user.name == item {
190 | self.passWord.stringValue = user.password
191 | }
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/12306ForMac/StationData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StationDataService.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 15/8/4.
6 | // Copyright (c) 2015年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Station {
12 | //首字母拼音 比如 bj
13 | var FirstLetter:String
14 | //车站名
15 | var Name:String
16 | //电报码
17 | var Code:String
18 | //全拼
19 | var Spell:String
20 | }
21 |
22 | // "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js"
23 | class StationNameJs{
24 | fileprivate static let sharedManager = StationNameJs()
25 | class var sharedInstance: StationNameJs {
26 | return sharedManager
27 | }
28 |
29 | var allStation:[Station]
30 |
31 | var allStationMap:[String:Station]
32 |
33 | fileprivate init()
34 | {
35 | self.allStation = [Station]()
36 | self.allStationMap = [String:Station]()
37 |
38 | //1.8983
39 | let path = Bundle.main.path(forResource: "station_name", ofType: "js")
40 | let stationInfo = try! NSString(contentsOfFile: path!, encoding: String.Encoding.utf8.rawValue) as String
41 |
42 | if let matches = Regex("@[a-z]+\\|([^\\|]+)\\|([a-z]+)\\|([a-z]+)\\|([a-z]+)\\|").getMatches(stationInfo)
43 | {
44 | for match in matches
45 | {
46 | let oneStation = Station(FirstLetter:match[3],Name:match[0],Code:match[1],Spell:match[2])
47 | self.allStation.append(oneStation)
48 | self.allStationMap[oneStation.Name] = oneStation
49 | }
50 | }
51 |
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/12306ForMac/TicketViewControllers/PassengerSelectViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PassengerSelectViewController.swift
3 | //
4 | //
5 | // Created by fancymax on 15/10/7.
6 | //
7 | //
8 |
9 | import Cocoa
10 |
11 | class PassengerSelectViewController: NSViewController,NSTableViewDataSource,NSTableViewDelegate{
12 | @IBOutlet weak var passengerTable: NSTableView!
13 | var passengers = [PassengerDTO]()
14 | let maxSelectedPassengerCount = 5
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 | }
19 |
20 | func reloadPassenger(_ passengersToShow:[PassengerDTO]){
21 | self.passengers = passengersToShow
22 | self.passengerTable.reloadData()
23 | }
24 |
25 | func numberOfRows(in tableView: NSTableView) -> Int {
26 | return self.passengers.count
27 | }
28 |
29 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
30 | return self.passengers[row]
31 | }
32 |
33 | @IBAction func checkPassenger(_ sender:NSButton){
34 | if isMaxPassengerNumber(exclude:sender.title) {
35 | sender.state = NSOffState
36 |
37 | for passenger in passengers{
38 | if passenger.passenger_name == sender.title {
39 | passenger.isChecked = false
40 | break
41 | }
42 | }
43 |
44 | return
45 | }
46 |
47 | let row = passengerTable.row(for: sender)
48 | NotificationCenter.default.post(name: Notification.Name.App.DidCheckPassenger, object: passengers[row].passenger_id_no)
49 | }
50 |
51 | func isMaxPassengerNumber(exclude excludePassenger:String)->Bool {
52 | var count = 0
53 | for passenger in passengers{
54 | if (passenger.isChecked)&&(passenger.passenger_name != excludePassenger) {
55 | count += 1
56 | }
57 | }
58 |
59 | if count >= maxSelectedPassengerCount {
60 | return true
61 | }
62 | else {
63 | return false
64 | }
65 | }
66 |
67 | func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
68 | return AutoCompleteTableRowView()
69 | }
70 |
71 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
72 | return false
73 | }
74 |
75 | deinit{
76 | NotificationCenter.default.removeObserver(self)
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/12306ForMac/TicketViewControllers/PassengerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HorizontalListObjectViewController.swift
3 | //
4 | //
5 | // Created by fancymax on 15/10/5.
6 | //
7 | //
8 |
9 | import Cocoa
10 |
11 | class PassengerViewController: NSViewController {
12 |
13 | var passenger:PassengerDTO?
14 |
15 | @IBOutlet weak var passengerCheckBox: NSButton!
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | passengerCheckBox.title = passenger!.passenger_name
21 | }
22 |
23 | @IBAction func unSelectPassenger(_ sender: NSButton) {
24 | passenger?.isChecked = false
25 | sender.state = NSOnState
26 | self.view.isHidden = true
27 | }
28 |
29 | func select(){
30 | if passenger!.isChecked {
31 | self.view.isHidden = false
32 | }
33 | else{
34 | self.view.isHidden = true
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/12306ForMac/TicketViewControllers/PassengerViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/12306ForMac/TicketViewControllers/TrainCodeDetailViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrainCodeDetailViewController.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/06/12.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class TrainCodeDetailViewController: NSViewController {
12 | @IBOutlet weak var priceLbl: NSTextField!
13 | @IBOutlet weak var trainCodeDetailTable: NSTableView!
14 |
15 | private let _trainDetailParam: QueryTrainCodeParam
16 | private let _trainPriceParam: QueryTrainPriceParam
17 | var trainCodeDetails: TrainCodeDetails?
18 |
19 | init(trainDetailParams:QueryTrainCodeParam,trainPriceParams:QueryTrainPriceParam) {
20 | _trainDetailParam = trainDetailParams
21 | _trainPriceParam = trainPriceParams
22 | super.init(nibName: nil, bundle: nil)!
23 | }
24 |
25 | required init?(coder: NSCoder) {
26 | fatalError("init(coder:) has not been implemented")
27 | }
28 |
29 | override func viewDidLoad() {
30 | super.viewDidLoad()
31 |
32 | // setup Table Header
33 | for col in trainCodeDetailTable.tableColumns {
34 | col.headerCell = TrainCodeDetailHeaderCell(textCell: col.headerCell.stringValue)
35 | col.headerCell.alignment = .center
36 | }
37 |
38 | let successHandler = { (trainDetails:TrainCodeDetails)->() in
39 | self.trainCodeDetails = trainDetails
40 | self.trainCodeDetailTable.reloadData()
41 | }
42 | let failureHandler = {(error:NSError)->() in }
43 | Service.sharedInstance.queryTrainDetailFlowWith(_trainDetailParam, success: successHandler, failure: failureHandler)
44 |
45 | let priceSuccessHandler = { (trainPrice:TrainPrice)->() in
46 | self.priceLbl.stringValue = trainPrice.trainPriceStr
47 |
48 | }
49 | Service.sharedInstance.queryTrainPriceFlowWith(_trainPriceParam, success: priceSuccessHandler, failure: failureHandler)
50 | }
51 |
52 | }
53 |
54 | // MARK: - NSTableViewDataSource
55 | extension TrainCodeDetailViewController: NSTableViewDataSource{
56 | func numberOfRows(in tableView: NSTableView) -> Int {
57 | if trainCodeDetails == nil {
58 | return 0
59 | }
60 | return trainCodeDetails!.trainNos.count
61 | }
62 |
63 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
64 | return trainCodeDetails!.trainNos[row]
65 | }
66 | }
67 |
68 | // MARK: - NSTableViewDelegate
69 | extension TrainCodeDetailViewController:NSTableViewDelegate {
70 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
71 | return false
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/ClickableDatePicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClickableDatePicker.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/8/2.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ClickableDatePicker: NSDatePicker {
12 |
13 | @IBInspectable var clickable:Bool = true
14 |
15 | override func mouseDown(with theEvent: NSEvent) {
16 | if self.clickable {
17 | sendAction(self.action, to: self.target)
18 | }
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/ContentBackgroundView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentBackgroundView.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/6/15.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ContentBackgroundView: NSView {
12 |
13 | override func draw(_ dirtyRect: NSRect) {
14 | super.draw(dirtyRect)
15 |
16 | Theme.GobalTheme.backgroundColor.set()
17 | NSRectFill(dirtyRect)
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/FilterTrainCodeTransformer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FilterTrainCodeTransformer.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/9/12.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @objc(FilterTrainCodeTransformer)
12 | class FilterTrainCodeTransformer: ValueTransformer {
13 |
14 | override class func allowsReverseTransformation()->Bool {
15 | return false
16 | }
17 |
18 | override class func transformedValueClass() -> AnyClass {
19 | return NSString.self
20 | }
21 |
22 | override func transformedValue(_ value: Any?) -> Any? {
23 | if value == nil {
24 | return nil
25 | }
26 | let presentation = value as! String
27 | let range = presentation.range(of: "|1")!
28 | return presentation.substring(to: range.lowerBound)
29 | }
30 |
31 | }
32 |
33 | @objc(FilterTrainTimeTransformer)
34 | class FilterTrainTimeTransformer: ValueTransformer {
35 |
36 | override class func allowsReverseTransformation()->Bool {
37 | return false
38 | }
39 |
40 | override class func transformedValueClass() -> AnyClass {
41 | return NSString.self
42 | }
43 |
44 | override func transformedValue(_ value: Any?) -> Any? {
45 | if value == nil {
46 | return nil
47 | }
48 | let presentation = value as! String
49 | let range1 = presentation.range(of: "|1")!
50 | let range2 = presentation.range(of: "|2")!
51 | return presentation.substring(with: range1.upperBound..Bool {
59 | return false
60 | }
61 |
62 | override class func transformedValueClass() -> AnyClass {
63 | return NSString.self
64 | }
65 |
66 | override func transformedValue(_ value: Any?) -> Any? {
67 | if value == nil {
68 | return nil
69 | }
70 | let presentation = value as! String
71 | let range = presentation.range(of: "|2")!
72 | return presentation.substring(from: range.upperBound)
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/GlassView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GlassView.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 16/1/28.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class GlassView: NSView{
12 | var backgroundColor:NSColor?
13 |
14 | required init?(coder: NSCoder) {
15 | super.init(coder: coder)
16 | self.commonInit()
17 | }
18 |
19 | override init(frame frameRect: NSRect) {
20 | super.init(frame: frameRect)
21 | self.commonInit()
22 | }
23 |
24 | func commonInit(){
25 | self.backgroundColor = NSColor.clear
26 | }
27 |
28 | override func draw(_ dirtyRect: NSRect) {
29 | NSGraphicsContext.saveGraphicsState()
30 | self.backgroundColor?.set()
31 | NSRectFill(dirtyRect)
32 | NSGraphicsContext.restoreGraphicsState()
33 | }
34 |
35 | }
36 |
37 |
38 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/InfoButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfoButton.swift
3 | // InfoButton
4 | //
5 | // Created by Kauntey Suryawanshi on 25/06/15.
6 | // Copyright (c) 2015 Kauntey Suryawanshi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | @IBDesignable
13 | open class InfoButton : NSControl, NSPopoverDelegate {
14 | var mainSize: CGFloat!
15 |
16 | @IBInspectable var showOnHover: Bool = false
17 | @IBInspectable var fillMode: Bool = true
18 | @IBInspectable var animatePopover: Bool = false
19 | @IBInspectable var content: String = ""
20 | @IBInspectable var primaryColor: NSColor = NSColor.scrollBarColor
21 | var secondaryColor: NSColor = NSColor.white
22 |
23 | var mouseInside = false {
24 | didSet {
25 | self.needsDisplay = true
26 | if showOnHover {
27 | if popover == nil {
28 | popover = NSPopover(content: self.content, doesAnimate: self.animatePopover)
29 | }
30 | if mouseInside {
31 | popover.show(relativeTo: self.frame, of: self.superview!, preferredEdge: NSRectEdge.maxX)
32 | } else {
33 | popover.close()
34 | }
35 |
36 | }
37 | }
38 | }
39 |
40 | var trackingArea: NSTrackingArea!
41 | override open func updateTrackingAreas() {
42 | super.updateTrackingAreas()
43 | if trackingArea != nil {
44 | self.removeTrackingArea(trackingArea)
45 | }
46 | trackingArea = NSTrackingArea(rect: self.bounds, options: [NSTrackingAreaOptions.mouseEnteredAndExited, NSTrackingAreaOptions.activeAlways], owner: self, userInfo: nil)
47 | self.addTrackingArea(trackingArea)
48 | }
49 |
50 | fileprivate var stringAttributeDict = [String: AnyObject]()
51 | fileprivate var circlePath: NSBezierPath!
52 |
53 | var popover: NSPopover!
54 |
55 |
56 | required public init?(coder: NSCoder) {
57 | super.init(coder: coder)
58 | let frameSize = self.frame.size
59 | if frameSize.width != frameSize.height {
60 | self.frame.size.height = self.frame.size.width
61 | }
62 | self.mainSize = self.frame.size.height
63 | stringAttributeDict[NSFontAttributeName] = NSFont.systemFont(ofSize: mainSize * 0.6)
64 |
65 | let inSet: CGFloat = 2
66 | let rect = NSMakeRect(inSet, inSet, mainSize - inSet * 2, mainSize - inSet * 2)
67 | circlePath = NSBezierPath(ovalIn: rect)
68 | }
69 |
70 |
71 | override open func draw(_ dirtyRect: NSRect) {
72 | var activeColor: NSColor!
73 | if mouseInside || (popover != nil && popover!.isShown){
74 | activeColor = primaryColor
75 | } else {
76 | activeColor = primaryColor.withAlphaComponent(0.35)
77 | }
78 |
79 | if fillMode {
80 | activeColor.setFill()
81 | circlePath.fill()
82 | stringAttributeDict[NSForegroundColorAttributeName] = secondaryColor
83 | } else {
84 | activeColor.setStroke()
85 | circlePath.stroke()
86 | stringAttributeDict[NSForegroundColorAttributeName] = (mouseInside ? primaryColor : primaryColor.withAlphaComponent(0.35))
87 | }
88 |
89 | let attributedString = NSAttributedString(string: "?", attributes: stringAttributeDict)
90 | let stringLocation = NSMakePoint(mainSize / 2 - attributedString.size().width / 2, mainSize / 2 - attributedString.size().height / 2)
91 | attributedString.draw(at: stringLocation)
92 | }
93 |
94 | override open func mouseDown(with theEvent: NSEvent) {
95 | if popover == nil {
96 | popover = NSPopover(content: self.content, doesAnimate: self.animatePopover)
97 | }
98 | if popover.isShown {
99 | popover.close()
100 | } else {
101 | popover.show(relativeTo: self.frame, of: self.superview!, preferredEdge: NSRectEdge.maxX)
102 | }
103 | }
104 |
105 | override open func mouseEntered(with theEvent: NSEvent) { mouseInside = true }
106 | override open func mouseExited(with theEvent: NSEvent) { mouseInside = false }
107 |
108 | }
109 |
110 | //MARK: Extension for making a popover from string
111 | extension NSPopover {
112 |
113 | convenience init(content: String, doesAnimate: Bool) {
114 | self.init()
115 |
116 | self.behavior = NSPopoverBehavior.transient
117 | self.animates = doesAnimate
118 | self.contentViewController = NSViewController()
119 | self.contentViewController!.view = NSView(frame: NSZeroRect)//remove this ??
120 |
121 | let popoverMargin = CGFloat(20)
122 | let textField: NSTextField = {
123 | content in
124 | let textField = NSTextField(frame: NSZeroRect)
125 |
126 | textField.isEditable = false
127 | textField.stringValue = content
128 | textField.isBordered = false
129 | textField.drawsBackground = false
130 | textField.sizeToFit()
131 | textField.setFrameOrigin(NSMakePoint(popoverMargin, popoverMargin))
132 | return textField
133 | }(content)
134 |
135 | self.contentViewController!.view.addSubview(textField)
136 | var viewSize = textField.frame.size; viewSize.width += (popoverMargin * 2); viewSize.height += (popoverMargin * 2)
137 | self.contentSize = viewSize
138 |
139 | }
140 | }
141 | //NSMinXEdge NSMinYEdge NSMaxXEdge NSMaxYEdge
142 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/LoginButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginButton.swift
3 | // HoverButtonDemo
4 | //
5 | // Created by fancymax on 16/1/29.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class LoginButton: NSButton{
12 | var hovered: Bool = false
13 |
14 | var textColor: NSColor = NSColor.black
15 |
16 | required init?(coder: NSCoder) {
17 | super.init(coder: coder)
18 | self.commonInit()
19 | }
20 |
21 | override init(frame frameRect: NSRect) {
22 | super.init(frame: frameRect)
23 | self.commonInit()
24 | }
25 |
26 | func commonInit(){
27 | // self.wantsLayer = true
28 | self.createTrackingArea()
29 | self.hovered = false
30 | }
31 |
32 | fileprivate var trackingArea: NSTrackingArea!
33 | func createTrackingArea(){
34 | if(self.trackingArea != nil){
35 | self.removeTrackingArea(self.trackingArea!)
36 | }
37 | let circleRect = self.bounds
38 | let flag = NSTrackingAreaOptions.mouseEnteredAndExited.rawValue + NSTrackingAreaOptions.activeInActiveApp.rawValue
39 | self.trackingArea = NSTrackingArea(rect: circleRect, options: NSTrackingAreaOptions(rawValue: flag), owner: self, userInfo: nil)
40 | self.addTrackingArea(self.trackingArea)
41 | }
42 |
43 | override func mouseEntered(with theEvent: NSEvent) {
44 | if !self.isEnabled {
45 | return
46 | }
47 |
48 | self.hovered = true
49 | self.needsDisplay = true
50 | }
51 |
52 | override func mouseExited(with theEvent: NSEvent) {
53 | self.hovered = false
54 | self.needsDisplay = true
55 | }
56 |
57 | func drawText(_ text:String, inRect:NSRect){
58 | let aParagraghStyle = NSMutableParagraphStyle()
59 | aParagraghStyle.lineBreakMode = NSLineBreakMode.byWordWrapping
60 | aParagraghStyle.alignment = NSTextAlignment.left
61 |
62 | let attrs = [NSParagraphStyleAttributeName:aParagraghStyle, NSFontAttributeName:self.font!, NSForegroundColorAttributeName:self.textColor] as [String : Any]
63 | let size = (text as NSString).size(withAttributes: attrs)
64 | let r:NSRect = NSMakeRect(inRect.origin.x,
65 | inRect.origin.y + (inRect.size.height - size.height)/2.0 - 2,
66 | inRect.size.width,
67 | size.height)
68 | (text as NSString).draw(in: r, withAttributes: attrs)
69 | }
70 |
71 | override func draw(_ dirtyRect: NSRect) {
72 | NSGraphicsContext.saveGraphicsState()
73 |
74 | var imageRect = NSRect()
75 | if let image = self.image{
76 | imageRect.size.height = self.bounds.height - 6
77 | imageRect.size.width = imageRect.size.height
78 | imageRect.origin.x = 3
79 | imageRect.origin.y = 3
80 | image.draw(in: imageRect)
81 | }
82 | var textRect = self.bounds
83 | textRect.origin.x += imageRect.size.width + 10
84 | textRect.origin.y += 1
85 | textRect.size.width -= imageRect.size.width + 5
86 |
87 | if (hovered){
88 | var rect = self.bounds
89 | rect.size.height -= 2
90 | rect.size.width -= 2
91 | rect.origin.x += 1
92 | rect.origin.y += 1
93 | let bgPath = NSBezierPath(roundedRect: rect, xRadius: 5, yRadius: 5)
94 | NSColor.lightGray.set()
95 | bgPath.lineWidth = 1
96 | bgPath.stroke()
97 | }
98 |
99 | drawText(self.title, inRect: textRect)
100 |
101 | NSGraphicsContext.restoreGraphicsState()
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/LunarCalendar/LunarSolarConverter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LunarSolarConverter.swift
3 | // LunarSolarConverter
4 | //
5 | // Created by isee15 on 15/1/17.
6 | // Copyright (c) 2015年 isee15. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class LunarSolarConverter {
12 | static private let lunarDayStrs = ["初一","初二","初三","初四","初五","初六","初七","初八","初九","初十",
13 | "十一","十二","十三","十四","十五","十六","十七","十八","十九","二十",
14 | "廿一","廿二","廿三","廿四","廿五","廿六","廿七","廿八","廿九","三十","卅一","卅二"]
15 | static private let lunarMonthStrs = ["春节","二月","三月","四月","五月","六月","七月","八月","九月","十月","冬月","腊月"]
16 |
17 | static private let solarFestival = [
18 | "d0101":"元旦节",
19 | "d0214":"情人节",
20 | "d0308":"妇女节",
21 | "d0312":"植树节",
22 | "d0401":"愚人节",
23 | "d0501":"劳动节",
24 | "d0504":"青年节",
25 | "d0601":"儿童节",
26 | "d0701":"建党节",
27 | "d0801":"建军节",
28 | "d0910":"教师节",
29 | "d1001":"国庆节",
30 | "d1031":"万圣节",
31 | "d1111":"光棍节",
32 | "d1224":"平安夜",
33 | "d1225":"圣诞节"
34 | ]
35 | static private let lunarFestival = [
36 | "d0101":"春节",
37 | "d0115":"元宵节",
38 | "d0505":"端午节",
39 | "d0707":"七夕节",
40 | "d0815":"中秋节",
41 | "d1223":"小年",
42 | "d0100":"除夕" //除夕暂不支持显示
43 | ]
44 |
45 | static private func formatDay(month:Int,day:Int) -> String {
46 | return String(format: "d%02d%02d", month,day)
47 | }
48 |
49 | static func Conventer2lunarStr(_ solar: Date) -> String{
50 | let solarComponents = Calendar.current.dateComponents([.day,.month], from: solar)
51 | let solarFestivalKey = formatDay(month: solarComponents.month!, day: solarComponents.day!)
52 | for key in solarFestival.keys {
53 | if key == solarFestivalKey {
54 | return solarFestival[key]!
55 | }
56 | }
57 |
58 | let lunarCal = Calendar(identifier: Calendar.Identifier.chinese)
59 | let lunarComponents = lunarCal.dateComponents([.day,.month], from: solar)
60 | let lunarFestivalKey = formatDay(month: lunarComponents.month!, day: lunarComponents.day!)
61 | for key in lunarFestival.keys {
62 | if key == lunarFestivalKey {
63 | return lunarFestival[key]!
64 | }
65 | }
66 |
67 | if lunarComponents.day != 1 {
68 | return lunarDayStrs[lunarComponents.day! - 1]
69 | }
70 | else{
71 | return lunarMonthStrs[lunarComponents.month! - 1]
72 | }
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/NSTableView+ContextMenu.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSTableView+ContextMenu.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/12/6.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | //user define Context Menu
13 | protocol ContextMenuDelegate: NSObjectProtocol {
14 | func tableView(aTableView:NSTableView, menuForRows rows:IndexSet) -> NSMenu?
15 | }
16 |
17 | extension NSTableView {
18 |
19 | open override func menu(for event: NSEvent) -> NSMenu? {
20 |
21 | let location = self.convert(event.locationInWindow, from: nil)
22 | let row = self.row(at: location)
23 | if ((row < 0) || (event.type != NSRightMouseDown)) {
24 | return super.menu(for: event)
25 | }
26 |
27 | var selected = self.selectedRowIndexes
28 | if !selected.contains(row) {
29 | selected = IndexSet(integer:row)
30 | self.selectRowIndexes(selected, byExtendingSelection: false)
31 | }
32 |
33 | if let contextMenuDelegate = self.delegate {
34 | if contextMenuDelegate.responds(to: Selector(("tableView:menuForRows:"))){
35 | return (contextMenuDelegate as! ContextMenuDelegate).tableView(aTableView: self,menuForRows:selected)
36 | }
37 | }
38 |
39 | return super.menu(for: event)
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/RandCodeImageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RandCodeImageView.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 15/8/12.
6 | // Copyright (c) 2015年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class RandCodeImageView:NSImageView {
12 |
13 | struct ImageDot {
14 | var randCodeX: Int
15 | var randCodeY: Int
16 | var pointX:CGFloat
17 | var pointY:CGFloat
18 | }
19 |
20 | fileprivate var imageDots = [ImageDot]()
21 |
22 | var randCodeStr:String?{
23 | get{
24 | if imageDots.count == 0{
25 | return nil
26 | }
27 | var str = "\(imageDots[0].randCodeX),\(imageDots[0].randCodeY)"
28 | if imageDots.count >= 2
29 | {
30 | for i in 1...imageDots.count - 1{
31 | str = str + ",\(imageDots[i].randCodeX),\(imageDots[i].randCodeY)"
32 | }
33 | }
34 | return str
35 | }
36 | }
37 |
38 | func clearRandCodes()
39 | {
40 | imageDots = [ImageDot]()
41 | needsDisplay = true
42 | }
43 |
44 | override func mouseDown(with theEvent: NSEvent) {
45 | let frameOffsetInWindow = convert(self.frame.origin, from: nil)
46 |
47 | let imageOriginX = self.frame.origin.x - frameOffsetInWindow.x
48 | let imageOriginY = self.frame.origin.y + self.bounds.height - frameOffsetInWindow.y
49 | let mouseX = theEvent.locationInWindow.x
50 | let mouseY = theEvent.locationInWindow.y
51 | let randCodeX = (mouseX - imageOriginX)/1.2
52 | let randCodeY = (imageOriginY - mouseY)/1.2 - 30
53 |
54 | if ((randCodeX < 0) || (randCodeY < 0)){
55 | return
56 | }
57 |
58 | let pointX = mouseX - (self.frame.origin.x - frameOffsetInWindow.x)
59 | let pointY = mouseY - (self.frame.origin.y - frameOffsetInWindow.y)
60 |
61 | var isAdd = true
62 | if imageDots.count != 0
63 | {
64 | for i in 0...imageDots.count - 1
65 | {
66 | if (abs(pointX - imageDots[i].pointX) < 10) && (abs(pointY - imageDots[i].pointY) < 10)
67 | {
68 | imageDots.remove(at: i)
69 | isAdd = false
70 | break
71 | }
72 | }
73 | }
74 | if isAdd
75 | {
76 | imageDots.append(ImageDot(randCodeX: Int(randCodeX), randCodeY: Int(randCodeY), pointX: pointX, pointY: pointY))
77 | }
78 |
79 | needsDisplay = true
80 | }
81 |
82 | override func draw(_ dirtyRect: NSRect) {
83 | super.draw(dirtyRect)
84 |
85 | NSColor.green.set()
86 |
87 | func drawDot(_ pointX: CGFloat,pointY: CGFloat)
88 | {
89 | let dotRect = CGRect(origin: NSPoint(x: pointX, y: pointY), size: CGSize.zero).insetBy(dx:-10, dy:-10)
90 | NSBezierPath(ovalIn: dotRect).fill()
91 | }
92 |
93 | for point in imageDots
94 | {
95 | drawDot(point.pointX,pointY: point.pointY)
96 | }
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/RandCodeImageView2.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RandCodeImageView2.swift
3 | // SelectImageDemo
4 | //
5 | // Created by fancymax on 16/3/8.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class RandCodeImageView2:NSImageView {
12 | struct ImageSection {
13 | var rowIndex: Int
14 | var colIndex: Int
15 |
16 | func isSameSection(_ section:ImageSection)->Bool{
17 | if (section.rowIndex == rowIndex) && (section.colIndex == colIndex){
18 | return true
19 | }
20 | else{
21 | return false
22 | }
23 | }
24 | }
25 |
26 | fileprivate var imageSections = [ImageSection]()
27 | fileprivate func convertSectionToRandCode(_ section:ImageSection)->(Int,Int){
28 | var randX = 0
29 | var randY = 0
30 | if section.rowIndex == 0 {
31 | randY = 110
32 | }
33 | else{
34 | randY = 40
35 | }
36 |
37 | if section.colIndex == 0{
38 | randX = 40
39 | }
40 | else if section.colIndex == 1{
41 | randX = 110
42 | }
43 | else if section.colIndex == 2{
44 | randX = 180
45 | }
46 | else{
47 | randX = 255
48 | }
49 | return (randX,randY)
50 | }
51 | var randCodeStr:String?{
52 | get{
53 | if imageSections.count == 0{
54 | return nil
55 | }
56 | var (randX,randY) = convertSectionToRandCode(imageSections[0])
57 | var str = "\(randX),\(randY)"
58 | if imageSections.count >= 2{
59 | for i in 1...imageSections.count - 1 {
60 | (randX, randY) = convertSectionToRandCode(imageSections[i])
61 | str += ",\(randX),\(randY)"
62 | }
63 | }
64 | return str
65 | }
66 | }
67 |
68 | override var acceptsFirstResponder: Bool {
69 | return true
70 | }
71 |
72 | override func becomeFirstResponder() -> Bool {
73 | return true
74 | }
75 |
76 | override func resignFirstResponder() -> Bool {
77 | return true
78 | }
79 |
80 | override func mouseDown(with theEvent: NSEvent) {
81 | let section = indentifySection(theEvent)
82 |
83 | var shouldAdd = true
84 | if imageSections.count > 0{
85 | for i in 0...imageSections.count - 1 {
86 | if imageSections[i].isSameSection(section){
87 | imageSections.remove(at: i)
88 | shouldAdd = false
89 | break
90 | }
91 | }
92 | }
93 |
94 | if shouldAdd {
95 | imageSections.append(section)
96 | }
97 |
98 | needsDisplay = true
99 | }
100 |
101 | override func draw(_ dirtyRect: NSRect) {
102 | super.draw(dirtyRect)
103 |
104 | NSColor.red.set()
105 |
106 | for section in imageSections
107 | {
108 | drawSection(section)
109 | }
110 | }
111 |
112 | func clearRandCodes()
113 | {
114 | imageSections = [ImageSection]()
115 | needsDisplay = true
116 | }
117 |
118 | //识别点击在哪个区域
119 | fileprivate func indentifySection(_ theEvent: NSEvent) ->ImageSection{
120 | var section = ImageSection(rowIndex: 1, colIndex: 1)
121 |
122 | let frameOffsetInWindow = convert(self.frame.origin, from: nil)
123 | let mouseX = theEvent.locationInWindow.x
124 | let mouseY = theEvent.locationInWindow.y
125 | let pointX = mouseX - (self.frame.origin.x - frameOffsetInWindow.x)
126 | let pointY = mouseY - (self.frame.origin.y - frameOffsetInWindow.y)
127 |
128 | if pointY < 100 {
129 | section.rowIndex = 0
130 | }
131 | else{
132 | section.rowIndex = 1
133 | }
134 |
135 | if pointX < 85 {
136 | section.colIndex = 0
137 | }
138 | else if pointX < 175 {
139 | section.colIndex = 1
140 | }
141 | else if pointX < 260 {
142 | section.colIndex = 2
143 | }
144 | else if pointX < 345 {
145 | section.colIndex = 3
146 | }
147 | else{
148 | section.colIndex = 3
149 | }
150 |
151 | return section
152 | }
153 |
154 |
155 | //dama to section
156 | fileprivate func damaPoint2Section(X pointX:Double,Y pointY:Double) -> ImageSection{
157 | var section = ImageSection(rowIndex: 1, colIndex: 1)
158 |
159 | if pointY > 110 {
160 | section.rowIndex = 0 //从下往上
161 | }
162 | else{
163 | section.rowIndex = 1
164 | }
165 |
166 | if pointX < 75 {
167 | section.colIndex = 0
168 | }
169 | else if pointX < 146 {
170 | section.colIndex = 1
171 | }
172 | else if pointX < 220 {
173 | section.colIndex = 2
174 | }
175 | else{
176 | section.colIndex = 3
177 | }
178 | return section
179 | }
180 |
181 | //119,65|24,76
182 | func drawDamaCodes(_ damaCodes:String){
183 | let damaFrameStrs = damaCodes.components(separatedBy: "|")
184 | for damaFrameStr in damaFrameStrs {
185 | let damaFramePair = damaFrameStr.components(separatedBy: ",")
186 | let pointX = Double(damaFramePair[0])
187 | let pointY = Double(damaFramePair[1])
188 |
189 | imageSections.append(damaPoint2Section(X:pointX!, Y: pointY!))
190 | }
191 | needsDisplay = true
192 | }
193 |
194 | //绘制特定正方形区域
195 | fileprivate func drawSection(_ section:ImageSection){
196 | let point = CGPoint(x: 4 + section.colIndex/2 + section.colIndex * 85,
197 | y: 10 + section.rowIndex + section.rowIndex * 85)
198 | let size = CGSize(width: 85, height: 85)
199 | let rect = NSRect(origin: point, size: size)
200 | let path = NSBezierPath(rect: rect)
201 |
202 | let lineDash:[CGFloat] = [4.0,2.0]
203 | path.setLineDash(lineDash, count: 2, phase: 0.0)
204 | path.flatness = 0.8
205 | path.windingRule = NSWindingRule.evenOddWindingRule
206 | path.lineWidth = 2
207 | path.stroke()
208 | }
209 |
210 | }
211 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/Theme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Theme.swift
3 | // WWDC
4 | //
5 | // Created by Guilherme Rambo on 18/04/15.
6 | // Copyright (c) 2015 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | private let _SharedThemeInstance = Theme()
12 |
13 | class Theme: NSObject {
14 |
15 | class var GobalTheme: Theme {
16 | return _SharedThemeInstance
17 | }
18 |
19 | let backgroundColor = NSColor(calibratedRed:0.921569, green:0.921569, blue:0.921569, alpha:1.0)
20 | }
--------------------------------------------------------------------------------
/12306ForMac/UserControls/TrainCodeDetailHeaderCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrainCodeDetailHeaderCell.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/6/15.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class TrainCodeDetailHeaderCell: NSTableHeaderCell {
12 |
13 | override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
14 | let (borderRect, fillRect) = cellFrame.divided(atDistance: 1.0, from: .maxYEdge)
15 |
16 | //header bottom line
17 | NSColor(calibratedRed: 0.850887, green: 0.851034, blue: 0.850878, alpha: 1.0).set()
18 | NSRectFill(borderRect)
19 |
20 | Theme.GobalTheme.backgroundColor.set()
21 | NSRectFill(fillRect)
22 | self.drawInterior(withFrame: fillRect.insetBy(dx: 0.0, dy: 1.0), in: controlView)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/TrainInfoTableCellView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrainStationTableCellView.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/6/10.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class TrainTableCellView: NSTableCellView {
12 | var ticketInfo: QueryLeftNewDTO! {
13 | didSet {
14 | updateUI()
15 | }
16 | }
17 |
18 | var selected = false
19 | //change color by backgroundStyle not selected status
20 | override var backgroundStyle: NSBackgroundStyle {
21 | didSet {
22 | updateTint()
23 | }
24 | }
25 |
26 | internal func updateUI() {
27 |
28 | }
29 |
30 | internal func updateTint() {
31 | if backgroundStyle == .dark {
32 | setSelectedControlStyle()
33 | }
34 | else{
35 | setUnSelectedControlStyle()
36 | }
37 | }
38 |
39 | internal func setSelectedControlStyle() {
40 |
41 | }
42 |
43 | internal func setUnSelectedControlStyle() {
44 |
45 | }
46 |
47 | }
48 |
49 | class TrainStationTableCellView: TrainTableCellView {
50 |
51 | @IBOutlet weak fileprivate var stationMarkField: NSTextField!
52 | @IBOutlet weak fileprivate var stationField: NSTextField!
53 | @IBOutlet weak fileprivate var timeField: NSTextField!
54 |
55 | override func setSelectedControlStyle() {
56 | stationMarkField.textColor = NSColor(calibratedWhite: 1.0, alpha: 0.70)
57 | }
58 |
59 | override func setUnSelectedControlStyle() {
60 | stationMarkField.textColor = NSColor(calibratedWhite: 0.0, alpha: 0.45)
61 | }
62 | }
63 |
64 | // MARK: - 发站
65 | class StartStationTableCellView: TrainStationTableCellView {
66 | override func updateUI() {
67 | stationField.stringValue = ticketInfo.FromStationName!
68 | timeField.stringValue = ticketInfo.start_time!
69 | if ticketInfo.isStartStation {
70 | stationMarkField.isHidden = false
71 | }
72 | else{
73 | stationMarkField.isHidden = true
74 | }
75 | }
76 | }
77 |
78 | // MARK: - 到站
79 | class EndStationTableCellView: TrainStationTableCellView {
80 | override func updateUI() {
81 | stationField.stringValue = ticketInfo.ToStationName!
82 | timeField.stringValue = ticketInfo.arrive_time!
83 | if ticketInfo.isEndStation {
84 | stationMarkField.isHidden = false
85 | }
86 | else {
87 | stationMarkField.isHidden = true
88 | }
89 | }
90 | }
91 |
92 | // MARK: - 车次
93 | class TrainCodeTableCellView: TrainTableCellView {
94 | @IBOutlet weak fileprivate var trainCodeBtn: NSButton!
95 |
96 | func setTarget(_ target:AnyObject?, action:Selector){
97 | trainCodeBtn.target = target
98 | trainCodeBtn.action = action
99 | }
100 | }
101 |
102 | // MARK: - 余票信息
103 | class TrainInfoTableCellView: TrainTableCellView {
104 | @IBOutlet weak fileprivate var messageField: NSTextField!
105 | @IBOutlet weak fileprivate var SwzBtn: NSButton!
106 | @IBOutlet weak fileprivate var TzBtn: NSButton!
107 | @IBOutlet weak fileprivate var ZyBtn: NSButton!
108 | @IBOutlet weak fileprivate var ZeBtn: NSButton!
109 | @IBOutlet weak fileprivate var GrBtn: NSButton!
110 | @IBOutlet weak fileprivate var RwBtn: NSButton!
111 | @IBOutlet weak fileprivate var YwBtn: NSButton!
112 | @IBOutlet weak fileprivate var RzBtn: NSButton!
113 | @IBOutlet weak fileprivate var YzBtn: NSButton!
114 | @IBOutlet weak fileprivate var WzBtn: NSButton!
115 |
116 | var dictOfBtn:[Int:NSButton]{
117 | get {
118 | return [1: SwzBtn, 2: TzBtn, 3: ZyBtn, 4: ZeBtn, 5: GrBtn, 6: RwBtn, 7: YwBtn, 8: RzBtn, 9: YzBtn, 10: WzBtn]
119 | }
120 | }
121 |
122 | func setTarget(_ target:AnyObject?, action:Selector){
123 | for btn in dictOfBtn.values {
124 | btn.target = target
125 | btn.action = action
126 | }
127 | }
128 |
129 | override func setSelectedControlStyle() {
130 | messageField.textColor = NSColor(calibratedWhite: 1.0, alpha: 0.70)
131 |
132 | for btn in dictOfBtn.values {
133 | (btn as! LoginButton).textColor = NSColor(calibratedWhite: 1.0, alpha: 1)
134 | }
135 | }
136 |
137 | override func setUnSelectedControlStyle() {
138 | messageField.textColor = NSColor(calibratedWhite: 0.0, alpha: 0.45)
139 |
140 | for btn in dictOfBtn.values {
141 | (btn as! LoginButton).textColor = NSColor(calibratedRed: 0.270588, green: 0.541176, blue: 0.913725, alpha: 1.0)
142 | }
143 | }
144 |
145 | override func updateUI() {
146 |
147 | func setTicketButton(_ ticket:String,sender:NSButton){
148 | if ((ticket == "--")||(ticket == "无")||(ticket == "*")||(ticket == "")){
149 | sender.isHidden = true
150 | return
151 | }
152 |
153 | sender.isHidden = false
154 | sender.isEnabled = true
155 | if (ticket == "有"){
156 | sender.title = sender.alternateTitle + "(有票)"
157 | }
158 | else{
159 | sender.title = sender.alternateTitle + "(\(ticket)张)"
160 | }
161 |
162 | }
163 |
164 | setTicketButton(ticketInfo.Swz_Num, sender: SwzBtn)
165 | setTicketButton(ticketInfo.Tz_Num, sender: TzBtn)
166 | setTicketButton(ticketInfo.Zy_Num, sender: ZyBtn)
167 | setTicketButton(ticketInfo.Ze_Num, sender: ZeBtn)
168 | setTicketButton(ticketInfo.Gr_Num, sender: GrBtn)
169 | setTicketButton(ticketInfo.Rw_Num, sender: RwBtn)
170 | setTicketButton(ticketInfo.Yw_Num, sender: YwBtn)
171 | setTicketButton(ticketInfo.Rz_Num, sender: RzBtn)
172 | setTicketButton(ticketInfo.Yz_Num, sender: YzBtn)
173 | setTicketButton(ticketInfo.Wz_Num, sender: WzBtn)
174 |
175 | if ticketInfo.canWebBuy == "Y" {
176 | messageField.isHidden = true
177 | }
178 | else if ticketInfo.canWebBuy == "N" {
179 | if ticketInfo.hasTicket {
180 | messageField.stringValue = " 本车次无法购买,可重新查询"
181 | }
182 | else {
183 | messageField.stringValue = " 本车次暂无可售车票"
184 | }
185 | messageField.isHidden = false
186 | }
187 | else if ticketInfo.canWebBuy == "IS_TIME_NOT_BUY"{
188 |
189 | for btn in dictOfBtn.values {
190 | btn.isEnabled = false
191 | }
192 |
193 | if ticketInfo.buttonTextInfo == "23:00-06:00系统维护时间" {
194 | if ticketInfo.hasTicket {
195 | messageField.isHidden = true
196 | }
197 | else{
198 | messageField.stringValue = " 本车次暂无可售车票"
199 | messageField.isHidden = false
200 | }
201 | }
202 | else
203 | {
204 | if let range = ticketInfo.buttonTextInfo.range(of: "
"){
205 | ticketInfo.buttonTextInfo.removeSubrange(range)
206 | }
207 | messageField.stringValue = " " + ticketInfo.buttonTextInfo
208 | messageField.isHidden = false
209 | }
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/TrainStationTableCellView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrainStationTableCellView.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/6/10.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class TrainStationTableCellView: NSTableCellView {
12 |
13 | @IBOutlet weak private var stationMarkField: NSTextField!
14 | @IBOutlet weak private var stationField: NSTextField!
15 | @IBOutlet weak private var timeField: NSTextField!
16 | var ticketInfo: QueryLeftNewDTO! {
17 | didSet {
18 | updateUI()
19 | }
20 | }
21 |
22 | var selected = false {
23 | didSet{
24 | updateTint()
25 | }
26 | }
27 |
28 | override var backgroundStyle: NSBackgroundStyle {
29 | didSet{
30 | stationMarkField.cell?.backgroundStyle = .Light
31 | }
32 | }
33 |
34 | internal func updateUI() {
35 |
36 | }
37 |
38 | private func updateTint() {
39 | if selected {
40 | stationMarkField.textColor = NSColor(calibratedWhite: 1.0, alpha: 0.70)
41 | }
42 | else{
43 | stationMarkField.textColor = NSColor(calibratedWhite: 0.0, alpha: 0.70)
44 | }
45 | }
46 |
47 | }
48 |
49 | class StartStationTableCellView: TrainStationTableCellView {
50 | override func updateUI() {
51 | stationField.stringValue = ticketInfo.FromStationName!
52 | timeField.stringValue = ticketInfo.start_time!
53 | if ticketInfo.isStartStation {
54 | stationMarkField.hidden = false
55 | }
56 | else{
57 | stationMarkField.hidden = true
58 | }
59 | }
60 | }
61 |
62 | class EndStationTableCellView: TrainStationTableCellView {
63 | override func updateUI() {
64 | stationField.stringValue = ticketInfo.ToStationName!
65 | timeField.stringValue = ticketInfo.arrive_time!
66 | if ticketInfo.isEndStation {
67 | stationMarkField.hidden = false
68 | }
69 | else {
70 | stationMarkField.hidden = true
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/TrainTableRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrainTableRowView.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/6/10.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class TrainTableRowView: NSTableRowView {
12 |
13 | override var isOpaque: Bool {
14 | return false
15 | }
16 |
17 | override var isSelected: Bool {
18 | didSet {
19 | updateSubviewsInterestedInSelectionState()
20 | }
21 | }
22 |
23 | fileprivate func updateSubviewsInterestedInSelectionState() {
24 | guard subviews.count > 0 else { return }
25 |
26 | for view in subviews {
27 | if view.isKind(of: TrainTableCellView.self) {
28 | let stationCellView = view as! TrainTableCellView
29 | stationCellView.selected = isSelected
30 | }
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/URLButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLButton.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/7/15.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class URLButton: NSButton {
12 |
13 | fileprivate var hovered: Bool = false
14 |
15 | required init?(coder: NSCoder) {
16 | super.init(coder: coder)
17 | self.commonInit()
18 | }
19 |
20 | override init(frame frameRect: NSRect) {
21 | super.init(frame: frameRect)
22 | self.commonInit()
23 | }
24 |
25 | func commonInit(){
26 | // self.wantsLayer = true
27 | self.createTrackingArea()
28 | self.hovered = false
29 | }
30 |
31 | fileprivate var trackingArea: NSTrackingArea!
32 | func createTrackingArea(){
33 | if(self.trackingArea != nil){
34 | self.removeTrackingArea(self.trackingArea!)
35 | }
36 | let circleRect = self.bounds
37 | let flag = NSTrackingAreaOptions.mouseEnteredAndExited.rawValue + NSTrackingAreaOptions.activeInActiveApp.rawValue
38 | self.trackingArea = NSTrackingArea(rect: circleRect, options: NSTrackingAreaOptions(rawValue: flag), owner: self, userInfo: nil)
39 | self.addTrackingArea(self.trackingArea)
40 | }
41 |
42 | override func mouseEntered(with theEvent: NSEvent) {
43 | self.hovered = true
44 | self.needsDisplay = true
45 | }
46 |
47 | override func mouseExited(with theEvent: NSEvent) {
48 | self.hovered = false
49 | self.needsDisplay = true
50 | }
51 |
52 | override func resetCursorRects() {
53 | super.resetCursorRects()
54 | self.addCursorRect(self.bounds, cursor: NSCursor.pointingHand())
55 | }
56 |
57 | override func draw(_ dirtyRect: NSRect) {
58 | super.draw(dirtyRect)
59 |
60 | if (hovered){
61 | let bottomLine = NSBezierPath()
62 | bottomLine.move(to: NSMakePoint(NSMinX(bounds), NSMaxY(bounds)))
63 | bottomLine.line(to: NSMakePoint(NSMaxX(bounds), NSMaxY(bounds)))
64 | bottomLine.lineWidth = 2.0
65 | bottomLine.stroke()
66 | }
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/12306ForMac/UserControls/UrlLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UrlLabel.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/8/10.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class UrlLabel: NSTextField {
12 |
13 | @IBInspectable var urlString:String!
14 |
15 | override init(frame frameRect: NSRect) {
16 | super.init(frame: frameRect)
17 | commonInit()
18 | }
19 |
20 | required init?(coder: NSCoder) {
21 | super.init(coder: coder)
22 | commonInit()
23 | }
24 |
25 | func commonInit(){
26 | self.isSelectable = false
27 | self.isEditable = false
28 | self.drawsBackground = false
29 |
30 | let attrs = [NSForegroundColorAttributeName: NSColor.blue, NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue] as [String : Any]
31 | let attributeStr = NSAttributedString(string: self.stringValue, attributes: attrs)
32 | self.attributedStringValue = attributeStr
33 | }
34 |
35 | override func mouseUp(with theEvent: NSEvent) {
36 | var curPoint = theEvent.locationInWindow
37 | curPoint = self.convert(curPoint, from: nil)
38 | if !NSPointInRect(curPoint, self.bounds) {
39 | return
40 | }
41 | NSWorkspace.shared().open(URL(string: urlString)!);
42 | }
43 |
44 | override func resetCursorRects() {
45 | super.resetCursorRects()
46 | self.addCursorRect(self.bounds, cursor: NSCursor.pointingHand())
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/12306ForMac/Utilities/CalendarManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CalendarManager.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/12/5.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import EventKit
11 |
12 | class CalendarManager:NSObject {
13 | private let eventStore:EKEventStore
14 | private var isAccessToEventStoreGranted:Bool
15 |
16 | fileprivate static let sharedManager = CalendarManager()
17 | class var sharedInstance: CalendarManager {
18 | return sharedManager
19 | }
20 |
21 | fileprivate override init () {
22 | isAccessToEventStoreGranted = false
23 | eventStore = EKEventStore()
24 | }
25 |
26 | private func createCalendar() {
27 | if !isAccessToEventStoreGranted {
28 | return
29 | }
30 | var shouldCreateCalendar = false
31 | if let calendarId = UserDefaults.standard.string(forKey: "calendarId") {
32 | let calendar = eventStore.calendar(withIdentifier: calendarId)
33 |
34 | if calendar == nil {
35 | shouldCreateCalendar = true
36 | }
37 | }
38 | else {
39 | shouldCreateCalendar = true
40 | }
41 | if !shouldCreateCalendar {
42 | return
43 | }
44 |
45 | let calendar = EKCalendar(for: .event, eventStore: eventStore)
46 | calendar.title = "12306ForMac"
47 | calendar.color = NSColor.gray
48 | calendar.source = eventStore.defaultCalendarForNewEvents.source
49 | do {
50 | try self.eventStore.saveCalendar(calendar, commit: true)
51 | logger.info("calendarId = \(calendar.calendarIdentifier)")
52 | UserDefaults.standard.set(calendar.calendarIdentifier, forKey: "calendarId")
53 | }
54 | catch {
55 | logger.error(error)
56 | }
57 | }
58 |
59 | @discardableResult
60 | func updateAuthorizationStatus() -> Bool {
61 | switch EKEventStore.authorizationStatus(for: .event) {
62 | case .authorized:
63 | self.isAccessToEventStoreGranted = true
64 | self.createCalendar()
65 | return true
66 | case .notDetermined:
67 | self.eventStore.requestAccess(to: .event, completion: {[unowned self] granted,error in
68 | DispatchQueue.main.async {
69 | self.isAccessToEventStoreGranted = granted
70 | self.createCalendar()
71 | }
72 | })
73 | return false
74 | case .restricted, .denied:
75 | isAccessToEventStoreGranted = false
76 | return false
77 | }
78 | }
79 |
80 | func createEvent(title:String, startDate:Date, endDate:Date)->Bool {
81 | if !isAccessToEventStoreGranted {
82 | return false
83 | }
84 |
85 | let createEventHandler = {(calendar:EKCalendar) -> () in
86 | let event = EKEvent(eventStore: self.eventStore)
87 | event.title = title
88 | event.startDate = startDate
89 | event.endDate = endDate
90 | event.calendar = calendar
91 | do {
92 | try self.eventStore.save(event, span: .thisEvent, commit: true)
93 | } catch {
94 | logger.error(error)
95 | }
96 | }
97 |
98 | if let calendarId = UserDefaults.standard.string(forKey: "calendarId") {
99 | if let calendar = eventStore.calendar(withIdentifier: calendarId) {
100 | createEventHandler(calendar)
101 | }
102 | else {
103 | logger.error("Calendar = nil,create")
104 | self.createCalendar()
105 |
106 | if let calendarId = UserDefaults.standard.string(forKey: "calendarId") {
107 | if let calendar = eventStore.calendar(withIdentifier: calendarId) {
108 | createEventHandler(calendar)
109 | }
110 | else{
111 | logger.error("Calendar = nil,again")
112 | }
113 | }
114 | }
115 | }
116 | return true
117 | }
118 |
119 | }
120 |
121 |
--------------------------------------------------------------------------------
/12306ForMac/Utilities/FSPreventSystemSleep.h:
--------------------------------------------------------------------------------
1 | //
2 | // FSPreventSystemSleep.h
3 | //
4 | // Created by fancymax on 16/01/2018.
5 | //
6 |
7 | #import
8 |
9 | @interface FSPreventSystemSleep : NSObject
10 |
11 | -(void) preventSystemSleep:(BOOL)disabled;
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/12306ForMac/Utilities/FSPreventSystemSleep.m:
--------------------------------------------------------------------------------
1 | //
2 | // FSPreventSystemSleep.m
3 | //
4 | // Created by fancymax on 16/01/2018.
5 | //
6 |
7 | #import "FSPreventSystemSleep.h"
8 | #import
9 |
10 | NSString * const kMPXPowerSaveAssertion = @"Query Tickets";
11 |
12 | @implementation FSPreventSystemSleep
13 | IOPMAssertionID nonSleepHandler;
14 |
15 | - (instancetype)init {
16 | if (self = [super init]) {
17 | nonSleepHandler = kIOPMNullAssertionID;
18 | }
19 |
20 | return self;
21 | }
22 |
23 | - (void)dealloc {
24 | if (nonSleepHandler != kIOPMNullAssertionID) {
25 | IOPMAssertionRelease(nonSleepHandler);
26 | nonSleepHandler = kIOPMNullAssertionID;
27 | }
28 | }
29 |
30 | -(void)preventSystemSleep:(BOOL)disabled {
31 | if (disabled) {
32 | if (nonSleepHandler == kIOPMNullAssertionID) {
33 | IOReturn err =
34 | IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleSystemSleep, kIOPMAssertionLevelOn,
35 | (__bridge CFStringRef)kMPXPowerSaveAssertion, &nonSleepHandler);
36 | if (err != kIOReturnSuccess) {
37 | NSLog(@"Can't preventSystemSleep");
38 | }
39 | }
40 | } else {
41 | if (nonSleepHandler != kIOPMNullAssertionID) {
42 | IOPMAssertionRelease(nonSleepHandler);
43 | nonSleepHandler = kIOPMNullAssertionID;
44 | }
45 | }
46 | }
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/12306ForMac/Utilities/Notifications.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Notifications.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2016/11/14.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Notification.Name {
12 | public struct App {
13 | public static let DidLogin = Notification.Name(rawValue: "com.12306ForMac.App.DidLogin")
14 |
15 | public static let DidLogout = Notification.Name(rawValue: "com.12306ForMac.App.DidLogout")
16 |
17 | public static let DidStartQueryTicket = Notification.Name(rawValue: "com.12306ForMac.App.DidStartQueryTicket")
18 |
19 | public static let DidRefilterQueryTicket = Notification.Name(rawValue: "com.12306ForMac.App.DidRefilterQueryTicket")
20 |
21 | public static let DidTrainFilterKeyChange = Notification.Name(rawValue: "com.12306ForMac.App.DidTrainFilterKeyChange")
22 |
23 | public static let DidExcludeTrainSubmit = Notification.Name(rawValue: "com.12306ForMac.App.DidExcludeTrainSubmit")
24 |
25 | public static let DidSubmit = Notification.Name(rawValue: "com.12306ForMac.App.DidSubmit")
26 |
27 | public static let DidCheckPassenger = Notification.Name(rawValue: "com.12306ForMac.App.DidCheckPassenger")
28 |
29 | public static let DidAutoLogin = Notification.Name(rawValue: "com.12306ForMac.App.DidAutoLogin")
30 |
31 | public static let DidAutoSubmit = Notification.Name(rawValue: "com.12306ForMac.App.DidAutoSubmit")
32 | public static let DidAutoSubmitWithoutRandCode = Notification.Name(rawValue: "com.12306ForMac.App.DidAutoSubmitWithoutRandCode")
33 |
34 | public static let DidAddDefaultPassenger = Notification.Name(rawValue: "com.12306ForMac.App.DidAddDefaultPassenger")
35 |
36 | public static let DidDamaGetBalance = Notification.Name(rawValue: "com.12306ForMac.App.DidDamaGetBalance")
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/12306ForMac/Utilities/NotifySpeaker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotifySpeaker.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 16/9/17.
6 | // Copyright © 2016年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AppKit
11 |
12 | class NotifySpeaker {
13 | fileprivate static let sharedManager = NotifySpeaker()
14 | class var sharedInstance: NotifySpeaker {
15 | return sharedManager
16 | }
17 |
18 | fileprivate let speachSynth: NSSpeechSynthesizer
19 |
20 | fileprivate init () {
21 | speachSynth = NSSpeechSynthesizer()
22 | let isSuccess = speachSynth.setVoice("com.apple.speech.synthesis.voice.mei-jia")
23 | if !isSuccess {
24 | speachSynth.setVoice("com.apple.speech.synthesis.voice.ting-ting")
25 | }
26 | }
27 |
28 | func notify() {
29 | if GeneralPreferenceManager.sharedInstance.isNotifyTicket {
30 | speachSynth.startSpeaking(GeneralPreferenceManager.sharedInstance.notifyStr)
31 | }
32 | }
33 |
34 | func stopNotify(){
35 | speachSynth.stopSpeaking()
36 | }
37 |
38 | func notifyLogin() {
39 | if GeneralPreferenceManager.sharedInstance.isNotifyLogin {
40 | speachSynth.startSpeaking(GeneralPreferenceManager.sharedInstance.notifyLoginStr)
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/12306ForMac/Utilities/QueryDefaultManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaultManager.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 15/11/26.
6 | // Copyright © 2015年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class QueryDefaultManager {
12 | static let sharedInstance = QueryDefaultManager()
13 |
14 | private let userNameKey = "userName"
15 | private let userPasswordKey = "userPassword"
16 | private let fromStationKey = "fromStation"
17 | private let toStationKey = "toStation"
18 | private let queryDateKey = "queryDate"
19 | private let selectedPassenger = "selectedPassenger"
20 | private let allSelectedDate = "allSelectedDate"
21 | private let ticketTaskManager = "ticketTaskManager"
22 | private let userDefaults = UserDefaults.standard
23 |
24 | private init()
25 | {
26 | registerUserDefault()
27 | }
28 |
29 | private func registerUserDefault()
30 | {
31 | let firstDefault = [fromStationKey: "深圳",
32 | toStationKey:"上海",queryDateKey:Date()] as [String : Any]
33 | userDefaults.register(defaults: firstDefault)
34 | }
35 |
36 | var lastUserName:String?{
37 | get{
38 | return userDefaults.object(forKey: userNameKey) as? String
39 | }
40 | set(newValue){
41 | userDefaults.set(newValue, forKey: userNameKey)
42 | }
43 | }
44 |
45 | var lastUserPassword:String?{
46 | get{
47 | return userDefaults.object(forKey: userPasswordKey) as? String
48 | }
49 | set(newValue){
50 | userDefaults.set(newValue, forKey: userPasswordKey)
51 | }
52 | }
53 |
54 | var lastFromStation:String{
55 | get{
56 | return userDefaults.object(forKey: fromStationKey) as! String
57 | }
58 | set(newValue){
59 | userDefaults.set(newValue, forKey: fromStationKey)
60 | }
61 | }
62 |
63 | var lastToStation:String{
64 | get{
65 | return userDefaults.object(forKey: toStationKey) as! String
66 | }
67 | set(newValue){
68 | userDefaults.set(newValue, forKey: toStationKey)
69 | }
70 | }
71 |
72 | var lastQueryDate:Date{
73 | get{
74 | return userDefaults.object(forKey: queryDateKey) as! Date
75 | }
76 | set(newValue){
77 | userDefaults.set(newValue, forKey: queryDateKey)
78 | }
79 | }
80 |
81 | var lastSelectedPassenger:String? {
82 | get {
83 | return userDefaults.object(forKey: selectedPassenger) as? String
84 | }
85 | set(newValue) {
86 | userDefaults.set(newValue,forKey: selectedPassenger)
87 | }
88 | }
89 |
90 | var lastAllSelectedDates:[Date]? {
91 | get {
92 | return userDefaults.array(forKey: allSelectedDate) as? [Date]
93 | }
94 | set(newValue) {
95 | userDefaults.set(newValue,forKey: allSelectedDate)
96 | }
97 | }
98 |
99 | var lastTicketTaskManager:String? {
100 | get {
101 | return userDefaults.object(forKey: ticketTaskManager) as? String
102 | }
103 | set(newValue) {
104 | userDefaults.set(newValue,forKey: ticketTaskManager)
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/12306ForMac/Utilities/ReminderManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReminderManager.swift
3 | // 12306ForMac
4 | //
5 | // Created by fancymax on 2/24/2017.
6 | // Copyright © 2017年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import EventKit
11 |
12 | class ReminderManager: NSObject {
13 |
14 | private let eventStore:EKEventStore
15 | private var isAccessToEventStoreGranted:Bool
16 |
17 | fileprivate static let sharedManager = ReminderManager()
18 | class var sharedInstance: ReminderManager {
19 | return sharedManager
20 | }
21 |
22 | private override init () {
23 | isAccessToEventStoreGranted = false
24 | eventStore = EKEventStore()
25 | }
26 |
27 | @discardableResult
28 | func updateAuthorizationStatus()->Bool {
29 | switch EKEventStore.authorizationStatus(for: .reminder) {
30 | case .authorized:
31 | self.isAccessToEventStoreGranted = true
32 | return true
33 | case .notDetermined:
34 | self.eventStore.requestAccess(to: .reminder, completion: {[unowned self] granted,error in
35 | DispatchQueue.main.async {
36 | self.isAccessToEventStoreGranted = granted
37 | }
38 | })
39 | return false
40 | case .restricted, .denied:
41 | isAccessToEventStoreGranted = false
42 | return false
43 | }
44 | }
45 |
46 | func createReminder(_ title:String, startDate:Date) {
47 | if !isAccessToEventStoreGranted {
48 | return
49 | }
50 |
51 | let reminder = EKReminder(eventStore: self.eventStore)
52 |
53 | reminder.calendar = eventStore.defaultCalendarForNewReminders()
54 | reminder.title = title
55 |
56 | var date = Date(timeInterval: 5, since: Date())
57 | var alarm = EKAlarm(absoluteDate: date)
58 | reminder.addAlarm(alarm)
59 |
60 | date = Date(timeInterval: 10, since: Date())
61 | alarm = EKAlarm(absoluteDate: date)
62 | reminder.addAlarm(alarm)
63 |
64 | date = Date(timeInterval: 15, since: Date())
65 | alarm = EKAlarm(absoluteDate: date)
66 | reminder.addAlarm(alarm)
67 |
68 | try! eventStore.save(reminder, commit: true)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/12306ForMac/Utilities/SwiftyRegex.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRegex.swift
3 | // Train12306
4 | //
5 | // Created by fancymax on 15/7/31.
6 | // Copyright (c) 2015年 fancy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Regex {
12 | let internalExpression: NSRegularExpression?
13 | let pattern: String
14 |
15 | init(_ pattern: String) {
16 | self.pattern = pattern
17 | do {
18 | self.internalExpression = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
19 | } catch _ as NSError {
20 | self.internalExpression = nil
21 | }
22 | }
23 |
24 | func getMatches(_ input: String) -> [[String]]? {
25 | var res = [[String]]()
26 | let myRange = NSMakeRange(0, input.characters.count)
27 | if let matches = self.internalExpression?.matches(in: input, options: [], range:myRange)
28 | {
29 | for match in matches
30 | {
31 | var groupMatch = [String]()
32 | for i in 1.. 0{
41 | return res
42 | }
43 | else{
44 | return nil
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
1 | github "Alamofire/Alamofire" == 4.0
2 | github "ccgus/fmdb" == 2.6.2
3 | github "mxcl/OMGHTTPURLRQ" == 3.2.2
4 | github "mxcl/PromiseKit" == 4.1.0
5 | github "DaveWoodCom/XCGLogger" == 5.0.5
6 | github "shpakovski/MASPreferences" == 1.1.4
7 | github "SwiftyJSON/SwiftyJSON"
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "Alamofire/Alamofire" "4.0.0"
2 | github "DaveWoodCom/XCGLogger" "5.0.5"
3 | github "SwiftyJSON/SwiftyJSON" "4.0.0"
4 | github "ccgus/fmdb" "2.6.2"
5 | github "mxcl/OMGHTTPURLRQ" "3.2.2"
6 | github "mxcl/PromiseKit" "4.1.0"
7 | github "shpakovski/MASPreferences" "1.1.4"
8 |
--------------------------------------------------------------------------------
/DJProgressHUD/DJActivityIndicator.h:
--------------------------------------------------------------------------------
1 | //
2 | // DJProgressIndicator.h
3 | // Playground
4 | //
5 | // Created by Daniel Jackson on 5/11/14.
6 | // Copyright (c) 2014 Daniel Jackson. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface DJActivityIndicator : NSView
12 |
13 | @property BOOL isAnimating;
14 |
15 | - (void)setColor:(NSColor *)value;
16 | - (void)setBackgroundColor:(NSColor *)value;
17 |
18 | - (void)stopAnimation:(id)sender;
19 | - (void)startAnimation:(id)sender;
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/DJProgressHUD/DJActivityIndicator.m:
--------------------------------------------------------------------------------
1 | //
2 | // DJProgressIndicator.m
3 | // Playground
4 | //
5 | // Created by Daniel Jackson on 5/11/14.
6 | // Copyright (c) 2014 Daniel Jackson. All rights reserved.
7 | //
8 |
9 | #import "DJActivityIndicator.h"
10 |
11 | #define kAlphaWhenStopped 0.15
12 | #define kFadeMultiplier 0.85
13 |
14 | @interface DJActivityIndicator ()
15 | {
16 | int position;
17 | NSMutableArray* finColors;
18 |
19 | BOOL isFadingOut;
20 | NSTimer* animationTimer;
21 |
22 | NSColor* foreColor;
23 | NSColor* backColor;
24 | }
25 | @end
26 |
27 | @implementation DJActivityIndicator
28 |
29 | - (id)initWithFrame:(NSRect)frame
30 | {
31 | self = [super initWithFrame:frame];
32 | if (self) {
33 | position = 0;
34 | int numFins = 12;
35 |
36 | finColors = [[NSMutableArray alloc] initWithCapacity:numFins];
37 |
38 | _isAnimating = NO;
39 | isFadingOut = NO;
40 |
41 | foreColor = [NSColor blackColor];
42 | backColor = [NSColor clearColor];
43 |
44 | for(int i=0; i= size.height)
59 | theMaxSize = size.height;
60 | else
61 | theMaxSize = size.width;
62 |
63 | [backColor set];
64 | [NSBezierPath fillRect:[self bounds]];
65 |
66 | CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
67 | [NSGraphicsContext saveGraphicsState];
68 |
69 | CGContextTranslateCTM(currentContext,[self bounds].size.width/2,[self bounds].size.height/2);
70 |
71 | NSBezierPath *path = [[NSBezierPath alloc] init];
72 | CGFloat lineWidth = 0.0859375 * theMaxSize;
73 | CGFloat lineStart = 0.234375 * theMaxSize;
74 | CGFloat lineEnd = 0.421875 * theMaxSize;
75 | [path setLineWidth:lineWidth];
76 | [path setLineCapStyle:NSRoundLineCapStyle];
77 | [path moveToPoint:NSMakePoint(0,lineStart)];
78 | [path lineToPoint:NSMakePoint(0,lineEnd)];
79 |
80 | for (int i=0; i 0) {
136 | position--;
137 | }
138 | else {
139 | position = (int)finColors.count - 1;
140 | }
141 |
142 | CGFloat minAlpha = kAlphaWhenStopped;
143 | for (int i=0; i 0.01) {
155 | done = NO;
156 | break;
157 | }
158 | }
159 | if (done) {
160 | [self actuallyStopAnimation];
161 | }
162 | }
163 | else {
164 | finColors[position] = foreColor;
165 | }
166 |
167 | [self setNeedsDisplay:YES];
168 |
169 |
170 | }
171 |
172 | - (void)actuallyStartAnimation
173 | {
174 | [self actuallyStopAnimation];
175 |
176 | _isAnimating = YES;
177 | isFadingOut = NO;
178 |
179 | position = 1;
180 |
181 | animationTimer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05
182 | target:self
183 | selector:@selector(updateFrame:)
184 | userInfo:nil
185 | repeats:YES];
186 |
187 | [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSRunLoopCommonModes];
188 | [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSDefaultRunLoopMode];
189 | [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSEventTrackingRunLoopMode];
190 | }
191 |
192 | - (void)actuallyStopAnimation
193 | {
194 | _isAnimating = NO;
195 | isFadingOut = NO;
196 |
197 | if (animationTimer) {
198 | // we were using timer-based animation
199 | [animationTimer invalidate];
200 | animationTimer = nil;
201 | }
202 | //[self setNeedsDisplay:YES];
203 | }
204 |
205 |
206 |
207 |
208 | @end
209 |
--------------------------------------------------------------------------------
/DJProgressHUD/DJLayerView.h:
--------------------------------------------------------------------------------
1 | //
2 | // DJLayerView.h
3 | // Playground
4 | //
5 | // Created by fancymax on 16/10/24.
6 | // Copyright © 2016年 Daniel Jackson. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface DJLayerView : NSView
12 |
13 | +(void)showStatus:(NSString*)status FromView:(NSView *)view;
14 | +(void)dismiss;
15 |
16 | // Customization
17 | #define pMaxWidth1 250
18 | #define pMaxHeight1 200
19 |
20 | //General Popup Values
21 | @property (nonatomic) CGVector pOffset;
22 | @property (nonatomic) CGFloat pAlpha;
23 |
24 | //Padding
25 | @property (nonatomic) CGFloat pPadding;
26 |
27 | @property (nonatomic) CGSize indicatorSize;
28 | @property (nonatomic) CGVector indicatorOffset;
29 | @property (nonatomic) CGSize labelSize;
30 | @property (nonatomic) CGVector labelOffset;
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/DJProgressHUD/DJLayerView.m:
--------------------------------------------------------------------------------
1 | //
2 | // DJLayerView.m
3 | // Playground
4 | //
5 | // Created by fancymax on 16/10/24.
6 | // Copyright © 2016年 Daniel Jackson. All rights reserved.
7 | //
8 |
9 | #import "DJLayerView.h"
10 | #import "DJActivityIndicator.h"
11 |
12 | @interface DJLayerView(){
13 | NSView* parentView;
14 |
15 | CGSize pSize; //This is set automatically based on the content
16 | DJActivityIndicator* activityIndicator;
17 | NSTextField* label;
18 | }
19 |
20 | @end
21 |
22 | @implementation DJLayerView
23 |
24 | +(void)showStatus:(NSString *)status FromView:(NSView *)parentView{
25 | [[self instance] showStatus:status FromView:parentView];
26 | }
27 |
28 | +(void)dismiss {
29 | [[self instance] finishHideView];
30 | }
31 |
32 | -(void)finishHideView
33 | {
34 | [self removeFromSuperview];
35 | parentView = nil;
36 |
37 | [activityIndicator stopAnimation:nil];
38 | }
39 |
40 | -(void)showStatus:(NSString*)status FromView:(NSView *)view {
41 | parentView = view;
42 | label.stringValue = status;
43 | [activityIndicator startAnimation:nil];
44 |
45 | if (!self.superview) {
46 | [parentView addSubview:self];
47 | }
48 |
49 | [self updateLayout];
50 |
51 | CGColorRef bgcolor = CGColorCreateGenericRGB(0.05, 0.05, 0.05, 0.8);
52 | self.layer.backgroundColor = bgcolor;
53 | self.layer.cornerRadius = 15.0;
54 | [self setFrame:[self getCenterWithinRect:parentView.frame scale:1]];
55 | }
56 |
57 | - (NSRect)getCenterWithinRect:(NSRect)parentFrame scale:(CGFloat)scale
58 | {
59 | NSRect result;
60 | CGFloat newWidth = pSize.width*scale;
61 | CGFloat newHeight = pSize.height*scale;
62 | result.origin.x = parentFrame.size.width/2 - newWidth/2 + _pOffset.dx;
63 | result.origin.y = parentFrame.size.height/2 - newHeight/2 + _pOffset.dy;
64 | result.size.width = newWidth;
65 | result.size.height = newHeight;
66 |
67 | return result;
68 | }
69 |
70 | - (void)updateLayout
71 | {
72 | CGSize maxContentSize = CGSizeMake(pMaxWidth1-(_pPadding*2), pMaxHeight1-(_pPadding*2));
73 | CGSize minContentSize = CGSizeMake(_indicatorSize.width, _indicatorSize.height);
74 |
75 | CGFloat stringWidth = [label.stringValue sizeWithAttributes:@{ NSFontAttributeName : label.font }].width + 5;
76 | float stringHeight = [self heightForString:label.stringValue font:label.font width:maxContentSize.width] + 8;
77 |
78 | if(label.stringValue == nil || label.stringValue.length == 0)
79 | stringHeight = 0;
80 |
81 | stringWidth = (stringWidth > minContentSize.width) ? stringWidth : minContentSize.width;
82 | if(stringWidth > maxContentSize.width)
83 | stringWidth = maxContentSize.width;
84 |
85 | CGFloat maxStringHeight = maxContentSize.height-_indicatorSize.height-(_pPadding+(_pPadding/2));
86 | stringHeight = (stringHeight > maxStringHeight) ? maxStringHeight : stringHeight;
87 |
88 | CGFloat popupWidth = stringWidth+(_pPadding*2);
89 |
90 | CGFloat lW = stringWidth;
91 | CGFloat lH = stringHeight;
92 | CGFloat lX = _pPadding;
93 | CGFloat lY = (stringHeight == 0) ? 0 : _pPadding;
94 | [label setFrame:NSMakeRect(lX, lY, lW, lH)];
95 |
96 | CGFloat spaceBetween = (stringHeight != 0) ? _pPadding/3 : _pPadding;
97 |
98 | CGFloat iW = _indicatorSize.width;
99 | CGFloat iH = _indicatorSize.height;
100 | CGFloat iX = ((lW+(_pPadding*2))/2)-(iW/2); //center it
101 | CGFloat iY = lY+lH+(spaceBetween);
102 | NSRect indicatorRect = NSMakeRect(iX, iY, iW, iH);
103 | activityIndicator.frame = indicatorRect;
104 |
105 | CGFloat spaceOnTop = (stringHeight != 0) ? _pPadding/3 : 0;
106 |
107 | [activityIndicator setColor:[NSColor whiteColor]];
108 |
109 | pSize.width = popupWidth;
110 | pSize.height = iY+iH+_pPadding+spaceOnTop;//+(_pPadding/2);
111 |
112 | [self setAutoresizesSubviews:YES];
113 | }
114 |
115 | -(CGFloat) heightForString:(NSString *)myString font:(NSFont*) myFont width:(CGFloat)myWidth
116 | {
117 | NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:myString];
118 | NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(myWidth, FLT_MAX)];
119 | ;
120 | NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
121 | [layoutManager addTextContainer:textContainer];
122 | [textStorage addLayoutManager:layoutManager];
123 | [textStorage addAttribute:NSFontAttributeName value:myFont
124 | range:NSMakeRange(0, [textStorage length])];
125 | [textContainer setLineFragmentPadding:0.0];
126 |
127 | (void) [layoutManager glyphRangeForTextContainer:textContainer];
128 | return [layoutManager
129 | usedRectForTextContainer:textContainer].size.height;
130 | }
131 |
132 | - (void)initializePopup
133 | {
134 | [self setWantsLayer:YES];
135 |
136 | self.autoresizingMask = NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin;
137 |
138 | activityIndicator = [[DJActivityIndicator alloc] init];
139 | label = [[NSTextField alloc] init];
140 |
141 | [self addSubview:label];
142 | [self addSubview:activityIndicator];
143 |
144 | //----DEFAULT VALUES----
145 |
146 | _pOffset = CGVectorMake(0, 0);
147 | _pAlpha = 0.9;
148 | _pPadding = 10;
149 |
150 | _indicatorSize = CGSizeMake(30, 30);
151 | _indicatorOffset = CGVectorMake(0, 0);
152 |
153 | [label setBezeled:NO];
154 | [label setDrawsBackground:NO];
155 | [label setEditable:NO];
156 | [label setSelectable:NO];
157 |
158 | label.font = [NSFont systemFontOfSize:13.0];
159 | [label setTextColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.85]];
160 | }
161 |
162 | + (DJLayerView *) instance
163 | {
164 | static dispatch_once_t once;
165 | static DJLayerView *sharedView;
166 | dispatch_once(&once, ^ {
167 | sharedView = [[self alloc] init];
168 | [sharedView initializePopup];
169 | });
170 |
171 | return sharedView;
172 | }
173 |
174 | @end
175 |
--------------------------------------------------------------------------------
/DJProgressHUD/DJTipHUD.h:
--------------------------------------------------------------------------------
1 | //
2 | // CPProgressView.h
3 | // Cloud Play OSX
4 | //
5 | // Created by Daniel Jackson on 4/22/14.
6 | // Copyright (c) 2014 Daniel Jackson. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 |
13 | @interface DJTipHUD : NSView
14 |
15 | + (void)showStatus:(NSString*)status FromView:(NSView*)view;
16 |
17 | @property (nonatomic, readonly) BOOL displaying;
18 |
19 | // Customization
20 | #define pMaxWidth1 250
21 | #define pMaxHeight1 200
22 |
23 | //General Popup Values
24 | @property (nonatomic) CGVector pOffset;
25 | @property (nonatomic) CGFloat pAlpha;
26 |
27 | //Padding
28 | @property (nonatomic) CGFloat pPadding;
29 |
30 | @property (nonatomic) CGSize indicatorSize;
31 | @property (nonatomic) CGVector indicatorOffset;
32 | @property (nonatomic) CGSize labelSize;
33 | @property (nonatomic) CGVector labelOffset;
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/DJProgressHUD/DJTipHUD.m:
--------------------------------------------------------------------------------
1 | //
2 | // CPProgressView.m
3 | // Cloud Play OSX
4 | //
5 | // Created by Daniel Jackson on 4/22/14.
6 | // Copyright (c) 2014 Daniel Jackson. All rights reserved.
7 | //
8 |
9 | #import "DJTipHUD.h"
10 |
11 | typedef void (^CompletionHander)(void);
12 |
13 | @interface DJTipHUD ()
14 | {
15 | NSView* parentView;
16 | CGSize pSize; //This is set automatically based on the content
17 | NSTextField* label;
18 | }
19 |
20 | @end
21 |
22 | @implementation DJTipHUD
23 |
24 | #pragma mark -
25 | #pragma mark Class Methods
26 |
27 | + (void)showStatus:(NSString*)status FromView:(NSView*)view
28 | {
29 | [[self instance] showStatus:status FromView:view];
30 | }
31 |
32 | #pragma mark -
33 | #pragma mark Master Methods
34 |
35 | - (void)showStatus:(NSString*)status FromView:(NSView*)view
36 | {
37 | if (_displaying) {
38 | return;
39 | }
40 |
41 | parentView = view;
42 | label.stringValue = status;
43 |
44 | if(!self.superview) {
45 | [parentView addSubview:self];
46 | _displaying = true;
47 | }
48 |
49 | CGColorRef bgcolor = CGColorCreateGenericRGB(0.05, 0.05, 0.05, 0.8);
50 | self.layer.backgroundColor = bgcolor;
51 | self.layer.cornerRadius = 15.0;
52 |
53 | [self updateLayout];
54 | NSRect size = [self getCenterWithinRect:parentView.frame scale:1.0];
55 | [self setFrame:size];
56 | // [self setFrame:[self getCenterWithinRect:parentView.frame scale:1.0]];
57 |
58 | NSInteger interval = 2;
59 | if ([status length] >= 20) {
60 | interval = 4;
61 | }
62 | if ([status length] >= 30) {
63 | interval = 6;
64 | }
65 | [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(finishHideView) userInfo:nil repeats:NO];
66 | }
67 |
68 | #pragma mark -
69 | #pragma mark Instance Methods
70 |
71 | -(void)finishHideView
72 | {
73 | [self removeFromSuperview];
74 | parentView = nil;
75 | _displaying = false;
76 | }
77 |
78 | #pragma mark -
79 | #pragma mark Laying It Out
80 |
81 | - (void)updateLayout
82 | {
83 | CGSize maxContentSize = CGSizeMake(pMaxWidth1-(_pPadding*2), pMaxHeight1-(_pPadding*2));
84 | CGSize minContentSize = CGSizeMake(_indicatorSize.width, _indicatorSize.height);
85 |
86 | CGFloat stringWidth = [label.stringValue sizeWithAttributes:@{ NSFontAttributeName : label.font }].width + 5;
87 | float stringHeight = [self heightForString:label.stringValue font:label.font width:maxContentSize.width] + 8;
88 |
89 | if(label.stringValue == nil || label.stringValue.length == 0)
90 | stringHeight = 0;
91 |
92 | stringWidth = (stringWidth > minContentSize.width) ? stringWidth : minContentSize.width;
93 | if(stringWidth > maxContentSize.width)
94 | stringWidth = maxContentSize.width;
95 |
96 | CGFloat maxStringHeight = maxContentSize.height-_indicatorSize.height-(_pPadding+(_pPadding/2));
97 | stringHeight = (stringHeight > maxStringHeight) ? maxStringHeight : stringHeight;
98 |
99 | CGFloat popupWidth = stringWidth+(_pPadding*2);
100 |
101 | CGFloat lW = stringWidth;
102 | CGFloat lH = stringHeight;
103 | CGFloat lX = _pPadding;
104 | CGFloat lY = (stringHeight == 0) ? 0 : _pPadding;
105 | [label setFrame:NSMakeRect(lX, lY, lW, lH)];
106 |
107 | CGFloat spaceBetween = (stringHeight != 0) ? _pPadding/3 : _pPadding;
108 |
109 | // CGFloat iW = _indicatorSize.width;
110 | CGFloat iH = _indicatorSize.height;
111 | // CGFloat iX = ((lW+(_pPadding*2))/2)-(iW/2); //center it
112 | CGFloat iY = lY+lH+(spaceBetween);
113 |
114 | CGFloat spaceOnTop = (stringHeight != 0) ? _pPadding/3 : 0;
115 |
116 | pSize.width = popupWidth;
117 | pSize.height = iY+iH+_pPadding+spaceOnTop;//+(_pPadding/2);
118 |
119 | [self setAutoresizesSubviews:YES];
120 | }
121 |
122 | #pragma mark -
123 | #pragma mark Other
124 |
125 | -(CGFloat) heightForString:(NSString *)myString font:(NSFont*) myFont width:(CGFloat)myWidth
126 | {
127 | NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:myString];
128 | NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(myWidth, FLT_MAX)];
129 | ;
130 | NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
131 | [layoutManager addTextContainer:textContainer];
132 | [textStorage addLayoutManager:layoutManager];
133 | [textStorage addAttribute:NSFontAttributeName value:myFont
134 | range:NSMakeRange(0, [textStorage length])];
135 | [textContainer setLineFragmentPadding:0.0];
136 |
137 | (void) [layoutManager glyphRangeForTextContainer:textContainer];
138 | return [layoutManager
139 | usedRectForTextContainer:textContainer].size.height;
140 | }
141 |
142 | - (NSRect)getCenterWithinRect:(NSRect)parentFrame scale:(CGFloat)scale
143 | {
144 | NSRect result;
145 | CGFloat newWidth = pSize.width*scale;
146 | CGFloat newHeight = pSize.height*scale;
147 | result.origin.x = parentFrame.size.width/2 - newWidth/2 + _pOffset.dx;
148 | result.origin.y = parentFrame.size.height/2 - newHeight/2 + _pOffset.dy;
149 | result.size.width = newWidth;
150 | result.size.height = newHeight;
151 |
152 | return result;
153 | }
154 |
155 | #pragma mark -
156 |
157 | - (void)initializePopup
158 | {
159 | [self setWantsLayer:YES];
160 |
161 | self.autoresizingMask = NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin;
162 |
163 | label = [[NSTextField alloc] init];
164 |
165 |
166 | [self addSubview:label];
167 |
168 | //----DEFAULT VALUES----
169 |
170 | _pOffset = CGVectorMake(0, 0);
171 | _pAlpha = 0.9;
172 | _pPadding = 10;
173 |
174 | _indicatorSize = CGSizeMake(0, 0);
175 | _indicatorOffset = CGVectorMake(0, 0);
176 |
177 | [label setBezeled:NO];
178 | [label setDrawsBackground:NO];
179 | [label setEditable:NO];
180 | [label setSelectable:NO];
181 |
182 | label.font = [NSFont systemFontOfSize:13.0];
183 | [label setTextColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.85]];
184 | }
185 |
186 | + (DJTipHUD *) instance
187 | {
188 | static dispatch_once_t once;
189 | static DJTipHUD *sharedView;
190 | dispatch_once(&once, ^ {
191 | sharedView = [[self alloc] init];
192 | [sharedView initializePopup];
193 | });
194 |
195 | return sharedView;
196 | }
197 |
198 | @end
199 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Max Lin
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 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 12306ForMac
2 |
3 | 仅做学习参考,随着12306接口的变更可能无法正常使用。
4 |
5 | Mac版12306 订票/捡票 助手。
6 |
7 | 以前要么开Windows虚拟机,要么使用官方Web,现在可以使用12306ForMac订票助手。
8 |
9 | 注意系统要求 **OS X10.11** 以上
10 |
11 | 
12 |
13 | # 开发
14 |
15 | 1. OS X 10.13/Xcode 9.0/Swift 3.2/brew
16 | 2. $ brew install carthage
17 | 3. $ git clone --recursive https://github.com/fancymax/12306ForMac.git
18 | 5. $ cd 12306ForMac
19 | 4. $ carthage update --platform macOS
20 |
21 | # 感谢
22 |
23 | 本项目基于 Alamofire、PromiseKit、FMDB、MASPreferences等进行开发,在此表示感谢。
24 |
25 |
--------------------------------------------------------------------------------
/screenshot/12306ForMac.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/screenshot/12306ForMac.jpg
--------------------------------------------------------------------------------
/screenshot/donate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/screenshot/donate.png
--------------------------------------------------------------------------------