├── CharlesGuide-Example.md
├── CharlesGuide-Install.md
├── CharlesGuide-Usage.md
├── CharlesGuide
├── .gitignore
├── .swift-version
├── Brewfile
├── CharlesGuide.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata
│ │ │ └── nmint8m.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcuserdata
│ │ └── nmint8m.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── CharlesGuide.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── CharlesGuide
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ └── ItunesArtwork@2x.png
│ │ ├── Contents.json
│ │ ├── img-check-manual-device.imageset
│ │ │ ├── Contents.json
│ │ │ └── img-config-device4.png
│ │ ├── img-check-proxy.imageset
│ │ │ ├── Contents.json
│ │ │ └── img-proxy3.png
│ │ ├── img-check-ssl-cer.imageset
│ │ │ ├── Contents.json
│ │ │ └── img-ssl5.png
│ │ ├── img-check-ssl-install.imageset
│ │ │ ├── Contents.json
│ │ │ └── img-ssl1.png
│ │ ├── img-check-ssl.imageset
│ │ │ ├── Contents.json
│ │ │ └── img-ssl21.png
│ │ ├── img-check-trust-ssl-simu.imageset
│ │ │ ├── Contents.json
│ │ │ └── img-config-simu3.png
│ │ ├── img-pokemon-ball.imageset
│ │ │ ├── Contents.json
│ │ │ └── img-pokemon-ball@1x.png
│ │ └── img-simu8.imageset
│ │ │ ├── Contents.json
│ │ │ └── img-simu8.png
│ ├── CheckList
│ │ ├── Cell
│ │ │ ├── ChecklistCell.swift
│ │ │ ├── ChecklistCell.xib
│ │ │ └── ChecklistCellViewModel.swift
│ │ ├── CheckListViewModel.swift
│ │ ├── ChecklistVC.swift
│ │ └── ChecklistVC.xib
│ ├── Info.plist
│ ├── LaunchScreen.storyboard
│ ├── PokemonList
│ │ ├── Cell
│ │ │ ├── PokemonCell.swift
│ │ │ ├── PokemonCell.xib
│ │ │ └── PokemonCellViewModel.swift
│ │ ├── PokemonListVC.swift
│ │ ├── PokemonListVC.xib
│ │ └── PokemonListViewModel.swift
│ └── Service
│ │ ├── API.swift
│ │ ├── APIManager.swift
│ │ ├── Models.swift
│ │ └── PokemonService.swift
├── Gemfile
├── Gemfile.lock
├── Podfile
└── Podfile.lock
├── Images
├── img-access1.png
├── img-access2.png
├── img-breakpoint1.png
├── img-breakpoint2.png
├── img-breakpoint3.png
├── img-config-device1.png
├── img-config-device2.png
├── img-config-device3.png
├── img-config-device4.png
├── img-config-simu0.png
├── img-config-simu1.png
├── img-config-simu2.png
├── img-config-simu3.png
├── img-ex1.png
├── img-ex2.png
├── img-ex3.png
├── img-ex4.png
├── img-ex5.png
├── img-ex6.png
├── img-ex7.png
├── img-ex8.png
├── img-handle.png
├── img-host1.png
├── img-host2.png
├── img-host3.png
├── img-icon.png
├── img-install1.png
├── img-install2.png
├── img-install3.png
├── img-install4.png
├── img-interface.png
├── img-local-ip1.png
├── img-local-ip2.png
├── img-pikachu@1x.png
├── img-pokemon-1.jpg
├── img-pokemon-ball@1x.png
├── img-proxy1.png
├── img-proxy2.png
├── img-proxy3.png
├── img-record1.png
├── img-record2.png
├── img-record3.png
├── img-record4.png
├── img-record5.png
├── img-register1.png
├── img-register2.png
├── img-register3.png
├── img-simu1.png
├── img-simu2.png
├── img-simu3.png
├── img-simu4.png
├── img-simu5.png
├── img-simu6.png
├── img-simu7.png
├── img-simu8.png
├── img-ssl0.png
├── img-ssl1.png
├── img-ssl2.png
├── img-ssl21.png
├── img-ssl22.png
├── img-ssl23.png
├── img-ssl3.png
├── img-ssl4.png
├── img-ssl5.png
├── img-throttlle1.png
└── img-throttlle2.png
├── LICENSE
└── README.md
/CharlesGuide-Example.md:
--------------------------------------------------------------------------------
1 | # Charles Guide - Example
2 |
3 | _Written by **Nguyen Minh Tam**_
4 |
5 | Ở phần trước mình đã trình bày cách cài đặt môi trường và cách làm việc cùng Charles. Trong phần này mình sẽ demo sử dụng Charles debug app nhỏ sử dụng API Pokemon [https://pokeapi.co](https://pokeapi.co). Bắt đầu thôi nào! 📱
6 |
7 | ### Cài đặt project example
8 |
9 | Mở Terminal, đến folder `CharlesGuide` (bằng cách kéo thả vào Terminal), chạy lần lượt:
10 |
11 | ```
12 | bundle install
13 | ```
14 |
15 | ```
16 | bundle exec pod install
17 | ```
18 |
19 | Mở Charles và làm các bước sau:
20 |
21 | - Cài đặt focus vào host: `pokeapi.co`
22 | - Cài đặt recording setting cho host: `pokeapi.co`
23 | - Cài đặt breakpoint setting cho path: `api/v2/pokemon` và tick chọn cả `Request` và `Response`
24 |
25 | > **My note:**
26 | >
27 | > Như mình đã đề cập ở phần trước, nên cài đặt focus và recording setting trỏ tới host, còn breakpoint setting chỉ cần quy định path là được.
28 |
29 | Sau khi cài đặt, mở file `CharlesGuide.xcworkspace` và run project.
30 |
31 | | | |
32 | |---|---|
33 | | Màn hình khởi động | Nhấn Bỏ qua / Bắt đầu |
34 |
35 | Lúc này giao diện của Charles như sau:
36 |
37 |
38 |
39 |
40 |
41 | Như bạn đã thấy, chúng ta có thể sửa request infor (như url, header,...) khi chọn tab `Edit request`. Nhưng lúc này ta sẽ tạm bỏ qua. Tiếp tục chọn `Execute`.
42 |
43 | Khi response trả về, giao diện của Charles sẽ như sau:
44 |
45 |
46 |
47 |
48 |
49 | Vào tab `JSON Text` trong `Edit response`, copy dán đoạn JSON phía dưới vào và chọn `Execute`.
50 |
51 | ```
52 | {
53 | "count": 1,
54 | "next": "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
55 | "previous": null,
56 | "results": [{
57 | "name": "Pikachu",
58 | "url": "https://pokeapi.co/api/v2/pokemon/1/"
59 | }]
60 | }
61 | ```
62 |
63 | Kết quả trên simulator/device sẽ hiển thị như sau:
64 |
65 |
66 |
67 |
68 |
69 | Như vậy, bạn đã biết cách sửa JSON của response, tiếp theo chúng ta sẽ đi tới sửa Headers của response. Bấm `Trang tiếp >`, làm như các bước trên và đợi response trả về. Bật tab `Header` tonse `Edit response`, bạn sẽ thấy như sau:
70 |
71 |
72 |
73 |
74 |
75 | Sửa `:status: 200` thành và chọn `Execute`.
76 |
77 | ```
78 | :status: 404
79 | ```
80 |
81 | Kết quả trên simulator/device sẽ hiển thị như sau:
82 |
83 |
84 |
85 |
86 |
87 | Sau khi nhấn `OK` để tắt pop up thì mình sẽ gọi lại API một lần nữa.
88 |
89 | Sau ví dụ đơn giản trên, mình muốn đề cập các vấn đề thường gặp khi bạn không debug được với Charles. Các lý do như sau:
90 |
91 | - Liên quan tới config lúc đầu. Đọc lại [Hướng dẫn cài đặt Charles](./CharlesGuide-Install.md) 🔧
92 | - Cài đặt sai host cho `Focus Host` và `Recording Settings`
93 | - Cài đặt sai path cho `Breakpoint Setting`.
94 | - Chưa cài đặt SSL Proxying Certificates. Đọc lại [Hướng dẫn cài đặt Charles](./CharlesGuide-Install.md) 🔧
95 | - App của bạn đang cache data response. Cụ thể như demo app đang sử dụng thư viện `Alamofire`, nó sẽ cache lại response, bởi vậy ở `AppDelegate` mình đã cài đặt không cho nó cache nữa:
96 |
97 | ```swift
98 | // AppDelegate.swift
99 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
100 | // Config not cache data
101 | URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
102 | ...
103 | }
104 | ```
105 |
106 | Cám ơn các bạn đã đọc hết tài liệu **Charles Guide**. Ủng hộ mình tiếp tục viết blog bằng cách bấm ⭐️ cho mình nhé!
107 |
108 | ### Reference
109 |
110 | Đọc lại [Hướng dẫn cài đặt Charles](./CharlesGuide-Install.md) 🔧
111 |
112 | Đọc lại [Hướng dẫn sử dụng Charles](./CharlesGuide-Usage.md) 🗒
113 |
114 | Quay lại [Charles Guide](https://github.com/nmint8m/charlesguide)
--------------------------------------------------------------------------------
/CharlesGuide-Install.md:
--------------------------------------------------------------------------------
1 | # Charles Guide - Install
2 |
3 | *Written by __Nguyen Minh Tam__*
4 |
5 | Nếu bạn là một mobile developer, mình khá chắc là bạn đã từng gặp qua tình huống éo le `vỡ UI` ít nhất một lần trong đời. `Vỡ UI` thường được bắt gặp khi mà bạn có một cái text, nội dung của cái text này được lấy bằng cách truy xuất database, hoặc từ kết quả mà API trả về. Cơ mà bạn lại quên mất việc kiểm tra UI khi nội dung cái text này dài ra trên các màn hình khác nhau, dẫn tới hậu quả UI của bạn banh chè.
6 |
7 | Mục đích của bài viết này giúp các bạn làm quen với một tool rất chy là bá đạo, cân từ debugging đến testing.
8 |
9 | Ví dụ nhé, nó giúp bạn có thể test các hiển thị lên UI của nhiều data set khác nhau như thế nào, ngay cả khi bạn chưa implement API. Trong một diễn biến khác, nó có thể giúp bạn debugging cách bạn call API đã đúng chưa: kiểm tra bạn đang gọi GET/POST/..., header ra sao, parameter như nào,... Ngoài ra nó còn cho phép bạn test những trường hợp download mạng chậm nữa.
10 |
11 | Và cái tool thần thánh được nhắc đến trong bài viết này chính là `Charles - Web Debugging Proxy Application`.
12 |
13 | > __Note:__ Các ví dụ mình đề cập trong bài viết này là những kinh nghiệm mình có được khi trong implement app trên iOS. Mình nghĩ về cơ bản thì develop Android và iOS khá giống nhau, nên chắc tài liệu này cũng có chút hữu ích với với các bạn Android developer.
14 |
15 | ## Mục lục
16 | - [Install Charles](#install-charles)
17 | - [Configure Charles và môi trường](#configure-charles-và-môi-trường)
18 | - [Config Proxy](#config-proxy)
19 | - [Enable macOS Proxy for Charles](#enable-macos-proxy-for-charles)
20 | - [Config proxy cho iOS Device](#config-proxy-cho-ios-device)
21 | - [Config proxy cho iOS Simulator](#config-proxy-cho-ios-simulator)
22 | - [Configuring SSL Proxying Certificates](#configuring-ssl-proxying-certificates)
23 | - [Add Charles CA Certificate](#add-charles-ca-certificate)
24 | - [Enable SSL Proxying Setting](#enable-ssl-proxying-setting)
25 | - [Trusting Charles's SSL Certificates](#trusting-charles-s-ssl-certificates)
26 |
27 | ## Install Charles
28 |
29 | Để cài đặt Charles cần:
30 |
31 | - Truy cập vào đường link [https://www.charlesproxy.com](https://www.charlesproxy.com/download/) và download file installer về máy.
32 |
33 |
34 |
35 |
36 |
37 | - Khởi động installer đã down về, hoàn thành theo chỉ dẫn:
38 |
39 |
40 |
41 |
42 |
43 |
44 | - Khởi động Charles:
45 |
46 |
47 |
48 |
49 |
50 | - Gói Charles free cho 30 ngày, vào đây để mua license nhé. Sau đó thì đi tới __Help > Register Charles... > Điền Register Name và License Key__ để đăng ký rồi restart lại Charles.
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | ## Configure Charles And Environment
60 |
61 | Phần set up Charles và môi trường là phần vô cùng quan trọng. Nếu bạn set up môi trường không đúng hoặc không đầy đủ, điều tất yếu là bạn sẽ chả thể làm việc được với nó.
62 |
63 | Để giúp bạn có thể kiểm tra liệu mình đã set up đúng chưa, thì sau đây là checklist các bước mình sử dụng:
64 |
65 | - [x] Config Proxy
66 | - [x] Bật macOS Proxy cho Charles
67 | - [x] Config proxy cho device
68 | - [x] Config Configuring SSL Proxying Certificates
69 | - [x] Thêm Charles CA Certificate cho máy mac
70 | - [x] Bật SSL Proxy cho máy mac và cài đặt SSL cho tất cả host name
71 | - [x] Bật trust SSL Certìicate cho device/simulator
72 |
73 | ### Config Proxy
74 |
75 | Proxy là một Internet server làm nhiệm vụ chuyển tiếp thông tin và kiểm soát tạo sự an toàn cho việc truy cập Internet của các máy client.
76 |
77 | Khi sử dụng Charles trên máy Mac, bạn cần config cho Charles sử dụng macOS Proxy.
78 |
79 | #### Enable macOS Proxy for Charles
80 |
81 | Lần đầu sử dụng Charles bạn sẽ được tự động hỏi về việc cấp quyền macOS Proxy như sau. Chọn __Grant Privileges__ và nhập user name và password:
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | Sau khi enable macOS Proxy cho Charles, dấu tick sẽ xuất hiện bên cạnh mục __View > macOS Proxy__ như thế này:
90 |
91 |
92 |
93 |
94 |
95 | Nếu bước trên bạn chọn __Not yet__ trong lúc cài đặt __Automatic macOS Proxy Configuration__, lần tới bạn có thế cài đặt thông qua __View > Proxy Settings...__.
96 |
97 | #### Config proxy cho iOS Device
98 |
99 | Khi bạn chọn debug app của bạn trên real device cùng với Charles, bạn cần phải trỏ HTTP Proxy của device đến máy tính mà bạn đang sử dụng.
100 |
101 | > __Lưu ý:__ Máy tính và điện thoại của bạn phải xài cùng wifi.
102 |
103 | Config cho device như sau:
104 |
105 | - Vào __Settings > Wifi__.
106 | - Chọn network đang kết nối tới.
107 | - Chọn __Config Proxy__.
108 | - Chọn __Manual__ và điền vào form, trong đó:
109 | - Server: Địa chỉ IP của máy tính đang chạy Charles
110 | - Port: Cổng mà Charles chạy (thường là 8888)
111 | - Authentication: Off
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Để biết local IP của máy tính, vào __Help > Local IP Address__.
123 |
124 |
125 |
126 |
127 |
128 |
129 | Tiếp tục, config access control cho máy mac như sau:
130 |
131 | - Vào __Proxy > Access Control Settings…__ để cho phép các device được phép kết nối với Charles bằng cách thêm mới.
132 |
133 |
134 |
135 |
136 |
137 |
138 | > __Lưu ý__:
139 | > Sau khi testing và debug trên device xong, nhớ setting lại wifi của device, bằng cách vào __Setting > Wifi__, chọn wifi sử dụng, chọn __Config Proxy > Off__. Nếu không làm vậy thì điện thoại của bạn không xài wifi được đâu.
140 |
141 | #### Config proxy cho iOS Simulator
142 |
143 | Simulator đã sử dụng system proxy, nên không cần phải config proxy cho simulator nữa. Nếu simulator vẫn gặp trục trặc, hãy khởi động lại simulator.
144 |
145 | ### Configuring SSL Proxying Certificates
146 |
147 |
148 |
149 |
150 |
151 | Charles có thể được sử dụng như một HTTPS proxy ở giữa, cho phép bạn đọc dữ liệu giao tiếp giữa web browser và SSL web server. Thay vì browser sẽ đọc certificate của server, Charles lúc này sẽ tạo một certificate cho server và xác thực bằng chính root certificate của nó (Charles CA Certificate). Charles nhận certificate của server trong khi browser của bạn nhận certificate của Charles. Vậy nên bạn sẽ nhận được thông báo bảo mật nói rằng `the root authority is not trusted`. Thông báo này sẽ không xuất hiện nữa nếu bạn đã thêm Charles CA Certificate vào trusted certificates.
152 |
153 | #### Add Charles CA Certificate
154 |
155 | - Vào __Help > SSL Proxying > Install Charles Root Certificates__.
156 | - Thêm __Certificates__.
157 | - Chọn __Charles Proxy CA__.
158 | - Chọn __Always Trust__.
159 | - Kết quả sẽ được như sau.
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | Tiếp theo, bạn cần xác định những host name mà bạn muốn bật SSL proxy. Với mình thì mình bật cho tất cả các host name.
174 |
175 | #### Enable SSL Proxying Setting
176 |
177 | - __Proxy > SSL Proxying Settings...__
178 | - Ở tab __SSL Proxying__, chọn __Enable SSL Proxying__.
179 | - Thêm __Location__.
180 | - Khởi động lại browser đang sử dụng (Safari/Chrome/...) để áp dụng thay đổi.
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | #### Trusting Charles's SSL Certificates
190 |
191 | Vì kể từ lúc này Charles sẽ tự tạo certificate của chính nó, được xác thực bằng Charles Root Certificate, nên khi bạn test hoặc debug trên device/simulator, bạn sẽ nhận được cảnh báo Charles Root Certificate không thuộc trong list trusted root certificate của device/simulator. Đó là lý do ta cài đặt Charles’s Root Certificate và bật Certificate Trust.
192 |
193 | __Đối với iOS Device__
194 |
195 | - Thực hiện cài đặt iOS device sử dụng Charles proxy như trong mục [Config proxy cho iOS Device](#config-proxy-cho-iOS-Device)
196 | - Mở Safari và truy cập tới [https://chls.pro/ssl](https://chls.pro/ssl) và chọn `Install` SSL certificate.
197 | - Tiếp tục vào __Settings > General > About > Certificate Trust Settings__.
198 | - Dưới mục __Enable full trust for root certificates__, bật cetificate cho Charles Proxy.
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 | __Đối với iOS Simulator__
207 |
208 | - Cài cho tất cả các simulator:
209 | - Tắt tất cả simulator
210 | - Trên Charles, vào __Help > SSL Proxying > Install Charles Root Certificate in iOS Simulators__.
211 |
212 |
213 |
214 |
215 |
216 | - Cách trên đôi khi không thành công. Ta có thể thực hiện cài cho simulator bạn cần dùng như sau:
217 | - Vào __Help > SSL Proxying > Save Charles Root Certificate…__ để lưu file *.pem ra Desktop.
218 | - Kéo thả file *.pem vừa tạo vào simulator.
219 |
220 |
221 |
222 |
223 |
224 |
225 | - Thực hiện cài đặt certificate cho simulator.
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | - Bật trust certificate cho simulator bằng cách:
236 | - Vào __Settings > General > About > Certificate Trust Settings__.
237 | - Dưới mục __Enable full trust for root certificates__, bật cetificate cho Charles Proxy.
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 | Nhắc lại checklist lần nữa để các bạn không quên:
246 |
247 | - [x] Config Proxy
248 | - [x] Bật macOS Proxy cho Charles
249 | - [x] Config proxy cho device
250 | - [x] Config Configuring SSL Proxying Certificates
251 | - [x] Thêm Charles CA Certificate cho máy mac
252 | - [x] Bật SSL Proxy cho máy mac và cài đặt SSL cho tất cả host name
253 | - [x] Bật trust SSL Certificate cho device/simulator
254 |
255 | Phía trên là các bước cài đặt môi trường làm việc cùng Charles. Hi vọng mọi người đã biết được một vài kiến thức hữu ích khi đọc tài liệu này. Ủng hộ mình một ⭐️ để có thêm động lực viết bài nhé! Cảm ơn mọi người. 🎉
256 |
257 | ### Reference
258 |
259 | Đọc tiếp [Hướng dẫn sử dụng Charles](./CharlesGuide-Usage.md) 🗒
260 |
261 | Đọc tiếp [Demo debugging an iOS app with dummy data from Charles](./CharlesGuide-Example.md) 🐞
262 |
263 | Quay lại [Charles Guide](https://github.com/nmint8m/charlesguide)
--------------------------------------------------------------------------------
/CharlesGuide-Usage.md:
--------------------------------------------------------------------------------
1 | # Charles Guide - Usage
2 |
3 | _Written by **Nguyen Minh Tam**_
4 |
5 | Ở phần trước mình đã trình bày cách cài đặt môi trường để làm việc cùng Charles. Trong phần này mình sẽ đề cập đến giao diện, những chức năng cơ bản và thường được sử dụng nhất của nó. Let's check it out! 🥇
6 |
7 | ## Mục lục
8 |
9 | - [Work with Charles Proxy](#work-with-charles-proxy)
10 | - [Application Interface](#application-interface)
11 | - [Focus](#focus)
12 | - [Recording settings](#recording-settings)
13 | - [Breakpoint](#breakpoint)
14 | - [Handling breakpoint](#handling-breakpoint)
15 | - [Throttle settings](#throttle-settings)
16 |
17 | ## Work with Charles Proxy
18 |
19 | ### Application Interface
20 |
21 |
22 |
23 |
24 |
25 | Các nút thông dụng:
26 |
27 | 1. **Clear the current session**:
28 | - Session chứa tất cả các thông tin được ghi lại.
29 | - Khi session đầy/busy, có thể clean session.
30 |
31 | 2. **Start/stop recording**:
32 | - Record là chức năng căn bản của Charles.
33 | - Request và response được lưu lại vào session hiện tại chỉ khi chức năng Record bật.
34 | - Request hiển thị trên màn hình session khi nó được lưu lại. Có thể xem request ở 2 chế độ: Structure và Sequence.
35 |
36 | 3. **Start/stop throttling**: Điều chỉnh băng thông
37 |
38 | 4. **Enable/Disable breakpoints**: Bật breakpoint để debug request/response
39 |
40 | 5. **Compose**: Soạn một request mới
41 |
42 | 6. **Repeat**: Thực hiện lại request được chọn
43 |
44 | 7. **Tool**: Active/deactive các tool như
45 | - Breakpoint
46 | - No caching
47 |
48 | 8. **Settings**:
49 | - Recording settings
50 | - Access control settings
51 |
52 | Nhưng trước khi đi vào sử dụng, chúng ta sẽ đi qua một số bước giúp hạn chế focus vào các host không cần thiết và cài đặt record lại những request/response cần thiết.
53 |
54 | ### Focus
55 |
56 | Vì có rất nhiều request và response từ vô số host trả về. Bước focus này giúp Charles tách riêng những host mà chúng ta cần quan tâm, tránh quá tải.
57 |
58 | - Vào **View > Focused Host > Add focused hosts...**
59 | - Adding protocol/host/port: `*.t8m.dev`
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | ### Recording settings
70 |
71 | Bước này dùng để lọc ra những thứ mà chúng ta sẽ record lại trong session.
72 |
73 | - Vào **Settings** của session cần lọc, hoặc vào **Proxy > Recording Settings...**
74 | - Chọn tab **Include**
75 | - Thêm Locations.
76 |
77 | > **My note:** Thường thì mình chỉ thêm host vào đối với Record settings, còn path/query thì mình hay đặt trong Breakpoint setting. Còn protocol/port/query thì mình để trống, và chỉ quan tâm nó khi debug request. Mình sẽ nói rõ hơn ở phía dưới.
78 |
79 |
80 |
81 | hoặc
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | > **My note:** Nếu như bạn không thấy trên Charles hiển thị request mà bạn đang debug, thì rất có thể bạn đã nhầm hoặc miss location ở bước này. Nhớ kiểm tra lại bạn đã add host bạn cần và check nó chưa nhé. 💣💣💣
93 |
94 | Vậy là xong phần config cơ bản, tiếp theo đây chúng ta sẽ nói về cách debug cùng Breakpoint và Throttle settings. Trước khi debug với một web/ios/android app, bạn cần phải cài đặt môi trường và cài certificate đầy đủ. Đọc cài đặt môi trường debug cho iOS app tại [đây](./CharlesGuide-Install.md)
95 |
96 | Check list:
97 |
98 | - [x] Config Proxy
99 | - [x] Bật macOS Proxy cho Charles
100 | - [x] Config proxy cho device
101 | - [x] Config Configuring SSL Proxying Certificates
102 | - [x] Thêm Charles CA Certificate cho máy mac
103 | - [x] Bật SSL Proxy cho máy mac và cài đặt SSL cho tất cả host name
104 | - [x] Bật trust SSL Certificate cho device/simulator
105 |
106 | Phần đầu tiên, ta sẽ học cách đặt breakpoint.
107 |
108 | ### Breakpoint
109 |
110 | - Vào **Proxy > Breakpoint Settings...**
111 | - Thêm breakpoint:
112 | - Adding host: Vì mình đã cài đặt trong `Recording setting` ở phía trên rồi, nên ở bước này có thể bỏ qua. Nếu bạn điển phần này, thì nhớ phải check lại trong Recording Setting xem host bạn điền đã được add và check chưa.
113 | - Adding path: `v1/item/search`
114 | - Add query: `name=somename` Có thể để trống bước này
115 | - Check breakpoints: `request/response`
116 | - Nếu bạn tick `request`, thì khi app gọi tới url này, bạn có thể thay đổi thông tin của request như heading, parameter, method,...
117 | - Nếu bạn tick `response`, bạn có khả năng thay đổi các thông tin như cấu trúc response xml, code,...
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | Trên đây là ví dụ breakpoint đặt ở `response` với path là `v1/item/search`. Vậy khi app gọi tới url này và server phản hồi, Charles sẽ bật cửa sổ breakpoint request lên. Cách handle breakpoint sẽ được đề cập ngay bên dưới.
128 |
129 | ### Handling breakpoint
130 |
131 | Sau khi đã đặt được breakpoint thì bước này dễ như ăn kẹo.
132 |
133 |
134 |
135 |
136 |
137 | - Tab **Overview** để theo dõi thông tin cơ bản của request và response: url, protocol, response code, method, content type...
138 | - Tab **Request** để xem thông tin chi tiết của request
139 | - Tab **Edit Response** để xem và sửa đổi thông tin chi tiết của response. Ví dụ như:
140 | - Header: Editing header
141 | - JSON text: Editing body
142 | - Tab **Execute** để tiếp tục
143 |
144 | ### Throttle settings
145 |
146 | Như mình đã đề cập, chức năng này được sử dụng để điều chỉnh băng thông chậm: download, upload...
147 |
148 | - Vào **Proxy > Throttle Settings...**
149 | - Config throttle setting
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | Phía trên là những chức năng cơ bản và thường được sử dụng nhất của Charles. Hi vọng mọi người đã biết được một vài kiến thức hữu ích khi đọc tài liệu này. Ủng hộ mình một ⭐️ để có thêm động lực viết bài nhé! Cảm ơn mọi người. 🎉
158 |
159 | ### Reference
160 |
161 | Đọc lại [Hướng dẫn cài đặt Charles](./CharlesGuide-Install.md) 🔧
162 |
163 | Đọc tiếp [Demo debugging an iOS app with dummy data from Charles](./CharlesGuide-Example.md) 🐞
164 |
165 | Quay lại [Charles Guide](https://github.com/nmint8m/charlesguide)
--------------------------------------------------------------------------------
/CharlesGuide/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ## Build generated
3 | build/
4 | DerivedData/
5 |
6 | ## Various settings
7 | *.pbxuser
8 | !default.pbxuser
9 | *.mode1v3
10 | !default.mode1v3
11 | *.mode2v3
12 | !default.mode2v3
13 | *.perspectivev3
14 | !default.perspectivev3
15 | xcuserdata/
16 |
17 | ## Other
18 | *.moved-aside
19 | *.xccheckout
20 | *.xcscmblueprint
21 |
22 | ## Obj-C/Swift specific
23 | *.hmap
24 | *.ipa
25 | *.dSYM.zip
26 | *.dSYM
27 |
28 | ## Playgrounds
29 | timeline.xctimeline
30 | playground.xcworkspace
31 |
32 | # Swift Package Manager
33 | .build/
34 | BuildExport
35 |
36 | # CocoaPods
37 | Pods
38 |
39 | # Carthage
40 | Carthage/Build
41 |
42 | # fastlane
43 |
44 | fastlane/report.xml
45 | fastlane/Preview.html
46 | fastlane/screenshots
47 | fastlane/test_output
48 | fastlane/README.md
49 | fastlane/release_notes.txt
50 |
51 | # Bundler
52 | .bundle
53 | vendor/bundle
54 | *.coverage.txt
55 | swiftlint-report.json
56 |
--------------------------------------------------------------------------------
/CharlesGuide/.swift-version:
--------------------------------------------------------------------------------
1 | 4.0.3
--------------------------------------------------------------------------------
/CharlesGuide/Brewfile:
--------------------------------------------------------------------------------
1 | brew 'jq'
2 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 22535C0D225C5EC40000F46A /* PokemonService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22535C0C225C5EC40000F46A /* PokemonService.swift */; };
11 | 22535C12225C63B50000F46A /* PokemonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22535C10225C63B50000F46A /* PokemonCell.swift */; };
12 | 22535C13225C63B50000F46A /* PokemonCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 22535C11225C63B50000F46A /* PokemonCell.xib */; };
13 | 22535C15225C63CE0000F46A /* PokemonListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22535C14225C63CE0000F46A /* PokemonListViewModel.swift */; };
14 | 22535C17225C63DC0000F46A /* PokemonCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22535C16225C63DC0000F46A /* PokemonCellViewModel.swift */; };
15 | 225C701C225A4D6F0077D006 /* APIManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225C701B225A4D6F0077D006 /* APIManager.swift */; };
16 | 225C701F225A54CC0077D006 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225C701E225A54CC0077D006 /* Models.swift */; };
17 | 2272BC2F225CA0E600846CC4 /* CheckListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2272BC2E225CA0E600846CC4 /* CheckListViewModel.swift */; };
18 | 2272BC31225CA15E00846CC4 /* ChecklistCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2272BC30225CA15E00846CC4 /* ChecklistCellViewModel.swift */; };
19 | 229581532259D43A00B1343B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 229581522259D43A00B1343B /* LaunchScreen.storyboard */; };
20 | 229581562259D5A300B1343B /* ChecklistVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229581542259D5A300B1343B /* ChecklistVC.swift */; };
21 | 229581572259D5A300B1343B /* ChecklistVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 229581552259D5A300B1343B /* ChecklistVC.xib */; };
22 | 2295815C2259DF6B00B1343B /* PokemonListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2295815A2259DF6B00B1343B /* PokemonListVC.swift */; };
23 | 2295815D2259DF6B00B1343B /* PokemonListVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2295815B2259DF6B00B1343B /* PokemonListVC.xib */; };
24 | 229581692259E00700B1343B /* ChecklistCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229581672259E00700B1343B /* ChecklistCell.swift */; };
25 | 2295816A2259E00700B1343B /* ChecklistCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 229581682259E00700B1343B /* ChecklistCell.xib */; };
26 | 22C094D42259F6F800EFED9D /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C094D32259F6F800EFED9D /* API.swift */; };
27 | 22E4AD9522361C2100164CE0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22E4AD9422361C2100164CE0 /* AppDelegate.swift */; };
28 | 22E4AD9C22361C2400164CE0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 22E4AD9B22361C2400164CE0 /* Assets.xcassets */; };
29 | DC890A6963AFE115DC731D9A /* Pods_CharlesGuide.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83553143EBC88C79029F136A /* Pods_CharlesGuide.framework */; };
30 | /* End PBXBuildFile section */
31 |
32 | /* Begin PBXFileReference section */
33 | 22535C0C225C5EC40000F46A /* PokemonService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PokemonService.swift; sourceTree = ""; };
34 | 22535C10225C63B50000F46A /* PokemonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PokemonCell.swift; sourceTree = ""; };
35 | 22535C11225C63B50000F46A /* PokemonCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PokemonCell.xib; sourceTree = ""; };
36 | 22535C14225C63CE0000F46A /* PokemonListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PokemonListViewModel.swift; sourceTree = ""; };
37 | 22535C16225C63DC0000F46A /* PokemonCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PokemonCellViewModel.swift; sourceTree = ""; };
38 | 225C701B225A4D6F0077D006 /* APIManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIManager.swift; sourceTree = ""; };
39 | 225C701E225A54CC0077D006 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; };
40 | 2272BC2E225CA0E600846CC4 /* CheckListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckListViewModel.swift; sourceTree = ""; };
41 | 2272BC30225CA15E00846CC4 /* ChecklistCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChecklistCellViewModel.swift; sourceTree = ""; };
42 | 229581522259D43A00B1343B /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; };
43 | 229581542259D5A300B1343B /* ChecklistVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChecklistVC.swift; sourceTree = ""; };
44 | 229581552259D5A300B1343B /* ChecklistVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChecklistVC.xib; sourceTree = ""; };
45 | 2295815A2259DF6B00B1343B /* PokemonListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PokemonListVC.swift; sourceTree = ""; };
46 | 2295815B2259DF6B00B1343B /* PokemonListVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PokemonListVC.xib; sourceTree = ""; };
47 | 229581672259E00700B1343B /* ChecklistCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChecklistCell.swift; sourceTree = ""; };
48 | 229581682259E00700B1343B /* ChecklistCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChecklistCell.xib; sourceTree = ""; };
49 | 22C094D32259F6F800EFED9D /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; };
50 | 22E4AD9122361C2100164CE0 /* CharlesGuide.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CharlesGuide.app; sourceTree = BUILT_PRODUCTS_DIR; };
51 | 22E4AD9422361C2100164CE0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
52 | 22E4AD9B22361C2400164CE0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
53 | 22E4ADA022361C2400164CE0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
54 | 3091CF309803E9A527B71F24 /* Pods-CharlesGuide.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CharlesGuide.release.xcconfig"; path = "Pods/Target Support Files/Pods-CharlesGuide/Pods-CharlesGuide.release.xcconfig"; sourceTree = ""; };
55 | 83553143EBC88C79029F136A /* Pods_CharlesGuide.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CharlesGuide.framework; sourceTree = BUILT_PRODUCTS_DIR; };
56 | 855A86287F5C3FF8EB4C681B /* Pods-CharlesGuide.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CharlesGuide.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CharlesGuide/Pods-CharlesGuide.debug.xcconfig"; sourceTree = ""; };
57 | /* End PBXFileReference section */
58 |
59 | /* Begin PBXFrameworksBuildPhase section */
60 | 22E4AD8E22361C2100164CE0 /* Frameworks */ = {
61 | isa = PBXFrameworksBuildPhase;
62 | buildActionMask = 2147483647;
63 | files = (
64 | DC890A6963AFE115DC731D9A /* Pods_CharlesGuide.framework in Frameworks */,
65 | );
66 | runOnlyForDeploymentPostprocessing = 0;
67 | };
68 | /* End PBXFrameworksBuildPhase section */
69 |
70 | /* Begin PBXGroup section */
71 | 22535C0F225C639B0000F46A /* Cell */ = {
72 | isa = PBXGroup;
73 | children = (
74 | 22535C10225C63B50000F46A /* PokemonCell.swift */,
75 | 22535C11225C63B50000F46A /* PokemonCell.xib */,
76 | 22535C16225C63DC0000F46A /* PokemonCellViewModel.swift */,
77 | );
78 | path = Cell;
79 | sourceTree = "";
80 | };
81 | 229581582259DF2600B1343B /* CheckList */ = {
82 | isa = PBXGroup;
83 | children = (
84 | 2295815E2259DF9700B1343B /* Cell */,
85 | 229581542259D5A300B1343B /* ChecklistVC.swift */,
86 | 229581552259D5A300B1343B /* ChecklistVC.xib */,
87 | 2272BC2E225CA0E600846CC4 /* CheckListViewModel.swift */,
88 | );
89 | path = CheckList;
90 | sourceTree = "";
91 | };
92 | 229581592259DF4F00B1343B /* PokemonList */ = {
93 | isa = PBXGroup;
94 | children = (
95 | 22535C0F225C639B0000F46A /* Cell */,
96 | 2295815A2259DF6B00B1343B /* PokemonListVC.swift */,
97 | 2295815B2259DF6B00B1343B /* PokemonListVC.xib */,
98 | 22535C14225C63CE0000F46A /* PokemonListViewModel.swift */,
99 | );
100 | path = PokemonList;
101 | sourceTree = "";
102 | };
103 | 2295815E2259DF9700B1343B /* Cell */ = {
104 | isa = PBXGroup;
105 | children = (
106 | 229581672259E00700B1343B /* ChecklistCell.swift */,
107 | 229581682259E00700B1343B /* ChecklistCell.xib */,
108 | 2272BC30225CA15E00846CC4 /* ChecklistCellViewModel.swift */,
109 | );
110 | path = Cell;
111 | sourceTree = "";
112 | };
113 | 22C094D22259F6CA00EFED9D /* Service */ = {
114 | isa = PBXGroup;
115 | children = (
116 | 22C094D32259F6F800EFED9D /* API.swift */,
117 | 225C701B225A4D6F0077D006 /* APIManager.swift */,
118 | 225C701E225A54CC0077D006 /* Models.swift */,
119 | 22535C0C225C5EC40000F46A /* PokemonService.swift */,
120 | );
121 | path = Service;
122 | sourceTree = "";
123 | };
124 | 22E4AD8822361C2100164CE0 = {
125 | isa = PBXGroup;
126 | children = (
127 | 22E4AD9322361C2100164CE0 /* CharlesGuide */,
128 | 22E4AD9222361C2100164CE0 /* Products */,
129 | 2E07D2D771D986F42442D12D /* Pods */,
130 | D0C28807BAB3E6E35F7B3412 /* Frameworks */,
131 | );
132 | sourceTree = "";
133 | };
134 | 22E4AD9222361C2100164CE0 /* Products */ = {
135 | isa = PBXGroup;
136 | children = (
137 | 22E4AD9122361C2100164CE0 /* CharlesGuide.app */,
138 | );
139 | name = Products;
140 | sourceTree = "";
141 | };
142 | 22E4AD9322361C2100164CE0 /* CharlesGuide */ = {
143 | isa = PBXGroup;
144 | children = (
145 | 22C094D22259F6CA00EFED9D /* Service */,
146 | 229581592259DF4F00B1343B /* PokemonList */,
147 | 229581582259DF2600B1343B /* CheckList */,
148 | 22E4AD9422361C2100164CE0 /* AppDelegate.swift */,
149 | 22E4AD9B22361C2400164CE0 /* Assets.xcassets */,
150 | 22E4ADA022361C2400164CE0 /* Info.plist */,
151 | 229581522259D43A00B1343B /* LaunchScreen.storyboard */,
152 | );
153 | path = CharlesGuide;
154 | sourceTree = "";
155 | };
156 | 2E07D2D771D986F42442D12D /* Pods */ = {
157 | isa = PBXGroup;
158 | children = (
159 | 855A86287F5C3FF8EB4C681B /* Pods-CharlesGuide.debug.xcconfig */,
160 | 3091CF309803E9A527B71F24 /* Pods-CharlesGuide.release.xcconfig */,
161 | );
162 | name = Pods;
163 | sourceTree = "";
164 | };
165 | D0C28807BAB3E6E35F7B3412 /* Frameworks */ = {
166 | isa = PBXGroup;
167 | children = (
168 | 83553143EBC88C79029F136A /* Pods_CharlesGuide.framework */,
169 | );
170 | name = Frameworks;
171 | sourceTree = "";
172 | };
173 | /* End PBXGroup section */
174 |
175 | /* Begin PBXNativeTarget section */
176 | 22E4AD9022361C2100164CE0 /* CharlesGuide */ = {
177 | isa = PBXNativeTarget;
178 | buildConfigurationList = 22E4ADA322361C2400164CE0 /* Build configuration list for PBXNativeTarget "CharlesGuide" */;
179 | buildPhases = (
180 | E3F2FF138B599D64C47AE19A /* [CP] Check Pods Manifest.lock */,
181 | 22E4AD8D22361C2100164CE0 /* Sources */,
182 | 22E4AD8E22361C2100164CE0 /* Frameworks */,
183 | 22E4AD8F22361C2100164CE0 /* Resources */,
184 | C4FA06F8491C0F374B3454AE /* [CP] Embed Pods Frameworks */,
185 | );
186 | buildRules = (
187 | );
188 | dependencies = (
189 | );
190 | name = CharlesGuide;
191 | productName = CharlesGuide;
192 | productReference = 22E4AD9122361C2100164CE0 /* CharlesGuide.app */;
193 | productType = "com.apple.product-type.application";
194 | };
195 | /* End PBXNativeTarget section */
196 |
197 | /* Begin PBXProject section */
198 | 22E4AD8922361C2100164CE0 /* Project object */ = {
199 | isa = PBXProject;
200 | attributes = {
201 | LastSwiftUpdateCheck = 1010;
202 | LastUpgradeCheck = 1010;
203 | ORGANIZATIONNAME = "Tam Nguyen M.";
204 | TargetAttributes = {
205 | 22E4AD9022361C2100164CE0 = {
206 | CreatedOnToolsVersion = 10.1;
207 | };
208 | };
209 | };
210 | buildConfigurationList = 22E4AD8C22361C2100164CE0 /* Build configuration list for PBXProject "CharlesGuide" */;
211 | compatibilityVersion = "Xcode 9.3";
212 | developmentRegion = en;
213 | hasScannedForEncodings = 0;
214 | knownRegions = (
215 | en,
216 | Base,
217 | );
218 | mainGroup = 22E4AD8822361C2100164CE0;
219 | productRefGroup = 22E4AD9222361C2100164CE0 /* Products */;
220 | projectDirPath = "";
221 | projectRoot = "";
222 | targets = (
223 | 22E4AD9022361C2100164CE0 /* CharlesGuide */,
224 | );
225 | };
226 | /* End PBXProject section */
227 |
228 | /* Begin PBXResourcesBuildPhase section */
229 | 22E4AD8F22361C2100164CE0 /* Resources */ = {
230 | isa = PBXResourcesBuildPhase;
231 | buildActionMask = 2147483647;
232 | files = (
233 | 229581532259D43A00B1343B /* LaunchScreen.storyboard in Resources */,
234 | 2295815D2259DF6B00B1343B /* PokemonListVC.xib in Resources */,
235 | 22E4AD9C22361C2400164CE0 /* Assets.xcassets in Resources */,
236 | 2295816A2259E00700B1343B /* ChecklistCell.xib in Resources */,
237 | 229581572259D5A300B1343B /* ChecklistVC.xib in Resources */,
238 | 22535C13225C63B50000F46A /* PokemonCell.xib in Resources */,
239 | );
240 | runOnlyForDeploymentPostprocessing = 0;
241 | };
242 | /* End PBXResourcesBuildPhase section */
243 |
244 | /* Begin PBXShellScriptBuildPhase section */
245 | C4FA06F8491C0F374B3454AE /* [CP] Embed Pods Frameworks */ = {
246 | isa = PBXShellScriptBuildPhase;
247 | buildActionMask = 2147483647;
248 | files = (
249 | );
250 | inputFileListPaths = (
251 | );
252 | inputPaths = (
253 | "${SRCROOT}/Pods/Target Support Files/Pods-CharlesGuide/Pods-CharlesGuide-frameworks.sh",
254 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework",
255 | "${BUILT_PRODUCTS_DIR}/ObjectMapper/ObjectMapper.framework",
256 | "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
257 | );
258 | name = "[CP] Embed Pods Frameworks";
259 | outputFileListPaths = (
260 | );
261 | outputPaths = (
262 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework",
263 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ObjectMapper.framework",
264 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
265 | );
266 | runOnlyForDeploymentPostprocessing = 0;
267 | shellPath = /bin/sh;
268 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CharlesGuide/Pods-CharlesGuide-frameworks.sh\"\n";
269 | showEnvVarsInLog = 0;
270 | };
271 | E3F2FF138B599D64C47AE19A /* [CP] Check Pods Manifest.lock */ = {
272 | isa = PBXShellScriptBuildPhase;
273 | buildActionMask = 2147483647;
274 | files = (
275 | );
276 | inputFileListPaths = (
277 | );
278 | inputPaths = (
279 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
280 | "${PODS_ROOT}/Manifest.lock",
281 | );
282 | name = "[CP] Check Pods Manifest.lock";
283 | outputFileListPaths = (
284 | );
285 | outputPaths = (
286 | "$(DERIVED_FILE_DIR)/Pods-CharlesGuide-checkManifestLockResult.txt",
287 | );
288 | runOnlyForDeploymentPostprocessing = 0;
289 | shellPath = /bin/sh;
290 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
291 | showEnvVarsInLog = 0;
292 | };
293 | /* End PBXShellScriptBuildPhase section */
294 |
295 | /* Begin PBXSourcesBuildPhase section */
296 | 22E4AD8D22361C2100164CE0 /* Sources */ = {
297 | isa = PBXSourcesBuildPhase;
298 | buildActionMask = 2147483647;
299 | files = (
300 | 229581692259E00700B1343B /* ChecklistCell.swift in Sources */,
301 | 2272BC31225CA15E00846CC4 /* ChecklistCellViewModel.swift in Sources */,
302 | 22535C0D225C5EC40000F46A /* PokemonService.swift in Sources */,
303 | 22C094D42259F6F800EFED9D /* API.swift in Sources */,
304 | 2295815C2259DF6B00B1343B /* PokemonListVC.swift in Sources */,
305 | 229581562259D5A300B1343B /* ChecklistVC.swift in Sources */,
306 | 225C701C225A4D6F0077D006 /* APIManager.swift in Sources */,
307 | 22E4AD9522361C2100164CE0 /* AppDelegate.swift in Sources */,
308 | 225C701F225A54CC0077D006 /* Models.swift in Sources */,
309 | 22535C17225C63DC0000F46A /* PokemonCellViewModel.swift in Sources */,
310 | 2272BC2F225CA0E600846CC4 /* CheckListViewModel.swift in Sources */,
311 | 22535C15225C63CE0000F46A /* PokemonListViewModel.swift in Sources */,
312 | 22535C12225C63B50000F46A /* PokemonCell.swift in Sources */,
313 | );
314 | runOnlyForDeploymentPostprocessing = 0;
315 | };
316 | /* End PBXSourcesBuildPhase section */
317 |
318 | /* Begin XCBuildConfiguration section */
319 | 22E4ADA122361C2400164CE0 /* Debug */ = {
320 | isa = XCBuildConfiguration;
321 | buildSettings = {
322 | ALWAYS_SEARCH_USER_PATHS = NO;
323 | CLANG_ANALYZER_NONNULL = YES;
324 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
326 | CLANG_CXX_LIBRARY = "libc++";
327 | CLANG_ENABLE_MODULES = YES;
328 | CLANG_ENABLE_OBJC_ARC = YES;
329 | CLANG_ENABLE_OBJC_WEAK = YES;
330 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
331 | CLANG_WARN_BOOL_CONVERSION = YES;
332 | CLANG_WARN_COMMA = YES;
333 | CLANG_WARN_CONSTANT_CONVERSION = YES;
334 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
336 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
337 | CLANG_WARN_EMPTY_BODY = YES;
338 | CLANG_WARN_ENUM_CONVERSION = YES;
339 | CLANG_WARN_INFINITE_RECURSION = YES;
340 | CLANG_WARN_INT_CONVERSION = YES;
341 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
342 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
343 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
345 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
346 | CLANG_WARN_STRICT_PROTOTYPES = YES;
347 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
348 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
349 | CLANG_WARN_UNREACHABLE_CODE = YES;
350 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
351 | CODE_SIGN_IDENTITY = "iPhone Developer";
352 | COPY_PHASE_STRIP = NO;
353 | DEBUG_INFORMATION_FORMAT = dwarf;
354 | ENABLE_STRICT_OBJC_MSGSEND = YES;
355 | ENABLE_TESTABILITY = YES;
356 | GCC_C_LANGUAGE_STANDARD = gnu11;
357 | GCC_DYNAMIC_NO_PIC = NO;
358 | GCC_NO_COMMON_BLOCKS = YES;
359 | GCC_OPTIMIZATION_LEVEL = 0;
360 | GCC_PREPROCESSOR_DEFINITIONS = (
361 | "DEBUG=1",
362 | "$(inherited)",
363 | );
364 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
365 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
366 | GCC_WARN_UNDECLARED_SELECTOR = YES;
367 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
368 | GCC_WARN_UNUSED_FUNCTION = YES;
369 | GCC_WARN_UNUSED_VARIABLE = YES;
370 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
371 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
372 | MTL_FAST_MATH = YES;
373 | ONLY_ACTIVE_ARCH = YES;
374 | SDKROOT = iphoneos;
375 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
376 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
377 | };
378 | name = Debug;
379 | };
380 | 22E4ADA222361C2400164CE0 /* Release */ = {
381 | isa = XCBuildConfiguration;
382 | buildSettings = {
383 | ALWAYS_SEARCH_USER_PATHS = NO;
384 | CLANG_ANALYZER_NONNULL = YES;
385 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
387 | CLANG_CXX_LIBRARY = "libc++";
388 | CLANG_ENABLE_MODULES = YES;
389 | CLANG_ENABLE_OBJC_ARC = YES;
390 | CLANG_ENABLE_OBJC_WEAK = YES;
391 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
392 | CLANG_WARN_BOOL_CONVERSION = YES;
393 | CLANG_WARN_COMMA = YES;
394 | CLANG_WARN_CONSTANT_CONVERSION = YES;
395 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
396 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
397 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
398 | CLANG_WARN_EMPTY_BODY = YES;
399 | CLANG_WARN_ENUM_CONVERSION = YES;
400 | CLANG_WARN_INFINITE_RECURSION = YES;
401 | CLANG_WARN_INT_CONVERSION = YES;
402 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
403 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
404 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
405 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
406 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
407 | CLANG_WARN_STRICT_PROTOTYPES = YES;
408 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
409 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
410 | CLANG_WARN_UNREACHABLE_CODE = YES;
411 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
412 | CODE_SIGN_IDENTITY = "iPhone Developer";
413 | COPY_PHASE_STRIP = NO;
414 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
415 | ENABLE_NS_ASSERTIONS = NO;
416 | ENABLE_STRICT_OBJC_MSGSEND = YES;
417 | GCC_C_LANGUAGE_STANDARD = gnu11;
418 | GCC_NO_COMMON_BLOCKS = YES;
419 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
420 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
421 | GCC_WARN_UNDECLARED_SELECTOR = YES;
422 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
423 | GCC_WARN_UNUSED_FUNCTION = YES;
424 | GCC_WARN_UNUSED_VARIABLE = YES;
425 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
426 | MTL_ENABLE_DEBUG_INFO = NO;
427 | MTL_FAST_MATH = YES;
428 | SDKROOT = iphoneos;
429 | SWIFT_COMPILATION_MODE = wholemodule;
430 | SWIFT_OPTIMIZATION_LEVEL = "-O";
431 | VALIDATE_PRODUCT = YES;
432 | };
433 | name = Release;
434 | };
435 | 22E4ADA422361C2400164CE0 /* Debug */ = {
436 | isa = XCBuildConfiguration;
437 | baseConfigurationReference = 855A86287F5C3FF8EB4C681B /* Pods-CharlesGuide.debug.xcconfig */;
438 | buildSettings = {
439 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
440 | CODE_SIGN_STYLE = Automatic;
441 | DEVELOPMENT_TEAM = D4Q882ZZC3;
442 | INFOPLIST_FILE = CharlesGuide/Info.plist;
443 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
444 | LD_RUNPATH_SEARCH_PATHS = (
445 | "$(inherited)",
446 | "@executable_path/Frameworks",
447 | );
448 | PRODUCT_BUNDLE_IDENTIFIER = dev.nmint8m.charlesguide;
449 | PRODUCT_NAME = "$(TARGET_NAME)";
450 | SWIFT_VERSION = 4.2;
451 | TARGETED_DEVICE_FAMILY = "1,2";
452 | };
453 | name = Debug;
454 | };
455 | 22E4ADA522361C2400164CE0 /* Release */ = {
456 | isa = XCBuildConfiguration;
457 | baseConfigurationReference = 3091CF309803E9A527B71F24 /* Pods-CharlesGuide.release.xcconfig */;
458 | buildSettings = {
459 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
460 | CODE_SIGN_STYLE = Automatic;
461 | DEVELOPMENT_TEAM = D4Q882ZZC3;
462 | INFOPLIST_FILE = CharlesGuide/Info.plist;
463 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
464 | LD_RUNPATH_SEARCH_PATHS = (
465 | "$(inherited)",
466 | "@executable_path/Frameworks",
467 | );
468 | PRODUCT_BUNDLE_IDENTIFIER = dev.nmint8m.charlesguide;
469 | PRODUCT_NAME = "$(TARGET_NAME)";
470 | SWIFT_VERSION = 4.2;
471 | TARGETED_DEVICE_FAMILY = "1,2";
472 | };
473 | name = Release;
474 | };
475 | /* End XCBuildConfiguration section */
476 |
477 | /* Begin XCConfigurationList section */
478 | 22E4AD8C22361C2100164CE0 /* Build configuration list for PBXProject "CharlesGuide" */ = {
479 | isa = XCConfigurationList;
480 | buildConfigurations = (
481 | 22E4ADA122361C2400164CE0 /* Debug */,
482 | 22E4ADA222361C2400164CE0 /* Release */,
483 | );
484 | defaultConfigurationIsVisible = 0;
485 | defaultConfigurationName = Release;
486 | };
487 | 22E4ADA322361C2400164CE0 /* Build configuration list for PBXNativeTarget "CharlesGuide" */ = {
488 | isa = XCConfigurationList;
489 | buildConfigurations = (
490 | 22E4ADA422361C2400164CE0 /* Debug */,
491 | 22E4ADA522361C2400164CE0 /* Release */,
492 | );
493 | defaultConfigurationIsVisible = 0;
494 | defaultConfigurationName = Release;
495 | };
496 | /* End XCConfigurationList section */
497 | };
498 | rootObject = 22E4AD8922361C2100164CE0 /* Project object */;
499 | }
500 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide.xcodeproj/project.xcworkspace/xcuserdata/nmint8m.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide.xcodeproj/project.xcworkspace/xcuserdata/nmint8m.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide.xcodeproj/xcuserdata/nmint8m.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | CharlesGuide.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 4
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 3/11/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Config not cache data
18 | URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
19 |
20 | // Config network indicator
21 | UIApplication.shared.isNetworkActivityIndicatorVisible = true
22 |
23 | // Config window
24 | window = UIWindow(frame: UIScreen.main.bounds)
25 | window?.backgroundColor = .white
26 | window?.makeKeyAndVisible()
27 | window?.rootViewController = ChecklistVC()
28 | return true
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "ItunesArtwork@2x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-manual-device.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "img-config-device4.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 | }
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-manual-device.imageset/img-config-device4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-manual-device.imageset/img-config-device4.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-proxy.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "img-proxy3.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 | }
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-proxy.imageset/img-proxy3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-proxy.imageset/img-proxy3.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-cer.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "img-ssl5.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 | }
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-cer.imageset/img-ssl5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-cer.imageset/img-ssl5.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-install.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "img-ssl1.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 | }
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-install.imageset/img-ssl1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-install.imageset/img-ssl1.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "img-ssl21.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 | }
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl.imageset/img-ssl21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl.imageset/img-ssl21.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-trust-ssl-simu.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "img-config-simu3.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 | }
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-trust-ssl-simu.imageset/img-config-simu3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-trust-ssl-simu.imageset/img-config-simu3.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-pokemon-ball.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "img-pokemon-ball@1x.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 | }
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-pokemon-ball.imageset/img-pokemon-ball@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-pokemon-ball.imageset/img-pokemon-ball@1x.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-simu8.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "img-simu8.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 | }
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Assets.xcassets/img-simu8.imageset/img-simu8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-simu8.imageset/img-simu8.png
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/CheckList/Cell/ChecklistCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistCell.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/7/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol ChecklistCellDelegate: class {
12 | func cell(_ cell: ChecklistCell, perform action: ChecklistCell.Action)
13 | }
14 |
15 | final class ChecklistCell: UICollectionViewCell {
16 |
17 | weak var delegate: ChecklistCellDelegate?
18 | var viewModel = ChecklistCellViewModel() {
19 | didSet {
20 | configCell()
21 | }
22 | }
23 |
24 | @IBOutlet private weak var imageView: UIImageView!
25 | @IBOutlet private weak var titleLabel: UILabel!
26 | @IBOutlet private weak var subtitleLabel: UILabel!
27 | @IBOutlet weak var startButton: UIButton!
28 |
29 | override func awakeFromNib() {
30 | super.awakeFromNib()
31 | }
32 |
33 | func configCell() {
34 | imageView.image = viewModel.image
35 | titleLabel.text = viewModel.title
36 | subtitleLabel.text = viewModel.subtitle
37 | startButton.setTitle(viewModel.buttonString, for: .normal)
38 | }
39 |
40 | @IBAction func startButtonTouchUpInside(_ sender: Any) {
41 | delegate?.cell(self, perform: .start)
42 | }
43 |
44 | enum Action {
45 | case start
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/CheckList/Cell/ChecklistCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/CheckList/Cell/ChecklistCellViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistCellViewModel.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/9/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ChecklistCellViewModel {
12 |
13 | private(set) var image: UIImage?
14 | private(set) var title: String = ""
15 | private(set) var subtitle: String = ""
16 | private var isLastCell: Bool = false
17 |
18 | var buttonString: String {
19 | return isLastCell ? "Bắt đầu" : "Bỏ qua"
20 | }
21 |
22 | init(image: UIImage? = nil,
23 | title: String = "",
24 | subtitle: String = "",
25 | isLastCell: Bool = false) {
26 | self.image = image
27 | self.title = title
28 | self.subtitle = subtitle
29 | self.isLastCell = isLastCell
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/CheckList/CheckListViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CheckListViewModel.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/9/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class CheckListViewModel {
12 | func numberOfCell() -> Int {
13 | return 6
14 | }
15 |
16 | func viewModelForCell(indexPath: IndexPath) -> ChecklistCellViewModel {
17 | guard let checkList = CheckList(rawValue: indexPath.row) else { return ChecklistCellViewModel() }
18 | return ChecklistCellViewModel(image: checkList.image,
19 | title: "Bước \(indexPath.row + 1): \(checkList.title)",
20 | subtitle: checkList.subtilte,
21 | isLastCell: indexPath.row == 5)
22 | }
23 | }
24 |
25 | extension CheckListViewModel {
26 | enum CheckList: Int {
27 | case configProxy = 0
28 | case configProxyForDevice
29 | case configSSLProxyingCertificates
30 | case addCharlesCACertificate
31 | case turnOnSSLProxy
32 | case trustSSLCertificateOnDeviceSimu
33 |
34 | var title: String {
35 | switch self {
36 | case .configProxy:
37 | return "Config Proxy, bật macOS Proxy cho Charles"
38 | case .configProxyForDevice:
39 | return "Config proxy cho device"
40 | case .configSSLProxyingCertificates:
41 | return "Config Configuring SSL Proxying Certificates"
42 | case .addCharlesCACertificate:
43 | return "Thêm Charles CA Certificate cho máy mac"
44 | case .turnOnSSLProxy:
45 | return "Bật SSL Proxy cho máy mac và cài đặt SSL cho tất cả host name"
46 | case .trustSSLCertificateOnDeviceSimu:
47 | return "Bật trust SSL Certificate cho device/simulator"
48 | }
49 | }
50 |
51 | var subtilte: String {
52 | switch self {
53 | case .configProxy:
54 | return "Config: View > Proxy Settings...\nTurn on: View > macOS Proxy"
55 | case .configProxyForDevice:
56 | return "Settings > Wifi > Choosen wifi > Config Proxy > Manual"
57 | case .configSSLProxyingCertificates:
58 | return "Help > SSL Proxying > Install Charles Root Certificates"
59 | case .addCharlesCACertificate:
60 | return "Certificates > Charles Proxy CA > Always Trust"
61 | case .turnOnSSLProxy:
62 | return "Proxy > SSL Proxying Settings..."
63 | case .trustSSLCertificateOnDeviceSimu:
64 | return "Device: Settings > General > About > Certificate Trust Settings\nSimulator: Help > SSL Proxying > Install Charles Root Certificate in iOS Simulators"
65 | }
66 | }
67 |
68 | var image: UIImage? {
69 | let name: String
70 | switch self {
71 | case .configProxy:
72 | name = "img-check-proxy"
73 | case .configProxyForDevice:
74 | name = "img-check-manual-device"
75 | case .configSSLProxyingCertificates:
76 | name = "img-check-ssl-install"
77 | case .addCharlesCACertificate:
78 | name = "img-check-ssl-cer"
79 | case .turnOnSSLProxy:
80 | name = "img-check-ssl"
81 | case .trustSSLCertificateOnDeviceSimu:
82 | name = "img-check-trust-ssl-simu"
83 | }
84 | guard let image = UIImage(named: name) else { return nil }
85 | return image
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/CheckList/ChecklistVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistVC.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/7/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ChecklistVC: UIViewController {
12 | var viewModel = CheckListViewModel()
13 |
14 | static private let cellID = "ChecklistCell"
15 |
16 | @IBOutlet private weak var collectionView: UICollectionView!
17 |
18 | override var preferredStatusBarStyle: UIStatusBarStyle {
19 | return .lightContent
20 | }
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 | configCollectionView()
25 | }
26 |
27 | private func configCollectionView() {
28 | let nib = UINib(nibName: ChecklistVC.cellID, bundle: nil)
29 | collectionView.register(nib,
30 | forCellWithReuseIdentifier: ChecklistVC.cellID)
31 | collectionView.dataSource = self
32 | collectionView.delegate = self
33 | }
34 | }
35 |
36 | extension ChecklistVC: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
37 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
38 | return 6
39 | }
40 |
41 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
42 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ChecklistVC.cellID,
43 | for: indexPath) as? ChecklistCell
44 | else { return UICollectionViewCell() }
45 | cell.viewModel = viewModel.viewModelForCell(indexPath: indexPath)
46 | cell.delegate = self
47 | return cell
48 | }
49 |
50 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
51 | guard let window = UIApplication.shared.keyWindow else {
52 | return UIScreen.main.bounds.size
53 | }
54 | let screenSize = UIScreen.main.bounds.size
55 | return CGSize(width: screenSize.width,
56 | height: screenSize.height - window.safeAreaInsets.top - window.safeAreaInsets.bottom - 45)
57 | }
58 | }
59 |
60 | extension ChecklistVC: ChecklistCellDelegate {
61 | func cell(_ cell: ChecklistCell, perform action: ChecklistCell.Action) {
62 | let vc = PokemonListVC()
63 | present(vc, animated: true, completion: nil)
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/CheckList/ChecklistVC.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Pokemon
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UIStatusBarStyle
32 | UIStatusBarStyleLightContent
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/PokemonList/Cell/PokemonCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PokemonCell.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/9/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class PokemonCell: UITableViewCell {
12 |
13 | @IBOutlet private weak var containerView: UIView!
14 | @IBOutlet private weak var nameLabel: UILabel!
15 | @IBOutlet private weak var urlLabel: UILabel!
16 |
17 | var viewModel = PokemonCellViewModel(Pokemon()) {
18 | didSet {
19 | updateCell()
20 | }
21 | }
22 |
23 | override func awakeFromNib() {
24 | super.awakeFromNib()
25 | containerView.layer.cornerRadius = 5
26 | containerView.clipsToBounds = true
27 | }
28 |
29 | override func setSelected(_ selected: Bool, animated: Bool) {
30 | super.setSelected(false, animated: false)
31 | }
32 |
33 | func updateCell() {
34 | nameLabel.text = viewModel.pokemonName
35 | urlLabel.text = viewModel.pokemonURLString
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/PokemonList/Cell/PokemonCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/PokemonList/Cell/PokemonCellViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PokemonCellViewModel.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/9/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final class PokemonCellViewModel {
12 | let pokemon: Pokemon
13 |
14 | var pokemonName: String { return pokemon.name }
15 |
16 | var pokemonURLString: String { return pokemon.urlString }
17 |
18 | init(_ pokemon: Pokemon) {
19 | self.pokemon = pokemon
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/PokemonList/PokemonListVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PokemonListVC.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/7/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class PokemonListVC: UIViewController {
12 |
13 | // MARK: - Properties
14 | var viewModel = PokemonListViewModel()
15 | private let cellIDString = "PokemonCell"
16 |
17 | // MARK: - IBOutlet
18 | @IBOutlet private weak var statusLabel: UILabel!
19 | @IBOutlet private weak var tableView: UITableView!
20 | @IBOutlet private weak var previousButton: UIButton!
21 |
22 | override var preferredStatusBarStyle: UIStatusBarStyle {
23 | return .lightContent
24 | }
25 |
26 | override func viewDidLoad() {
27 | super.viewDidLoad()
28 | configTableView()
29 | configStatusLabel()
30 | configButtons()
31 | loadData(pageType: .current)
32 | }
33 |
34 | private func configTableView() {
35 | let nib = UINib(nibName: cellIDString, bundle: Bundle.main)
36 | tableView.register(nib, forCellReuseIdentifier: cellIDString)
37 | tableView.dataSource = self
38 | }
39 |
40 | private func configStatusLabel() {
41 | statusLabel.text = viewModel.status
42 | }
43 |
44 | private func configButtons() {
45 | previousButton.isEnabled = viewModel.previousURLString != ""
46 | previousButton.alpha = viewModel.previousURLString != "" ? 1 : 0.5
47 | }
48 |
49 | private func loadData(pageType: PokemonListViewModel.PageType) {
50 | viewModel.getListPokemon(pageType: pageType) { [weak self] (result) in
51 | guard let this = self else { return }
52 | switch result {
53 | case .success:
54 | this.tableView.reloadData()
55 | this.configStatusLabel()
56 | this.configButtons()
57 | case .failure(let error):
58 | let alert = UIAlertController(title: "Error",
59 | message: "Error happened with code: \(error.code)",
60 | preferredStyle: .alert)
61 | let action = UIAlertAction(title: "OK",
62 | style: .default,
63 | handler:
64 | { _ in
65 | this.loadData(pageType: .current)
66 | })
67 | alert.addAction(action)
68 | this.present(alert,
69 | animated: true,
70 | completion: nil)
71 | }
72 | }
73 | }
74 |
75 | @IBAction func closeButtonTouchUpInside(_ sender: Any) {
76 | dismiss(animated: true, completion: nil)
77 | }
78 |
79 | @IBAction func pageButtonTouchUpInside(_ sender: UIButton) {
80 | let pageType: PokemonListViewModel.PageType = sender.tag == 0 ? .previous : .next
81 | loadData(pageType: pageType)
82 | }
83 | }
84 |
85 | extension PokemonListVC: UITableViewDataSource {
86 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
87 | return viewModel.numberOfPokemon()
88 | }
89 |
90 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
91 | guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIDString, for: indexPath) as? PokemonCell else { return UITableViewCell() }
92 | cell.viewModel = viewModel.viewModelForCell(indexPath: indexPath)
93 | return cell
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/PokemonList/PokemonListVC.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/PokemonList/PokemonListViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PokemonListViewModel.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/9/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final class PokemonListViewModel {
12 |
13 | var listResult = ListResultPokemon()
14 | var currentURLString = API.Path.Pokemon.pokemon + "?offset=0&limit=20"
15 |
16 | var status: String {
17 | if listResult.count == 0 {
18 | return "Không có pokemon."
19 | }
20 | return listResult.count > 1 ? "Hiện có \(listResult.count) pokemon." : "Hiện chỉ có 1 pokemon."
21 | }
22 |
23 | var previousURLString: String { return listResult.previous }
24 |
25 | var nextURLString: String { return listResult.next }
26 |
27 | var pokemons: [Pokemon] { return listResult.results }
28 |
29 | func getListPokemon(pageType: PageType, completion: CompletionHandler?) {
30 | switch pageType {
31 | case .previous: getListPokemon(urlString: previousURLString, completion: completion)
32 | case .current: getListPokemon(urlString: currentURLString, completion: completion)
33 | case .next: getListPokemon(urlString: nextURLString, completion: completion)
34 | }
35 | }
36 |
37 | func getListPokemon(urlString: String, completion: CompletionHandler?) {
38 | PokemonService.getListPokemon(urlString: urlString) { [weak self] (result) in
39 | guard let this = self else { return }
40 | switch result {
41 | case .success(let listResult):
42 | this.listResult = listResult
43 | this.currentURLString = urlString
44 | completion?(.success)
45 | case .failure(let error):
46 | completion?(.failure(error))
47 | }
48 | }
49 | }
50 |
51 | func numberOfPokemon() -> Int {
52 | return pokemons.count
53 | }
54 |
55 | func viewModelForCell(indexPath: IndexPath) -> PokemonCellViewModel {
56 | guard indexPath.row < pokemons.count else { return PokemonCellViewModel(Pokemon()) }
57 | return PokemonCellViewModel(pokemons[indexPath.row])
58 | }
59 | }
60 |
61 | extension PokemonListViewModel {
62 | enum PageType {
63 | case previous
64 | case current
65 | case next
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Service/API.swift:
--------------------------------------------------------------------------------
1 | //
2 | // API.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/7/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 | import ObjectMapper
12 |
13 | struct API {
14 | struct Path {
15 | static let baseURL = "https://pokeapi.co/api/v2"
16 |
17 | struct Pokemon {
18 | static var pokemon: String { return API.Path.baseURL / "pokemon" }
19 | }
20 | }
21 |
22 | enum Errors: Error {
23 | case badRequest
24 | case notFound
25 | case authentication
26 | case noResponse
27 | case jsonFormat
28 | case networkNotConnect
29 |
30 | // TODO: - Define more errors https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
31 |
32 | var code: Int {
33 | switch self {
34 | case .badRequest: return 400
35 | case .notFound: return 404
36 | case .authentication: return 500
37 | case .noResponse: return 1000
38 | case .jsonFormat: return 1001
39 | case .networkNotConnect: return 1002
40 | }
41 | }
42 |
43 | var description: String {
44 | switch self {
45 | case .badRequest: return "Bad request"
46 | case .notFound: return "Not found"
47 | case .authentication: return "This authentication is not valid"
48 | case .noResponse: return "No response"
49 | case .jsonFormat: return "JSON format is wrong"
50 | case .networkNotConnect: return "Cannot connect network"
51 | }
52 | }
53 | }
54 | }
55 |
56 | infix operator /: AdditionPrecedence
57 |
58 | extension String {
59 | static func / (left: String, right: String) -> String {
60 | return left + "/" + right
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Service/APIManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIManager.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/7/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 |
12 | enum CompletionResult {
13 | case success
14 | case failure(Error)
15 | }
16 | typealias CompletionHandler = (CompletionResult) -> Void
17 | typealias Completion = (Result) -> Void
18 | typealias JSObject = [String: Any]
19 |
20 | final class APIManager {
21 |
22 | static let shared = APIManager()
23 |
24 | private init() {}
25 |
26 | // Chứa header mặc định cần phải có trong mỗi request
27 | static var defaultHeader: [String: String] = {
28 | var header: [String: String] = [:]
29 | /* Header mặc định như là
30 | - Access token
31 | - App version
32 | - OS device
33 | - ...
34 | */
35 | return header
36 | }()
37 |
38 | static let networkManager: NetworkReachabilityManager = {
39 | guard let networkManager = NetworkReachabilityManager() else {
40 | fatalError("Network manager cannot be generated")
41 | }
42 | return networkManager
43 | }()
44 |
45 | @discardableResult
46 | func request(path: String,
47 | method: HTTPMethod,
48 | parameters: Parameters? = nil,
49 | encoding: ParameterEncoding = URLEncoding.default,
50 | headers: HTTPHeaders? = nil,
51 | completion: Completion? = nil) -> Request? {
52 |
53 | // Kiểm tra path và network -> URL có tồn tại hay không, nếu không thì return nil
54 | guard APIManager.networkManager.isReachable,
55 | let url = URL(string: path) else {
56 | return nil
57 | }
58 |
59 | // Thêm header riêng vào defaultHeader
60 | var allHeaders: HTTPHeaders = APIManager.defaultHeader
61 | if let headers = headers {
62 | for pair in headers {
63 | allHeaders[pair.key] = pair.value
64 | }
65 | }
66 |
67 | // Tạo DataRequest
68 | let request = Alamofire.request(url,
69 | method: method,
70 | parameters: parameters,
71 | encoding: encoding,
72 | headers: allHeaders
73 | ).responseJSON { response in
74 | completion?(response.result)
75 | }
76 | return request
77 | }
78 | }
79 |
80 | // MARK: - Xử lý response của data request
81 | extension DataRequest {
82 |
83 | // Chuyển việc serializer của alamofire -> serializer của mình
84 | func responseJSON(completion: @escaping (DataResponse) -> Void) -> Self {
85 | return response(responseSerializer: responseSerializer(),
86 | completionHandler: completion)
87 | }
88 |
89 | /** Serialize của mình tự define có nhiều nhiệm vụ:
90 | - Ví dụ như xem thử cấu trúc JSON trả về đúng hay không
91 | - Đối với một số status code error nhất định thì mình sẽ xử lý khác hẳn với những status khác.
92 | - Giả dụ như yêu cầu của KH đối với status 500 -> luôn luôn là lỗi authen -> mình phải show pop up, log out tài khoản đang sử dụng và trở lại màn hình splash chả hạn. --> TH này mình có thể bắn Notification đến AppDelegate để show pop up và change root view controller...
93 | */
94 | func responseSerializer() -> DataResponseSerializer {
95 | return DataResponseSerializer(serializeResponse: { _, response , data, error in
96 | return self.responseJSONSerializer(response: response,
97 | data: data,
98 | error: error)
99 | })
100 | }
101 | }
102 |
103 | // MARK: - Xử lý JSON trả về
104 | extension Request {
105 | /* Vì sao không phải DataRequest mà là Request?
106 | - Vì mình muốn sử dụng chung 1 json serialize cho tất cả các request: data, download,...
107 | - class DataRequest thừa kế Request*/
108 |
109 | func responseJSONSerializer(response: HTTPURLResponse?,
110 | data: Data?,
111 | error: Error?) -> Result {
112 |
113 | guard APIManager.networkManager.isReachable else {
114 | return .failure(API.Errors.networkNotConnect)
115 | }
116 |
117 | // Khi response không trả về và không có lỗi error --> failure với error no response
118 | if let error = error {
119 | return .failure(error)
120 | }
121 |
122 | guard let response = response else {
123 | return .failure(API.Errors.noResponse)
124 | }
125 |
126 | let statusCode = response.statusCode
127 |
128 | guard statusCode == 200 else {
129 | if statusCode == API.Errors.badRequest.code {
130 | return .failure(API.Errors.badRequest)
131 | } else if statusCode == API.Errors.authentication.code {
132 | return .failure(API.Errors.authentication)
133 | }
134 | return .failure(API.Errors.notFound)
135 | }
136 |
137 | if let data = data, let json = data.toJSON() {
138 | // Success with JSON response
139 | return .success(json)
140 | } else if let data = data, let string = data.toString(), string.isEmpty {
141 | // Success but no JSON response
142 | return .success([:])
143 | }
144 | // Failure vì JSON format
145 | return .failure(API.Errors.jsonFormat)
146 | }
147 | }
148 |
149 | // MARK: - Xử lý error
150 | extension Error {
151 | public var code: Int {
152 | if let this = self as? API.Errors {
153 | return this.code
154 | }
155 | let this = self as NSError
156 | return this.code
157 | }
158 | }
159 |
160 | // MARK: - Xử lý Data
161 | extension Data {
162 | public func toJSON() -> Any? {
163 | do {
164 | return try JSONSerialization.jsonObject(with: self, options: JSONSerialization.ReadingOptions.allowFragments)
165 | } catch {
166 | return nil
167 | }
168 | }
169 |
170 | public func toString(_ encoding: String.Encoding = String.Encoding.utf8) -> String? {
171 | return String(data: self, encoding: encoding)
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Service/Models.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Models.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/7/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ObjectMapper
11 |
12 | final class Pokemon: Mappable {
13 | var name: String = ""
14 | var urlString: String = ""
15 |
16 | init() {}
17 |
18 | init?(map: Map) {}
19 |
20 | func mapping(map: Map) {
21 | name <- map["name"]
22 | urlString <- map["url"]
23 | }
24 | }
25 |
26 | final class ListResultPokemon: Mappable {
27 | var count: Int = 0
28 | var previous: String = ""
29 | var next: String = ""
30 | var results: [Pokemon] = []
31 |
32 | init() {}
33 |
34 | init?(map: Map) {}
35 |
36 | func mapping(map: Map) {
37 | count <- map["count"]
38 | previous <- map["previous"]
39 | next <- map["next"]
40 | results <- map["results"]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/CharlesGuide/CharlesGuide/Service/PokemonService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PokemonService.swift
3 | // CharlesGuide
4 | //
5 | // Created by Tam Nguyen M. on 4/9/19.
6 | // Copyright © 2019 Tam Nguyen M. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 | import ObjectMapper
12 |
13 | final class PokemonService {
14 | @discardableResult
15 | static func getListPokemon(urlString: String, completion: Completion?) -> Request? {
16 | return APIManager.shared.request(path: urlString,
17 | method: .get,
18 | completion:
19 | { result in
20 | DispatchQueue.main.async {
21 | switch result {
22 | case .success(let value):
23 | guard let json = value as? JSObject,
24 | let listResult = Mapper().map(JSON: json) else {
25 | completion?(.failure(API.Errors.jsonFormat))
26 | return
27 | }
28 | completion?(.success(listResult))
29 | case .failure(let error):
30 | completion?(.failure(error))
31 | }
32 | }
33 | })
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/CharlesGuide/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem 'cocoapods', '1.5.3'
--------------------------------------------------------------------------------
/CharlesGuide/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.0)
5 | activesupport (4.2.11.1)
6 | i18n (~> 0.7)
7 | minitest (~> 5.1)
8 | thread_safe (~> 0.3, >= 0.3.4)
9 | tzinfo (~> 1.1)
10 | atomos (0.1.3)
11 | claide (1.0.2)
12 | cocoapods (1.5.3)
13 | activesupport (>= 4.0.2, < 5)
14 | claide (>= 1.0.2, < 2.0)
15 | cocoapods-core (= 1.5.3)
16 | cocoapods-deintegrate (>= 1.0.2, < 2.0)
17 | cocoapods-downloader (>= 1.2.0, < 2.0)
18 | cocoapods-plugins (>= 1.0.0, < 2.0)
19 | cocoapods-search (>= 1.0.0, < 2.0)
20 | cocoapods-stats (>= 1.0.0, < 2.0)
21 | cocoapods-trunk (>= 1.3.0, < 2.0)
22 | cocoapods-try (>= 1.1.0, < 2.0)
23 | colored2 (~> 3.1)
24 | escape (~> 0.0.4)
25 | fourflusher (~> 2.0.1)
26 | gh_inspector (~> 1.0)
27 | molinillo (~> 0.6.5)
28 | nap (~> 1.0)
29 | ruby-macho (~> 1.1)
30 | xcodeproj (>= 1.5.7, < 2.0)
31 | cocoapods-core (1.5.3)
32 | activesupport (>= 4.0.2, < 6)
33 | fuzzy_match (~> 2.0.4)
34 | nap (~> 1.0)
35 | cocoapods-deintegrate (1.0.4)
36 | cocoapods-downloader (1.2.2)
37 | cocoapods-plugins (1.0.0)
38 | nap
39 | cocoapods-search (1.0.0)
40 | cocoapods-stats (1.1.0)
41 | cocoapods-trunk (1.3.1)
42 | nap (>= 0.8, < 2.0)
43 | netrc (~> 0.11)
44 | cocoapods-try (1.1.0)
45 | colored2 (3.1.2)
46 | concurrent-ruby (1.1.5)
47 | escape (0.0.4)
48 | fourflusher (2.0.1)
49 | fuzzy_match (2.0.4)
50 | gh_inspector (1.1.3)
51 | i18n (0.9.5)
52 | concurrent-ruby (~> 1.0)
53 | minitest (5.11.3)
54 | molinillo (0.6.6)
55 | nanaimo (0.2.6)
56 | nap (1.1.0)
57 | netrc (0.11.0)
58 | ruby-macho (1.4.0)
59 | thread_safe (0.3.6)
60 | tzinfo (1.2.5)
61 | thread_safe (~> 0.1)
62 | xcodeproj (1.8.2)
63 | CFPropertyList (>= 2.3.3, < 4.0)
64 | atomos (~> 0.1.3)
65 | claide (>= 1.0.2, < 2.0)
66 | colored2 (~> 3.1)
67 | nanaimo (~> 0.2.6)
68 |
69 | PLATFORMS
70 | ruby
71 |
72 | DEPENDENCIES
73 | cocoapods (= 1.5.3)
74 |
75 | BUNDLED WITH
76 | 2.0.1
77 |
--------------------------------------------------------------------------------
/CharlesGuide/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '9.0'
2 |
3 | target 'CharlesGuide' do
4 | use_frameworks!
5 |
6 | pod 'ObjectMapper', '3.3.0'
7 | pod 'SDWebImage', '4.4.1'
8 | pod 'Alamofire', '4.7.3'
9 |
10 | end
11 |
--------------------------------------------------------------------------------
/CharlesGuide/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Alamofire (4.7.3)
3 | - ObjectMapper (3.3.0)
4 | - SDWebImage (4.4.1):
5 | - SDWebImage/Core (= 4.4.1)
6 | - SDWebImage/Core (4.4.1)
7 |
8 | DEPENDENCIES:
9 | - Alamofire (= 4.7.3)
10 | - ObjectMapper (= 3.3.0)
11 | - SDWebImage (= 4.4.1)
12 |
13 | SPEC REPOS:
14 | https://github.com/cocoapods/specs.git:
15 | - Alamofire
16 | - ObjectMapper
17 | - SDWebImage
18 |
19 | SPEC CHECKSUMS:
20 | Alamofire: c7287b6e5d7da964a70935e5db17046b7fde6568
21 | ObjectMapper: b612bf8c8e99c4dc0bb6013a51f7c27966ed5da9
22 | SDWebImage: 47e9b5b925cbce75946c23f0c42dd19464189af4
23 |
24 | PODFILE CHECKSUM: 820cd939f86f597a8240b6051c8943af41fe604c
25 |
26 | COCOAPODS: 1.5.3
27 |
--------------------------------------------------------------------------------
/Images/img-access1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-access1.png
--------------------------------------------------------------------------------
/Images/img-access2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-access2.png
--------------------------------------------------------------------------------
/Images/img-breakpoint1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-breakpoint1.png
--------------------------------------------------------------------------------
/Images/img-breakpoint2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-breakpoint2.png
--------------------------------------------------------------------------------
/Images/img-breakpoint3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-breakpoint3.png
--------------------------------------------------------------------------------
/Images/img-config-device1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-device1.png
--------------------------------------------------------------------------------
/Images/img-config-device2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-device2.png
--------------------------------------------------------------------------------
/Images/img-config-device3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-device3.png
--------------------------------------------------------------------------------
/Images/img-config-device4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-device4.png
--------------------------------------------------------------------------------
/Images/img-config-simu0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-simu0.png
--------------------------------------------------------------------------------
/Images/img-config-simu1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-simu1.png
--------------------------------------------------------------------------------
/Images/img-config-simu2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-simu2.png
--------------------------------------------------------------------------------
/Images/img-config-simu3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-simu3.png
--------------------------------------------------------------------------------
/Images/img-ex1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex1.png
--------------------------------------------------------------------------------
/Images/img-ex2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex2.png
--------------------------------------------------------------------------------
/Images/img-ex3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex3.png
--------------------------------------------------------------------------------
/Images/img-ex4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex4.png
--------------------------------------------------------------------------------
/Images/img-ex5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex5.png
--------------------------------------------------------------------------------
/Images/img-ex6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex6.png
--------------------------------------------------------------------------------
/Images/img-ex7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex7.png
--------------------------------------------------------------------------------
/Images/img-ex8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex8.png
--------------------------------------------------------------------------------
/Images/img-handle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-handle.png
--------------------------------------------------------------------------------
/Images/img-host1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-host1.png
--------------------------------------------------------------------------------
/Images/img-host2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-host2.png
--------------------------------------------------------------------------------
/Images/img-host3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-host3.png
--------------------------------------------------------------------------------
/Images/img-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-icon.png
--------------------------------------------------------------------------------
/Images/img-install1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-install1.png
--------------------------------------------------------------------------------
/Images/img-install2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-install2.png
--------------------------------------------------------------------------------
/Images/img-install3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-install3.png
--------------------------------------------------------------------------------
/Images/img-install4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-install4.png
--------------------------------------------------------------------------------
/Images/img-interface.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-interface.png
--------------------------------------------------------------------------------
/Images/img-local-ip1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-local-ip1.png
--------------------------------------------------------------------------------
/Images/img-local-ip2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-local-ip2.png
--------------------------------------------------------------------------------
/Images/img-pikachu@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-pikachu@1x.png
--------------------------------------------------------------------------------
/Images/img-pokemon-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-pokemon-1.jpg
--------------------------------------------------------------------------------
/Images/img-pokemon-ball@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-pokemon-ball@1x.png
--------------------------------------------------------------------------------
/Images/img-proxy1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-proxy1.png
--------------------------------------------------------------------------------
/Images/img-proxy2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-proxy2.png
--------------------------------------------------------------------------------
/Images/img-proxy3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-proxy3.png
--------------------------------------------------------------------------------
/Images/img-record1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-record1.png
--------------------------------------------------------------------------------
/Images/img-record2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-record2.png
--------------------------------------------------------------------------------
/Images/img-record3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-record3.png
--------------------------------------------------------------------------------
/Images/img-record4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-record4.png
--------------------------------------------------------------------------------
/Images/img-record5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-record5.png
--------------------------------------------------------------------------------
/Images/img-register1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-register1.png
--------------------------------------------------------------------------------
/Images/img-register2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-register2.png
--------------------------------------------------------------------------------
/Images/img-register3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-register3.png
--------------------------------------------------------------------------------
/Images/img-simu1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu1.png
--------------------------------------------------------------------------------
/Images/img-simu2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu2.png
--------------------------------------------------------------------------------
/Images/img-simu3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu3.png
--------------------------------------------------------------------------------
/Images/img-simu4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu4.png
--------------------------------------------------------------------------------
/Images/img-simu5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu5.png
--------------------------------------------------------------------------------
/Images/img-simu6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu6.png
--------------------------------------------------------------------------------
/Images/img-simu7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu7.png
--------------------------------------------------------------------------------
/Images/img-simu8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu8.png
--------------------------------------------------------------------------------
/Images/img-ssl0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl0.png
--------------------------------------------------------------------------------
/Images/img-ssl1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl1.png
--------------------------------------------------------------------------------
/Images/img-ssl2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl2.png
--------------------------------------------------------------------------------
/Images/img-ssl21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl21.png
--------------------------------------------------------------------------------
/Images/img-ssl22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl22.png
--------------------------------------------------------------------------------
/Images/img-ssl23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl23.png
--------------------------------------------------------------------------------
/Images/img-ssl3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl3.png
--------------------------------------------------------------------------------
/Images/img-ssl4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl4.png
--------------------------------------------------------------------------------
/Images/img-ssl5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl5.png
--------------------------------------------------------------------------------
/Images/img-throttlle1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-throttlle1.png
--------------------------------------------------------------------------------
/Images/img-throttlle2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-throttlle2.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Tam Nguyen M.
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Charles Guide
2 |
3 | _Written by **Nguyen Minh Tam**_
4 |
5 | Tài liệu này đề cập đến cách **cài đặt**, những **chức năng cơ bản** và thường được sử dụng nhất của Charles. Hi vọng mọi người sẽ tìm được những kiến thức hữu ích khi đọc tài liệu này. 👍
6 |
7 |
8 |
9 |
10 |
11 | ## Tài liệu hướng dẫn:
12 |
13 | - [x] [Hướng dẫn cài đặt Charles](./CharlesGuide-Install.md) 🔧
14 |
15 | - [x] [Hướng dẫn sử dụng Charles](./CharlesGuide-Usage.md) 🗒
16 |
17 | - [x] [Demo debugging an iOS app with dummy data from Charles](./CharlesGuide-Example.md) 🐞
18 |
19 | Ủng hộ mình một ⭐️ để có thêm động lực viết bài nhé! Cảm ơn mọi người. 🎉
20 |
21 | | |
22 | |---|
23 | | |
24 | | |
--------------------------------------------------------------------------------