├── Assets
├── Icon.png
└── Screenshots
│ ├── boards.PNG
│ ├── card.PNG
│ ├── stack.w.cards.PNG
│ └── stack.wo.cards.PNG
├── Cartfile
├── Cartfile.resolved
├── LICENSE
├── README.md
├── iOS Deck.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcuserdata
│ └── zac.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── iOS Deck
├── Boards
│ ├── BoardView.swift
│ ├── BoardsListView.swift
│ ├── BoardsView.swift
│ ├── BoardsViewModel.swift
│ └── RefreshScrollView.swift
├── Cards
│ ├── CardListItemView.swift
│ ├── CardView.swift
│ └── DescriptionView.swift
├── Controllers
│ ├── AuthController.swift
│ ├── DataManager.swift
│ └── SyncManager.swift
├── Helpers
│ ├── IntrospectExtensions.swift
│ ├── KeychainPasswordItem.swift
│ ├── Settings.swift
│ └── Utils.swift
├── Login
│ ├── LoginView.swift
│ ├── LoginViewController.swift
│ ├── LoginWebView.swift
│ └── WebViewController.swift
├── Models
│ ├── CardModelExtension.swift
│ ├── Notifications.swift
│ └── User.swift
├── Nextcloud
│ ├── Nextcloud+auth.swift
│ ├── Nextcloud+deck.swift
│ ├── Nextcloud+user.swift
│ └── Nextcloud.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Resources
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon.png
│ │ │ ├── icon_20pt.png
│ │ │ ├── icon_20pt@2x-1.png
│ │ │ ├── icon_20pt@2x.png
│ │ │ ├── icon_20pt@3x.png
│ │ │ ├── icon_29pt.png
│ │ │ ├── icon_29pt@2x-1.png
│ │ │ ├── icon_29pt@2x.png
│ │ │ ├── icon_29pt@3x.png
│ │ │ ├── icon_40pt.png
│ │ │ ├── icon_40pt@2x-1.png
│ │ │ ├── icon_40pt@2x.png
│ │ │ ├── icon_40pt@3x.png
│ │ │ ├── icon_60pt@2x.png
│ │ │ ├── icon_60pt@3x.png
│ │ │ ├── icon_76pt.png
│ │ │ ├── icon_76pt@2x.png
│ │ │ └── icon_83.5@2x.png
│ │ ├── Contents.json
│ │ └── NextcloudIconWhite.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Nextcloud-logo-white-1.png
│ │ │ ├── Nextcloud-logo-white-2.png
│ │ │ └── Nextcloud-logo-white.png
│ ├── Info.plist
│ └── NextcloudDeck.xcdatamodeld
│ │ └── NextcloudDeck.xcdatamodel
│ │ └── contents
├── Root
│ ├── RootView.swift
│ └── iOS_DeckApp.swift
├── Settings
│ ├── SettingsView.swift
│ └── SettingsViewController.swift
├── Stacks
│ ├── StackListView.swift
│ ├── StackView.swift
│ └── StacksViewModel.swift
├── Styles
│ └── ButtonStyles.swift
├── Upcoming
│ └── UpcomingView.swift
└── Views
│ ├── ActivityIndicator.swift
│ └── NavigationBarModifier.swift
└── tmp.xcconfig
/Assets/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/Assets/Icon.png
--------------------------------------------------------------------------------
/Assets/Screenshots/boards.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/Assets/Screenshots/boards.PNG
--------------------------------------------------------------------------------
/Assets/Screenshots/card.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/Assets/Screenshots/card.PNG
--------------------------------------------------------------------------------
/Assets/Screenshots/stack.w.cards.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/Assets/Screenshots/stack.w.cards.PNG
--------------------------------------------------------------------------------
/Assets/Screenshots/stack.wo.cards.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/Assets/Screenshots/stack.wo.cards.PNG
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
1 | github "cbackas/ios-communication-library" "master"
2 |
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "Alamofire/Alamofire" "5.2.2"
2 | github "SwiftyJSON/SwiftyJSON" "5.0.0"
3 | github "cbackas/ios-communication-library" "8018ceb8bdb0cd9fb1bdaacfcbe0a8ce2268f8e0"
4 | github "yahoojapan/SwiftyXMLParser" "5.2.0"
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iOS-Deck
2 | Nextcloud Deck app for iOS
3 |
4 | Built using as much swiftUI as possible
5 |
6 | Utilizes Nextcloud/iOS-Communication-Library (or a fork of it)
7 |
8 | #### Early Screenshots (so wip obviously)
9 | Boards | Populated Stack | Card View |
10 | :-------------------------:|:-------------------------:|:-------------------------:
11 |
|
|
12 |
--------------------------------------------------------------------------------
/iOS Deck.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 110E936D24ECB91400079C8C /* SyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E936C24ECB91400079C8C /* SyncManager.swift */; };
11 | 110E937124ED660B00079C8C /* boards.PNG in Resources */ = {isa = PBXBuildFile; fileRef = 110E936E24ED660A00079C8C /* boards.PNG */; };
12 | 110E937324ED660B00079C8C /* stack.wo.cards.PNG in Resources */ = {isa = PBXBuildFile; fileRef = 110E937024ED660B00079C8C /* stack.wo.cards.PNG */; };
13 | 110E937624EDE72300079C8C /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 110E937524EDE72300079C8C /* Icon.png */; };
14 | 1144E89B24FEE0090014FC3B /* card.PNG in Resources */ = {isa = PBXBuildFile; fileRef = 1144E89A24FEE0090014FC3B /* card.PNG */; };
15 | 1144E89D24FEE06D0014FC3B /* stack.w.cards.PNG in Resources */ = {isa = PBXBuildFile; fileRef = 1144E89C24FEE06D0014FC3B /* stack.w.cards.PNG */; };
16 | 1153B21D24E8F68D00DF821B /* Nextcloud+auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153B21C24E8F68D00DF821B /* Nextcloud+auth.swift */; };
17 | 1153B21F24E8F76F00DF821B /* Nextcloud+user.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153B21E24E8F76F00DF821B /* Nextcloud+user.swift */; };
18 | 1153B22124E8F7B500DF821B /* Nextcloud+deck.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153B22024E8F7B500DF821B /* Nextcloud+deck.swift */; };
19 | 1153B22824EB193600DF821B /* CardModelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153B22724EB193600DF821B /* CardModelExtension.swift */; };
20 | 1153B22D24EB97D800DF821B /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153B22C24EB97D800DF821B /* DataManager.swift */; };
21 | 118989F424DD3C9200DD0D5F /* BoardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118989F324DD3C9200DD0D5F /* BoardView.swift */; };
22 | 118989F724DD421700DD0D5F /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118989F624DD421700DD0D5F /* ActivityIndicator.swift */; };
23 | 118989F924DDF63200DD0D5F /* NavigationBarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118989F824DDF63200DD0D5F /* NavigationBarModifier.swift */; };
24 | 118989FB24DE53BE00DD0D5F /* CardListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118989FA24DE53BE00DD0D5F /* CardListItemView.swift */; };
25 | 11898A0124DE684C00DD0D5F /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11898A0024DE684C00DD0D5F /* StackView.swift */; };
26 | 118F1A0C24EE2D9F00A1301D /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118F1A0B24EE2D9F00A1301D /* CardView.swift */; };
27 | 118F1A0F24EECA3800A1301D /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 118F1A0E24EECA3800A1301D /* Introspect */; };
28 | 118F1A1124EECDB200A1301D /* IntrospectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118F1A1024EECDB200A1301D /* IntrospectExtensions.swift */; };
29 | 118F1A1524F2FDB900A1301D /* DescriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118F1A1424F2FDB600A1301D /* DescriptionView.swift */; };
30 | 11B4C3DC24DB38600073C176 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B4C3DB24DB38600073C176 /* LoginView.swift */; };
31 | 11B4C3DE24DB42930073C176 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B4C3DD24DB42930073C176 /* WebViewController.swift */; };
32 | 11B4C3E424DBCF690073C176 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B4C3E324DBCF690073C176 /* RootView.swift */; };
33 | 11B4C3E924DBDC720073C176 /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B4C3E824DBDC720073C176 /* ButtonStyles.swift */; };
34 | 11B4C3EB24DC9BA80073C176 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B4C3EA24DC9BA70073C176 /* Notifications.swift */; };
35 | 11B4C3F824DD23570073C176 /* RefreshScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B4C3F724DD23560073C176 /* RefreshScrollView.swift */; };
36 | 11B4C3FA24DD2C790073C176 /* BoardsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B4C3F924DD2C780073C176 /* BoardsListView.swift */; };
37 | 11BE96A024DA6C1B002BD03C /* iOS_DeckApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE969F24DA6C1B002BD03C /* iOS_DeckApp.swift */; };
38 | 11BE96A424DA6C1D002BD03C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 11BE96A324DA6C1D002BD03C /* Assets.xcassets */; };
39 | 11BE96A724DA6C1D002BD03C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 11BE96A624DA6C1D002BD03C /* Preview Assets.xcassets */; };
40 | 11BE96BA24DA6CE8002BD03C /* BoardsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96B224DA6CE7002BD03C /* BoardsView.swift */; };
41 | 11BE96BD24DA6CE8002BD03C /* UpcomingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96B524DA6CE7002BD03C /* UpcomingView.swift */; };
42 | 11BE96C024DA6CE8002BD03C /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96B824DA6CE8002BD03C /* SettingsView.swift */; };
43 | 11BE96C824DA6D25002BD03C /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96C724DA6D25002BD03C /* User.swift */; };
44 | 11BE96CD24DA6D30002BD03C /* KeychainPasswordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96C924DA6D30002BD03C /* KeychainPasswordItem.swift */; };
45 | 11BE96CE24DA6D30002BD03C /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96CA24DA6D30002BD03C /* Utils.swift */; };
46 | 11BE96CF24DA6D30002BD03C /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96CB24DA6D30002BD03C /* Settings.swift */; };
47 | 11BE96D024DA6D30002BD03C /* Nextcloud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96CC24DA6D30002BD03C /* Nextcloud.swift */; };
48 | 11BE96D424DA6D3A002BD03C /* BoardsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96D124DA6D3A002BD03C /* BoardsViewModel.swift */; };
49 | 11BE96D524DA6D3A002BD03C /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96D224DA6D3A002BD03C /* SettingsViewController.swift */; };
50 | 11BE96D624DA6D3A002BD03C /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96D324DA6D3A002BD03C /* LoginViewController.swift */; };
51 | 11BE96D824DA6D4E002BD03C /* AuthController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96D724DA6D4D002BD03C /* AuthController.swift */; };
52 | 11BE96DA24DA6D89002BD03C /* tmp.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 11BE96D924DA6D89002BD03C /* tmp.xcconfig */; };
53 | 11BE96DC24DA6D8D002BD03C /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = 11BE96DB24DA6D8D002BD03C /* Cartfile */; };
54 | 11BE96EF24DB047B002BD03C /* LoginWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BE96EE24DB047B002BD03C /* LoginWebView.swift */; };
55 | 11E2082824E10815003593AE /* StacksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E2082724E10815003593AE /* StacksViewModel.swift */; };
56 | 11E2082A24E1C09D003593AE /* StackListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E2082924E1C09D003593AE /* StackListView.swift */; };
57 | /* End PBXBuildFile section */
58 |
59 | /* Begin PBXFileReference section */
60 | 110E936C24ECB91400079C8C /* SyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncManager.swift; sourceTree = ""; };
61 | 110E936E24ED660A00079C8C /* boards.PNG */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = boards.PNG; sourceTree = ""; };
62 | 110E937024ED660B00079C8C /* stack.wo.cards.PNG */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = stack.wo.cards.PNG; sourceTree = ""; };
63 | 110E937524EDE72300079C8C /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = ""; };
64 | 1144E89A24FEE0090014FC3B /* card.PNG */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = card.PNG; sourceTree = ""; };
65 | 1144E89C24FEE06D0014FC3B /* stack.w.cards.PNG */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = stack.w.cards.PNG; sourceTree = ""; };
66 | 1153B21C24E8F68D00DF821B /* Nextcloud+auth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Nextcloud+auth.swift"; sourceTree = ""; };
67 | 1153B21E24E8F76F00DF821B /* Nextcloud+user.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Nextcloud+user.swift"; sourceTree = ""; };
68 | 1153B22024E8F7B500DF821B /* Nextcloud+deck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Nextcloud+deck.swift"; sourceTree = ""; };
69 | 1153B22724EB193600DF821B /* CardModelExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardModelExtension.swift; sourceTree = ""; };
70 | 1153B22C24EB97D800DF821B /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; };
71 | 118989F324DD3C9200DD0D5F /* BoardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardView.swift; sourceTree = ""; };
72 | 118989F624DD421700DD0D5F /* ActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = ""; };
73 | 118989F824DDF63200DD0D5F /* NavigationBarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarModifier.swift; sourceTree = ""; };
74 | 118989FA24DE53BE00DD0D5F /* CardListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardListItemView.swift; sourceTree = ""; };
75 | 11898A0024DE684C00DD0D5F /* StackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = ""; };
76 | 118F1A0B24EE2D9F00A1301D /* CardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = ""; };
77 | 118F1A1024EECDB200A1301D /* IntrospectExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntrospectExtensions.swift; sourceTree = ""; };
78 | 118F1A1424F2FDB600A1301D /* DescriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptionView.swift; sourceTree = ""; };
79 | 11B4C3DB24DB38600073C176 /* LoginView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; };
80 | 11B4C3DD24DB42930073C176 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; };
81 | 11B4C3E324DBCF690073C176 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; };
82 | 11B4C3E824DBDC720073C176 /* ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStyles.swift; sourceTree = ""; };
83 | 11B4C3EA24DC9BA70073C176 /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; };
84 | 11B4C3F724DD23560073C176 /* RefreshScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshScrollView.swift; sourceTree = ""; };
85 | 11B4C3F924DD2C780073C176 /* BoardsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardsListView.swift; sourceTree = ""; };
86 | 11BE969C24DA6C1B002BD03C /* iOS Deck.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Deck.app"; sourceTree = BUILT_PRODUCTS_DIR; };
87 | 11BE969F24DA6C1B002BD03C /* iOS_DeckApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOS_DeckApp.swift; sourceTree = ""; };
88 | 11BE96A324DA6C1D002BD03C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
89 | 11BE96A624DA6C1D002BD03C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
90 | 11BE96A824DA6C1D002BD03C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
91 | 11BE96B224DA6CE7002BD03C /* BoardsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardsView.swift; sourceTree = ""; };
92 | 11BE96B524DA6CE7002BD03C /* UpcomingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpcomingView.swift; sourceTree = ""; };
93 | 11BE96B824DA6CE8002BD03C /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; };
94 | 11BE96C724DA6D25002BD03C /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; };
95 | 11BE96C924DA6D30002BD03C /* KeychainPasswordItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainPasswordItem.swift; sourceTree = ""; };
96 | 11BE96CA24DA6D30002BD03C /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; };
97 | 11BE96CB24DA6D30002BD03C /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; };
98 | 11BE96CC24DA6D30002BD03C /* Nextcloud.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Nextcloud.swift; sourceTree = ""; };
99 | 11BE96D124DA6D3A002BD03C /* BoardsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardsViewModel.swift; sourceTree = ""; };
100 | 11BE96D224DA6D3A002BD03C /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; };
101 | 11BE96D324DA6D3A002BD03C /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; };
102 | 11BE96D724DA6D4D002BD03C /* AuthController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthController.swift; sourceTree = ""; };
103 | 11BE96D924DA6D89002BD03C /* tmp.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = tmp.xcconfig; sourceTree = ""; };
104 | 11BE96DB24DA6D8D002BD03C /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = ""; };
105 | 11BE96EE24DB047B002BD03C /* LoginWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginWebView.swift; sourceTree = ""; };
106 | 11E2082724E10815003593AE /* StacksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StacksViewModel.swift; sourceTree = ""; };
107 | 11E2082924E1C09D003593AE /* StackListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackListView.swift; sourceTree = ""; };
108 | /* End PBXFileReference section */
109 |
110 | /* Begin PBXFrameworksBuildPhase section */
111 | 11BE969924DA6C1B002BD03C /* Frameworks */ = {
112 | isa = PBXFrameworksBuildPhase;
113 | buildActionMask = 2147483647;
114 | files = (
115 | 118F1A0F24EECA3800A1301D /* Introspect in Frameworks */,
116 | );
117 | runOnlyForDeploymentPostprocessing = 0;
118 | };
119 | /* End PBXFrameworksBuildPhase section */
120 |
121 | /* Begin PBXGroup section */
122 | 110E937424EDE70B00079C8C /* Assets */ = {
123 | isa = PBXGroup;
124 | children = (
125 | 110E937524EDE72300079C8C /* Icon.png */,
126 | 11898A0224DE77ED00DD0D5F /* Screenshots */,
127 | );
128 | path = Assets;
129 | sourceTree = "";
130 | };
131 | 1153B21B24E8F55300DF821B /* Nextcloud */ = {
132 | isa = PBXGroup;
133 | children = (
134 | 11BE96CC24DA6D30002BD03C /* Nextcloud.swift */,
135 | 1153B21C24E8F68D00DF821B /* Nextcloud+auth.swift */,
136 | 1153B21E24E8F76F00DF821B /* Nextcloud+user.swift */,
137 | 1153B22024E8F7B500DF821B /* Nextcloud+deck.swift */,
138 | );
139 | path = Nextcloud;
140 | sourceTree = "";
141 | };
142 | 118989F524DD41B600DD0D5F /* Views */ = {
143 | isa = PBXGroup;
144 | children = (
145 | 118989F624DD421700DD0D5F /* ActivityIndicator.swift */,
146 | 118989F824DDF63200DD0D5F /* NavigationBarModifier.swift */,
147 | );
148 | path = Views;
149 | sourceTree = "";
150 | };
151 | 118989FC24DE53C100DD0D5F /* Cards */ = {
152 | isa = PBXGroup;
153 | children = (
154 | 118989FA24DE53BE00DD0D5F /* CardListItemView.swift */,
155 | 118F1A0B24EE2D9F00A1301D /* CardView.swift */,
156 | 118F1A1424F2FDB600A1301D /* DescriptionView.swift */,
157 | );
158 | path = Cards;
159 | sourceTree = "";
160 | };
161 | 118989FF24DE681500DD0D5F /* Stacks */ = {
162 | isa = PBXGroup;
163 | children = (
164 | 11898A0024DE684C00DD0D5F /* StackView.swift */,
165 | 11E2082724E10815003593AE /* StacksViewModel.swift */,
166 | 11E2082924E1C09D003593AE /* StackListView.swift */,
167 | );
168 | path = Stacks;
169 | sourceTree = "";
170 | };
171 | 11898A0224DE77ED00DD0D5F /* Screenshots */ = {
172 | isa = PBXGroup;
173 | children = (
174 | 1144E89C24FEE06D0014FC3B /* stack.w.cards.PNG */,
175 | 1144E89A24FEE0090014FC3B /* card.PNG */,
176 | 110E936E24ED660A00079C8C /* boards.PNG */,
177 | 110E937024ED660B00079C8C /* stack.wo.cards.PNG */,
178 | );
179 | path = Screenshots;
180 | sourceTree = "";
181 | };
182 | 11B4C3E724DBDC670073C176 /* Styles */ = {
183 | isa = PBXGroup;
184 | children = (
185 | 11B4C3E824DBDC720073C176 /* ButtonStyles.swift */,
186 | );
187 | path = Styles;
188 | sourceTree = "";
189 | };
190 | 11B4C3FB24DD31D40073C176 /* Boards */ = {
191 | isa = PBXGroup;
192 | children = (
193 | 11B4C3F724DD23560073C176 /* RefreshScrollView.swift */,
194 | 11BE96B224DA6CE7002BD03C /* BoardsView.swift */,
195 | 11B4C3F924DD2C780073C176 /* BoardsListView.swift */,
196 | 11BE96D124DA6D3A002BD03C /* BoardsViewModel.swift */,
197 | 118989F324DD3C9200DD0D5F /* BoardView.swift */,
198 | );
199 | path = Boards;
200 | sourceTree = "";
201 | };
202 | 11B4C3FC24DD31F00073C176 /* Login */ = {
203 | isa = PBXGroup;
204 | children = (
205 | 11B4C3DB24DB38600073C176 /* LoginView.swift */,
206 | 11BE96EE24DB047B002BD03C /* LoginWebView.swift */,
207 | 11BE96D324DA6D3A002BD03C /* LoginViewController.swift */,
208 | 11B4C3DD24DB42930073C176 /* WebViewController.swift */,
209 | );
210 | path = Login;
211 | sourceTree = "";
212 | };
213 | 11B4C3FD24DD323F0073C176 /* Settings */ = {
214 | isa = PBXGroup;
215 | children = (
216 | 11BE96B824DA6CE8002BD03C /* SettingsView.swift */,
217 | 11BE96D224DA6D3A002BD03C /* SettingsViewController.swift */,
218 | );
219 | path = Settings;
220 | sourceTree = "";
221 | };
222 | 11B4C3FE24DD32510073C176 /* Root */ = {
223 | isa = PBXGroup;
224 | children = (
225 | 11BE969F24DA6C1B002BD03C /* iOS_DeckApp.swift */,
226 | 11B4C3E324DBCF690073C176 /* RootView.swift */,
227 | );
228 | path = Root;
229 | sourceTree = "";
230 | };
231 | 11B4C3FF24DD328B0073C176 /* Upcoming */ = {
232 | isa = PBXGroup;
233 | children = (
234 | 11BE96B524DA6CE7002BD03C /* UpcomingView.swift */,
235 | );
236 | path = Upcoming;
237 | sourceTree = "";
238 | };
239 | 11B4C40024DD32C80073C176 /* Controllers */ = {
240 | isa = PBXGroup;
241 | children = (
242 | 11BE96D724DA6D4D002BD03C /* AuthController.swift */,
243 | 1153B22C24EB97D800DF821B /* DataManager.swift */,
244 | 110E936C24ECB91400079C8C /* SyncManager.swift */,
245 | );
246 | path = Controllers;
247 | sourceTree = "";
248 | };
249 | 11BE969324DA6C1B002BD03C = {
250 | isa = PBXGroup;
251 | children = (
252 | 110E937424EDE70B00079C8C /* Assets */,
253 | 11BE96D924DA6D89002BD03C /* tmp.xcconfig */,
254 | 11BE96DB24DA6D8D002BD03C /* Cartfile */,
255 | 11BE969E24DA6C1B002BD03C /* iOS Deck */,
256 | 11BE969D24DA6C1B002BD03C /* Products */,
257 | );
258 | sourceTree = "";
259 | };
260 | 11BE969D24DA6C1B002BD03C /* Products */ = {
261 | isa = PBXGroup;
262 | children = (
263 | 11BE969C24DA6C1B002BD03C /* iOS Deck.app */,
264 | );
265 | name = Products;
266 | sourceTree = "";
267 | };
268 | 11BE969E24DA6C1B002BD03C /* iOS Deck */ = {
269 | isa = PBXGroup;
270 | children = (
271 | 1153B21B24E8F55300DF821B /* Nextcloud */,
272 | 118989F524DD41B600DD0D5F /* Views */,
273 | 11BE96C224DA6CFC002BD03C /* Models */,
274 | 11B4C3FE24DD32510073C176 /* Root */,
275 | 11B4C3FC24DD31F00073C176 /* Login */,
276 | 11B4C3FB24DD31D40073C176 /* Boards */,
277 | 118989FF24DE681500DD0D5F /* Stacks */,
278 | 118989FC24DE53C100DD0D5F /* Cards */,
279 | 11B4C3FF24DD328B0073C176 /* Upcoming */,
280 | 11B4C3FD24DD323F0073C176 /* Settings */,
281 | 11B4C3E724DBDC670073C176 /* Styles */,
282 | 11B4C40024DD32C80073C176 /* Controllers */,
283 | 11BE96C324DA6D02002BD03C /* Helpers */,
284 | 11BE96AE24DA6C4C002BD03C /* Resources */,
285 | 11BE96A524DA6C1D002BD03C /* Preview Content */,
286 | );
287 | path = "iOS Deck";
288 | sourceTree = "";
289 | };
290 | 11BE96A524DA6C1D002BD03C /* Preview Content */ = {
291 | isa = PBXGroup;
292 | children = (
293 | 11BE96A624DA6C1D002BD03C /* Preview Assets.xcassets */,
294 | );
295 | path = "Preview Content";
296 | sourceTree = "";
297 | };
298 | 11BE96AE24DA6C4C002BD03C /* Resources */ = {
299 | isa = PBXGroup;
300 | children = (
301 | 11BE96A824DA6C1D002BD03C /* Info.plist */,
302 | 11BE96A324DA6C1D002BD03C /* Assets.xcassets */,
303 | );
304 | path = Resources;
305 | sourceTree = "";
306 | };
307 | 11BE96C224DA6CFC002BD03C /* Models */ = {
308 | isa = PBXGroup;
309 | children = (
310 | 11BE96C724DA6D25002BD03C /* User.swift */,
311 | 11B4C3EA24DC9BA70073C176 /* Notifications.swift */,
312 | 1153B22724EB193600DF821B /* CardModelExtension.swift */,
313 | );
314 | path = Models;
315 | sourceTree = "";
316 | };
317 | 11BE96C324DA6D02002BD03C /* Helpers */ = {
318 | isa = PBXGroup;
319 | children = (
320 | 11BE96C924DA6D30002BD03C /* KeychainPasswordItem.swift */,
321 | 11BE96CB24DA6D30002BD03C /* Settings.swift */,
322 | 11BE96CA24DA6D30002BD03C /* Utils.swift */,
323 | 118F1A1024EECDB200A1301D /* IntrospectExtensions.swift */,
324 | );
325 | path = Helpers;
326 | sourceTree = "";
327 | };
328 | /* End PBXGroup section */
329 |
330 | /* Begin PBXNativeTarget section */
331 | 11BE969B24DA6C1B002BD03C /* iOS Deck */ = {
332 | isa = PBXNativeTarget;
333 | buildConfigurationList = 11BE96AB24DA6C1D002BD03C /* Build configuration list for PBXNativeTarget "iOS Deck" */;
334 | buildPhases = (
335 | 11BE969824DA6C1B002BD03C /* Sources */,
336 | 11BE969924DA6C1B002BD03C /* Frameworks */,
337 | 11BE969A24DA6C1B002BD03C /* Resources */,
338 | 11BE96DD24DA6D9E002BD03C /* ShellScript */,
339 | );
340 | buildRules = (
341 | );
342 | dependencies = (
343 | );
344 | name = "iOS Deck";
345 | packageProductDependencies = (
346 | 118F1A0E24EECA3800A1301D /* Introspect */,
347 | );
348 | productName = "iOS Deck";
349 | productReference = 11BE969C24DA6C1B002BD03C /* iOS Deck.app */;
350 | productType = "com.apple.product-type.application";
351 | };
352 | /* End PBXNativeTarget section */
353 |
354 | /* Begin PBXProject section */
355 | 11BE969424DA6C1B002BD03C /* Project object */ = {
356 | isa = PBXProject;
357 | attributes = {
358 | LastSwiftUpdateCheck = 1200;
359 | LastUpgradeCheck = 1200;
360 | TargetAttributes = {
361 | 11BE969B24DA6C1B002BD03C = {
362 | CreatedOnToolsVersion = 12.0;
363 | };
364 | };
365 | };
366 | buildConfigurationList = 11BE969724DA6C1B002BD03C /* Build configuration list for PBXProject "iOS Deck" */;
367 | compatibilityVersion = "Xcode 9.3";
368 | developmentRegion = en;
369 | hasScannedForEncodings = 0;
370 | knownRegions = (
371 | en,
372 | Base,
373 | );
374 | mainGroup = 11BE969324DA6C1B002BD03C;
375 | packageReferences = (
376 | 118F1A0D24EECA3800A1301D /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
377 | );
378 | productRefGroup = 11BE969D24DA6C1B002BD03C /* Products */;
379 | projectDirPath = "";
380 | projectRoot = "";
381 | targets = (
382 | 11BE969B24DA6C1B002BD03C /* iOS Deck */,
383 | );
384 | };
385 | /* End PBXProject section */
386 |
387 | /* Begin PBXResourcesBuildPhase section */
388 | 11BE969A24DA6C1B002BD03C /* Resources */ = {
389 | isa = PBXResourcesBuildPhase;
390 | buildActionMask = 2147483647;
391 | files = (
392 | 110E937124ED660B00079C8C /* boards.PNG in Resources */,
393 | 1144E89B24FEE0090014FC3B /* card.PNG in Resources */,
394 | 11BE96DA24DA6D89002BD03C /* tmp.xcconfig in Resources */,
395 | 110E937324ED660B00079C8C /* stack.wo.cards.PNG in Resources */,
396 | 110E937624EDE72300079C8C /* Icon.png in Resources */,
397 | 11BE96A724DA6C1D002BD03C /* Preview Assets.xcassets in Resources */,
398 | 11BE96DC24DA6D8D002BD03C /* Cartfile in Resources */,
399 | 11BE96A424DA6C1D002BD03C /* Assets.xcassets in Resources */,
400 | 1144E89D24FEE06D0014FC3B /* stack.w.cards.PNG in Resources */,
401 | );
402 | runOnlyForDeploymentPostprocessing = 0;
403 | };
404 | /* End PBXResourcesBuildPhase section */
405 |
406 | /* Begin PBXShellScriptBuildPhase section */
407 | 11BE96DD24DA6D9E002BD03C /* ShellScript */ = {
408 | isa = PBXShellScriptBuildPhase;
409 | buildActionMask = 2147483647;
410 | files = (
411 | );
412 | inputFileListPaths = (
413 | );
414 | inputPaths = (
415 | "$(SRCROOT)/Carthage/Build/iOS/SwiftyXMLParser.framework",
416 | "$(SRCROOT)/Carthage/Build/iOS/SwiftyJSON.framework",
417 | "$(SRCROOT)/Carthage/Build/iOS/NCCommunication.framework",
418 | "$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework",
419 | );
420 | outputFileListPaths = (
421 | );
422 | outputPaths = (
423 | "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/SwiftyXMLParser.framework",
424 | "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/SwiftyJSON.framework",
425 | "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/NCCommunication.framework",
426 | "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Alamofire.framework",
427 | );
428 | runOnlyForDeploymentPostprocessing = 0;
429 | shellPath = /bin/sh;
430 | shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n/usr/local/bin/carthage copy-frameworks\n";
431 | };
432 | /* End PBXShellScriptBuildPhase section */
433 |
434 | /* Begin PBXSourcesBuildPhase section */
435 | 11BE969824DA6C1B002BD03C /* Sources */ = {
436 | isa = PBXSourcesBuildPhase;
437 | buildActionMask = 2147483647;
438 | files = (
439 | 11B4C3EB24DC9BA80073C176 /* Notifications.swift in Sources */,
440 | 11BE96D624DA6D3A002BD03C /* LoginViewController.swift in Sources */,
441 | 118989F924DDF63200DD0D5F /* NavigationBarModifier.swift in Sources */,
442 | 1153B21F24E8F76F00DF821B /* Nextcloud+user.swift in Sources */,
443 | 11B4C3DE24DB42930073C176 /* WebViewController.swift in Sources */,
444 | 11BE96D824DA6D4E002BD03C /* AuthController.swift in Sources */,
445 | 11B4C3FA24DD2C790073C176 /* BoardsListView.swift in Sources */,
446 | 1153B22124E8F7B500DF821B /* Nextcloud+deck.swift in Sources */,
447 | 11B4C3E424DBCF690073C176 /* RootView.swift in Sources */,
448 | 11BE96EF24DB047B002BD03C /* LoginWebView.swift in Sources */,
449 | 118F1A0C24EE2D9F00A1301D /* CardView.swift in Sources */,
450 | 11BE96BD24DA6CE8002BD03C /* UpcomingView.swift in Sources */,
451 | 11BE96CF24DA6D30002BD03C /* Settings.swift in Sources */,
452 | 11BE96C024DA6CE8002BD03C /* SettingsView.swift in Sources */,
453 | 11BE96CE24DA6D30002BD03C /* Utils.swift in Sources */,
454 | 118989F424DD3C9200DD0D5F /* BoardView.swift in Sources */,
455 | 110E936D24ECB91400079C8C /* SyncManager.swift in Sources */,
456 | 11E2082A24E1C09D003593AE /* StackListView.swift in Sources */,
457 | 11898A0124DE684C00DD0D5F /* StackView.swift in Sources */,
458 | 118989F724DD421700DD0D5F /* ActivityIndicator.swift in Sources */,
459 | 118F1A1524F2FDB900A1301D /* DescriptionView.swift in Sources */,
460 | 118F1A1124EECDB200A1301D /* IntrospectExtensions.swift in Sources */,
461 | 118989FB24DE53BE00DD0D5F /* CardListItemView.swift in Sources */,
462 | 11E2082824E10815003593AE /* StacksViewModel.swift in Sources */,
463 | 11BE96D424DA6D3A002BD03C /* BoardsViewModel.swift in Sources */,
464 | 11B4C3E924DBDC720073C176 /* ButtonStyles.swift in Sources */,
465 | 11B4C3F824DD23570073C176 /* RefreshScrollView.swift in Sources */,
466 | 1153B21D24E8F68D00DF821B /* Nextcloud+auth.swift in Sources */,
467 | 11BE96BA24DA6CE8002BD03C /* BoardsView.swift in Sources */,
468 | 11B4C3DC24DB38600073C176 /* LoginView.swift in Sources */,
469 | 11BE96A024DA6C1B002BD03C /* iOS_DeckApp.swift in Sources */,
470 | 11BE96D524DA6D3A002BD03C /* SettingsViewController.swift in Sources */,
471 | 11BE96D024DA6D30002BD03C /* Nextcloud.swift in Sources */,
472 | 1153B22D24EB97D800DF821B /* DataManager.swift in Sources */,
473 | 11BE96C824DA6D25002BD03C /* User.swift in Sources */,
474 | 1153B22824EB193600DF821B /* CardModelExtension.swift in Sources */,
475 | 11BE96CD24DA6D30002BD03C /* KeychainPasswordItem.swift in Sources */,
476 | );
477 | runOnlyForDeploymentPostprocessing = 0;
478 | };
479 | /* End PBXSourcesBuildPhase section */
480 |
481 | /* Begin XCBuildConfiguration section */
482 | 11BE96A924DA6C1D002BD03C /* Debug */ = {
483 | isa = XCBuildConfiguration;
484 | buildSettings = {
485 | ALWAYS_SEARCH_USER_PATHS = NO;
486 | CLANG_ANALYZER_NONNULL = YES;
487 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
488 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
489 | CLANG_CXX_LIBRARY = "libc++";
490 | CLANG_ENABLE_MODULES = YES;
491 | CLANG_ENABLE_OBJC_ARC = YES;
492 | CLANG_ENABLE_OBJC_WEAK = YES;
493 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
494 | CLANG_WARN_BOOL_CONVERSION = YES;
495 | CLANG_WARN_COMMA = YES;
496 | CLANG_WARN_CONSTANT_CONVERSION = YES;
497 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
498 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
499 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
500 | CLANG_WARN_EMPTY_BODY = YES;
501 | CLANG_WARN_ENUM_CONVERSION = YES;
502 | CLANG_WARN_INFINITE_RECURSION = YES;
503 | CLANG_WARN_INT_CONVERSION = YES;
504 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
505 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
506 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
507 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
508 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
509 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
510 | CLANG_WARN_STRICT_PROTOTYPES = YES;
511 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
512 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
513 | CLANG_WARN_UNREACHABLE_CODE = YES;
514 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
515 | COPY_PHASE_STRIP = NO;
516 | DEBUG_INFORMATION_FORMAT = dwarf;
517 | ENABLE_STRICT_OBJC_MSGSEND = YES;
518 | ENABLE_TESTABILITY = YES;
519 | GCC_C_LANGUAGE_STANDARD = gnu11;
520 | GCC_DYNAMIC_NO_PIC = NO;
521 | GCC_NO_COMMON_BLOCKS = YES;
522 | GCC_OPTIMIZATION_LEVEL = 0;
523 | GCC_PREPROCESSOR_DEFINITIONS = (
524 | "DEBUG=1",
525 | "$(inherited)",
526 | );
527 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
528 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
529 | GCC_WARN_UNDECLARED_SELECTOR = YES;
530 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
531 | GCC_WARN_UNUSED_FUNCTION = YES;
532 | GCC_WARN_UNUSED_VARIABLE = YES;
533 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
534 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
535 | MTL_FAST_MATH = YES;
536 | ONLY_ACTIVE_ARCH = YES;
537 | SDKROOT = iphoneos;
538 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
539 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
540 | };
541 | name = Debug;
542 | };
543 | 11BE96AA24DA6C1D002BD03C /* Release */ = {
544 | isa = XCBuildConfiguration;
545 | buildSettings = {
546 | ALWAYS_SEARCH_USER_PATHS = NO;
547 | CLANG_ANALYZER_NONNULL = YES;
548 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
549 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
550 | CLANG_CXX_LIBRARY = "libc++";
551 | CLANG_ENABLE_MODULES = YES;
552 | CLANG_ENABLE_OBJC_ARC = YES;
553 | CLANG_ENABLE_OBJC_WEAK = YES;
554 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
555 | CLANG_WARN_BOOL_CONVERSION = YES;
556 | CLANG_WARN_COMMA = YES;
557 | CLANG_WARN_CONSTANT_CONVERSION = YES;
558 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
559 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
560 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
561 | CLANG_WARN_EMPTY_BODY = YES;
562 | CLANG_WARN_ENUM_CONVERSION = YES;
563 | CLANG_WARN_INFINITE_RECURSION = YES;
564 | CLANG_WARN_INT_CONVERSION = YES;
565 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
566 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
567 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
568 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
569 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
570 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
571 | CLANG_WARN_STRICT_PROTOTYPES = YES;
572 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
573 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
574 | CLANG_WARN_UNREACHABLE_CODE = YES;
575 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
576 | COPY_PHASE_STRIP = NO;
577 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
578 | ENABLE_NS_ASSERTIONS = NO;
579 | ENABLE_STRICT_OBJC_MSGSEND = YES;
580 | GCC_C_LANGUAGE_STANDARD = gnu11;
581 | GCC_NO_COMMON_BLOCKS = YES;
582 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
583 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
584 | GCC_WARN_UNDECLARED_SELECTOR = YES;
585 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
586 | GCC_WARN_UNUSED_FUNCTION = YES;
587 | GCC_WARN_UNUSED_VARIABLE = YES;
588 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
589 | MTL_ENABLE_DEBUG_INFO = NO;
590 | MTL_FAST_MATH = YES;
591 | ONLY_ACTIVE_ARCH = YES;
592 | SDKROOT = iphoneos;
593 | SWIFT_COMPILATION_MODE = wholemodule;
594 | SWIFT_OPTIMIZATION_LEVEL = "-O";
595 | VALIDATE_PRODUCT = YES;
596 | };
597 | name = Release;
598 | };
599 | 11BE96AC24DA6C1D002BD03C /* Debug */ = {
600 | isa = XCBuildConfiguration;
601 | buildSettings = {
602 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
603 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
604 | CODE_SIGN_STYLE = Automatic;
605 | CURRENT_PROJECT_VERSION = 1;
606 | DEVELOPMENT_ASSET_PATHS = "\"iOS Deck/Preview Content\"";
607 | DEVELOPMENT_TEAM = 3UDJ7Y5C8B;
608 | ENABLE_PREVIEWS = YES;
609 | FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/iOS";
610 | INFOPLIST_FILE = "iOS Deck/Resources/Info.plist";
611 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
612 | LD_RUNPATH_SEARCH_PATHS = (
613 | "$(inherited)",
614 | "@executable_path/Frameworks",
615 | );
616 | MARKETING_VERSION = 0.1;
617 | PRODUCT_BUNDLE_IDENTIFIER = "cback.iOS-Deck";
618 | PRODUCT_NAME = "$(TARGET_NAME)";
619 | SWIFT_VERSION = 5.0;
620 | TARGETED_DEVICE_FAMILY = "1,2";
621 | };
622 | name = Debug;
623 | };
624 | 11BE96AD24DA6C1D002BD03C /* Release */ = {
625 | isa = XCBuildConfiguration;
626 | buildSettings = {
627 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
628 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
629 | CODE_SIGN_STYLE = Automatic;
630 | CURRENT_PROJECT_VERSION = 1;
631 | DEVELOPMENT_ASSET_PATHS = "\"iOS Deck/Preview Content\"";
632 | DEVELOPMENT_TEAM = 3UDJ7Y5C8B;
633 | ENABLE_PREVIEWS = YES;
634 | FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/iOS";
635 | INFOPLIST_FILE = "iOS Deck/Resources/Info.plist";
636 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
637 | LD_RUNPATH_SEARCH_PATHS = (
638 | "$(inherited)",
639 | "@executable_path/Frameworks",
640 | );
641 | MARKETING_VERSION = 0.1;
642 | PRODUCT_BUNDLE_IDENTIFIER = "cback.iOS-Deck";
643 | PRODUCT_NAME = "$(TARGET_NAME)";
644 | SWIFT_VERSION = 5.0;
645 | TARGETED_DEVICE_FAMILY = "1,2";
646 | };
647 | name = Release;
648 | };
649 | /* End XCBuildConfiguration section */
650 |
651 | /* Begin XCConfigurationList section */
652 | 11BE969724DA6C1B002BD03C /* Build configuration list for PBXProject "iOS Deck" */ = {
653 | isa = XCConfigurationList;
654 | buildConfigurations = (
655 | 11BE96A924DA6C1D002BD03C /* Debug */,
656 | 11BE96AA24DA6C1D002BD03C /* Release */,
657 | );
658 | defaultConfigurationIsVisible = 0;
659 | defaultConfigurationName = Release;
660 | };
661 | 11BE96AB24DA6C1D002BD03C /* Build configuration list for PBXNativeTarget "iOS Deck" */ = {
662 | isa = XCConfigurationList;
663 | buildConfigurations = (
664 | 11BE96AC24DA6C1D002BD03C /* Debug */,
665 | 11BE96AD24DA6C1D002BD03C /* Release */,
666 | );
667 | defaultConfigurationIsVisible = 0;
668 | defaultConfigurationName = Release;
669 | };
670 | /* End XCConfigurationList section */
671 |
672 | /* Begin XCRemoteSwiftPackageReference section */
673 | 118F1A0D24EECA3800A1301D /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
674 | isa = XCRemoteSwiftPackageReference;
675 | repositoryURL = "https://github.com/siteline/SwiftUI-Introspect.git";
676 | requirement = {
677 | kind = upToNextMajorVersion;
678 | minimumVersion = 0.1.0;
679 | };
680 | };
681 | /* End XCRemoteSwiftPackageReference section */
682 |
683 | /* Begin XCSwiftPackageProductDependency section */
684 | 118F1A0E24EECA3800A1301D /* Introspect */ = {
685 | isa = XCSwiftPackageProductDependency;
686 | package = 118F1A0D24EECA3800A1301D /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
687 | productName = Introspect;
688 | };
689 | /* End XCSwiftPackageProductDependency section */
690 | };
691 | rootObject = 11BE969424DA6C1B002BD03C /* Project object */;
692 | }
693 |
--------------------------------------------------------------------------------
/iOS Deck.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/iOS Deck.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iOS Deck.xcodeproj/xcuserdata/zac.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | iOS Deck.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 11BE969B24DA6C1B002BD03C
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/iOS Deck/Boards/BoardView.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import NCCommunication
6 | import Network
7 |
8 | struct BoardView: View {
9 | @State var board: NCCommunicationDeckBoards
10 | @ObservedObject var viewModel: BoardsViewModel
11 |
12 | @Environment(\.presentationMode) var presentationMode
13 |
14 | @State private var fetchingData: Bool = true
15 |
16 | @State private var hasData: Bool = false
17 | @State private var hasConnection: Bool = false
18 |
19 | // network connection monitoring
20 | private let monitor = NWPathMonitor()
21 | private let queue = DispatchQueue(label: "Monitor")
22 |
23 | @State private var showCard: Bool = false
24 | @State private var visibleCard: NCCommunicationDeckCards? = nil
25 |
26 | var body: some View {
27 | let boardColor = Color(Color(hex: "#\(board.color)").uiColor().adjust(by: -10)!)
28 | let hasEditPerm = board.permissions.PERMISSION_EDIT
29 |
30 | GeometryReader {
31 | geo in
32 | ZStack {
33 | if (hasData) {
34 | if (viewModel.stackModel == nil) {
35 | Text("No Stacks here squad fam")
36 | } else {
37 | GeometryReader { geometry in
38 | TabView() {
39 | ForEach(viewModel.stackModel!.stacks) {
40 | stack in
41 | StackView(stack: stack, permissionToEdit: (hasConnection && hasEditPerm), viewModel: viewModel.stackModel!, color: boardColor, size: geometry.size)
42 | }
43 | }
44 | .tabViewStyle(PageTabViewStyle())
45 | }
46 | }
47 | }
48 | }
49 | .sheet(isPresented: $showCard, content: {
50 | if (visibleCard != nil) {
51 | CardView(card: visibleCard!, viewModel: viewModel.stackModel!)
52 | }
53 | })
54 | .frame(width: geo.size.width, height: geo.size.height)
55 | .background(Color(hex: "#\(board.color)"))
56 | .navigationBarTitle(board.title, displayMode: .inline)
57 | .navigationBarColor(Color(hex: "#\(board.color)").uiColor().adjust(by: -10))
58 | .navigationBarBackButtonHidden(true)
59 | .navigationBarItems(leading: Button("Back"){self.presentationMode.wrappedValue.dismiss()}, trailing: ReloadView)
60 | .onAppear(perform: onAppear)
61 | .onReceive(NotificationCenter.default.publisher(for: .cardSelected), perform: { output in
62 | let card = output.object as? NCCommunicationDeckCards
63 | if (card != nil) {
64 | visibleCard = card
65 | showCard = true
66 | } else {
67 | showCard = false
68 | visibleCard = nil
69 | }
70 | })
71 | }
72 | }
73 |
74 | private func onAppear() {
75 | monitor.pathUpdateHandler = { path in
76 | hasConnection = (path.status == .satisfied)
77 | if (hasConnection) {
78 | getData()
79 | }
80 | }
81 | monitor.start(queue: queue)
82 |
83 | let savedStacks = DataManager().getStacks(board.id)
84 | viewModel.stackModel = StacksViewModel(savedStacks)
85 |
86 | if (hasConnection) {
87 | getData()
88 | } else {
89 | hasData = (viewModel.stackModel?.stacks.count ?? 0 > 0)
90 | }
91 | }
92 |
93 | private func getData() {
94 | hasData = false
95 | fetchingData = true
96 | viewModel.updateStacks(boardID: board.id) {
97 | (loaded) in
98 | if (loaded) {
99 | hasData = (viewModel.stackModel?.stacks.count ?? 0 > 0)
100 | fetchingData = false
101 | }
102 | }
103 | }
104 |
105 | private var ReloadView: some View {
106 | ZStack {
107 | if (!hasConnection) {
108 | Image(systemName: "wifi.exclamationmark")
109 | } else if (fetchingData) {
110 | ActivityIndicator(shouldAnimate: $fetchingData)
111 | } else {
112 | Button(action: getData) {
113 | Image(systemName: "arrow.clockwise")
114 | }
115 | }
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/iOS Deck/Boards/BoardsListView.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import NCCommunication
6 |
7 | struct BoardsListView: View {
8 | @ObservedObject var viewModel: BoardsViewModel
9 |
10 | var body: some View {
11 | ScrollView {
12 | ForEach(viewModel.boards) { board in
13 | NavigationLink(destination: BoardView(board: board, viewModel: viewModel)) {
14 | row(forBoard: board)
15 | }
16 | }
17 | Text("Boards: \(viewModel.boards.count)")
18 | }
19 | .onAppear() {
20 | viewModel.updateBoards()
21 | }
22 | }
23 |
24 | private func row(forBoard board: NCCommunicationDeckBoards) -> some View {
25 | HStack {
26 | Image(uiImage: UIImage())
27 | .resizable()
28 | .clipShape(Circle())
29 | .overlay(Circle().fill(Color.init(hex: "#\(board.color)")))
30 | .frame(width: 20, height: 20)
31 | VStack(alignment: .leading) {
32 | Text(board.title)
33 | }
34 | Spacer()
35 | }
36 | .padding()
37 | }
38 | }
39 |
40 | extension Color {
41 | init(hex: String) {
42 | let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
43 | var int: UInt64 = 0
44 | Scanner(string: hex).scanHexInt64(&int)
45 | let a, r, g, b: UInt64
46 | switch hex.count {
47 | case 3: // RGB (12-bit)
48 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
49 | case 6: // RGB (24-bit)
50 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
51 | case 8: // ARGB (32-bit)
52 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
53 | default:
54 | (a, r, g, b) = (1, 1, 1, 0)
55 | }
56 |
57 | self.init(
58 | .sRGB,
59 | red: Double(r) / 255,
60 | green: Double(g) / 255,
61 | blue: Double(b) / 255,
62 | opacity: Double(a) / 255
63 | )
64 | }
65 |
66 | mutating func darker() {
67 | guard let color = self.uiColor().adjust(by: -10) else {
68 | return
69 | }
70 | self = Color.init(color)
71 | }
72 |
73 | func uiColor() -> UIColor {
74 | let components = self.components()
75 | return UIColor(red: components.r, green: components.g, blue: components.b, alpha: components.a)
76 | }
77 |
78 | private func components() -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
79 |
80 | let scanner = Scanner(string: self.description.trimmingCharacters(in: CharacterSet.alphanumerics.inverted))
81 | var hexNumber: UInt64 = 0
82 | var r: CGFloat = 0.0, g: CGFloat = 0.0, b: CGFloat = 0.0, a: CGFloat = 0.0
83 |
84 | let result = scanner.scanHexInt64(&hexNumber)
85 | if result {
86 | r = CGFloat((hexNumber & 0xff000000) >> 24) / 255
87 | g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255
88 | b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255
89 | a = CGFloat(hexNumber & 0x000000ff) / 255
90 | }
91 | return (r, g, b, a)
92 | }
93 |
94 | }
95 |
96 | extension UIColor {
97 | public convenience init?(hex: String) {
98 | let r, g, b, a: CGFloat
99 |
100 | if hex.hasPrefix("#") {
101 | let start = hex.index(hex.startIndex, offsetBy: 1)
102 | let hexColor = String(hex[start...])
103 |
104 | if hexColor.count == 8 {
105 | let scanner = Scanner(string: hexColor)
106 | var hexNumber: UInt64 = 0
107 |
108 | if scanner.scanHexInt64(&hexNumber) {
109 | r = CGFloat((hexNumber & 0xff000000) >> 24) / 255
110 | g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255
111 | b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255
112 | a = CGFloat(hexNumber & 0x000000ff) / 255
113 |
114 | self.init(red: r, green: g, blue: b, alpha: a)
115 | return
116 | }
117 | }
118 | }
119 | return nil
120 | }
121 |
122 | func adjust(by percentage: CGFloat = 30.0) -> UIColor? {
123 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
124 | if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
125 | return UIColor(red: min(red + percentage/100, 1.0),
126 | green: min(green + percentage/100, 1.0),
127 | blue: min(blue + percentage/100, 1.0),
128 | alpha: alpha)
129 | } else {
130 | return nil
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/iOS Deck/Boards/BoardsView.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 |
6 | struct BoardsView: View {
7 | var body: some View {
8 | GeometryReader { geometry in
9 | NavigationView {
10 | RefreshScrollView(size: geometry.size)
11 | .navigationBarTitle("Boards", displayMode: .automatic)
12 | .navigationBarItems(leading:
13 | Button() {
14 | print("Edit tapped!")
15 | } label: {
16 | Text("Edit")
17 | .foregroundColor(Color.white)
18 | },
19 | trailing:
20 | Button() {
21 | print("Add tapped!")
22 | } label: {
23 | Image(systemName: "plus")
24 | .foregroundColor(Color.white)
25 | }
26 | )
27 | .accentColor(Color.white)
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/iOS Deck/Boards/BoardsViewModel.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import Foundation
5 | import NCCommunication
6 |
7 | final class BoardsViewModel: ObservableObject {
8 | @Published var boards: [NCCommunicationDeckBoards] = []
9 | @Published var stackModel: StacksViewModel? = nil
10 |
11 | init() {
12 | self.boards = DataManager().getBoards()
13 | }
14 |
15 | func updateBoards() {
16 | Nextcloud.shared.getBoards() {
17 | (boards) in
18 | self.boards = boards
19 | DataManager().setBoards(boards)
20 | }
21 | }
22 |
23 | func updateBoards(closure: @escaping ((_ loaded: Bool) -> Void)) {
24 | Nextcloud.shared.getBoards() {
25 | (boards) in
26 | self.boards = boards
27 | DataManager().setBoards(boards)
28 | closure(true)
29 | }
30 | }
31 |
32 | func updateStacks(boardID: Int, closure: @escaping ((_ loaded: Bool) -> Void)) {
33 | Nextcloud.shared.getStacks(boardID: boardID) {
34 | (stacks) in
35 | self.stackModel!.stacks = stacks
36 | DataManager().setStacks(stacks)
37 | closure(true)
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/iOS Deck/Boards/RefreshScrollView.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import UIKit
5 | import SwiftUI
6 |
7 | struct RefreshScrollView: UIViewRepresentable {
8 | var size: CGSize
9 |
10 | let viewModel = BoardsViewModel()
11 |
12 | func makeUIView(context: Context) -> UIScrollView {
13 | let scrollView = UIScrollView()
14 | scrollView.refreshControl = UIRefreshControl()
15 | scrollView.refreshControl?.addTarget(context.coordinator, action: #selector(Coordinator.handleRefreshControl(sender:)), for: .valueChanged)
16 |
17 | let refreshVC = UIHostingController(rootView: BoardsListView(viewModel: viewModel))
18 | refreshVC.view.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
19 |
20 | scrollView.addSubview(refreshVC.view)
21 |
22 | return scrollView
23 | }
24 |
25 | func updateUIView(_ uiView: UIScrollView, context: Context) {}
26 |
27 | func makeCoordinator() -> Coordinator {
28 | Coordinator(self, viewModel: viewModel)
29 | }
30 |
31 | class Coordinator: NSObject {
32 | var refreshScrollView: RefreshScrollView
33 | var viewModel: BoardsViewModel
34 |
35 | init(_ refreshScrollView: RefreshScrollView, viewModel: BoardsViewModel) {
36 | self.refreshScrollView = refreshScrollView
37 | self.viewModel = viewModel
38 | }
39 |
40 | @objc func handleRefreshControl(sender: UIRefreshControl) {
41 | viewModel.updateBoards()
42 | sender.endRefreshing()
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/iOS Deck/Cards/CardListItemView.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import NCCommunication
6 | import MobileCoreServices
7 |
8 | struct CardListItemView: View {
9 | var card: NCCommunicationDeckCards
10 |
11 | // @State private var cardPresented: Bool = false
12 |
13 | // @State private var textLines: Int = 0
14 | // @State private var checklistLines: Int = 0
15 | // @State private var checkedLines: Int = 0
16 | //
17 | // @State private var hasTextLines: Bool = false
18 | // @State private var hasChecklistLines: Bool = false
19 | // @State private var hasLabels: Bool = false
20 |
21 | var body: some View {
22 | let (textLines, checklistLines, checkedLines): (Int, Int, Int) = card.getDescriptionNumbers()
23 | let hasTextLines: Bool = textLines > 0
24 | let hasChecklistLines: Bool = checklistLines > 0
25 | let hasLabels: Bool = card.labels?.count ?? 0 > 0
26 |
27 | ZStack {
28 | RoundedRectangle(cornerRadius: 5, style: .continuous)
29 | .fill(Color.init(hex: "#181a1b"))
30 | VStack(alignment: .leading) {
31 | Text(card.title)
32 | .foregroundColor(Color.white)
33 | .padding(.horizontal, 5)
34 | .padding(.top, 3)
35 | Spacer()
36 | HStack {
37 | if (hasLabels) {
38 | ForEach(card.labels ?? []) {
39 | label in
40 | RoundedRectangle(cornerRadius: 4, style: .continuous)
41 | .fill(Color.init(hex: "#\(label.color)"))
42 | .frame(height: 4.5)
43 | .padding(.bottom, (hasTextLines || hasChecklistLines) ? -4 : 3)
44 | }
45 | } else {
46 | Spacer()
47 | .frame(height: 0)
48 | }
49 |
50 | if (hasTextLines) {
51 | Text("") // trick this group into having same height/padding as checklist group
52 | Image(systemName: "text.justifyleft")
53 | .font(.caption)
54 | .padding(.leading, -6)
55 | }
56 | if (hasChecklistLines) {
57 | Text("\(checkedLines)/\(checklistLines)")
58 | .font(.caption)
59 | .padding(.trailing, -3)
60 | Image(systemName: "checkmark")
61 | .font(.caption)
62 | .padding(.leading, -3)
63 | }
64 | }
65 | .padding(.bottom, 2)
66 | .padding(.horizontal, 5)
67 | }
68 | }
69 | .padding(.vertical, 5)
70 | .padding(.horizontal, 5)
71 | .onAppear() {
72 |
73 | }
74 | .onTapGesture(count: 1, perform: {
75 | NotificationCenter.default.post(name: .cardSelected, object: card)
76 | })
77 | }
78 | }
79 |
80 | extension NCCommunicationDeckCards {
81 | func getDescriptionNumbers() -> (Int, Int, Int) {
82 | let lines = self.getDescriptionLines()
83 | if (lines.count > 0) {
84 | let checkListLines = lines.filter({ $0.checkListItem != nil })
85 | let checkedLines = checkListLines.filter({ $0.checkListItem!.checked })
86 | return (lines.count - checkListLines.count, checkListLines.count, checkedLines.count)
87 | }
88 | return (0, 0, 0)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/iOS Deck/Cards/CardView.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import NCCommunication
6 |
7 | struct CardView: View {
8 | @State var card: NCCommunicationDeckCards
9 | @ObservedObject var viewModel: StacksViewModel
10 |
11 | @State private var editingTitle: Bool = false
12 | @State private var showingDescription: Bool = false
13 | @State private var editingDescription: Bool = false
14 | @State private var textChanged: Bool = false
15 |
16 | @State private var titleChars: Int = 0
17 |
18 | var body: some View {
19 | NavigationView {
20 | GeometryReader {
21 | geo in
22 | Form {
23 | Section {
24 | if (!editingTitle) {
25 | Title
26 | .onTapGesture {
27 | self.editingTitle = true
28 | }
29 | } else {
30 | TitleTextArea
31 | }
32 | DescriptionPreview(card: $card, showingDescription: $showingDescription, editingDescription: $editingDescription, viewModel: viewModel)
33 | }
34 | Section {
35 | NavigationLink(destination: StackPicker) {
36 | HStack {
37 | Label("Labels", systemImage: "tag.fill")
38 | Spacer()
39 | HStack {
40 | if (card.labels?.count ?? 0 > 0) {
41 | ForEach(card.labels ?? []) {
42 | label in
43 | RoundedRectangle(cornerRadius: 4, style: .continuous)
44 | .fill(Color.init(hex: "#\(label.color)"))
45 | .frame(width: 4.5)
46 | }
47 | }
48 | }
49 | }
50 | }
51 | NavigationLink(destination: StackPicker) {
52 | Label("Users", systemImage: "person.2.fill")
53 | }
54 | NavigationLink(destination: StackPicker) {
55 | Label("Activity", systemImage: "bolt.fill")
56 | }
57 | NavigationLink(destination: StackPicker) {
58 | Label("Comments", systemImage: "text.bubble.fill")
59 | }
60 | NavigationLink(destination: StackPicker) {
61 | Label("Attachments", systemImage: "paperclip")
62 | }
63 | }
64 |
65 | Section {
66 | NavigationLink(destination: StackPicker) {
67 | Label("Move", systemImage: "square.stack.3d.down.right.fill")
68 | }
69 | Button() {
70 | print("Delete card.")
71 | } label: {
72 | Label("Delete", systemImage: "trash.fill")
73 | .foregroundColor(.red)
74 | }
75 | }
76 | }
77 | .padding(.horizontal, -5)
78 | .onAppear() {
79 | UITableView.appearance().tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNonzeroMagnitude))
80 | UITableView.appearance().alwaysBounceVertical = false
81 | }
82 | }
83 | .navigationBarTitle("stack/board", displayMode: .inline)
84 | .navigationBarColor(.systemBackground)
85 | // .navigationBarItems(trailing: editButton)
86 | }
87 | .onDisappear() {
88 | NotificationCenter.default.post(name: .cardSelected, object: nil)
89 | }
90 | .isModalInPresentation(textChanged)
91 | }
92 |
93 | private var Title: some View {
94 | VStack(alignment: .leading) {
95 | HStack(alignment: .top) {
96 | Text(card.title)
97 | .lineLimit(4)
98 | .font(.title3)
99 | Spacer()
100 | }
101 | }
102 | .padding(5)
103 | .padding(.top, 7)
104 | }
105 |
106 | private var TitleTextArea: some View {
107 | let limit = 100
108 | let binding = Binding(get: {
109 | card.title
110 | }, set: {
111 | if ($0.count <= limit) {
112 | card.title = $0
113 | titleChars = $0.count
114 | }
115 | })
116 |
117 | return ZStack {
118 | TextEditor(text: binding)
119 | .font(.title3)
120 | .lineLimit(3)
121 | .zIndex(1)
122 | .onAppear() {
123 | titleChars = card.title.count
124 | }
125 | .padding(.top, 4)
126 | HStack {
127 | Spacer()
128 | VStack {
129 | ZStack {
130 | RoundedRectangle(cornerRadius: 5, style: .continuous)
131 | .fill((titleChars == limit) ? Color.red : Color.green)
132 | Text("\(titleChars)/\(limit)")
133 | .font(.caption2)
134 | }
135 | .frame(width: 45, height: 15)
136 | .padding(10)
137 | Spacer()
138 | }
139 | }
140 | .zIndex(2)
141 | }.frame(height: 90)
142 | }
143 |
144 | private var StackPicker: some View {
145 | ScrollView {
146 | Text("Boobs")
147 | }
148 | }
149 |
150 | // private var editButton: some View {
151 | // Button() {
152 | // self.editMode.toggle()
153 | // } label: {
154 | // if (editMode) {
155 | // Text("Save")
156 | // .foregroundColor(Color.blue)
157 | // } else {
158 | // Text("Edit")
159 | // }
160 | // }
161 | // }
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/iOS Deck/Cards/DescriptionView.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import NCCommunication
6 |
7 | struct DescriptionPreview: View {
8 | @Binding var card: NCCommunicationDeckCards
9 | @Binding var showingDescription: Bool
10 | @Binding var editingDescription: Bool
11 | @ObservedObject var viewModel: StacksViewModel
12 |
13 | @State private var hideDescriptionPreview: Bool = false
14 |
15 | private let limit = 250
16 |
17 | var body: some View {
18 | let (stackIndex, cardIndex) = viewModel.getCardIndexesByID(card.id)!
19 |
20 | VStack(alignment: .leading) {
21 | HStack {
22 | if (viewModel.stacks[stackIndex].cards![cardIndex].desc == "") {
23 | Text("Tap to add description...")
24 | .foregroundColor(Color.gray)
25 | .font(.body)
26 | } else {
27 | // this is dumb but wrapping this thing in a bool check makes it rerender correctly when coming back from the full screen cover
28 | (hideDescriptionPreview) ? PreviewDescriptionText : PreviewDescriptionText
29 | }
30 | Spacer()
31 | }
32 | Spacer()
33 | }
34 | .frame(maxHeight: 200)
35 | .padding(.horizontal, 5)
36 | .padding(.bottom, 5)
37 | .padding(.top, 8)
38 | .fullScreenCover(isPresented: $showingDescription) {
39 | // onDismiss
40 | viewModel.syncCard(card)
41 | showingDescription = false
42 | hideDescriptionPreview = false
43 | } content: {
44 | DescriptionScreen
45 | .onAppear() {
46 | hideDescriptionPreview = true
47 | }
48 | }
49 | .onTapGesture {
50 | showingDescription = true
51 | }
52 | }
53 |
54 | private var DescriptionScreen: some View {
55 | return NavigationView {
56 | Group {
57 | if (editingDescription) {
58 | EditDescriptionView
59 | } else {
60 | ViewDescriptionView
61 | }
62 | }
63 | .navigationBarTitle("", displayMode: .inline)
64 | .navigationBarColor(.secondarySystemBackground)
65 | .navigationBarItems(leading: Button() {
66 | self.showingDescription = false
67 | } label: {
68 | Text("Back")
69 | }, trailing: editButton)
70 | }
71 | }
72 |
73 | private var PreviewDescriptionText: some View {
74 | let (stackIndex, cardIndex) = viewModel.getCardIndexesByID(card.id)!
75 |
76 | let list = viewModel.stacks[stackIndex].cards![cardIndex].getDescriptionLines()
77 | var limitedList: [DescriptionLine] = []
78 | for line in list {
79 | if (line.index >= 7) {
80 | limitedList.append(DescriptionLine(index: line.index, lineText: "...", checkListItem: nil))
81 | break
82 | }
83 | limitedList.append(line)
84 | }
85 | return VStack {
86 | ForEach(limitedList, id: \.self) {
87 | item in
88 | DescriptionLineView(card: viewModel.stacks[stackIndex].cards![cardIndex], item: item, viewModel: viewModel, toggleAble: false)
89 | }
90 | }
91 | }
92 |
93 | private var EditDescriptionView: some View {
94 | let (stackIndex, cardIndex) = viewModel.getCardIndexesByID(card.id)!
95 | let binding = Binding(get: {
96 | viewModel.stacks[stackIndex].cards![cardIndex].desc
97 | }, set: {
98 | if ($0.count <= limit) {
99 | viewModel.stacks[stackIndex].cards![cardIndex].desc = $0
100 | }
101 | })
102 |
103 | return TextEditor(text: binding)
104 | .font(.body)
105 | .zIndex(1)
106 | }
107 |
108 | private var ViewDescriptionView: some View {
109 | let (stackIndex, cardIndex) = viewModel.getCardIndexesByID(card.id)!
110 |
111 | return ScrollView {
112 | VStack(alignment: .leading) {
113 | ForEach(viewModel.stacks[stackIndex].cards![cardIndex].getDescriptionLines(), id: \.self) {
114 | item in
115 | DescriptionLineView(card: viewModel.stacks[stackIndex].cards![cardIndex], item: item, viewModel: viewModel, toggleAble: true)
116 | }
117 | }
118 | .padding(15)
119 | }
120 | }
121 |
122 | private var editButton: some View {
123 | Button() {
124 | self.editingDescription.toggle()
125 | } label: {
126 | if (editingDescription) {
127 | Text("Save")
128 | .foregroundColor(Color.blue)
129 | } else {
130 | Text("Edit")
131 | }
132 | }
133 | }
134 | }
135 |
136 | struct DescriptionLineView: View {
137 | @State var card: NCCommunicationDeckCards
138 | @State var item: DescriptionLine
139 | @ObservedObject var viewModel: StacksViewModel
140 | @State var toggleAble: Bool
141 |
142 | var body: some View {
143 | if (item.checkListItem != nil) {
144 | HStack {
145 | Button(action: (toggleAble) ? checkListItemTapped : {}) {
146 | HStack {
147 | Text(item.checkListItem!.leadingSpaces)
148 | Image(systemName: item.checkListItem!.checked ? "checkmark.square" : "square")
149 | Text(item.checkListItem!.text)
150 | }
151 | .font(.title3)
152 | }
153 | Spacer()
154 | }
155 | } else {
156 | HStack {
157 | Text(item.lineText)
158 | Spacer()
159 | }
160 | }
161 | }
162 |
163 | private func checkListItemTapped() {
164 | item.checkListItem!.checked.toggle()
165 | viewModel.updateDescriptionCheckbox(stackID: card.stackId, cardID: card.id, item.checkListItem!.checked, range: item.checkListItem!.checkBoxRange)
166 | }
167 | }
168 |
169 |
170 | extension NCCommunicationDeckCards {
171 | func getDescriptionLines() -> [DescriptionLine] {
172 | var items: [DescriptionLine] = []
173 |
174 | let lines = self.desc.split(whereSeparator: \.isNewline)
175 | lines.enumerated().forEach({
176 | (index, line) in
177 | var descriptionLine = DescriptionLine(index: index, lineText: line, checkListItem: nil)
178 | if let checkListItem = getCheckBoxFromLine(line) {
179 | descriptionLine.checkListItem = checkListItem
180 | }
181 | items.append(descriptionLine)
182 | })
183 |
184 | return items
185 | }
186 |
187 | private func getCheckBoxFromLine(_ line: Substring) -> CheckListItem? {
188 | let findGroups = String(line).findGroups(for:"( +)?- \\[(x|\\ )\\] (.+)")
189 | if (!findGroups.isEmpty) {
190 | let groups = findGroups.first!
191 | let checkBoxRange = line.range(of: "- \\[(x|\\ )\\]", options: .regularExpression)
192 | return CheckListItem(checked: groups[2] == "x", text: String(groups[3]), leadingSpaces: String(groups[1]), checkBoxRange: checkBoxRange!)
193 | }
194 | return nil
195 | }
196 | }
197 |
198 | struct CheckListItem: Hashable {
199 | var checked: Bool
200 | var text: String
201 | var leadingSpaces: String
202 | var checkBoxRange: Range
203 | }
204 |
205 | struct DescriptionLine: Hashable {
206 | var index: Int
207 | var lineText: Substring
208 | var checkListItem: CheckListItem?
209 | }
210 |
211 | extension String {
212 | func findGroups(for regexPattern: String) -> [[Substring]] {
213 | do {
214 | let text = self
215 | let regex = try NSRegularExpression(pattern: regexPattern)
216 | let matches = regex.matches(in: text,
217 | range: NSRange(text.startIndex..., in: text))
218 | return matches.map { match in
219 | return (0.. 0
18 | } catch {
19 | return false
20 | }
21 | }
22 |
23 | static var getLoginFromKeychain: UserAuth? {
24 | guard let currentUser = Settings.currentUser else {
25 | return nil
26 | }
27 |
28 | do {
29 | let password = try KeychainPasswordItem(service: serviceName, account: currentUser.userName).readPassword()
30 | return UserAuth(user: currentUser, password: password)
31 | } catch {
32 | return nil
33 | }
34 | }
35 |
36 | class func login(_ user: User, password: String) throws {
37 | try KeychainPasswordItem(service: serviceName, account: user.userName).savePassword(password)
38 |
39 | Settings.currentUser = user
40 | Nextcloud.shared.downloadAvatar(userID: user.userName)
41 |
42 | NotificationCenter.default.post(name: .loginStatusChanged, object: nil)
43 | }
44 |
45 | class func logout() throws {
46 | guard let currentUser = Settings.currentUser else {
47 | return
48 | }
49 |
50 | try KeychainPasswordItem(service: serviceName, account: currentUser.userName).deleteItem()
51 |
52 | Nextcloud.shared.clearSetup(currentUser)
53 | Nextcloud.shared.clearUserAvatar()
54 | Settings.currentUser = nil
55 |
56 | NotificationCenter.default.post(name: .loginStatusChanged, object: nil)
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/iOS Deck/Controllers/DataManager.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import Foundation
5 | import NCCommunication
6 |
7 | class DataManager {
8 | struct SavedData: Codable {
9 | let boards: [NCCommunicationDeckBoards]
10 | let stacks: [Int:[NCCommunicationDeckStacks]]
11 | }
12 |
13 | private var boards: [NCCommunicationDeckBoards]
14 | private var stacks: [Int:[NCCommunicationDeckStacks]]
15 |
16 | private let filePath: URL
17 |
18 | init() {
19 | do {
20 | filePath = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true
21 | ).appendingPathComponent("Data")
22 | } catch let error {
23 | fatalError(error.localizedDescription)
24 | }
25 | self.boards = []
26 | self.stacks = [:]
27 | }
28 |
29 | public func resetData() -> DataManager {
30 | self.boards = []
31 | self.stacks = [:]
32 | // print("reset data")
33 | return self
34 | }
35 |
36 | public func deleteDataFile() {
37 | do {
38 | try FileManager.default.removeItem(at: filePath)
39 | print("Disk Data Reset")
40 | } catch let error {
41 | print("Error deleting data: \(error.localizedDescription)")
42 | }
43 | }
44 |
45 | private func readData() {
46 | let decoder = JSONDecoder()
47 | do {
48 | if let data = try? Data(contentsOf: filePath) {
49 | decoder.dataDecodingStrategy = .base64
50 | let savedData = try decoder.decode(SavedData.self, from: data)
51 | // print("Data: \(savedData)")
52 | self.boards = savedData.boards
53 | self.stacks = savedData.stacks
54 | }
55 | } catch let error {
56 | print(error.localizedDescription)
57 | deleteDataFile()
58 | let _ = resetData().save() // delete data
59 | }
60 | }
61 |
62 | private func save() {
63 | let encoder = JSONEncoder()
64 | do {
65 | let savedData = SavedData(boards: self.boards, stacks: self.stacks)
66 | let data = try encoder.encode(savedData)
67 | try data.write(to: filePath, options: .atomicWrite)
68 | let _ = resetData()
69 | } catch let error {
70 | print("Error while saving datas: \(error.localizedDescription)")
71 | }
72 | encoder.dataEncodingStrategy = .base64
73 | }
74 |
75 | }
76 |
77 | // MARK: - public methods getting and setting data on the disk
78 | extension DataManager {
79 | public func setBoards(_ boards: [NCCommunicationDeckBoards]) {
80 | readData()
81 | for board in boards {
82 | if let i = self.boards.firstIndex(where: { $0.id == board.id }) {
83 | self.boards[i] = board
84 | } else {
85 | self.boards.append(board)
86 | }
87 | }
88 | save()
89 | }
90 |
91 | public func getBoards() -> [NCCommunicationDeckBoards] {
92 | readData()
93 | return boards
94 | }
95 |
96 | public func setStacks(_ stacks: [NCCommunicationDeckStacks]) {
97 | readData()
98 | let boardID = stacks[0].boardId // all stacks in list should have same boardID
99 | self.stacks[boardID] = stacks
100 | save()
101 | }
102 |
103 | public func setStack(_ stack: NCCommunicationDeckStacks) {
104 | let boardID = stack.boardId
105 | let index = getStacks(boardID).firstIndex(where: { $0.id == stack.id })!
106 | self.stacks[boardID]?[index] = stack
107 | save()
108 | }
109 |
110 | public func getStacks(_ board: Int) -> [NCCommunicationDeckStacks] {
111 | readData()
112 | let boardStacks = self.stacks.first(where: { $0.key == board})?.value
113 | return boardStacks ?? []
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/iOS Deck/Controllers/SyncManager.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import Foundation
5 | import NCCommunication
6 |
7 | class SyncManager {
8 | public static let shared: SyncManager = {
9 | let instance = SyncManager()
10 | return instance
11 | }()
12 |
13 | private var inQueue: [QueuableCard] = []
14 |
15 | private var timerActive: Bool = false
16 | private var timeRemaining: Int = 2
17 |
18 | private var processing: Bool = false
19 |
20 | private struct QueuableCard {
21 | var boardID: Int
22 | var cardID: Int
23 | var card: NCCommunicationDeckCards
24 | }
25 |
26 |
27 | private var processingTimerActive: Bool = false
28 | private var processingTimerRemaining: Int = 1
29 | private func Sync() {
30 | if (processing) {
31 | if (processingTimerActive) {
32 | self.processingTimerRemaining = 1
33 | return
34 | }
35 |
36 | processingTimerActive = true
37 | let _ = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ t in
38 | if (self.processingTimerRemaining == 0) {
39 | t.invalidate()
40 | self.processingTimerRemaining = 1
41 | self.processingTimerActive = false
42 | DispatchQueue.main.async {
43 | self.Sync()
44 | }
45 | }
46 | self.processingTimerRemaining -= 1
47 | }
48 | } else {
49 | if (timerActive) {
50 | self.timeRemaining = 2
51 | print("Reset - Counting down")
52 | return
53 | }
54 |
55 | if (inQueue.count == 0) {
56 | return
57 | }
58 |
59 | timerActive = true
60 | var _ = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ t in
61 | if (self.timeRemaining == 0) {
62 | t.invalidate()
63 | self.processing = true
64 | self.timerActive = false
65 | self.timeRemaining = 2
66 |
67 | Nextcloud.shared.getStacks(boardID: self.inQueue.first!.boardID) {
68 | stacks in
69 | if let currentStack = stacks.first(where: { $0.id == self.inQueue.first!.card.stackId }) {
70 | self.processQueue(currentStack) {
71 | self.processing = false
72 | }
73 | }
74 | }
75 | } else {
76 | print(self.timeRemaining)
77 | self.timeRemaining -= 1
78 | }
79 | }
80 | }
81 | }
82 |
83 | private func processQueue(_ currentStack: NCCommunicationDeckStacks, closure: @escaping () -> Void) {
84 | var queue = self.inQueue
85 | self.inQueue = []
86 |
87 | func nextIteration() {
88 | if (queue.count == 0) {
89 | closure()
90 | return
91 | }
92 |
93 | if (queue.count > 0) {
94 | let queued = queue[0]
95 |
96 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
97 | if let currentCard = currentStack.cards?.first(where: { $0.id == queued.card.id }) {
98 | print(currentCard == queued.card)
99 | if (currentCard != queued.card) {
100 | Nextcloud.shared.updateCard(queued.boardID, queued.card) {
101 | (success) in
102 | if (success) {
103 | queue.removeFirst()
104 | } else {
105 | /// todo: dispatch a notification that boobity bops an alert about request failing or no internet connection or something
106 | }
107 | nextIteration()
108 | }
109 | } else { // dont update the card if it hasn't actually changed
110 | queue.removeFirst()
111 | nextIteration()
112 | }
113 | } else {
114 | Nextcloud.shared.updateCard(queued.boardID, queued.card) {
115 | (success) in
116 | if (success) {
117 | queue.removeFirst()
118 | } else {
119 | /// todo: dispatch a notification that boobity bops an alert about request failing or no internet connection or something
120 | }
121 | nextIteration()
122 | }
123 | }
124 | }
125 | }
126 | }
127 | nextIteration()
128 | }
129 |
130 | public func SyncStackCards(_ stack: NCCommunicationDeckStacks) {
131 | stack.cards?.forEach() {
132 | (card) in
133 | if let queueIndex = self.inQueue.firstIndex(where: { $0.cardID == card.id }) {
134 | self.inQueue[queueIndex] = QueuableCard(boardID: stack.boardId, cardID: card.id, card: card)
135 | } else {
136 | self.inQueue.append(QueuableCard(boardID: stack.boardId, cardID: card.id, card: card))
137 | }
138 | }
139 | self.Sync()
140 | }
141 |
142 | public func SyncCard(_ card: NCCommunicationDeckCards, boardID: Int) {
143 | if let queueIndex = inQueue.firstIndex(where: { $0.cardID == card.id }) {
144 | inQueue[queueIndex] = QueuableCard(boardID: boardID, cardID: card.id, card: card)
145 | } else {
146 | inQueue.append(QueuableCard(boardID: boardID, cardID: card.id, card: card))
147 | }
148 | Sync()
149 | }
150 | }
151 |
152 |
--------------------------------------------------------------------------------
/iOS Deck/Helpers/IntrospectExtensions.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import Introspect
6 |
7 | extension View {
8 | /// A Boolean value indicating whether the view controller enforces a modal behavior.
9 | ///
10 | /// The default value of this property is `false`. When you set it to `true`, UIKit ignores events
11 | /// outside the view controller's bounds and prevents the interactive dismissal of the
12 | /// view controller while it is onscreen.
13 | public func isModalInPresentation(_ value: Bool) -> some View {
14 | introspectViewController {
15 | $0.isModalInPresentation = value
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/iOS Deck/Helpers/KeychainPasswordItem.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | A struct for accessing generic password keychain items.
7 | */
8 |
9 | import Foundation
10 |
11 | struct KeychainPasswordItem {
12 | // MARK: Types
13 |
14 | enum KeychainError: Error {
15 | case noPassword
16 | case unexpectedPasswordData
17 | case unexpectedItemData
18 | case unhandledError(status: OSStatus)
19 | }
20 |
21 | // MARK: Properties
22 |
23 | let service: String
24 |
25 | private(set) var account: String
26 |
27 | let accessGroup: String?
28 |
29 | // MARK: Intialization
30 |
31 | init(service: String, account: String, accessGroup: String? = nil) {
32 | self.service = service
33 | self.account = account
34 | self.accessGroup = accessGroup
35 | }
36 |
37 | // MARK: Keychain access
38 |
39 | func readPassword() throws -> String {
40 | /*
41 | Build a query to find the item that matches the service, account and
42 | access group.
43 | */
44 | var query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
45 | query[kSecMatchLimit as String] = kSecMatchLimitOne
46 | query[kSecReturnAttributes as String] = kCFBooleanTrue
47 | query[kSecReturnData as String] = kCFBooleanTrue
48 |
49 | // Try to fetch the existing keychain item that matches the query.
50 | var queryResult: AnyObject?
51 | let status = withUnsafeMutablePointer(to: &queryResult) {
52 | SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
53 | }
54 |
55 | // Check the return status and throw an error if appropriate.
56 | guard status != errSecItemNotFound else { throw KeychainError.noPassword }
57 | guard status == noErr else { throw KeychainError.unhandledError(status: status) }
58 |
59 | // Parse the password string from the query result.
60 | guard let existingItem = queryResult as? [String : AnyObject],
61 | let passwordData = existingItem[kSecValueData as String] as? Data,
62 | let password = String(data: passwordData, encoding: String.Encoding.utf8)
63 | else {
64 | throw KeychainError.unexpectedPasswordData
65 | }
66 |
67 | return password
68 | }
69 |
70 | func savePassword(_ password: String) throws {
71 | // Encode the password into an Data object.
72 | let encodedPassword = password.data(using: String.Encoding.utf8)!
73 |
74 | do {
75 | // Check for an existing item in the keychain.
76 | try _ = readPassword()
77 |
78 | // Update the existing item with the new password.
79 | var attributesToUpdate = [String : AnyObject]()
80 | attributesToUpdate[kSecValueData as String] = encodedPassword as AnyObject?
81 |
82 | let query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
83 | let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
84 |
85 | // Throw an error if an unexpected status was returned.
86 | guard status == noErr else { throw KeychainError.unhandledError(status: status) }
87 | }
88 | catch KeychainError.noPassword {
89 | /*
90 | No password was found in the keychain. Create a dictionary to save
91 | as a new keychain item.
92 | */
93 | var newItem = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
94 | newItem[kSecValueData as String] = encodedPassword as AnyObject?
95 |
96 | // Add a the new item to the keychain.
97 | let status = SecItemAdd(newItem as CFDictionary, nil)
98 |
99 | // Throw an error if an unexpected status was returned.
100 | guard status == noErr else { throw KeychainError.unhandledError(status: status) }
101 | }
102 | }
103 |
104 | mutating func renameAccount(_ newAccountName: String) throws {
105 | // Try to update an existing item with the new account name.
106 | var attributesToUpdate = [String : AnyObject]()
107 | attributesToUpdate[kSecAttrAccount as String] = newAccountName as AnyObject?
108 |
109 | let query = KeychainPasswordItem.keychainQuery(withService: service, account: self.account, accessGroup: accessGroup)
110 | let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
111 |
112 | // Throw an error if an unexpected status was returned.
113 | guard status == noErr || status == errSecItemNotFound else { throw KeychainError.unhandledError(status: status) }
114 |
115 | self.account = newAccountName
116 | }
117 |
118 | func deleteItem() throws {
119 | // Delete the existing item from the keychain.
120 | let query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
121 | let status = SecItemDelete(query as CFDictionary)
122 |
123 | // Throw an error if an unexpected status was returned.
124 | guard status == noErr || status == errSecItemNotFound else { throw KeychainError.unhandledError(status: status) }
125 | }
126 |
127 | static func passwordItems(forService service: String, accessGroup: String? = nil) throws -> [KeychainPasswordItem] {
128 | // Build a query for all items that match the service and access group.
129 | var query = KeychainPasswordItem.keychainQuery(withService: service, accessGroup: accessGroup)
130 | query[kSecMatchLimit as String] = kSecMatchLimitAll
131 | query[kSecReturnAttributes as String] = kCFBooleanTrue
132 | query[kSecReturnData as String] = kCFBooleanFalse
133 |
134 | // Fetch matching items from the keychain.
135 | var queryResult: AnyObject?
136 | let status = withUnsafeMutablePointer(to: &queryResult) {
137 | SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
138 | }
139 |
140 | // If no items were found, return an empty array.
141 | guard status != errSecItemNotFound else { return [] }
142 |
143 | // Throw an error if an unexpected status was returned.
144 | guard status == noErr else { throw KeychainError.unhandledError(status: status) }
145 |
146 | // Cast the query result to an array of dictionaries.
147 | guard let resultData = queryResult as? [[String : AnyObject]] else { throw KeychainError.unexpectedItemData }
148 |
149 | // Create a `KeychainPasswordItem` for each dictionary in the query result.
150 | var passwordItems = [KeychainPasswordItem]()
151 | for result in resultData {
152 | guard let account = result[kSecAttrAccount as String] as? String else { throw KeychainError.unexpectedItemData }
153 |
154 | let passwordItem = KeychainPasswordItem(service: service, account: account, accessGroup: accessGroup)
155 | passwordItems.append(passwordItem)
156 | }
157 |
158 | return passwordItems
159 | }
160 |
161 | // MARK: Convenience
162 |
163 | private static func keychainQuery(withService service: String, account: String? = nil, accessGroup: String? = nil) -> [String : AnyObject] {
164 | var query = [String : AnyObject]()
165 | query[kSecClass as String] = kSecClassGenericPassword
166 | query[kSecAttrService as String] = service as AnyObject?
167 |
168 | if let account = account {
169 | query[kSecAttrAccount as String] = account as AnyObject?
170 | }
171 |
172 | if let accessGroup = accessGroup {
173 | query[kSecAttrAccessGroup as String] = accessGroup as AnyObject?
174 | }
175 |
176 | return query
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/iOS Deck/Helpers/Settings.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import Foundation
5 |
6 | final class Settings {
7 |
8 | private enum Keys: String {
9 | case user = "current_user"
10 | }
11 |
12 | static var currentUser: User? {
13 | get {
14 | guard let data = UserDefaults.standard.data(forKey: Keys.user.rawValue) else {
15 | return nil
16 | }
17 | return try? JSONDecoder().decode(User.self, from: data)
18 | }
19 | set {
20 | if let data = try? JSONEncoder().encode(newValue) {
21 | UserDefaults.standard.set(data, forKey: Keys.user.rawValue)
22 | } else {
23 | UserDefaults.standard.removeObject(forKey: Keys.user.rawValue)
24 | }
25 | UserDefaults.standard.synchronize()
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/iOS Deck/Helpers/Utils.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import Foundation
5 |
6 | //MARK: Get Document Directory Paths
7 |
8 | //func getDocumentsDirectory(_ fileName: String) -> URL {
9 | // let documentPath = getDocumentsDirectory().appendingPathComponent(fileName)
10 | // return documentPath
11 | //}
12 |
13 | func getDocumentsDirectory(_ append: String? = nil) -> String {
14 | // var path = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
15 | var path: URL
16 | do {
17 | try path = FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
18 |
19 | guard let append = append else {
20 | return path.path
21 | }
22 | path = path.appendingPathComponent(append)
23 | return path.path
24 | } catch {
25 | return ""
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/iOS Deck/Login/LoginView.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 |
6 | struct LoginView: View {
7 | @State private var ncURL: String = ""
8 | @State private var loginURL: String = ""
9 |
10 | @State private var token: String = ""
11 | @State private var endpoint: String = ""
12 |
13 | @Environment(\.presentationMode) var presentationMode
14 |
15 | @State private var showWebView: Bool = false
16 |
17 |
18 | private let viewController = LoginViewController.shared
19 |
20 | var body: some View {
21 | VStack {
22 | Spacer()
23 | HStack {
24 | VStack {
25 | Image(uiImage: UIImage(named: "NextcloudIconWhite")!)
26 | .padding(.top, 0)
27 | Text("Connect to Nextcloud to access your Deck")
28 | .foregroundColor(Color.white)
29 | TextField("Nextcloud URL", text: $ncURL)
30 | .autocapitalization(.none)
31 | .disableAutocorrection(true)
32 | .keyboardType(.URL)
33 | .textFieldStyle(RoundedBorderTextFieldStyle())
34 | Button(action: {
35 | self.login()
36 | }) {
37 | Text("Connect to NextCloud")
38 | }
39 | .buttonStyle(RoundButtonStyle(bgColor: Color.white, fgColor: Color.black))
40 | .padding(.bottom, 10)
41 | .padding(.top, 10)
42 | }
43 | }
44 | .fullScreenCover(isPresented: $showWebView, onDismiss: dismiss) {
45 | LoginWebView(title: ncURL, loginURL: $loginURL, isPresented: $showWebView)
46 | .onAppear() {
47 | viewController.startPageChangeListener(endpoint: endpoint, token: token)
48 | }
49 | }
50 | .navigationBarTitle("")
51 | .navigationBarHidden(true)
52 | Spacer()
53 | }
54 | .background(Color.blue)
55 | .edgesIgnoringSafeArea(.all)
56 | }
57 |
58 | private func login() {
59 | self.viewController.getLoginURLRequest(url: self.ncURL) {
60 | (loginURL, token, endpoint) in
61 | self.loginURL = loginURL
62 | self.token = token
63 | self.endpoint = endpoint
64 | self.showWebView = true
65 | }
66 | }
67 |
68 | private func dismiss() -> Void {
69 | presentationMode.wrappedValue.dismiss()
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/iOS Deck/Login/LoginViewController.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import Foundation
5 | import SwiftUI
6 | import NCCommunication
7 |
8 | class LoginViewController {
9 | public static let shared: LoginViewController = {
10 | let instance = LoginViewController()
11 | return instance
12 | }()
13 |
14 | private var endpoint: String = ""
15 | private var token: String = ""
16 |
17 | func startPageChangeListener(endpoint: String, token: String) {
18 | self.endpoint = endpoint
19 | self.token = token
20 |
21 | NotificationCenter.default.addObserver(self, selector: #selector(pollAppPassword), name: .pageLoaded, object: nil)
22 | }
23 |
24 | @objc private func pollAppPassword() {
25 | // call out to the endpoint and check if the token has been authenticated
26 | Nextcloud.shared.pollAppPassword(endpoint: self.endpoint, token: self.token) {
27 | (server, loginName, appPassword) in
28 |
29 | guard let server = server else { return }
30 | guard let loginName = loginName else { return }
31 | guard let appPassword = appPassword else { return }
32 |
33 | let user = User(userName: loginName, serverURL: server)
34 |
35 | // save current user to userDefaults, save username and password to keychain, and give username, password, and serverURL to Nextcloud API instance
36 | do {
37 | Settings.currentUser = user
38 | try AuthController.login(user, password: appPassword)
39 | Nextcloud.shared.setupNextcloud(server: server, login: loginName, password: appPassword)
40 | } catch {
41 | fatalError("fatal error saving to keychain")
42 | }
43 | self.killPageChangeListener()
44 | }
45 | }
46 |
47 | func killPageChangeListener() {
48 | print("killing")
49 | NotificationCenter.default.removeObserver(self)
50 | }
51 |
52 | func getLoginURLRequest(url: String, closure: @escaping (String, String, String) -> Void) {
53 | Nextcloud.shared.getLoginRequestURL(url: url) {
54 | (loginURL, token, endpoint) in
55 | guard let loginURL = loginURL else { return }
56 | guard let token = token else { return }
57 | guard let endpoint = endpoint else { return }
58 |
59 | closure(loginURL, token, endpoint)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/iOS Deck/Login/LoginWebView.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import WebKit
6 | import NotificationCenter
7 |
8 | struct LoginWebView: View {
9 | @State var title: String = ""
10 | @Binding var loginURL: String
11 | @Binding var isPresented: Bool
12 |
13 | var body: some View {
14 | NavigationView {
15 | ZStack {
16 | Webview(url: $loginURL)
17 | }
18 | .navigationBarTitle(title, displayMode: .inline)
19 | .navigationBarItems(leading:
20 | Button("Back") {
21 | isPresented = false
22 | }
23 | )
24 | }
25 | .onDisappear() {
26 | isPresented = false
27 | }
28 | }
29 |
30 | private struct Webview: UIViewControllerRepresentable {
31 | @Binding var url: String
32 |
33 | func makeUIViewController(context: Context) -> WebviewController {
34 | let webviewController = WebviewController()
35 |
36 | let request = URLRequest(url: URL(string: url)!, cachePolicy: .reloadIgnoringCacheData)
37 |
38 | webviewController.webview.load(request)
39 |
40 | return webviewController
41 | }
42 |
43 | func updateUIViewController(_ webviewController: WebviewController, context: Context) {
44 | let request = URLRequest(url: URL(string: url)!, cachePolicy: .reloadIgnoringCacheData)
45 | webviewController.webview.load(request)
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/iOS Deck/Login/WebViewController.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import UIKit
5 | import WebKit
6 | import NotificationCenter
7 |
8 | class WebviewController: UIViewController {
9 | lazy var webview: WKWebView = WKWebView()
10 | lazy var progressbar: UIProgressView = UIProgressView()
11 |
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 |
15 | self.webview.frame = self.view.frame
16 | self.view.addSubview(self.webview)
17 |
18 | self.view.addSubview(self.progressbar)
19 | self.progressbar.translatesAutoresizingMaskIntoConstraints = false
20 | self.view.addConstraints([
21 | self.progressbar.topAnchor.constraint(equalTo: self.view.topAnchor),
22 | self.progressbar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
23 | self.progressbar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
24 | ])
25 |
26 | self.progressbar.progress = 0.1
27 | webview.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
28 | webview.addObserver(self, forKeyPath: "title", options: .new, context: nil)
29 | }
30 |
31 | // MARK: - Handle Observers
32 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
33 | switch keyPath {
34 | case "estimatedProgress": // handles blue loading indicator
35 | if self.webview.estimatedProgress >= 1.0 {
36 | UIView.animate(withDuration: 0.3, animations: { () in
37 | self.progressbar.alpha = 0.0
38 | }, completion: { finished in
39 | self.progressbar.setProgress(0.0, animated: false)
40 | })
41 | } else {
42 | self.progressbar.isHidden = false
43 | self.progressbar.alpha = 1.0
44 | progressbar.setProgress(Float(self.webview.estimatedProgress), animated: true)
45 | }
46 | case "title": // triggers every time a page (after the first one) loaded
47 | NotificationCenter.default.post(name: .pageLoaded, object: nil)
48 | default:
49 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/iOS Deck/Models/CardModelExtension.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import NCCommunication
5 | import MobileCoreServices
6 |
7 | extension NCCommunicationDeckCards: NSItemProviderReading, NSItemProviderWriting {
8 | public static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> NCCommunicationDeckCards {
9 | let decoder = JSONDecoder()
10 | let decodedCard = try decoder.decode(NCCommunicationDeckCards.self, from: data)
11 | return decodedCard
12 | }
13 |
14 | public static var writableTypeIdentifiersForItemProvider: [String] {
15 | return [kUTTypeData as String]
16 | }
17 |
18 | public func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
19 | let progress = Progress(totalUnitCount: 100)
20 | do {
21 | let data = try JSONEncoder().encode(self)
22 | progress.completedUnitCount = 100
23 | completionHandler(data, nil)
24 | } catch {
25 | completionHandler(nil, error)
26 | }
27 |
28 | return progress
29 | }
30 |
31 | public static var readableTypeIdentifiersForItemProvider: [String] {
32 | return [kUTTypeData as String]
33 | }
34 | }
35 |
36 | extension NCCommunicationDeckCards {
37 | static func ==(lhs: NCCommunicationDeckCards, rhs: NCCommunicationDeckCards) -> Bool {
38 | let title = lhs.title == rhs.title
39 | let desc = lhs.desc == rhs.desc
40 | let order = lhs.order == rhs.order
41 | return title && desc && order
42 | }
43 |
44 | static func !=(lhs: NCCommunicationDeckCards, rhs: NCCommunicationDeckCards) -> Bool {
45 | return !(lhs == rhs)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/iOS Deck/Models/Notifications.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import Foundation
5 |
6 | extension Notification.Name {
7 | static let loginStatusChanged = Notification.Name("com.cback.auth.changed")
8 | static let pageLoaded = Notification.Name("com.cback.webview.pageLoaded")
9 | static let boardLoaded = Notification.Name("com.cback.board.loaded")
10 | static let boardsLoaded = Notification.Name("com.cback.boards.loaded")
11 | static let cardSelected = Notification.Name("com.cback.card.selected")
12 | static let cardUpdated = Notification.Name("com.cback.card.updated")
13 | }
14 |
--------------------------------------------------------------------------------
/iOS Deck/Models/User.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import Foundation
5 |
6 | struct User: Codable {
7 | let userName: String
8 | let serverURL: String
9 | }
10 |
11 | struct UserAuth: Codable {
12 | let user: User
13 | let password: String
14 | }
15 |
--------------------------------------------------------------------------------
/iOS Deck/Nextcloud/Nextcloud+auth.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import NCCommunication
5 |
6 | extension Nextcloud {
7 | func getLoginRequestURL(url: String, closure: @escaping (String?, String?, String?) -> Void) {
8 | NCCommunication.shared.getLoginFlowV2(serverUrl: url) {
9 | (token: String?, endpoint: String?, login: String?, errorCode: Int, errorDescription: String) in
10 |
11 | closure(login, token, endpoint)
12 | }
13 | }
14 |
15 | func pollAppPassword(endpoint: String, token: String, closure: @escaping (String?, String?, String?) -> Void) {
16 | NCCommunication.shared.getLoginFlowV2Poll(token: token, endpoint: endpoint) {
17 | (server: String?, loginName: String?, appPassword: String?, errorCode: Int, errorDescription: String) in
18 |
19 | closure(server, loginName, appPassword)
20 | }
21 | }
22 |
23 | func setupNextcloud(server: String, login: String, password: String) {
24 | NCCommunicationCommon.shared.setup(account: "\(server)/\(login)", user: login, userId: login, password: password, url: server)
25 | }
26 |
27 | func setupNCCommFromKeychain() {
28 | if (AuthController.isKeychained) {
29 | let auth = AuthController.getLoginFromKeychain!
30 | let user = auth.user
31 | self.setupNextcloud(server: user.serverURL, login: user.userName, password: auth.password)
32 | }
33 | }
34 |
35 | func clearSetup(_ currentUser: User) {
36 | NCCommunicationCommon.shared.remove(account: "\(currentUser.serverURL)/\(currentUser.userName)")
37 | setupNextcloud(server: currentUser.serverURL, login: "", password: "")
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/iOS Deck/Nextcloud/Nextcloud+deck.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import NCCommunication
5 |
6 | extension Nextcloud {
7 | func getBoards(closure: @escaping ([NCCommunicationDeckBoards]) -> Void) {
8 | NCCommunication.shared.getBoards(completionHandler: {
9 | (account: String, boards: [NCCommunicationDeckBoards]?, errorCode: Int, errorDescription: String) in
10 | if (errorCode == 0) {
11 | closure(boards!)
12 | }
13 | })
14 | }
15 |
16 | func getStacks(boardID: Int, closure: @escaping ([NCCommunicationDeckStacks]) -> Void) {
17 | NCCommunication.shared.getStacks(boardID: boardID) {
18 | (account, stacks, errorCode, errorDescription) in
19 | if (errorCode == 0) {
20 | closure(stacks!)
21 | } else {
22 | print(errorCode, errorDescription)
23 | }
24 | }
25 | }
26 |
27 | func updateCard(_ board: Int, _ card: NCCommunicationDeckCards, closure: @escaping (Bool) -> Void ) {
28 | NCCommunication.shared.updateCard(boardID: board, card: card) { (account, cards, errorCode, errorDescription) in
29 | closure(errorCode == 0)
30 | }
31 | }
32 |
33 | // func getCards(boardID: Int, stackID: Int, closure: @escaping ([NCCommunicationDeckCards]?) -> Void) {
34 | // NCCommunication.shared.getCards(boardID: boardID, stackID: stackID) {
35 | // (account, cards, errorCode, errorDescription) in
36 | // if (errorCode == 0) {
37 | // closure(cards!)
38 | // } else {
39 | // print(errorCode, errorDescription)
40 | // closure(nil)
41 | // }
42 | // }
43 | // }
44 |
45 | // func moveCard(boardID: Int, card: NCCommunicationDeckCards, newStackID: Int?, closure: @escaping (Bool) -> Void) {
46 | // NCCommunication.shared.reorderCards(boardID: boardID, card: card, order: card.order, newStackID: newStackID) {
47 | // (account, cards, errorCode, errorDescription) in
48 | // if (errorCode == 0) {
49 | // NCCommunication.shared.updateCard(boardID: boardID, card: card) {
50 | // (account, card, errorCode, errorDescription) in
51 | // if (errorCode == 0) {
52 | // closure(true)
53 | // } else {
54 | // closure(false)
55 | // }
56 | // }
57 | // } else {
58 | // closure(false)
59 | // }
60 | // }
61 | // }
62 | }
63 |
--------------------------------------------------------------------------------
/iOS Deck/Nextcloud/Nextcloud+user.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import NCCommunication
5 |
6 | extension Nextcloud {
7 | func clearUserAvatar() {
8 | do {
9 | try FileManager.default.removeItem(atPath: avatarPath)
10 | } catch {
11 | print("Error removing avatar", error)
12 | }
13 | }
14 |
15 | func getUserProfile(closure: @escaping (NCCommunicationUserProfile?) -> Void) {
16 | NCCommunication.shared.getUserProfile(completionHandler: {
17 | (account: String, userProfile: NCCommunicationUserProfile?, errorCode: Int, errorDescription: String) in
18 | closure(userProfile)
19 | })
20 | }
21 |
22 |
23 | func downloadAvatar(userID: String) {
24 | NCCommunication.shared.downloadAvatar(userID: userID, fileNameLocalPath: avatarPath, size: 200) {
25 | (account: String, data: Data?, errorCode: Int, errorDescription: String) in
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/iOS Deck/Nextcloud/Nextcloud.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import NCCommunication
5 |
6 | class Nextcloud {
7 | public static let shared: Nextcloud = {
8 | let instance = Nextcloud()
9 | return instance
10 | }()
11 |
12 | let avatarPath = getDocumentsDirectory("avatar.png")
13 | }
14 |
--------------------------------------------------------------------------------
/iOS Deck/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_20pt@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "icon_20pt@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "icon_29pt@2x.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "icon_29pt@3x.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "icon_40pt@2x.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "icon_40pt@3x.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "icon_60pt@2x.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "icon_60pt@3x.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "icon_20pt.png",
53 | "idiom" : "ipad",
54 | "scale" : "1x",
55 | "size" : "20x20"
56 | },
57 | {
58 | "filename" : "icon_20pt@2x-1.png",
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "icon_29pt.png",
65 | "idiom" : "ipad",
66 | "scale" : "1x",
67 | "size" : "29x29"
68 | },
69 | {
70 | "filename" : "icon_29pt@2x-1.png",
71 | "idiom" : "ipad",
72 | "scale" : "2x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "icon_40pt.png",
77 | "idiom" : "ipad",
78 | "scale" : "1x",
79 | "size" : "40x40"
80 | },
81 | {
82 | "filename" : "icon_40pt@2x-1.png",
83 | "idiom" : "ipad",
84 | "scale" : "2x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "icon_76pt.png",
89 | "idiom" : "ipad",
90 | "scale" : "1x",
91 | "size" : "76x76"
92 | },
93 | {
94 | "filename" : "icon_76pt@2x.png",
95 | "idiom" : "ipad",
96 | "scale" : "2x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "icon_83.5@2x.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "83.5x83.5"
104 | },
105 | {
106 | "filename" : "Icon.png",
107 | "idiom" : "ios-marketing",
108 | "scale" : "1x",
109 | "size" : "1024x1024"
110 | }
111 | ],
112 | "info" : {
113 | "author" : "xcode",
114 | "version" : 1
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/NextcloudIconWhite.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Nextcloud-logo-white.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Nextcloud-logo-white-1.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Nextcloud-logo-white-2.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/NextcloudIconWhite.imageset/Nextcloud-logo-white-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/NextcloudIconWhite.imageset/Nextcloud-logo-white-1.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/NextcloudIconWhite.imageset/Nextcloud-logo-white-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/NextcloudIconWhite.imageset/Nextcloud-logo-white-2.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Assets.xcassets/NextcloudIconWhite.imageset/Nextcloud-logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cbackas/iOS-Deck/5061513dc9567c8d6611fd39f96a40f4c9056ab0/iOS Deck/Resources/Assets.xcassets/NextcloudIconWhite.imageset/Nextcloud-logo-white.png
--------------------------------------------------------------------------------
/iOS Deck/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 |
28 | UIApplicationSupportsIndirectInputEvents
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/iOS Deck/Resources/NextcloudDeck.xcdatamodeld/NextcloudDeck.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/iOS Deck/Root/RootView.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 |
6 | struct RootView: View {
7 | @Binding var notLoggedIn: Bool
8 |
9 | @State private var selection = 0
10 |
11 | var body: some View {
12 | if (notLoggedIn) {
13 | LoginView()
14 | } else {
15 | Tabs
16 | }
17 | }
18 |
19 | private var Tabs: some View {
20 | TabView(selection: $selection) {
21 | BoardsView()
22 | .tabItem {
23 | VStack {
24 | Image(systemName: "rectangle.stack.fill")
25 | Text("Boards")
26 | }
27 | }
28 | .tag(0)
29 | UpcommingView()
30 | .tabItem {
31 | VStack {
32 | Image(systemName: "list.bullet")
33 | Text("Due")
34 | }
35 | }
36 | .tag(1)
37 | SettingsView()
38 | .tabItem {
39 | VStack {
40 | Image(systemName: "gear")
41 | Text("Settings")
42 | }
43 | }
44 | .tag(2)
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/iOS Deck/Root/iOS_DeckApp.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import CoreData
6 |
7 | @main
8 | struct iOS_DeckApp: App {
9 | @Environment(\.scenePhase) private var scenePhase
10 |
11 | @State var notLoggedIn: Bool = false
12 |
13 | var body: some Scene {
14 | WindowGroup {
15 | RootView(notLoggedIn: $notLoggedIn)
16 | .onAppear() {
17 | setAuth()
18 | }
19 | .onReceive(NotificationCenter.default.publisher(for: .loginStatusChanged)) { _ in
20 | setAuth()
21 | }
22 | }
23 | .onChange(of: scenePhase) { phase in
24 | switch phase {
25 | case .active:
26 | print("active")
27 | case .inactive:
28 | print("inactive")
29 | case .background:
30 | print("background")
31 | // saveContext()
32 | default:
33 | print("default")
34 | }
35 | }
36 | }
37 |
38 | private func setAuth() {
39 | if (AuthController.isKeychained) {
40 | Nextcloud.shared.setupNCCommFromKeychain()
41 | self.notLoggedIn = false
42 | } else {
43 | self.notLoggedIn = true
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/iOS Deck/Settings/SettingsView.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import WebKit
6 |
7 | struct SettingsView: View {
8 | @State private var ncURL: String = "https://cloud.purplecloud.xyz"
9 | @State private var loginURL: String = ""
10 |
11 | @State private var displayName: String = "Display Name"
12 | @State private var avatarURL: String = ""
13 |
14 | private let avatarFilePath = getDocumentsDirectory("avatar.png")
15 |
16 | private let viewController = SettingsViewController.shared
17 |
18 | var body: some View {
19 | NavigationView {
20 | VStack {
21 | profileSnippit
22 |
23 | Spacer()
24 |
25 | Button() {
26 | self.getProfileInfo()
27 | } label: {
28 | Text("Get Profile Stuff")
29 | }
30 | .padding(5)
31 | .buttonStyle(RoundButtonStyle(bgColor: Color.white, fgColor: Color.black))
32 | Button() {
33 | DataManager().resetData().deleteDataFile()
34 | } label: {
35 | Text("Reset Cache")
36 | }
37 | .padding(5)
38 | .buttonStyle(RoundButtonStyle(bgColor: Color.white, fgColor: Color.black))
39 | }
40 | .onAppear() {
41 | getProfileInfo()
42 | }
43 | .navigationBarTitle("Settings", displayMode: .automatic)
44 | .navigationBarItems(trailing: logoutButton)
45 | }
46 | }
47 |
48 | private var logoutButton: some View {
49 | Button() {
50 | viewController.logout()
51 | } label: {
52 | Text("Log Out")
53 | }
54 | }
55 |
56 | private var profileSnippit: some View {
57 | VStack {
58 | Image(uiImage: UIImage(contentsOfFile: self.avatarFilePath) ?? UIImage())
59 | .resizable()
60 | .clipShape(Circle())
61 | .overlay(Circle().stroke(Color.blue, lineWidth: 2.0))
62 | .frame(width: 90.0, height: 90.0)
63 | Text(displayName)
64 | }
65 | }
66 |
67 | private func getProfileInfo() {
68 | self.viewController.getProfileSegmentInfo() {
69 | (displayName) in
70 | self.displayName = displayName
71 | }
72 | }
73 | }
74 |
75 | struct SettingsView_Previews: PreviewProvider {
76 | static var previews: some View {
77 | SettingsView()
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/iOS Deck/Settings/SettingsViewController.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import Foundation
5 | import SwiftUI
6 | import NCCommunication
7 |
8 | class SettingsViewController {
9 | public static let shared: SettingsViewController = {
10 | let instance = SettingsViewController()
11 | return instance
12 | }()
13 |
14 | let nextcloud = Nextcloud.shared
15 |
16 | func getProfileSegmentInfo(closure: @escaping (_ displayName: String) -> Void) {
17 | nextcloud.getUserProfile() {
18 | (userProfile: NCCommunicationUserProfile?) in
19 | guard let userProfile = userProfile else { return }
20 |
21 | closure(userProfile.displayName)
22 | }
23 | }
24 |
25 | func logout() {
26 | do {
27 | try AuthController.logout()
28 | print("Logged out.")
29 | } catch {
30 | print("Logout failed.")
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/iOS Deck/Stacks/StackListView.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import NCCommunication
6 | import MobileCoreServices
7 |
8 | struct StackListView: UIViewRepresentable {
9 | @State var stack: NCCommunicationDeckStacks
10 | @ObservedObject var viewModel: StacksViewModel
11 | @State var permissionToEdit: Bool
12 |
13 | func makeUIView(context: Context) -> UITableView {
14 | let tableView = UITableView(frame: .zero, style: .plain)
15 |
16 | if (viewModel.getStackByID(stack.id).cards?.count ?? 0 == 0) {
17 | let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
18 | label.center = CGPoint(x: 160, y: 285)
19 | label.textAlignment = .center
20 | label.text = "This stack is empty."
21 | tableView.backgroundView = label
22 | } else {
23 | tableView.backgroundView = nil
24 | }
25 |
26 | tableView.separatorStyle = .none
27 | tableView.backgroundColor = .clear
28 |
29 | tableView.translatesAutoresizingMaskIntoConstraints = false
30 | tableView.dragDelegate = context.coordinator
31 | tableView.dropDelegate = context.coordinator
32 |
33 | tableView.dragInteractionEnabled = permissionToEdit
34 |
35 | tableView.dataSource = context.coordinator
36 | tableView.delegate = context.coordinator
37 | tableView.register(HostingCell.self, forCellReuseIdentifier: "Cell")
38 | tableView.layoutIfNeeded()
39 |
40 | return tableView
41 | }
42 |
43 | func updateUIView(_ uiView: UITableView, context: Context) {
44 | uiView.beginUpdates()
45 | uiView.layoutIfNeeded()
46 |
47 | // check for changes to the card relavant to the listcarditemview to possibly refresh that row
48 | let oldCards = context.coordinator.stack.cards
49 | let newCards = viewModel.getStackByID(stack.id).cards
50 | var reloadRows: [IndexPath] = []
51 | newCards?.enumerated().forEach() {
52 | index, newCard in
53 | if let oldCard = oldCards?.firstIndex(where: { $0.id == newCard.id }) {
54 | ///todo check for label changes or title changes and reload row appropriately
55 | if (newCard.desc != oldCards![oldCard].desc) {
56 | reloadRows.append(IndexPath(row: index, section: 0))
57 | }
58 | }
59 | }
60 | uiView.reloadRows(at: reloadRows, with: .automatic)
61 | // keep whole stack up to date with new data
62 | context.coordinator.stack.cards = viewModel.getStackByID(stack.id).cards?.map({ $0.copy() }) as? [NCCommunicationDeckCards]
63 |
64 | uiView.dragInteractionEnabled = permissionToEdit
65 |
66 | if (viewModel.getStackByID(stack.id).cards?.count ?? 0 == 0) {
67 | let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
68 | label.center = CGPoint(x: 160, y: 285)
69 | label.textAlignment = .center
70 | label.text = "This stack is empty."
71 | uiView.backgroundView = label
72 | } else {
73 | uiView.backgroundView = nil
74 | }
75 |
76 | uiView.endUpdates()
77 | }
78 |
79 | func makeCoordinator() -> StackCoordinator {
80 | var stackSorted = stack
81 | stackSorted.cards = stackSorted.cards?.sorted(by: { $0.order < $1.order })
82 | return StackCoordinator(stack: stackSorted, viewModel: viewModel)
83 | }
84 | }
85 |
86 | class HostingCell: UITableViewCell {
87 | var host: UIHostingController?
88 | }
89 |
90 | class StackCoordinator: NSObject {
91 | var stack: NCCommunicationDeckStacks
92 | var viewModel: StacksViewModel
93 |
94 | init(stack: NCCommunicationDeckStacks, viewModel: StacksViewModel) {
95 | self.stack = stack
96 | self.viewModel = viewModel
97 | }
98 | }
99 |
100 | extension StackCoordinator: UITableViewDataSource, UITableViewDelegate {
101 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
102 | self.viewModel.getStackByID(self.stack.id).cards?.count ?? 0
103 | }
104 |
105 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
106 | let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! HostingCell
107 |
108 | let view = CardListItemView(card: viewModel.getStackByID(stack.id).cards![indexPath.row])
109 |
110 | // create & setup hosting controller only once
111 | if tableViewCell.host == nil {
112 | let controller = UIHostingController(rootView: AnyView(view))
113 | tableViewCell.host = controller
114 |
115 | let tableCellViewContent = controller.view!
116 | tableCellViewContent.translatesAutoresizingMaskIntoConstraints = false
117 |
118 | // make all backgrounds around the CardView clear
119 | tableCellViewContent.backgroundColor = .clear
120 | tableViewCell.backgroundColor = .clear
121 |
122 | // get rid of any possible background selection messing up the view
123 | tableView.allowsSelection = false
124 | tableView.allowsMultipleSelection = false
125 | tableViewCell.selectionStyle = .none
126 |
127 | tableViewCell.contentView.addSubview(tableCellViewContent)
128 | tableCellViewContent.topAnchor.constraint(equalTo: tableViewCell.contentView.topAnchor).isActive = true
129 | tableCellViewContent.leftAnchor.constraint(equalTo: tableViewCell.contentView.leftAnchor).isActive = true
130 | tableCellViewContent.bottomAnchor.constraint(equalTo: tableViewCell.contentView.bottomAnchor).isActive = true
131 | tableCellViewContent.rightAnchor.constraint(equalTo: tableViewCell.contentView.rightAnchor).isActive = true
132 | } else {
133 | // reused cell, so just set other SwiftUI root view
134 | tableViewCell.host?.rootView = AnyView(view)
135 | }
136 | tableViewCell.setNeedsLayout()
137 | return tableViewCell
138 | }
139 |
140 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
141 | tableView.deselectRow(at: indexPath, animated: true)
142 | }
143 | }
144 |
145 | extension StackCoordinator: UITableViewDragDelegate {
146 | // Provides the initial set of items (if any) to drag.
147 | func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
148 | guard let card = viewModel.getStackByID(stack.id).cards?[indexPath.row] else {
149 | return []
150 | }
151 |
152 | let itemProvider = NSItemProvider(object: card)
153 | let dragItem = UIDragItem(itemProvider: itemProvider)
154 | dragItem.localObject = card
155 | // pass context along for moving cards between
156 | session.localContext = (stack, indexPath, tableView)
157 | return [dragItem]
158 | }
159 |
160 | // make sure the background color of the cell stays clear when its being dragged
161 | func tableView(_ tableView: UITableView, dragPreviewParametersForRowAt indexPath: IndexPath) -> UIDragPreviewParameters? {
162 | let param = UIDragPreviewParameters()
163 | param.backgroundColor = .clear
164 | return param
165 | }
166 | }
167 |
168 | extension StackCoordinator: UITableViewDropDelegate, UIDropInteractionDelegate {
169 | func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
170 | if coordinator.session.hasItemsConforming(toTypeIdentifiers: [kUTTypeData as String]) {
171 | coordinator.session.loadObjects(ofClass: NCCommunicationDeckCards.self) {
172 | (cards) in
173 | guard let card = cards.first as? NCCommunicationDeckCards else {
174 | return
175 | }
176 |
177 | switch (coordinator.items.first?.sourceIndexPath, coordinator.destinationIndexPath) {
178 | case (.some(let sourceIndexPath), .some(let destinationIndexPath)):
179 | // Same Table View
180 | let updatedIndexPaths: [IndexPath]
181 | if sourceIndexPath.row < destinationIndexPath.row {
182 | updatedIndexPaths = (sourceIndexPath.row...destinationIndexPath.row).map { IndexPath(row: $0, section: 0) }
183 | } else if sourceIndexPath.row > destinationIndexPath.row {
184 | updatedIndexPaths = (destinationIndexPath.row...sourceIndexPath.row).map { IndexPath(row: $0, section: 0) }
185 | } else {
186 | updatedIndexPaths = []
187 | }
188 | tableView.beginUpdates()
189 | self.viewModel.moveCardInStack(card, sourceIndex: sourceIndexPath.row, destIndex: destinationIndexPath.row)
190 | tableView.reloadRows(at: updatedIndexPaths, with: .automatic)
191 | tableView.endUpdates()
192 | break
193 |
194 | case (nil, .some(let destinationIndexPath)):
195 | // Move data from a table to another table
196 |
197 | self.removeSourceTableData(localContext: coordinator.session.localDragSession?.localContext)
198 | tableView.beginUpdates()
199 | self.viewModel.insertCardIntoStack(card, stackID: self.stack.id, destIndex: destinationIndexPath.row)
200 | tableView.insertRows(at: [destinationIndexPath], with: .automatic)
201 | tableView.endUpdates()
202 | break
203 | case (nil, nil):
204 | // Insert data from a table to another table
205 | // when dragging things onto the bottom
206 | self.removeSourceTableData(localContext: coordinator.session.localDragSession?.localContext)
207 | tableView.beginUpdates()
208 | self.viewModel.insertCardIntoStack(card, stackID: self.stack.id, destIndex: nil)
209 | tableView.insertRows(at: [IndexPath(row: self.viewModel.getStackByID(self.stack.id).cards!.count - 1, section: 0)], with: .automatic)
210 | tableView.endUpdates()
211 | break
212 | default: break
213 | }
214 | }
215 | }
216 | }
217 |
218 | func removeSourceTableData(localContext: Any?) {
219 | if let (dataSource, sourceIndexPath, tableView) = localContext as? (NCCommunicationDeckStacks, IndexPath, UITableView) {
220 | tableView.beginUpdates()
221 | viewModel.removeCardFromStack(dataSource.id, sourceIndexPath.row)
222 | tableView.deleteRows(at: [sourceIndexPath], with: .automatic)
223 | tableView.endUpdates()
224 | }
225 | }
226 |
227 | func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
228 | return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
229 | }
230 |
231 | func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
232 | return UIDropProposal(operation: .move)
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/iOS Deck/Stacks/StackView.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 | import NCCommunication
6 | import MobileCoreServices
7 |
8 | struct StackView: View {
9 | @State var stack: NCCommunicationDeckStacks
10 | @State var permissionToEdit: Bool
11 | @ObservedObject var viewModel: StacksViewModel
12 | @State var color: Color
13 | @State var size: CGSize
14 |
15 | var body: some View {
16 | let stackIndex = viewModel.stacks.firstIndex(where: { $0.id == stack.id })!
17 | ZStack {
18 | RoundedRectangle(cornerRadius: 15, style: .continuous)
19 | .fill(color)
20 | VStack {
21 | HStack {
22 | Text(stack.title).bold()
23 | Spacer()
24 | }
25 | .padding(.leading, 10)
26 | .padding(.top, 10)
27 | Divider()
28 | .background(Color.white)
29 |
30 | StackListView(stack: viewModel.stacks[stackIndex], viewModel: viewModel, permissionToEdit: permissionToEdit)
31 | .padding(.bottom, 0)
32 |
33 | Divider()
34 | .background(Color.white)
35 | .padding(.bottom, 20)
36 | }
37 | }
38 | .frame(maxWidth: (size.width - 35), minHeight: 0, idealHeight: 0, maxHeight: (size.height - 30))
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/iOS Deck/Stacks/StacksViewModel.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import Foundation
5 | import NCCommunication
6 |
7 | final class StacksViewModel: ObservableObject {
8 | @Published var stacks: [NCCommunicationDeckStacks] = []
9 | @Published var board: NCCommunicationDeckBoards? = nil
10 |
11 | init(_ stacks: [NCCommunicationDeckStacks]) {
12 | self.stacks = stacks
13 | }
14 |
15 | func getStackByID(_ stackID: Int) -> NCCommunicationDeckStacks {
16 | let i = getIndexByStackID(stackID)
17 | return stacks[i!]
18 | }
19 |
20 | func moveCardInStack(_ card: NCCommunicationDeckCards, sourceIndex: Int, destIndex: Int) {
21 | if let i = getIndexByStackID(card.stackId) {
22 | card.order = destIndex
23 | stacks[i].cards?.remove(at: sourceIndex)
24 | stacks[i].cards?.insert(card, at: destIndex)
25 | setOrders(i)
26 |
27 | DataManager().setStack(stacks[i])
28 | SyncManager.shared.SyncStackCards(stacks[i])
29 | }
30 | }
31 |
32 | func insertCardIntoStack(_ card: NCCommunicationDeckCards, stackID: Int, destIndex: Int?) {
33 | if let i = getIndexByStackID(stackID) {
34 | if (destIndex != nil) {
35 | card.order = destIndex!
36 | card.stackId = stackID
37 | stacks[i].cards?.insert(card, at: destIndex!)
38 | } else {
39 | if (stacks[i].cards != nil) {
40 | card.order = stacks[i].cards!.count - 1
41 | card.stackId = stackID
42 | stacks[i].cards?.append(card)
43 | } else {
44 | card.order = 0
45 | card.stackId = stackID
46 | stacks[i].cards = [card]
47 | }
48 | }
49 | setOrders(i)
50 |
51 | DataManager().setStack(stacks[i])
52 | SyncManager.shared.SyncStackCards(stacks[i])
53 | }
54 | }
55 |
56 | func removeCardFromStack(_ stackID: Int, _ cardIndex: Int) {
57 | if let i = getIndexByStackID(stackID) {
58 | stacks[i].cards?.remove(at: cardIndex)
59 | setOrders(i)
60 |
61 | DataManager().setStack(stacks[i])
62 | SyncManager.shared.SyncStackCards(stacks[i])
63 | }
64 | }
65 |
66 | func updateDescriptionCheckbox(stackID: Int, cardID: Int, _ value: Bool, range: Range) {
67 | if let stackIndex = getIndexByStackID(stackID) {
68 | if let cardIndex = stacks[stackIndex].cards?.firstIndex(where: { $0.id == cardID }) {
69 | stacks[stackIndex].cards![cardIndex].desc.replaceSubrange(range, with: value ? "- [x]" : "- [ ]")
70 | }
71 | }
72 | }
73 |
74 | func syncCard(_ card: NCCommunicationDeckCards) {
75 | if let (stackIndex, cardIndex) = getCardIndexesByID(card.id) {
76 | DataManager().setStack(stacks[stackIndex])
77 | SyncManager.shared.SyncCard(stacks[stackIndex].cards![cardIndex], boardID: stacks[stackIndex].boardId)
78 | }
79 | }
80 |
81 | func getCardIndexesByID(_ cardID: Int) -> (stackIndex: Int, cardIndex: Int)? {
82 | let stack = stacks.firstIndex(where: {
83 | if ($0.cards != nil) {
84 | return ($0.cards!.first(where: { $0.id == cardID }) != nil)
85 | }
86 | return false
87 | })
88 | if (stack != nil) {
89 | let card = stacks[stack!].cards!.firstIndex(where: { $0.id == cardID })
90 | if (card != nil) {
91 | return (stack!, card!)
92 | }
93 | }
94 | return nil
95 | }
96 |
97 | // updates order for all cards to match index in list
98 | private func setOrders(_ stackIndex: Int) {
99 | for (index, card) in stacks[stackIndex].cards!.enumerated() {
100 | if (index != card.order) { stacks[stackIndex].cards![index].order = index }
101 | }
102 | }
103 |
104 | private func getIndexByStackID(_ stackID: Int) -> Int? {
105 | if let i = stacks.firstIndex(where: { $0.id == stackID }) {
106 | return i
107 | }
108 | return nil
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/iOS Deck/Styles/ButtonStyles.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 |
6 | struct RoundButtonStyle: ButtonStyle {
7 | var bgColor: Color = Color.white
8 | var fgColor: Color? = Color.black
9 | func makeBody(configuration: Self.Configuration) -> some View {
10 | configuration.label
11 | .foregroundColor(fgColor)
12 | .padding()
13 | .background(bgColor)
14 | .cornerRadius(15.0)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/iOS Deck/Upcoming/UpcomingView.swift:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 |
6 | struct UpcommingView: View {
7 | var body: some View {
8 | NavigationView {
9 | VStack {
10 | Text("Due View")
11 | }
12 | .navigationBarTitle("Due", displayMode: .automatic)
13 | }
14 | }
15 | }
16 |
17 | struct UpcommingView_Previews: PreviewProvider {
18 | static var previews: some View {
19 | UpcommingView()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/iOS Deck/Views/ActivityIndicator.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 |
6 | struct ActivityIndicator: UIViewRepresentable {
7 | @Binding var shouldAnimate: Bool
8 |
9 | func makeUIView(context: Context) -> UIActivityIndicatorView {
10 | return UIActivityIndicatorView()
11 | }
12 |
13 | func updateUIView(_ uiView: UIActivityIndicatorView,
14 | context: Context) {
15 | if self.shouldAnimate {
16 | uiView.startAnimating()
17 | } else {
18 | uiView.stopAnimating()
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/iOS Deck/Views/NavigationBarModifier.swift:
--------------------------------------------------------------------------------
1 | // Created for iOS Deck in 2020
2 | // Using Swift 5.0
3 |
4 | import SwiftUI
5 |
6 | struct NavigationBarModifier: ViewModifier {
7 |
8 | var backgroundColor: UIColor?
9 |
10 | init( backgroundColor: UIColor?) {
11 | self.backgroundColor = backgroundColor
12 | let coloredAppearance = UINavigationBarAppearance()
13 | coloredAppearance.configureWithTransparentBackground()
14 | coloredAppearance.backgroundColor = .clear
15 | coloredAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
16 | coloredAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
17 |
18 | UINavigationBar.appearance().standardAppearance = coloredAppearance
19 | UINavigationBar.appearance().compactAppearance = coloredAppearance
20 | UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
21 | UINavigationBar.appearance().tintColor = .white
22 |
23 | }
24 |
25 | func body(content: Content) -> some View {
26 | ZStack{
27 | content
28 | VStack {
29 | GeometryReader { geometry in
30 | Color(self.backgroundColor ?? .clear)
31 | .frame(height: geometry.safeAreaInsets.top)
32 | .edgesIgnoringSafeArea(.top)
33 | Spacer()
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
40 | extension View {
41 | func navigationBarColor(_ backgroundColor: UIColor?) -> some View {
42 | self.modifier(NavigationBarModifier(backgroundColor: backgroundColor))
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tmp.xcconfig:
--------------------------------------------------------------------------------
1 | // Created for ios-deck in 2020
2 | // Using Swift 5.0
3 |
4 | // Configuration settings file format documentation can be found at:
5 | // https://help.apple.com/xcode/#/dev745c5c974
6 | EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64=arm64 arm64e armv7 armv7s armv6 armv8
7 | EXCLUDED_ARCHS=$(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT))
8 |
--------------------------------------------------------------------------------