//

//  ViewController.swift

//  ImagePicker

//

//  Created by stayfoolish on 2018. 8. 28..

//  Copyright © 2018년 stayfoolish. All rights reserved.

//


import UIKit


class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate{


    lazy var imagePicker: UIImagePickerController = {

        let picker: UIImagePickerController = UIImagePickerController()

        picker.sourceType = .photoLibrary

        picker.delegate = self

        return picker

    }()

    

    @IBOutlet weak var imageView: UIImageView!

    

    @IBAction func touchUpSelectImageButton(_ sender: UIButton){

        self.present(self.imagePicker, animated: true, completion: nil)

    }

    

    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {

        self.dismiss(animated: true, completion: nil)

    }

    

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

        

        if let originalImage: UIImage = info[UIImagePickerControllerOriginalImage] as? UIImage {

            self.imageView.image = originalImage

        }

        

        self.dismiss(animated: true, completion: nil)

    }

    

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

    }


    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

        // Dispose of any resources that can be recreated.

    }



}













//

//  ViewController.swift

//  MusicPlayer

//

//  Created by stayfoolish on 2018. 8. 27..

//  Copyright © 2018년 stayfoolish. All rights reserved.

//


import UIKit

import AVFoundation


class ViewController: UIViewController, AVAudioPlayerDelegate {


    var player: AVAudioPlayer!

    var timer: Timer!

    

    @IBOutlet var playPauseButton: UIButton!

    @IBOutlet var timeLabel: UILabel!

    @IBOutlet var progressSlider: UISlider!

    

    

    func initializePlayer() {

        

        guard let soundAsset: NSDataAsset = NSDataAsset(name: "sound") else {

            print("음원 파일 에셋을 가져올 수 없습니다")

            return

        }

        

        do {

            try self.player = AVAudioPlayer(data: soundAsset.data)

            self.player.delegate = self

        } catch let error as NSError {

            print("플레이어 초기화 실패")

            print("코드 : \(error.code), 메세지 : \(error.localizedDescription)")

        }

        

        self.progressSlider.maximumValue = Float(self.player.duration)

        self.progressSlider.minimumValue = 0

        self.progressSlider.value = Float(self.player.currentTime)

    }

    

    func updateTimeLabelText(time: TimeInterval) {

        let minute: Int = Int(time / 60)

        let second: Int = Int(time.truncatingRemainder(dividingBy: 60))

        let milisecond: Int = Int(time.truncatingRemainder(dividingBy: 1) * 100)

        

        let timeText: String = String(format: "%02ld:%02ld:%02ld", minute, second, milisecond)

        

        self.timeLabel.text = timeText

    }

    

    func makeAndFireTimer() {

        self.timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { [unowned self] (timer: Timer) in

            

            if self.progressSlider.isTracking { return }

            

            self.updateTimeLabelText(time: self.player.currentTime)

            self.progressSlider.value = Float(self.player.currentTime)

        })

        self.timer.fire()

    }

    

    func invalidateTimer() {

        self.timer.invalidate()

        self.timer = nil

    }

    

    func addViewsWithCode() {

        self.addPlayPauseButton()

        self.addTimeLabel()

        self.addProgressSlider()

    }

    

    func addPlayPauseButton() {

        

        let button: UIButton = UIButton(type: UIButtonType.custom)

        button.translatesAutoresizingMaskIntoConstraints = false

        

        self.view.addSubview(button)

        

        button.setImage(UIImage(named: "button_play"), for: UIControlState.normal)

        button.setImage(UIImage(named: "button_pause"), for: UIControlState.selected)

        

        button.addTarget(self, action: #selector(self.touchUpPlayPauseButton(_:)), for: UIControlEvents.touchUpInside)

        

        let centerX: NSLayoutConstraint

        centerX = button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)

        

        let centerY: NSLayoutConstraint

        centerY = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.centerY, multiplier: 0.8, constant: 0)

        

        let width: NSLayoutConstraint

        width = button.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.5)

        

        let ratio: NSLayoutConstraint

        ratio = button.heightAnchor.constraint(equalTo: button.widthAnchor, multiplier: 1)

        

        centerX.isActive = true

        centerY.isActive = true

        width.isActive = true

        ratio.isActive = true

        

        self.playPauseButton = button

    }

    

    func addTimeLabel() {

        let timeLabel: UILabel = UILabel()

        timeLabel.translatesAutoresizingMaskIntoConstraints = false

        

        self.view.addSubview(timeLabel)

        

        timeLabel.textColor = UIColor.black

        timeLabel.textAlignment = NSTextAlignment.center

        timeLabel.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)

        

        let centerX: NSLayoutConstraint

        centerX = timeLabel.centerXAnchor.constraint(equalTo: self.playPauseButton.centerXAnchor)

        

        let top: NSLayoutConstraint

        top = timeLabel.topAnchor.constraint(equalTo: self.playPauseButton.bottomAnchor, constant: 8)

        

        centerX.isActive = true

        top.isActive = true

        

        self.timeLabel = timeLabel

        self.updateTimeLabelText(time: 0)

    }

    

    func addProgressSlider() {

        let slider: UISlider = UISlider()

        slider.translatesAutoresizingMaskIntoConstraints = false

        

        self.view.addSubview(slider)

        

        slider.minimumTrackTintColor = UIColor.red

        

        slider.addTarget(self, action: #selector(self.sliderValueChanged(_:)), for: UIControlEvents.valueChanged)

        

        let safeAreaGuide: UILayoutGuide = self.view.safeAreaLayoutGuide

        

        let centerX: NSLayoutConstraint

        centerX = slider.centerXAnchor.constraint(equalTo: self.timeLabel.centerXAnchor)

        

        let top: NSLayoutConstraint

        top = slider.topAnchor.constraint(equalTo: self.timeLabel.bottomAnchor, constant: 8)

        

        let leading: NSLayoutConstraint

        leading = slider.leadingAnchor.constraint(equalTo: safeAreaGuide.leadingAnchor, constant: 16)

        

        let trailing: NSLayoutConstraint

        trailing = slider.trailingAnchor.constraint(equalTo: safeAreaGuide.trailingAnchor, constant: -16)

        

        centerX.isActive = true

        top.isActive = true

        leading.isActive = true

        trailing.isActive = true

        

        self.progressSlider = slider

    }

    

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

        self.initializePlayer()

    }


    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

        // Dispose of any resources that can be recreated.

    }

    

    func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {

        

        guard let error: Error = error else {

            print("오디오 플레이어 디코드 오류발생")

            return

        }

        

        let message: String

        message = "오디오 플레이어 오류 발생 \(error.localizedDescription)"

        

        let alert: UIAlertController = UIAlertController(title: "알림", message: message, preferredStyle: UIAlertControllerStyle.alert)

        

        let okAction: UIAlertAction = UIAlertAction(title: "확인", style: UIAlertActionStyle.default) { (action: UIAlertAction) -> Void in

            

            self.dismiss(animated: true, completion: nil)

        }

        

        alert.addAction(okAction)

        self.present(alert, animated: true, completion: nil)

    }

    

    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {

        self.playPauseButton.isSelected = false

        self.progressSlider.value = 0

        self.updateTimeLabelText(time: 0)

        self.invalidateTimer()

    }

    

    

    @IBAction func touchUpPlayPauseButton(_ sender: UIButton){

        

        sender.isSelected = !sender.isSelected

        

        if sender.isSelected{

            self.player?.play()

        } else {

            self.player?.pause()

        }

        

        if sender.isSelected {

            self.makeAndFireTimer()

        }else {

            self.invalidateTimer()

        }

        

    }


    @IBAction func sliderValueChanged(_ sender: UISlider) {

        self.updateTimeLabelText(time: TimeInterval(sender.value))

        if sender.isTracking { return }

        self.player.currentTime = TimeInterval(sender.value)

    }

    

    

}











//

//  ViewController.swift

//  PickerView

//

//  Created by stayfoolish on 2018. 8. 24..

//  Copyright © 2018년 stayfoolish. All rights reserved.

//


import UIKit


class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {


    let MAX_ARRAY_NUM = 12

    let PICKER_VIEW_COLUMN = 1

    var imageArray = [UIImage?]()

    var imageFileName = ["1.jpg","2.jpg","3.jpg","4.jpg","5.jpg","6.jpg","7.jpg","8.jpg","9.jpg","10.jpg","11.jpg","12.jpg",]

    @IBOutlet var pickerImage: UIPickerView!

    @IBOutlet var lblImageFileNmae: UILabel!

    @IBOutlet var imageView: UIImageView!

    

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

        for i in 0 ..< MAX_ARRAY_NUM{

            let image = UIImage(named: imageFileName[i])

            imageArray.append(image)

        }

        

        lblImageFileNmae.text = imageFileName[0]

        imageView.image = imageArray[0]

    }


    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

        // Dispose of any resources that can be recreated.

    }


    func numberOfComponents(in pickerView: UIPickerView) -> Int {

        return PICKER_VIEW_COLUMN

    }

    

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {

        return imageFileName.count

    }

    

/*

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int , forComponent component: Int ) -> String?{

        return imageFileName[row]

    }

*/


    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component:Int,

                    reusing view: UIView?) -> UIView {

        let imageView = UIImageView(image:imageArray[row])

        imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 150)

        

        return imageView

    }

 

    

 

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int ){

        lblImageFileNmae.text = imageFileName[row]

        imageView.image = imageArray[row]

    }


}






//

//  ViewController.swift

//  DatePicker

//

//  Created by stayfoolish on 2018. 8. 22..

//  Copyright © 2018년 stayfoolish. All rights reserved.

//


import UIKit


class ViewController: UIViewController {

    

    let timeSelector: Selector = #selector(ViewController.updateTime)

    let interval = 1.0

    var count = 0

    

    


    @IBOutlet var lblCurrentTime: UILabel!

    @IBOutlet var lblPickerTime: UILabel!

    

    

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

        Timer.scheduledTimer(timeInterval: interval, target: self, selector: timeSelector,

                             userInfo: nil, repeats: true)

    }


    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

        // Dispose of any resources that can be recreated.

    }


    @IBAction func changeDatePicker(_ sender: UIDatePicker) {

        

        let datePickerView = sender

        let formatter = DateFormatter()

        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss EEE"

        lblPickerTime.text = "선택시간: " + formatter.string(from: datePickerView.date)

    }

    

    @objc func updateTime() {

//        lblCurrentTime.text = String(count)

//        count = count + 1

        let date = NSDate()

        

        let formatter = DateFormatter()

        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss EEE"

        lblCurrentTime.text = "현재시간: " + formatter.string(from: date as Date)

    }

    

}





//

//  ViewController.swift

//  ImageView

//

//  Created by stayfoolish on 2018. 8. 22..

//  Copyright © 2018년 stayfoolish. All rights reserved.

//


import UIKit


class ViewController: UIViewController {

    

    var isZoom = false

    var imgOn: UIImage?

    var imgOff: UIImage?

    

    

    @IBOutlet var imgView: UIImageView!

    @IBOutlet var btnResize: UIButton!

    

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

        

        imgOn = UIImage(named: "23efa0b90b7c7935d4e63e88ce863c25.jpg")

        imgOff = UIImage(named: "slack1.jpeg" )

        

        imgView.image = imgOn

    }


    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

        // Dispose of any resources that can be recreated.

    }


    @IBAction func btnResizeImage(_ sender: UIButton) {

        let scale:CGFloat = 2.0

        var newWidth:CGFloat, newHeight:CGFloat

        

        if (isZoom){ // true

            newWidth = imgView.frame.width/scale

            newHeight = imgView.frame.height/scale

            imgView.frame.size = CGSize(width: newWidth, height: newHeight)

            btnResize.setTitle("확대", for: .normal)

            

        }else {

            newWidth = imgView.frame.width*scale

            newHeight = imgView.frame.height*scale

            imgView.frame.size = CGSize(width: newWidth, height: newHeight)

            btnResize.setTitle("축소", for: .normal)

        }

        isZoom = !isZoom

    }

    

    @IBAction func switchImageOnOff(_ sender: UISwitch) {

        if sender.isOn{

            imgView.image = imgOn

        }else {

            imgView.image = imgOff

        }

    }

}



// 열거형 enum

// 다른 언어에 비해 swift 는 많은 기능을 가지고 있다.


import Swift


// enum은 타입이므로 대문자 카멜케이스를 사용하여 이름을 정의합니다.

// 각 case는 소문자 카멜케이스로 정의합니다.

// 각 case는 그 자체가 고유의 값입니다.


enum Weekday {

    case mon

    case tue

    case wed

    case thu, fri, sat, sun

}


var day: Weekday = Weekday.mon

day = .tue


print(day)


// 하나라도 값이 들어가지 않으면 default를 명시해야한다.

switch day{

case .mon, .tue, .wed, .thu:

    print("평일입니다")

case Weekday.fri:

    print("불금 파티!!")

case .sat, .sun:

    print("신나는 주말!!")

}


// C 언어의 enum처럼 정수값을 가질 수도 있습니다.

// rawValue를 사용하면 됩니다.

// case별로 각각 다른 값을 가져야합니다.



// C 언어처럼 자동으로 1씩 증가한다.

enum Fruit: Int {

    case apple = 0

    case grape = 1

    case peach

// case mango = 0

}


print("Fruit.peach.rawValue == \(Fruit.peach.rawValue)")

// Fruit.peach.rawValue == 2


// 정수 타입 뿐만 아니라 Hashable 프로토콜을 따르는 모든 타입이 원시값의 타입으로 지정될 수 있습니다.


enum School: String {

    case elementary = "초등"

    case middle = "중등"

    case high = "고등"

    case university

}


print("School.middle.rawValue == \(School.middle.rawValue)")

// school.middle.rawValue == 중등


print("School.university.rawValue == \(School.university.rawValue)")

// school.middle.rawValue == university



// 원시값을 통한 초기화


// rawValue를 통해 초기화 할 수 있습니다.

// rawValue가 case에 해당하지 않을 수 있으므로

// rawValue를 통해 초기화 한 인스턴스는 옵셔널 타입입니다.


// let apple: Fruit = Fruit(rawValue: 0)

let apple: Fruit? = Fruit(rawValue: 0)


if let orange: Fruit = Fruit(rawValue: 5) {

    print("rawValue 5에 해당하는 케이스는 \(orange)입니다")

}else {

    print("rawValue 5에 해당하는 케이스가 없습니다.")

} // rawValue 5에 해당하는 케이스가 없습니다.


// 메서드

enum Month {

    case dec, jan, feb

    case mar, apr, may

    case jun, jul, aug

    case sep, oct, nov

    

    func printMessage(){

        switch self {

        case .mar, .apr, .may:

            print("봄")

        case .jun, .jul, .aug:

            print("여름")

        case .sep, .oct, .nov:

            print("가을")

        case .dec, .jan, .feb:

            print("겨울")

        }

    }

}


Month.nov.printMessage()  // 가을


import Swift


// 클래스는 구조체와 유사하다.

// 구조체는 값 타입,  클래스는 참조 타입


class Sample {

    var mutableProperty: Int = 100 // 가변 프로퍼티

    let immutableProperty: Int = 100 // 불변 프로퍼티

    

    static var typeProperty: Int = 100 // 타입 프로퍼티

    

    // 인스턴스 메서드

    func instanceMethod() {

        print("instance method")

    }

    

    // 타입 메서드

    // 재정의 불가 타입 메서드 - static

    static func typeMethod() {

        print("type method - static")

    }

    

    // 재정의 가능 타입 메서드 - class

    class func classMethod() {

        print("type method - class")

    }

}


var mutableReference: Sample = Sample()


mutableReference.mutableProperty = 200

// mutableReference.immutableProperty = 200


let immutableReference: Sample = Sample()


immutableReference.mutableProperty = 200

// immutableReference = mutableReference


// 타입 프로퍼티 및 메서드


Sample.typeProperty = 300

Sample.typeMethod() // type method


//mutableReference.typeProperty = 400

//mutableReference.typeMethod()


class Student {

    var name: String = "unknown"

    var `class`: String = "Swift"

    

    class func selfIntroduce() {

        print("학생타입입니다")

    }

    

    func selfIntroduce() {

        print ("저는 \(self.class)반 \(name)입니다")

    }

}


Student.selfIntroduce() // 학생타입입니다.


var whoamI: Student = Student()

whoamI.name = "whoami"

whoamI.class = "swift"

whoamI.selfIntroduce() // 저는 swift반 whoami입니다


// let 으로 설정해도 변경 가능하다.

let whoareYou: Student = Student()

whoareYou.name = "whoareyou"

whoareYou.selfIntroduce() // 저는 Swift반 whoareyou입니다






import Swift


struct Sample {

    var mutableProperty: Int = 100 // 가변 프로퍼티

    let immutableProperty: Int = 100 // 불변 프로퍼티

    

    static var typeProperty: Int = 100 // 타입 프로퍼티

    

    // 인스턴스 메서드

    func instanceMethod() {

        print("instance method")

    }

    

    // 타입 메서드

    static func typeMethod() {

        print("type method")

    }

}


// 가변 인스턴스

var mutable: Sample = Sample()


mutable.mutableProperty = 200

// mutable.immutableProperty = 200


// 불변 인스턴스

let immutable: Sample = Sample()


// immutable.mutableProperty = 200

// imuutable.immutablePropery = 200


// 타입 프로퍼티 및 메서드

Sample.typeProperty = 300

Sample.typeMethod() // type property


// mutable.typeProperty = 400

// mutable.typeMethod()


struct Student {

    var name: String = "unknown"

    var `class`: String = "Swift"

    

    static func selfIntroduce() {

        print ("학생타입입니다")

    }

    

    func selfIntroduce() {

        print("저는 \(self.class)반 \(name)입니다")

    }

}


Student.selfIntroduce() // 저는 swift반 real_name입니다



var real_name: Student = Student()

real_name.name = "real_name"

real_name.class = "swift"

real_name.selfIntroduce()


let jina: Student = Student()

// 불변 인스턴스이므로 프로퍼티 값 변경 불가

// 컴파일 오류 발생

//jina.name = "jina"

jina.selfIntroduce()


'Swift > 기초&문법' 카테고리의 다른 글

스위프트 swift 열거형 enum  (0) 2018.08.21
스위프트 swift 클래스 class  (0) 2018.08.21
스위프트 swift 옵셔널 추출  (0) 2018.08.15
스위프트 swift 옵셔널  (0) 2018.08.14
스위프트 swift 반복문  (0) 2018.08.14

파이어베이스 ios 구글 로그인 방식을 구현해 보았습니다.



최대한 파이어베이스 문서 위주로 구현하였습니다.


https://firebase.google.com/docs



프로젝트를 만듭니다.




파이어베이스 콘솔로 이동하여서 프로젝트를 추가합니다.


https://console.firebase.google.com/





프로젝트를 추가한 후에는 앱을 추가해줍니다.





앱을 추가할 때는    xcode  에서 bundle identifier 에서 입력된 값을 넣어주어야 합니다.


googleservice-info.plist 다운 받을 때는 뒤에 ( ) 괄호가 붙지 않게 원본 그대로 수정해주셔야 합니다.







아래와 같이 (1)을 삭제해서 원본 그대로 수정해줍니다.









finder에서  googleservice-info.plist 파일을 xcode 로 복사해줍니다.



cooapod 를 설치해주어야 합니다. 



Cocoa pods install


https://guides.cocoapods.org/using/getting-started.html#getting-started





설치한 후에는 xcode project 실제 위치에 가서 pod init  후에 Podfile 을 아래와 같이 수정해줍니다.







수정한 후에는 pod install 을 수행해줍니다.




위에 경고대로 현재 열려있는 xcode 를 닫은 후에 아래와 같이 workspace를 open 해주면 아래와 같은 창이 실행됩니다.







firebase console 로 이동한 후에 authentication 에서 로그인 방법에서 google 을 사용가능하도록 수정해줍니다.






appdelegate와 viewcontroller 파일에 import 해줍니다.




googleservice-info.plist 에서 reversed_client_id 복사한 후에 url_type에  붙여넣습니다.



firebase 문서를 참고하면서 아래 스크립트들을 입력해줍니다.





아래와 같이 뷰를 추가해줍니다. 




뷰를 추가해 준 후에 class에서 아래와 같이 입력해줍니다.




버튼을 아래와 같이 연결해줍니다.





fireabase 문서에서 권장해주는 크기로 수정해줍니다.





appdelegate  에서 credential 밑에 아래와 같이 추가해줍니다.



firebase console에서 authentication 사용자를 보면 아직 사용자가 없다고 나옵니다.




xcode 에서 시뮬리에션을 실행해봅니다.





아이디와 패스워드를 입력하면 아래와 같이 로그인에 성공한 것을 확인할 수 있습니다. 




고맙습니다.

//: Playground - noun: a place where people can play


import Swift


// 옵셔널 추출 방법에는 optional binding 과 force unwrapping 이 있다.

// optional binding : nil 체크 + 안전한 값 추출


func printName(_ name: String) {

    print(name)

}


//var myName: String? = nil


// printName(myName)

// 전달되는 값의 타입이 다르기 때문에 컴파일 오류발생


var myName: String! = nil


if let name: String = myName {

    printName(name)

} else {

    print("myName == nil")

}


// name 상수는 if-let 구문 내에서만 사용가능합니다.

// 상수 사용범위를 벗어났기 때문에 컴파일 오류 발생

// printName(name)


var myName2: String? = "realName2"

var yourName: String? = nil

if let name = myName2, let friend = yourName {

    print("\(name) and \(friend)")

}// yourName 이 nil이기 때문에 실행되지 않는다.


yourName = "realYourName"


if let name = myName2, let friend = yourName {

    print("\(name) and \(friend)")

} // realName2 and realYourName



// Force Unwrapping 옵셔널의 값을 강제로 추출


func printName2(_ name: String){

    print(name)

}


var myName3: String? = "realName"


printName2(myName3!) // realName


myName3 = nil


// print(myName3!)

// 강제 추출시 값이 없으므로 런타임 오류 발생


var yourName3: String! = nil


// printName2(yourName3) //nil 값이 전달되기 때문에 런타임 오류 발생


// optional binding 방식을 추천한다.




'Swift > 기초&문법' 카테고리의 다른 글

스위프트 swift 클래스 class  (0) 2018.08.21
스위프트 swift 구조체  (0) 2018.08.20
스위프트 swift 옵셔널  (0) 2018.08.14
스위프트 swift 반복문  (0) 2018.08.14
스위프트 swift 조건문  (0) 2018.08.14

+ Recent posts