Code Monkey home page Code Monkey logo

papr's Introduction

🌁 Papr

Build Status Build Status

Papr is an unofficial Unsplash app for iOS.

πŸƒβ€β™‚οΈ Getting Started

git clone https://github.com/jdisho/Papr.git
cd Papr
pod install
open Papr.xcworkspace # or xed .

βš™οΈ Setup

To be able to log in during development, you'll need a Client ID and Client Secret.

To get these, register a new OAuth application on Unsplash.

Make sure the Authorization callback URL is set to papr://unsplash. The others can be filled in as you wish.

To add the Client ID and Client Secret to the App, follow these steps:

  1. In Xcode, go to Product> Scheme > Manage Schemes...
  2. Select Papr and click Edit...
  3. Go to Run > Arguments
  4. Add your Client ID (UNSPLASH_CLIENT_ID as key) and Client Secret (UNSPLASH_CLIENT_SECRET) to the Environment Variables.

πŸŽ‰ Why am I building this?

  1. Pushing RxSwift to its limits. πŸ”₯
  2. MVVM + Coordinator
  3. Using Codable, RxDataSources, Action.
  4. Exploring Unsplash and its API
  5. Fun thing!

❀️ Contributing

I intend for this project to be more as an educational resource, learn by open sourcing.

I am very open for feedback and contribution.

papr's People

Contributors

jdisho avatar maxzheleznyy avatar pinddfull avatar serjooo avatar spiderbob avatar thecodetalker avatar vaderdan avatar xingye avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

papr's Issues

Proposal - Sourcery automatic code generation for boilerplate

Hi what do you think about Sourcery (https://github.com/krzysztofzablocki/Sourcery)
It generates generates some of the duplicate/boilerplate code for you

I think that model protocols for every model have identical structure and could be removed (moved to autogenerated file) with this
See this gist,
https://gist.github.com/vaderdan/cfd9ef853f123143e580ec2e3664a8c1
Basicaly it scans for classes that extends Automodel and make protocols with inputs/outputs from them

https://www.caseyliss.com/2017/3/31/the-magic-of-sourcery

SceneCoordinator Transition

First of all I would like to thank you for this amazing project. Truly your project has become an educational resource for me!

My question is about the SceneCoordinator. I implemented the Coordinator with the same logic without RX, because I don't have the enough confidence to start using RX in projects for production. I have only one missing link to intercept when the View Controllers pop or a tab bar item is changed you make the SceneCoordinator the navigationControllerDelegate and tabBarControllerDelegate. I am wondering what the difference between this:

currentViewController.navigationController?.rx.delegate.setForwardToDelegate(self, retainDelegate: false)
currentViewController.tabBarController?.rx.delegate.setForwardToDelegate(self, retainDelegate: false)

And this code is:

currentViewController.navigationController?.delegate = self
currentViewController.tabBarController?.delegate = self

I'm guessing RX has a fancy way of preventing memory leaks in this case. How can I make my own implementation of setForwardToDelegate? Again thank you very much!

Show full date

Show full date on each photo if the date is greater than 1 week

unit testing for view model

Could you please write some tests for the view models and some important parts of the application to demonstrate unit testing in this architecture.

Use the app as a guest

The guest user can use the app, but when it comes to the actions that require outside the public scope, the guest user should be invited to Login with Unsplash.

screen shot 2018-02-23 at 13 10 56

Where would you put the code ''tableView.rx.itemSelected''?

Since you have put photoDetailsAction in HomeViewCellModel to transition to photoDetails. Now if I want to use tableView.rx.itemSelected. I have to put it in HomeView layer. Is it a little weird to do similar logic in different layers? One in view, one in cell?
How would you write the code? Thx

SceneCoodinator.swift=>actualViewController=>return issue

Papr/Coodinator/SceneCoodinator.swift

static func actualViewController(for viewController: UIViewController) -> UIViewController {
    var controller = viewController
    if let tabBarController = controller as? UITabBarController {
        guard let selectedViewController = tabBarController.selectedViewController else {
            return tabBarController
        }
        controller = selectedViewController
    }
    if let navigationController = viewController as? UINavigationController {
        return navigationController.viewControllers.first!
    }
    -return viewController  
    +return controller
}

Share a photo

Click on "..." to share a photo via UIActivityViewController

Detail Controller like bug

I found this bug
When I like photo in details screen and then go back, the tableview photo stays with unliked

Tapping on search results does nothing

When I go to search screen -> enter something in search bar -> tap on Photos with ...
it show all the photos, but when I tap on any of the search results (photos) it does nothing

I should open detail photo screen

Search photos, can't dismiss keyboard

When you go on the search, I'm unable to dismiss the keyboard

It should hide when I press on table cell or anywhere on the screen (tableView itself)

Papr crashes in iOS 12 and lower.

Since SVG format is not supported in iOS, the app crashes when it tries to access an image with a given resource name.

static var arrowUpRight: UIImage {
    if #available(iOS 13.0, *) {
        return UIImage(systemName: "arrow.up.right", withConfiguration: symbolConfigurationMedium)!#
    }
    return UIImage(imageLiteralResourceName: "arrow.up.right") // #CRASH
}

We can solve this issue by (I would like to avoid having a dependency for this use case) by converting the SVGs into .png or supporting iOS13 and higher.

Thoughts on this @serjooo (since you closed #89) ?

(This app is not planned to be released, so supporting only the latest iOS version seems fine to me.)

HomeViewController crashes in iOS12 and lower when trying to refresh

As @serjooo mentioned:

App crashes still on iOS 12 in the HomeViewController on line 82:

outputs.isRefreshing
.execute { [weak self] isRefreshing in
    if isRefreshing {
        self?.collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .top, animated: false)
        self?.collectionView.setContentOffset(CGPoint(x: 0.0, y: -(self?.refreshControl.frame.height ?? 0.0)), animated: true)
    } else {
        self?.collectionView.setContentOffset(.zero, animated: true)
    }
}
.bind(to: refreshControl.rx.isRefreshing)
.disposed(by: disposeBag)

Commenting out the isRefreshing code solves the problem as the app crashes on scrolling logic.

Swipe left-right to see prev-next photo

On the photo detail, swipe left-right to see the prev-next photo. This means that the PhotoDetail.swift should be a UICollectionViewCell instead of a UIViewController.

SceneCoordinator - iOS 13 Support

I noticed the app on iOS 13 has an issue on scene-coordinator because of the new behavior of iOS 13 modal( β€˜pull-to-dismissβ€˜). I think the coordinator is not getting this event to pop the current view from the navigation stack. Below is the error after trying to present VC once the previous VC dismissed by "pull-to-dismiss".

Warning: Attempt to present <XX:ViewController: 0x7f96b955f590> on <XX:ViewController: 0x7f96b9621de0> whose view is not in the window hierarchy!

Setting page

I would like to have a setting page. The settings page should give the possibility to:

  • Logout.
  • Switch between different Unsplash accounts.
  • Report a bug.
  • Change theme (Light <-> Dark)
  • Change app icon.

Unable to get the latest 'isLikedByUser'.

I imitated your code.
After I press the select button, isLikedByUser is always the initial value.

PostViewCellModel.swift

import Foundation
import RxSwift
import Action
import RealmSwift

protocol PostViewCellModelInput {
    var selectPostAction: Action<Post, Post> { get }
    var unselectPostAction: Action<Post, Post> { get }
}
protocol PostViewCellModelOutput {
    var postData: Observable<Post> { get }
    var isSelected: Observable<Bool> { get }
}
protocol PostViewCellModelType {
    var inputs: PostViewCellModelInput { get }
    var outputs: PostViewCellModelOutput { get }
}

final class PostViewCellModel: PostViewCellModelType, PostViewCellModelInput, PostViewCellModelOutput {
    var inputs: PostViewCellModelInput { return self }
    var outputs: PostViewCellModelOutput { return self }
    
    lazy var selectPostAction: Action<Post, Post> = {
        Action<Post, Post> { [unowned self] post in
            let configuration = Realm.Configuration(deleteRealmIfMigrationNeeded: true)
            let realm = try! Realm(configuration: configuration)
            
            let sharePost = SharePost()
            sharePost.ref = post.ref!
            sharePost.title = post.title
            sharePost.summary = post.summary
            sharePost.url = post.url

            try! realm.write {
                realm.add(sharePost, update: .modified)
            }

            return .just(post)
        }
    }()
    
    lazy var unselectPostAction: Action<Post, Post> = {
        Action<Post, Post> { [unowned self] post in
            let configuration = Realm.Configuration(deleteRealmIfMigrationNeeded: true)
            let realm = try! Realm(configuration: configuration)
            let sharePost = realm.objects(SharePost.self).filter("ref = %@", post.ref!)
            
            if (sharePost.count > 0) {
                try! realm.write {
                    realm.delete(sharePost.first!)
                }
            }
            
            return .just(post)
        }
    }()
 
    var postData: Observable<Post>
    var isSelected: Observable<Bool>
    
    private let service: PostServiceType
    private let cache: Cache
    private let sceneCoordinator: SceneCoordinatorType
    
    init(post: Post, service: PostServiceType = PostService(), cache: Cache = Cache.shared, sceneCoordinator: SceneCoordinatorType = SceneCoordinator.shared) {
        self.service = service
        self.cache = cache
        self.sceneCoordinator = sceneCoordinator
        
        postData = Observable.just(post)
        let cachedPostData = cache.getObject(ofType: Post.self, withId: post.ref ?? "").unwrap()
        
        isSelected = self.postData.merge(with: cachedPostData)
            .map { post in
                let configuration = Realm.Configuration(deleteRealmIfMigrationNeeded: true)
                let realm = try! Realm(configuration: configuration)

                if realm.object(ofType: SharePost.self, forPrimaryKey: post.ref!) != nil {
                    return true
                } else {
                    return false
                }
            }.unwrap()
    }
}

PostViewCell.swift

import Foundation
import UIKit
import RxSwift
import RxCocoa
import RealmSwift

class PostViewCell: UITableViewCell, BindableType, NibIdentifiable & ClassIdentifiable {
    var viewModel: PostViewCellModelType!
    
    @IBOutlet var selectButton: UIButton!
    
    private var disposeBag = DisposeBag()

    func bindViewModel() {
        let inputs = viewModel.inputs
        let outputs = viewModel.outputs
        
        Observable.combineLatest(outputs.isSelected, outputs.postData)
            .bind { [weak self] in
                self?.selectButton.rx.bind(to: $0 ? inputs.unselectPostAction: inputs.selectPostAction, input: $1)
            }
            .disposed(by: disposeBag)

        outputs.isSelected
            .map { $0 ? UIColor.init(red: 210 / 255, green: 233 / 255, blue: 1, alpha: 1) : .clear }
            .bind(to: self.rx.backgroundColor)
            .disposed(by: disposeBag)
    }
}

Like/Unlike issue

A photo at Home is not updated when it is liked/unliked from PhotoDetails

Show more details for a photo

Show more info for every photo:

  • Location (... and other meta data).
  • Related photos.
  • Featured collections.
  • Related tags.

Start with #92 first.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.