一个漂亮的照片应用程序创建,以便在GoodOnes找到一份好工作

NicePhotoApp – 发展日记

五月 4 – 12:17

我阅读了带回家的练习,以了解需要做什么。由于第 1 部分是最重要的,因此重点将放在前端,因此我将开始研究布局参考。将用于搜索的网站将被 https://dribbble.com/shots/https://mobbin.design/browse/ios/screens

五月 4 – 12:35

入职屏幕将基于模型:https://dribbble.com/shots/16540320-Onboarding-Screens。将有三个屏幕解释关闭和收藏过程如何在应用程序中工作。

五月 4 – 13:10

经过研究,决定使用列表制作应用程序的主屏幕,因为有必要使用滑动手势功能来收藏和关闭照片。使用网格(在显示照片的应用中更常见)无法使用此功能。

五月 4 – 13:10

为了使屏幕正常工作,必须使用 UserDefaults,以便它只显示一次。将创建一个服务以避免需要使用字符串并封装其使用。

May 4 – 13:33

We are going to localize the app to work in English and any other language that makes sense using a Localizable.strings file with a String Extension to make it easy to call property into a string value.localize

May 4 – 16:33

The onboarding screens are complete. The SwiftUI TabView component was used to navigate by gestures and also buttons at the bottom of the screen to ensure navigation by buttons. Now before implementing the main view of the app, I will work on the to allow the user to enter with a Google Account or without any account. Here’s what the onboarding screens look like:Login View

May 5 – 19:10

I will not use third party libraries to facilitate the use of Google APIs. I’ll use the oficial GoogleSignIn Framework to control oAuth flow and make calls to the Google Photos API using HTTP Request without libraries, just using URLSession and Combine. I’m more cautious when it comes to using third-party libraries, as it creates a dependency that other people need to maintain the library code to keep my product working. In this project the only exception is going to be related to the . Due the time limit and the complexity, I’ll use a library to implement that.perceptual hash algorithm

May 5 – 18:25

The architecture requested in the take-home exercise was MVVM, a simple architecture pattern that fits very well with SwiftUI. It does not rule out the fact that layers below the View Model can have a well-defined structure. One of the most used currently on the market is Clean Architecture, which will be used in this challenge.

The Clean Architecture is based on Use Cases that are unique actions the user takes on the system. For example the authentication, get photos from gallery or from google. they all should be unique classes responsible for a single action. It also has the layer that is responsible for choosing where to bring the data from ( or ), so the base data structure will be like this:RepositoryRemote LayerLocal Layer

All layers communicate through and use protocols to maintain the inverse dependency injection. This makes easier to maintain the code in case you need to change an entire layer, and it also makes easier to make mocks for unit tests.Since the app has the , it will be placed in the folder. So the folder structure will be like this:Combine FrameworkMMVVM Layerpresentation

May 5 – 22:20

The Login feature is finished, now, it is possible for the user to log into the app using their google account, or use it without any account. The ViewModel is responsible for using the necessary UseCases to bring the data of the google user if the chosen option is to login via Google. To keep the code clean, the LocalData layer is responsible for bringing the google user data if it is in Cache, otherwise the RemoteData layer will use oAuth to do the authentication process. Here it is how it looks like to login using Google:

May 5 – 23:00

The layer returns data from therefore it is dependent on the , and the layer returns local data from the gallery therefore it is dependent on the . To avoid any kind of dependency to make it easy the mocking to use SwiftUI previews and to facilitate future unit tests, the returns a unique model specific to the app, without dependence on any local or remote framework.Remote DataAPI'sCodable ProtocolLocal DataPhotos FrameworkRepository Layer

May 6 – 12:15

The PhotoListView feature will contain a view called that will show the user’s image and name if they are logged in with Google, or show a placeholder image and a name “Visitor” if they are not logged in. The list items will have the original file name and file creation date in a simple and straightforward layout.UserView

A View called will be responsible for loading the image remotely if it comes from google photos or locally if it comes from the gallery. To decide between image types, an Enum called was created.DynamicImageViewPhotoType

To get the data correctly, the ViewModel will choose where to bring the data from according to . If it exists, it means that the user has logged in with Google, in this case the use case that will be used will be . Otherwise the data must come from the gallery, so the use case used will be . As all layers communicate through combine, it is easy to map errors and models to other objects.NicePhotoUserGetGooglePhotosUseCaseGetGalleryPhotosUseCase

May 7 – 10:22

The data flow for downloading images from google and saving it as favorite will be the follow:

  • First we download the image using the URL retrived from google’s web service. Since it will be returned from backend, the layer will be used.RemoteData
  • Then we use the from library to check if we already have a similar image in user’s gallery. Since the photos to compare is in the gallery, the layer will be used.perceptual hash algorithmCocoaImageHashingLocalData
  • If so, we show an error, if not, we save it into the gallery and favorite it using .Photo Framework

Here’s what the error flow and message looks like:

May 7 – 12:10

In order to avoid loading images that have already been processed as favorites or dismissed, the was used to save the ID’s that have already passed through the process. Of course, a more refined database system would be better, but for simplicity and time, will be used. While the app is loading its data, a LoadingView with a nice animation will be shown. If there is no photos in the Gallery or in Google Photos or in , a will be shown. If we process an image (Delete or Favorite), it will be a simple remove animation from ListView to remove the item from the list.UserDefaultsUserDefaultsFavorites Tab ViewBoxEmptyView

May 9 – 19:00

I did some refactors, create some missing components and test the flow one more time. This readme file gives me headache because it does not load the gif files properly, so I changed the host website.

Here I have some improvments that can be made with more time:

1 – : They are extremely important to maintain code quality and avoid future bugs.Unit Tests

2 – : To save time, I just created some basic error enums, without any important information. This can be improved to give to the user a better information about what’s going wrong in his app.Improve Error Handling

3 – : I didn’t have much time to create animations, just a simple animation in the photo list. For a better user experience, can be used to create view animations or even some framework like .AnimationsSwiftUILottie

4 – : Even with the algorithm working, I don’t like to rely on a third party library for such important code for the app. So with more time I would like to write my own algorithm and have total control about what’s going on under the hood.Perceptual Hash AlgorithmCocoaImageHashing

5 – : The way that they were designed, it should validate all input data before moving on to the next layer, validations such as invalid ID’s, invalid tokens and so on should be made there.Use Cases

Please enjoy a quick video showing the app: https://vimeo.com/708010318

GitHub

点击跳转