├── fastlane ├── metadata │ ├── copyright.txt │ ├── en-US │ │ ├── keywords.txt │ │ ├── subtitle.txt │ │ ├── description.txt │ │ ├── marketing_url.txt │ │ ├── privacy_url.txt │ │ ├── release_notes.txt │ │ ├── support_url.txt │ │ ├── name.txt │ │ └── promotional_text.txt │ ├── primary_category.txt │ ├── secondary_category.txt │ ├── primary_first_sub_category.txt │ ├── review_information │ │ ├── notes.txt │ │ ├── demo_user.txt │ │ ├── first_name.txt │ │ ├── last_name.txt │ │ ├── phone_number.txt │ │ ├── demo_password.txt │ │ └── email_address.txt │ ├── primary_second_sub_category.txt │ ├── secondary_first_sub_category.txt │ ├── secondary_second_sub_category.txt │ └── trade_representative_contact_information │ │ ├── country.txt │ │ ├── state.txt │ │ ├── city_name.txt │ │ ├── postal_code.txt │ │ ├── address_line2.txt │ │ ├── is_displayed_on_app_store.txt │ │ ├── address_line1.txt │ │ └── trade_name.txt ├── Appfile ├── Matchfile ├── Deliverfile ├── screenshots │ └── README.txt ├── README.md ├── Fastfile └── report.xml ├── .gitignore ├── .Podfile.swp ├── mobile ├── resources │ ├── assets.xcassets │ │ ├── Contents.json │ │ ├── ui │ │ │ ├── Contents.json │ │ │ ├── fade-top.imageset │ │ │ │ ├── fade-top@2x.png │ │ │ │ ├── fade-top@3x.png │ │ │ │ └── Contents.json │ │ │ ├── fade-bottom.imageset │ │ │ │ ├── fade-bottom@2x.png │ │ │ │ ├── fade-bottom@3x.png │ │ │ │ └── Contents.json │ │ │ ├── dark-fade-top.imageset │ │ │ │ ├── dark-fade-top@2x.png │ │ │ │ ├── dark-fade-top@3x.png │ │ │ │ └── Contents.json │ │ │ ├── fade-top-blue.imageset │ │ │ │ ├── fade-top-blue@2x.png │ │ │ │ ├── fade-top-blue@3x.png │ │ │ │ └── Contents.json │ │ │ ├── fade-top-drag.imageset │ │ │ │ ├── fade-top-drag@2x.png │ │ │ │ ├── fade-top-drag@3x.png │ │ │ │ └── Contents.json │ │ │ ├── fade-top-candy.imageset │ │ │ │ ├── fade-top-candy@2x.png │ │ │ │ ├── fade-top-candy@3x.png │ │ │ │ └── Contents.json │ │ │ ├── fade-top-green.imageset │ │ │ │ ├── fade-top-green@2x.png │ │ │ │ ├── fade-top-green@3x.png │ │ │ │ └── Contents.json │ │ │ ├── dark-fade-bottom.imageset │ │ │ │ ├── dark-fade-bottom@2x.png │ │ │ │ ├── dark-fade-bottom@3x.png │ │ │ │ └── Contents.json │ │ │ ├── fade-bottom-blue.imageset │ │ │ │ ├── fade-bottom-blue@2x.png │ │ │ │ ├── fade-bottom-blue@3x.png │ │ │ │ └── Contents.json │ │ │ ├── fade-bottom-drag.imageset │ │ │ │ ├── fade-bottom-drag@2x.png │ │ │ │ ├── fade-bottom-drag@3x.png │ │ │ │ └── Contents.json │ │ │ ├── fade-top-dracula.imageset │ │ │ │ ├── fade-top-dracula@2x.png │ │ │ │ ├── fade-top-dracula@3x.png │ │ │ │ └── Contents.json │ │ │ ├── fade-bottom-candy.imageset │ │ │ │ ├── fade-bottom-candy@2x.png │ │ │ │ ├── fade-bottom-candy@3x.png │ │ │ │ └── Contents.json │ │ │ ├── fade-bottom-green.imageset │ │ │ │ ├── fade-bottom-green@2x.png │ │ │ │ ├── fade-bottom-green@3x.png │ │ │ │ └── Contents.json │ │ │ └── fade-bottom-dracula.imageset │ │ │ │ ├── fade-bottom-dracula@2x.png │ │ │ │ ├── fade-bottom-dracula@3x.png │ │ │ │ └── Contents.json │ │ ├── placeholder.imageset │ │ │ ├── placeholder@3x.png │ │ │ └── Contents.json │ │ └── Mojilist.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── ItunesArtwork@2x.png │ │ │ ├── Icon-Small-50x50@1x.png │ │ │ ├── Icon-Small-50x50@2x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── MojilistBlue@1x.png │ ├── MojilistBlue@2x.png │ ├── MojilistBlue@3x.png │ ├── MojilistCandy@1x.png │ ├── MojilistCandy@2x.png │ ├── MojilistCandy@3x.png │ ├── MojilistDark@1x.png │ ├── MojilistDark@2x.png │ ├── MojilistDark@3x.png │ ├── MojilistDrag@1x.png │ ├── MojilistDrag@2x.png │ ├── MojilistDrag@3x.png │ ├── MojilistDracula@1x.png │ ├── MojilistDracula@2x.png │ ├── MojilistDracula@3x.png │ ├── MojilistGreeny@1x.png │ ├── MojilistGreeny@2x.png │ └── MojilistGreeny@3x.png ├── views │ ├── Xibs.swift │ ├── AboutStoryboard.swift │ ├── Store.storyboard │ ├── MainStoryboard.swift │ └── LaunchScreen.storyboard ├── controllers │ ├── common │ │ ├── BaseDataViewModel.swift │ │ ├── BaseTableViewCell.swift │ │ ├── BaseCollectionViewCell.swift │ │ ├── BaseViewModel.swift │ │ ├── WebViewController.swift │ │ ├── FloatingButtons.swift │ │ ├── BaseTableViewController.swift │ │ ├── BaseCollectionViewController.swift │ │ ├── EmojiDropView.swift │ │ └── BaseViewController.swift │ ├── creating │ │ ├── CreateListViewModel.swift │ │ └── CreateListViewController.swift │ ├── selectPack │ │ ├── AsciiPackCell.swift │ │ ├── BasePackCell.swift │ │ ├── SelectPackViewModel.swift │ │ ├── ImagePackCell.swift │ │ └── SelectPackViewController.swift │ ├── lists │ │ ├── AsciiListCell.swift │ │ ├── BaseListCell.swift │ │ ├── ListsViewModel.swift │ │ ├── ImageListCell.swift │ │ └── ListsViewController.swift │ ├── store │ │ └── StoreViewController.swift │ ├── changeTheme │ │ ├── ChangeThemeViewModel.swift │ │ └── ChangeThemeViewController.swift │ ├── using │ │ ├── AsciiEmojiCell.swift │ │ ├── BaseEmojiCell.swift │ │ ├── ImageEmojiCell.swift │ │ └── UsingListViewModel.swift │ ├── selectEmojis │ │ └── SelectEmojisViewModel.swift │ ├── share │ │ └── ShareSnippetView.swift │ └── about │ │ └── SettingsViewModel.swift ├── core │ ├── Funcs.swift │ ├── StoryboardContext.swift │ ├── Aliases.swift │ ├── Constants.swift │ ├── AppConfig.swift │ ├── Sharing.swift │ ├── Operators.swift │ ├── Tracker.swift │ ├── Extensions.swift │ ├── Emojis.swift │ ├── Launcher.swift │ └── App.swift ├── AppDelegate.swift ├── models │ └── local │ │ ├── REmojiPackItem.swift │ │ ├── REmoji.swift │ │ ├── REmojiPack.swift │ │ └── REmojiList.swift ├── localized │ ├── zh-Hans.lproj │ │ └── Localizable.strings │ ├── ko.lproj │ │ └── Localizable.strings │ ├── ja.lproj │ │ └── Localizable.strings │ ├── en.lproj │ │ └── Localizable.strings │ ├── it.lproj │ │ └── Localizable.strings │ ├── de.lproj │ │ └── Localizable.strings │ ├── ru.lproj │ │ └── Localizable.strings │ ├── pt-BR.lproj │ │ └── Localizable.strings │ ├── pt-PT.lproj │ │ └── Localizable.strings │ ├── es.lproj │ │ └── Localizable.strings │ ├── es-419.lproj │ │ └── Localizable.strings │ └── fr.lproj │ │ └── Localizable.strings └── Info.plist ├── MyPlayground.playground ├── contents.xcplayground ├── playground.xcworkspace │ └── contents.xcworkspacedata └── Contents.swift ├── Emojilist.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcuserdata │ └── thiagoricieri.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── xcshareddata │ └── xcschemes │ └── Emojilist.xcscheme ├── Emojilist.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── tests ├── Info.plist └── EmojilistTests.swift ├── ui_tests ├── Info.plist └── EmojilistUITests.swift ├── README.md ├── .circleci-not-used └── config.yml ├── Podfile └── Podfile.lock /fastlane/metadata/copyright.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/keywords.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/subtitle.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | assets.xcassets/icons/ 3 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/description.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/marketing_url.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/privacy_url.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/release_notes.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/support_url.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/name.txt: -------------------------------------------------------------------------------- 1 | Pods_Emojilist 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/promotional_text.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_first_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/notes.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_second_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/demo_user.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/first_name.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/last_name.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/phone_number.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_first_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_second_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/demo_password.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/email_address.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.Podfile.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/.Podfile.swp -------------------------------------------------------------------------------- /fastlane/metadata/trade_representative_contact_information/country.txt: -------------------------------------------------------------------------------- 1 | Brazil 2 | -------------------------------------------------------------------------------- /fastlane/metadata/trade_representative_contact_information/state.txt: -------------------------------------------------------------------------------- 1 | Parana 2 | -------------------------------------------------------------------------------- /fastlane/metadata/trade_representative_contact_information/city_name.txt: -------------------------------------------------------------------------------- 1 | Curitiba 2 | -------------------------------------------------------------------------------- /fastlane/metadata/trade_representative_contact_information/postal_code.txt: -------------------------------------------------------------------------------- 1 | 80620-010 2 | -------------------------------------------------------------------------------- /fastlane/metadata/trade_representative_contact_information/address_line2.txt: -------------------------------------------------------------------------------- 1 | Agua Verde 2 | -------------------------------------------------------------------------------- /fastlane/metadata/trade_representative_contact_information/is_displayed_on_app_store.txt: -------------------------------------------------------------------------------- 1 | false 2 | -------------------------------------------------------------------------------- /fastlane/metadata/trade_representative_contact_information/address_line1.txt: -------------------------------------------------------------------------------- 1 | Av. Republica Argentina 1160, - Sala 408 2 | -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /fastlane/metadata/trade_representative_contact_information/trade_name.txt: -------------------------------------------------------------------------------- 1 | Ghost Ship Servicos em Tecnologia da Informacao LTDA ME 2 | -------------------------------------------------------------------------------- /mobile/resources/MojilistBlue@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistBlue@1x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistBlue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistBlue@2x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistBlue@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistBlue@3x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistCandy@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistCandy@1x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistCandy@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistCandy@2x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistCandy@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistCandy@3x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistDark@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistDark@1x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistDark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistDark@2x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistDark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistDark@3x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistDrag@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistDrag@1x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistDrag@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistDrag@2x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistDrag@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistDrag@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /mobile/resources/MojilistDracula@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistDracula@1x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistDracula@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistDracula@2x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistDracula@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistDracula@3x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistGreeny@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistGreeny@1x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistGreeny@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistGreeny@2x.png -------------------------------------------------------------------------------- /mobile/resources/MojilistGreeny@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/MojilistGreeny@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top.imageset/fade-top@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top.imageset/fade-top@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top.imageset/fade-top@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top.imageset/fade-top@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/placeholder.imageset/placeholder@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/placeholder.imageset/placeholder@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom.imageset/fade-bottom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom.imageset/fade-bottom@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom.imageset/fade-bottom@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom.imageset/fade-bottom@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-Small-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-Small-50x50@1x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-Small-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-Small-50x50@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/Mojilist.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/dark-fade-top.imageset/dark-fade-top@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/dark-fade-top.imageset/dark-fade-top@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/dark-fade-top.imageset/dark-fade-top@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/dark-fade-top.imageset/dark-fade-top@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-blue.imageset/fade-top-blue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top-blue.imageset/fade-top-blue@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-blue.imageset/fade-top-blue@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top-blue.imageset/fade-top-blue@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-drag.imageset/fade-top-drag@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top-drag.imageset/fade-top-drag@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-drag.imageset/fade-top-drag@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top-drag.imageset/fade-top-drag@3x.png -------------------------------------------------------------------------------- /MyPlayground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-candy.imageset/fade-top-candy@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top-candy.imageset/fade-top-candy@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-candy.imageset/fade-top-candy@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top-candy.imageset/fade-top-candy@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-green.imageset/fade-top-green@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top-green.imageset/fade-top-green@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-green.imageset/fade-top-green@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top-green.imageset/fade-top-green@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/dark-fade-bottom.imageset/dark-fade-bottom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/dark-fade-bottom.imageset/dark-fade-bottom@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/dark-fade-bottom.imageset/dark-fade-bottom@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/dark-fade-bottom.imageset/dark-fade-bottom@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-blue.imageset/fade-bottom-blue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom-blue.imageset/fade-bottom-blue@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-blue.imageset/fade-bottom-blue@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom-blue.imageset/fade-bottom-blue@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-drag.imageset/fade-bottom-drag@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom-drag.imageset/fade-bottom-drag@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-drag.imageset/fade-bottom-drag@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom-drag.imageset/fade-bottom-drag@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-dracula.imageset/fade-top-dracula@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top-dracula.imageset/fade-top-dracula@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-dracula.imageset/fade-top-dracula@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-top-dracula.imageset/fade-top-dracula@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-candy.imageset/fade-bottom-candy@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom-candy.imageset/fade-bottom-candy@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-candy.imageset/fade-bottom-candy@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom-candy.imageset/fade-bottom-candy@3x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-green.imageset/fade-bottom-green@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom-green.imageset/fade-bottom-green@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-green.imageset/fade-bottom-green@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom-green.imageset/fade-bottom-green@3x.png -------------------------------------------------------------------------------- /MyPlayground.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-dracula.imageset/fade-bottom-dracula@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom-dracula.imageset/fade-bottom-dracula@2x.png -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-dracula.imageset/fade-bottom-dracula@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoricieri/Mojilist/HEAD/mobile/resources/assets.xcassets/ui/fade-bottom-dracula.imageset/fade-bottom-dracula@3x.png -------------------------------------------------------------------------------- /Emojilist.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /mobile/views/Xibs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShareStoryboard.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 14/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Xibs { 12 | static let resources = "Resources" 13 | } 14 | -------------------------------------------------------------------------------- /Emojilist.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Emojilist.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | app_identifier "co.ghostship.ios.Emojilist" # The bundle identifier of your app 2 | apple_id "thiago@ghostship.co" # Your Apple email address 3 | team_id "8H5Z8M869P" # Developer Portal Team ID 4 | itc_team_id "118510554" 5 | 6 | # you can even provide different app identifiers, Apple IDs and team names per lane: 7 | # More information: https://docs.fastlane.tools/advanced/#appfile 8 | -------------------------------------------------------------------------------- /mobile/controllers/common/BaseDataViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseDataViewModel.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class BaseDataViewModel: BaseViewModel { 12 | 13 | var itemsCount: Int! { return 0 } 14 | 15 | func loadSource() { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mobile/views/AboutStoryboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutStoryboard.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 18/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class AboutStoryboard: StoryboardContext { 12 | 13 | struct Segue { 14 | static let toWebView = "toWebView" 15 | static let toChangeTheme = "toChangeTheme" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mobile/controllers/creating/CreateListViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateListViewModel.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class CreateListViewModel: BaseViewModel { 12 | 13 | func validateInput(listName: String?) -> Bool { 14 | return listName != nil && !listName!.isEmpty 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /fastlane/Matchfile: -------------------------------------------------------------------------------- 1 | git_url "https://github.com/thiagoricieri/Mojilist-Certificates" 2 | 3 | type "appstore" # The default type, can be: appstore, adhoc, enterprise or development 4 | 5 | # app_identifier ["tools.fastlane.app", "tools.fastlane.app2"] 6 | # username "user@fastlane.tools" # Your Apple Developer Portal username 7 | 8 | # For all available options run `fastlane match --help` 9 | # Remove the # in the beginning of the line to enable the other options 10 | -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "placeholder@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Emojilist.xcodeproj/xcuserdata/thiagoricieri.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Emojilist.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 3 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-top@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-top@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/views/Store.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-bottom@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-bottom@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /fastlane/Deliverfile: -------------------------------------------------------------------------------- 1 | ###################### More Options ###################### 2 | # If you want to have even more control, check out the documentation 3 | # https://docs.fastlane.tools/actions/deliver 4 | 5 | 6 | ###################### Automatically generated ###################### 7 | # Feel free to remove the following line if you use fastlane (which you should) 8 | 9 | app_identifier "co.ghostship.ios.Emojilist" # The bundle identifier of your app 10 | username "thiago@ghostship.co" # your Apple ID user 11 | -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/dark-fade-top.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "dark-fade-top@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "dark-fade-top@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-top-blue@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-top-blue@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-candy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-top-candy@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-top-candy@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-drag.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-top-drag@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-top-drag@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-green.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-top-green@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-top-green@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/dark-fade-bottom.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "dark-fade-bottom@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "dark-fade-bottom@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-bottom-blue@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-bottom-blue@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-drag.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-bottom-drag@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-bottom-drag@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-top-dracula.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-top-dracula@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-top-dracula@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/core/Funcs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Funcs.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 04/01/18. 6 | // Copyright © 2018 GhostShip. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | func UIColorFromRGB(rgb: UInt) -> UIColor { 13 | return UIColor( 14 | red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0, 15 | green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0, 16 | blue: CGFloat(rgb & 0x0000FF) / 255.0, 17 | alpha: CGFloat(1.0) 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-candy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-bottom-candy@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-bottom-candy@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-green.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-bottom-green@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-bottom-green@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/ui/fade-bottom-dracula.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fade-bottom-dracula@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "fade-bottom-dracula@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /fastlane/screenshots/README.txt: -------------------------------------------------------------------------------- 1 | Put all screenshots you want to use inside the folder of its language (e.g. en-US). 2 | The device type will automatically be recognized using the image resolution. Apple TV screenshots 3 | should be stored in a subdirectory named appleTV with language folders inside of it. iMessage 4 | screenshots, like Apple TV screenshots, should also be stored in a subdirectory named iMessage 5 | with language folders inside of it. 6 | 7 | The screenshots can be named whatever you want, but keep in mind they are sorted alphabetically. 8 | -------------------------------------------------------------------------------- /mobile/controllers/selectPack/AsciiPackCell.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // AsciiPackCell.swift 4 | // Emojilist 5 | // 6 | // Created by Thiago Ricieri on 20/01/2018. 7 | // Copyright © 2018 Ghost Ship. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | class AsciiPackCell: BasePackCell { 14 | 15 | static let identifier = "AsciiPackCell" 16 | 17 | @IBOutlet weak var emojiText: UILabel! 18 | 19 | override func configure(with item: EmojiPackViewModel) { 20 | super.configure(with: item) 21 | emojiText.text = item.inlineEmojis 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mobile/controllers/common/BaseTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 10/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class BaseTableViewCell: UITableViewCell { 13 | 14 | func applyTheme(_ theme: Theme) { 15 | theme.background(self) 16 | theme.background(self.contentView) 17 | 18 | if let v = self.backgroundView { theme.background(v) } 19 | if let v = self.selectedBackgroundView { theme.darkBackground(v) } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mobile/controllers/lists/AsciiListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsciiListCell.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class AsciiListCell: BaseListCell { 13 | 14 | static let identifier = "ListCell" 15 | static let cellHeight = CGFloat(100) 16 | 17 | @IBOutlet weak var emojiText: UILabel! 18 | 19 | override func configure(with item: EmojiListViewModel) { 20 | super.configure(with: item) 21 | emojiText.text = item.inlineEmojis 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mobile/controllers/common/BaseCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCollectionViewCell.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class BaseCollectionViewCell: UICollectionViewCell { 13 | 14 | func applyTheme(_ theme: Theme) { 15 | theme.background(self) 16 | theme.background(self.contentView) 17 | 18 | if let v = self.backgroundView { theme.background(v) } 19 | if let v = self.selectedBackgroundView { theme.darkBackground(v) } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mobile/controllers/store/StoreViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreViewController.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 09/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class StoreViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mobile/controllers/common/BaseViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewModel.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | 12 | class BaseViewModel { 13 | 14 | // MARK: - Convenience Vars 15 | 16 | var appDelegate: AppDelegate! { 17 | return UIApplication.shared.delegate as! AppDelegate 18 | } 19 | var app: App! { 20 | return appDelegate.app 21 | } 22 | var realm: Realm! { 23 | return app.realm 24 | } 25 | var theme: Theme! { 26 | return app.theme 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MyPlayground.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import UIKit 4 | 5 | let color: Int = 0xFF0000 6 | 7 | func itemsToFit(inWidth: Int, inHeight: Int, withMargin: Int, withSize: Int) -> Int { 8 | let rows = (inHeight - withMargin) / withSize 9 | let columns = (inWidth - withMargin * 2) / withSize 10 | print("Size \(withSize) = Rows \(rows) Columns \(columns) > \(columns * rows) items max") 11 | return columns * rows 12 | } 13 | 14 | let minSize = 50 15 | let maxSize = 220 16 | let maxWidth = 686 17 | let maxHeight = 514 18 | let margin = 8 19 | 20 | // 21 | for i in minSize...maxSize { 22 | itemsToFit(inWidth: maxWidth, inHeight: maxHeight, withMargin: margin, withSize: i) 23 | } 24 | -------------------------------------------------------------------------------- /mobile/controllers/selectPack/BasePackCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasePackCell.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 12/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SDWebImage 12 | 13 | class BasePackCell: BaseTableViewCell { 14 | 15 | static let cellHeight = CGFloat(100) 16 | 17 | @IBOutlet weak var packName: UILabel! 18 | @IBOutlet weak var separator: UIView! 19 | 20 | func configure(with item: EmojiPackViewModel) { 21 | packName.text = item.name.localized 22 | } 23 | 24 | override func applyTheme(_ theme: Theme) { 25 | super.applyTheme(theme) 26 | theme.separator(separator) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mobile/core/StoryboardContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoryboardContext.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 09/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class StoryboardContext { 13 | 14 | var storyboard: UIStoryboard! 15 | 16 | init(){} 17 | init(name: String){ 18 | storyboard = UIStoryboard(name: name, bundle: nil) 19 | } 20 | 21 | func controller(name: String) -> UIViewController { 22 | return storyboard.instantiateViewController(withIdentifier: name) 23 | } 24 | 25 | func firstController() -> UIViewController { 26 | return storyboard.instantiateInitialViewController()! 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mobile/controllers/changeTheme/ChangeThemeViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChangeThemeViewModel.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ChangeThemeViewModel: BaseDataViewModel { 12 | 13 | var source = [Visuals]() 14 | 15 | override var itemsCount: Int! { 16 | return source.count 17 | } 18 | 19 | override func loadSource() { 20 | super.loadSource() 21 | source = Theme.available 22 | } 23 | 24 | func item(at: Int) -> Visuals { 25 | return source[at] 26 | } 27 | 28 | func changeVisuals(_ newVisuals: Visuals) { 29 | app.changeVisuals(newVisuals) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ui_tests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /mobile/controllers/lists/BaseListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseListCell.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 10/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class BaseListCell: BaseTableViewCell { 13 | 14 | @IBOutlet weak var listName: UILabel! 15 | @IBOutlet weak var backgroundOverlay: UIView! 16 | @IBOutlet weak var separatorView: UIView! 17 | 18 | func configure(with item: EmojiListViewModel) { 19 | listName.text = item.name 20 | } 21 | 22 | override func applyTheme(_ theme: Theme) { 23 | super.applyTheme(theme) 24 | theme.primaryText(listName) 25 | theme.cellBackground(backgroundOverlay) 26 | theme.separator(separatorView) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mobile/controllers/lists/ListsViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListsViewModel.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ListsViewModel: BaseDataViewModel { 12 | 13 | var source = [EmojiListViewModel]() 14 | 15 | override var itemsCount: Int! { 16 | return source.count 17 | } 18 | 19 | override func loadSource() { 20 | super.loadSource() 21 | 22 | source = realm 23 | .objects(REmojiList.self) 24 | .sorted(byKeyPath: "name") 25 | .map { EmojiListViewModel(with: $0) } 26 | } 27 | 28 | func item(at indexPath: IndexPath) -> EmojiListViewModel { 29 | return source[indexPath.row] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mojilist 2 | 🛍 Create shopping lists with emojis! 3 | 4 | 5 | 6 | ## About 7 | Read more about this project [at my blog post](https://thiago.ricieri.com/open-sourcing-my-latest-ios-app). 8 | 9 | ## License 10 | ``` 11 | Copyright 2018 (c) Thiago Ricieri 12 | 13 | Licensed under the Apache License, Version 2.0 (the "License"); 14 | you may not use this file except in compliance with the License. 15 | You may obtain a copy of the License at 16 | 17 | http://www.apache.org/licenses/LICENSE-2.0 18 | 19 | Unless required by applicable law or agreed to in writing, software 20 | distributed under the License is distributed on an "AS IS" BASIS, 21 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | See the License for the specific language governing permissions and 23 | limitations under the License. 24 | ``` 25 | -------------------------------------------------------------------------------- /mobile/controllers/using/AsciiEmojiCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsciiEmojiCell.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 11/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class AsciiEmojiCell: BaseEmojiCell { 13 | 14 | static let identifier = "AsciiEmoji" 15 | 16 | @IBOutlet weak var emojiText: UILabel! 17 | 18 | override func configure(with emoji: EmojiPackItemViewModel) { 19 | super.configure(with: emoji) 20 | emojiText.text = emoji.name 21 | } 22 | 23 | override func configure(with emoji: EmojiViewModel) { 24 | super.configure(with: emoji) 25 | emojiText.text = emoji.name 26 | emojiText.alpha = emoji.alphaForCheckedStatus 27 | } 28 | 29 | override func uncheckEmoji() { 30 | emojiText.alpha = CGFloat(EmojiViewModel.uncheckedAlpha) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mobile/controllers/selectPack/SelectPackViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectPackViewModel.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class SelectPackViewModel: BaseDataViewModel { 12 | 13 | var source: [EmojiPackViewModel]! 14 | 15 | override var itemsCount: Int! { 16 | return source.count 17 | } 18 | 19 | override func loadSource() { 20 | super.loadSource() 21 | source = realm 22 | .objects(REmojiPack.self) 23 | .sorted(byKeyPath: "name") 24 | .map { EmojiPackViewModel(with: $0) } 25 | } 26 | 27 | func item(at indexPath: IndexPath) -> EmojiPackViewModel { 28 | return source[indexPath.row] 29 | } 30 | 31 | func trackChanged(toPack pack: EmojiPackViewModel) { 32 | Tracker.changedPack(packSlug: pack.slug) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mobile/controllers/using/BaseEmojiCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseEmojiCell.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 11/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import Spring 12 | 13 | class BaseEmojiCell: BaseCollectionViewCell { 14 | 15 | @IBOutlet weak var protectionBackground: UIView! 16 | @IBOutlet weak var springView: SpringView! 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | 21 | protectionBackground.clipsToBounds = true 22 | protectionBackground.layer.cornerRadius = protectionBackground.bounds.width/2 23 | } 24 | 25 | override func applyTheme(_ theme: Theme) { 26 | theme.darkBackground(protectionBackground) 27 | } 28 | 29 | func configure(with emoji: EmojiPackItemViewModel) { } 30 | func configure(with emoji: EmojiViewModel) { } 31 | func uncheckEmoji() { } 32 | } 33 | -------------------------------------------------------------------------------- /mobile/controllers/using/ImageEmojiCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEmojiCell.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 11/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SDWebImage 12 | 13 | class ImageEmojiCell: BaseEmojiCell { 14 | 15 | static let identifier = "ImageEmoji" 16 | 17 | @IBOutlet weak var emojiImage: UIImageView! 18 | 19 | override func configure(with emoji: EmojiPackItemViewModel) { 20 | super.configure(with: emoji) 21 | emojiImage.sd_setImage(with: emoji.imageUrl) 22 | } 23 | 24 | override func configure(with emoji: EmojiViewModel) { 25 | super.configure(with: emoji) 26 | emojiImage.sd_setImage(with: emoji.imageUrl) 27 | emojiImage.alpha = emoji.alphaForCheckedStatus 28 | } 29 | 30 | override func uncheckEmoji() { 31 | emojiImage.alpha = CGFloat(EmojiViewModel.uncheckedAlpha) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mobile/core/Aliases.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Aliases.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 04/01/18. 6 | // Copyright © 2018 GhostShip. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | import AVFoundation 12 | 13 | public typealias InBackgroundTuple = (URLRequest?, HTTPURLResponse?, Result) 14 | public typealias InBackground = (URLRequest?, HTTPURLResponse?, Result) -> Void 15 | public typealias InBackgroundResponse = (DataResponse) -> Void 16 | public typealias ApiObject = NSObject 17 | public typealias OnProcessFinished = (Bool, Any?) -> (Void) 18 | public typealias Dict = [String: AnyObject] 19 | public typealias LaunchParams = [UIApplicationLaunchOptionsKey: Any] 20 | 21 | public protocol Parametizable { 22 | func parametize() -> Dict 23 | } 24 | public protocol Objectable { 25 | func toApiObject() -> ApiObject 26 | } 27 | public protocol Routable { 28 | } 29 | 30 | public protocol Visibility { 31 | var isVisible : Bool { get set } 32 | } 33 | -------------------------------------------------------------------------------- /mobile/core/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 04/01/18. 6 | // Copyright © 2018 GhostShip. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | // MARK: - ENVIRONMENT 13 | public struct Env { 14 | 15 | public enum Host : Int { 16 | case staging 17 | case production 18 | } 19 | 20 | public struct Key { 21 | static let userAgent = "User-Agent" 22 | } 23 | 24 | public struct Promo { 25 | static let shareUrl = "https://ghostship.co/mojilist" 26 | static let email = "buh@ghostship.co" 27 | } 28 | 29 | public struct App { 30 | static let theming = "app-theming" 31 | static let defaultPack = "default-pack" 32 | static let maxEmojisPerRow = 7 33 | } 34 | } 35 | 36 | // MARK: - Credentials 37 | protocol Credentials { 38 | static var clientId : String { get } 39 | static var clientSecret : String { get } 40 | } 41 | -------------------------------------------------------------------------------- /mobile/controllers/selectPack/ImagePackCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePackCell.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class ImagePackCell: BasePackCell { 13 | 14 | static let identifier = "ImagePackCell" 15 | 16 | @IBOutlet var emojiImages: [UIImageView]! 17 | 18 | override func configure(with item: EmojiPackViewModel) { 19 | super.configure(with: item) 20 | 21 | emojiImages.forEach { imageView in 22 | let index = emojiImages.index(of: imageView) 23 | if let i = index, 24 | item.firstEmojis.indices.contains(i) { 25 | 26 | let emoji = item.firstEmojis[i] 27 | imageView.sd_setImage(with: emoji.imageUrl) 28 | } 29 | else { 30 | imageView.image = nil 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mobile/controllers/common/WebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 18/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | class WebViewController: BaseViewController { 13 | 14 | @IBOutlet weak var webView: WKWebView! 15 | 16 | var url = "" 17 | 18 | override func instantiateDependencies() { 19 | baseViewModel = BaseViewModel() 20 | } 21 | 22 | override func prepareViewForUser() { 23 | super.prepareViewForUser() 24 | 25 | let myURL = URL(string: url) 26 | let myRequest = URLRequest(url: myURL!) 27 | webView.load(myRequest) 28 | } 29 | 30 | @IBAction func actionSafari(sender: Any) { 31 | let myURL = URL(string: url) 32 | UIApplication.shared.open(myURL!, options: [:], completionHandler: nil) 33 | navigationController?.popViewController(animated: true) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mobile/controllers/lists/ImageListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageListCell.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class ImageListCell: BaseListCell { 13 | 14 | static let identifier = "ImageListCell" 15 | static let cellHeight = CGFloat(100) 16 | 17 | @IBOutlet var emojiImages: [UIImageView]! 18 | 19 | override func configure(with item: EmojiListViewModel) { 20 | super.configure(with: item) 21 | 22 | emojiImages.forEach { imageView in 23 | let index = emojiImages.index(of: imageView) 24 | if let i = index, 25 | item.firstEmojis.indices.contains(i) { 26 | 27 | let emoji = item.firstEmojis[i] 28 | imageView.sd_setImage(with: emoji.imageUrl) 29 | } 30 | else { 31 | imageView.image = nil 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios test 20 | ``` 21 | fastlane ios test 22 | ``` 23 | Runs all the tests 24 | ### ios beta 25 | ``` 26 | fastlane ios beta 27 | ``` 28 | Submit a new Beta Build to Apple TestFlight 29 | 30 | This will also make sure the profile is up to date 31 | ### ios release 32 | ``` 33 | fastlane ios release 34 | ``` 35 | Deploy a new version to the App Store 36 | 37 | ---- 38 | 39 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 40 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 41 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 42 | -------------------------------------------------------------------------------- /mobile/core/AppConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // App.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 04/01/18. 6 | // Copyright © 2018 GhostShip. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /* 12 | App protocol will represent 13 | default configuration to build 14 | and consume the application 15 | */ 16 | public protocol AppConfig { 17 | var environment: Env.Host { get } 18 | var name: String { get } 19 | var restUrl: String { get } 20 | var googleAnalytics: String { get } 21 | } 22 | 23 | // MARK: - Production App 24 | public struct ProductionAppConfigImpl: AppConfig { 25 | 26 | private(set) public var environment = Env.Host.production 27 | private(set) public var name: String = "Emojilist" 28 | private(set) public var restUrl: String = "https://" 29 | } 30 | 31 | // MARK: - Staging App 32 | public struct StagingAppConfigImpl: AppConfig { 33 | 34 | private(set) public var environment = Env.Host.staging 35 | private(set) public var name: String = "Emojilist Staging" 36 | private(set) public var restUrl: String = "https://" 37 | } 38 | -------------------------------------------------------------------------------- /mobile/controllers/common/FloatingButtons.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FloatButton.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 10/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class PrimaryFloatingButton: UIButton { 13 | 14 | override func awakeFromNib() { 15 | super.awakeFromNib() 16 | 17 | self.layer.cornerRadius = 14 18 | self.layer.shadowColor = UIColorFromRGB(rgb: 0x000000).cgColor 19 | self.layer.shadowRadius = 5 20 | self.layer.shadowOpacity = 0.2 21 | self.layer.shadowOffset = CGSize(width: 0, height: 8) 22 | } 23 | } 24 | 25 | class SecondaryFloatingButton: UIButton { 26 | 27 | override func awakeFromNib() { 28 | super.awakeFromNib() 29 | 30 | self.layer.cornerRadius = 8 31 | self.layer.shadowColor = UIColorFromRGB(rgb: 0x000000).cgColor 32 | self.layer.shadowRadius = 3 33 | self.layer.shadowOpacity = 0.2 34 | self.layer.shadowOffset = CGSize(width: 0, height: 6) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/EmojilistTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojilistTests.swift 3 | // EmojilistTests 4 | // 5 | // Created by Thiago Ricieri on 20/12/2017. 6 | // Copyright © 2017 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Emojilist 11 | 12 | class EmojilistTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /mobile/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/12/2017. 6 | // Copyright © 2017 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RealmSwift 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var launcher = Launcher() 16 | var window: UIWindow? 17 | var app: App = { 18 | #if DEBUG 19 | return StagingAppImpl() 20 | #else 21 | return ProductionAppImpl() 22 | #endif 23 | }() 24 | 25 | func application(_ application: UIApplication, 26 | didFinishLaunchingWithOptions launchOptions: LaunchParams?) -> Bool { 27 | 28 | launcher 29 | .setWindow(window) 30 | .shouldProvideCredentials(false) 31 | .setDefaultPack() 32 | .migrateRealm() 33 | .includeStandardPack() 34 | .setFabric() 35 | .setFacebook() 36 | .setTwitter() 37 | .setLaunchOptions(launchOptions) 38 | .startWith(app: app) 39 | 40 | return true 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mobile/models/local/REmojiPackItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // REmojiPackItem.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 10/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | 12 | class REmojiPackItem: Object { 13 | 14 | @objc dynamic var name = "" 15 | @objc dynamic var imageUrl = "" 16 | @objc dynamic var pack = "" 17 | } 18 | 19 | class EmojiPackItemViewModel: BaseViewModel { 20 | 21 | private var model: REmojiPackItem! 22 | 23 | var name: String! { 24 | return model.name 25 | } 26 | var pack: String! { 27 | return model.pack 28 | } 29 | var imageUrl: URL! { 30 | return URL(string: model.imageUrl)! 31 | } 32 | var hasImage: Bool! { 33 | return !model.imageUrl.isEmpty 34 | } 35 | 36 | init(with model: REmojiPackItem) { 37 | self.model = model 38 | } 39 | 40 | init(with viewModel: EmojiViewModel) { 41 | self.model = REmojiPackItem() 42 | self.model.name = viewModel.name 43 | self.model.imageUrl = viewModel.hasImage ? model.imageUrl : "" 44 | self.model.pack = viewModel.pack 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /mobile/views/MainStoryboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainStoryboard.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 11/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class MainStoryboard: StoryboardContext { 12 | 13 | struct Segue { 14 | static let toCreate = "toCreate" 15 | static let toSelectEmojis = "toSelectEmojis" 16 | static let toSelectPack = "toSelectPack" 17 | static let toUsingList = "toUsingList" 18 | static let toShare = "toShare" 19 | static let toSettings = "toSettings" 20 | static let toEditList = "toEditList" 21 | } 22 | 23 | func createListViewController() -> CreateListViewController { 24 | return controller(name: "CreateList") as! CreateListViewController 25 | } 26 | 27 | func selectEmojisViewController() -> SelectEmojisViewController { 28 | return controller(name: "SelectEmojis") as! SelectEmojisViewController 29 | } 30 | 31 | func selectPackViewController() -> SelectPackViewController { 32 | return controller(name: "SelectPack") as! SelectPackViewController 33 | } 34 | 35 | func usingListViewController() -> UsingListViewController { 36 | return controller(name: "UsingList") as! UsingListViewController 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ui_tests/EmojilistUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojilistUITests.swift 3 | // EmojilistUITests 4 | // 5 | // Created by Thiago Ricieri on 20/12/2017. 6 | // Copyright © 2017 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class EmojilistUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /.circleci-not-used/config.yml: -------------------------------------------------------------------------------- 1 | # iOS CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/ios-migrating-from-1-2/ for more details 4 | 5 | version: 2 6 | jobs: 7 | build: 8 | 9 | macos: 10 | xcode: "9.2.0" 11 | 12 | steps: 13 | - checkout 14 | 15 | - run: 16 | name: Update CocoaPods 17 | command: sudo gem install cocoapods 18 | 19 | - run: 20 | name: Update Fastlane 21 | command: sudo gem install fastlane 22 | 23 | - run: 24 | name: Run Match 25 | command: match appstore 26 | 27 | - run: 28 | name: Fastlane Beta 29 | command: fastlane beta 30 | 31 | # Build the app and run tests 32 | # - run: 33 | # name: Build and run tests 34 | # command: fastlane scan 35 | # environment: 36 | # SCAN_DEVICE: iPhone 6 37 | # SCAN_SCHEME: WebTests 38 | 39 | # Collect XML test results data to show in the UI, 40 | # and save the same XML files under test-results folder 41 | # in the Artifacts tab 42 | # - store_test_results: 43 | # path: test_output/report.xml 44 | # - store_artifacts: 45 | # path: /tmp/test-results 46 | # destination: scan-test-results 47 | # - store_artifacts: 48 | # path: ~/Library/Logs/scan 49 | # destination: scan-logs 50 | 51 | -------------------------------------------------------------------------------- /mobile/models/local/REmoji.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiItem.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 10/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | 12 | class REmoji: Object { 13 | 14 | @objc dynamic var name = "" 15 | @objc dynamic var imageUrl = "" 16 | @objc dynamic var pack = "" 17 | @objc dynamic var checked = false 18 | } 19 | 20 | class EmojiViewModel: BaseViewModel { 21 | 22 | private var model: REmoji! 23 | 24 | static let uncheckedAlpha = CGFloat(0.2) 25 | static let checkedAlpha = CGFloat(1.0) 26 | 27 | var name: String! { 28 | return model.name 29 | } 30 | var imageUrl: URL! { 31 | return URL(string: model.imageUrl)! 32 | } 33 | var pack: String! { 34 | return model.pack 35 | } 36 | var isChecked: Bool! { 37 | return model.checked 38 | } 39 | var hasImage: Bool! { 40 | return !model.imageUrl.isEmpty 41 | } 42 | var alphaForCheckedStatus: CGFloat! { 43 | return isChecked ? EmojiViewModel.checkedAlpha : EmojiViewModel.uncheckedAlpha 44 | } 45 | 46 | init(with model: REmoji) { 47 | self.model = model 48 | } 49 | 50 | func change(checked: Bool) { 51 | try! realm.write { 52 | model.checked = checked 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /mobile/controllers/using/UsingListViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UsingListViewModel.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class UsingListViewModel: BaseDataViewModel { 12 | 13 | var source: EmojiListViewModel! 14 | var name: String! { 15 | return source.name 16 | } 17 | 18 | override var itemsCount: Int! { 19 | return source.items.count 20 | } 21 | 22 | init(list: EmojiListViewModel) { 23 | self.source = list 24 | } 25 | 26 | func item(at indexPath: IndexPath) -> EmojiViewModel { 27 | return source.items[indexPath.row] 28 | } 29 | 30 | func toggleEmoji(at indexPath: IndexPath) { 31 | let emoji = item(at: indexPath) 32 | emoji.change(checked: !emoji.isChecked) 33 | } 34 | 35 | func reuseList() { 36 | Tracker.reusedList(itemsCount: itemsCount) 37 | source.uncheckCheckedEmojis() 38 | } 39 | 40 | func deleteList() { 41 | Tracker.deleteList(itemsCount: itemsCount) 42 | source.delete() 43 | } 44 | 45 | func trackSharing() { 46 | Tracker.shareImage() 47 | } 48 | 49 | func trackCompletedList() { 50 | Tracker.completeList( 51 | itemsCount: itemsCount, 52 | itemsUsed: source.checkedEmojis.count) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | def shared_pods 4 | pod 'Spring', :git => 'https://github.com/MengTo/Spring.git' 5 | pod 'MBProgressHUD' 6 | pod 'SDWebImage', '~> 3.7.3' 7 | pod 'Alamofire' 8 | pod 'Fabric' 9 | pod 'Crashlytics', '~> 3.8' 10 | pod 'RealmSwift' 11 | pod 'HTTPStatusCodes', '~> 3.1.0' 12 | pod 'DateToolsSwift' 13 | pod 'PopupDialog', '~> 0.6' 14 | pod 'Firebase/Core' 15 | end 16 | 17 | target 'Emojilist' do 18 | shared_pods 19 | 20 | target 'EmojilistTests' do 21 | inherit! :search_paths 22 | end 23 | 24 | target 'EmojilistUITests' do 25 | inherit! :search_paths 26 | end 27 | end 28 | 29 | #post_install do |installer| 30 | # installer.aggregate_targets.each do |target| 31 | # copy_pods_resources_path = "Pods/Target Support Files/#{target.name}/#{target.name}-resources.sh" 32 | # string_to_replace = '--compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"' 33 | # assets_compile_with_app_icon_arguments = '--compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${BUILD_DIR}/assetcatalog_generated_info.plist"' 34 | # text = File.read(copy_pods_resources_path) 35 | # new_contents = text.gsub(string_to_replace, assets_compile_with_app_icon_arguments) 36 | # File.open(copy_pods_resources_path, "w") {|file| file.puts new_contents } 37 | # end 38 | #end 39 | 40 | -------------------------------------------------------------------------------- /mobile/core/Sharing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sharing.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 15/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class Marketing { 13 | 14 | static func share(app controllerReady: (UIActivityViewController) -> Void) { 15 | let copyToShare = "About.Share.Copy".localized + " " + Env.Promo.shareUrl 16 | let urlToShare = URL(string: Env.Promo.shareUrl)! 17 | let activityViewController = UIActivityViewController( 18 | activityItems: [copyToShare, urlToShare], 19 | applicationActivities: nil) 20 | controllerReady(activityViewController) 21 | } 22 | 23 | static func share(list viewModel: EmojiListViewModel, 24 | controllerReady: (UIActivityViewController) -> Void) { 25 | 26 | let firstActivityItem = "\(viewModel.name) #mojilist" 27 | let secondActivityItem = URL(string: Env.Promo.shareUrl)! 28 | let shareView = Bundle.loadView(fromNib: Xibs.resources, withType: ShareSnippetView.self) 29 | shareView.configure(with: viewModel) 30 | 31 | let image = UIImage(view: shareView) 32 | let activityViewController = UIActivityViewController( 33 | activityItems: [firstActivityItem, secondActivityItem, image], 34 | applicationActivities: nil) 35 | 36 | controllerReady(activityViewController) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mobile/models/local/REmojiPack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // REmojiPack.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 10/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | 12 | class REmojiPack: Object { 13 | 14 | @objc dynamic var slug = "" 15 | @objc dynamic var name = "" 16 | @objc dynamic var url = "" 17 | @objc dynamic var ascii = false 18 | 19 | let emojis = List() 20 | } 21 | 22 | class EmojiPackViewModel: BaseViewModel { 23 | 24 | private var model: REmojiPack! 25 | 26 | var name: String! { 27 | return model.name 28 | } 29 | var slug: String! { 30 | return model.slug 31 | } 32 | var url: String! { 33 | return model.url 34 | } 35 | var isTextual: Bool! { 36 | return model.ascii 37 | } 38 | var items: [EmojiPackItemViewModel]! { 39 | return model.emojis.map { EmojiPackItemViewModel(with: $0) } 40 | } 41 | var firstEmojis: [EmojiPackItemViewModel]! { 42 | let maxEmojis = Env.App.maxEmojisPerRow 43 | return items.count > maxEmojis ? 44 | items[0...maxEmojis].map { $0 } : items 45 | } 46 | var inlineEmojis: String! { 47 | return firstEmojis.reduce("") { soFar, emoji -> String! in 48 | return soFar + " " + emoji.name 49 | } 50 | } 51 | 52 | init(with model: REmojiPack) { 53 | self.model = model 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /mobile/controllers/changeTheme/ChangeThemeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChangeThemeViewController.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 18/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class ChangeThemeViewController: BaseTableViewController { 13 | 14 | var viewModel: ChangeThemeViewModel! 15 | 16 | override func instantiateDependencies() { 17 | baseViewModel = ChangeThemeViewModel() 18 | viewModel = baseViewModel as! ChangeThemeViewModel 19 | } 20 | 21 | override func setViewStyle() { 22 | title = "ChangeTheme.Title".localized 23 | } 24 | 25 | // MARK: - Table View 26 | 27 | override func tableView(_ tableView: UITableView, 28 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 29 | 30 | let cell = tableView.dequeueReusableCell(withIdentifier: "Theme")! 31 | let newVisuals = viewModel.item(at: indexPath.row) 32 | 33 | let theme = viewModel.theme! 34 | theme.cellBackground(cell) 35 | theme.primaryText(cell.textLabel!) 36 | 37 | cell.textLabel?.text = newVisuals.identifier.localized 38 | return cell 39 | } 40 | 41 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 42 | return 60.0 43 | } 44 | 45 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 46 | tableView.deselectRow(at: indexPath, animated: true) 47 | 48 | let newVisuals = viewModel.item(at: indexPath.row) 49 | viewModel.changeVisuals(newVisuals) 50 | applyTheme(viewModel.theme) 51 | reload() 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /mobile/controllers/common/BaseTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTableViewController.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 10/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class BaseTableViewController: BaseViewController, 13 | UITableViewDelegate, 14 | UITableViewDataSource { 15 | 16 | @IBOutlet weak var table: UITableView! 17 | 18 | override func applyTheme(_ theme: Theme) { 19 | super.applyTheme(theme) 20 | theme.background(table) 21 | } 22 | 23 | // MARK: - View Rendering 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | table.delegate = self 28 | table.dataSource = self 29 | } 30 | 31 | override func viewWillAppear(_ animated: Bool) { 32 | super.viewWillAppear(animated) 33 | 34 | if let viewModel = baseViewModel as? BaseDataViewModel { 35 | viewModel.loadSource() 36 | reload() 37 | } 38 | } 39 | 40 | // MARK: - Table Delegate 41 | func reload() { 42 | table.reloadData() 43 | } 44 | 45 | func tableView(_ tableView: UITableView, 46 | numberOfRowsInSection section: Int) -> Int { 47 | if let viewModel = baseViewModel as? BaseDataViewModel { 48 | return viewModel.itemsCount 49 | } 50 | return 0 51 | } 52 | 53 | func tableView(_ tableView: UITableView, 54 | willDisplay cell: UITableViewCell, 55 | forRowAt indexPath: IndexPath) { 56 | if let c = cell as? BaseTableViewCell { 57 | c.applyTheme(baseViewModel.theme) 58 | } 59 | } 60 | 61 | func tableView(_ tableView: UITableView, 62 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 63 | return BaseTableViewCell() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # Customize this file, documentation can be found here: 2 | # https://docs.fastlane.tools/actions/ 3 | # All available actions: https://docs.fastlane.tools/actions 4 | # can also be listed using the `fastlane actions` command 5 | 6 | update_fastlane 7 | min_fastlane_version("2.72.0") 8 | default_platform(:ios) 9 | 10 | platform :ios do 11 | before_all do 12 | # ENV["SLACK_URL"] = "https://hooks.slack.com/services/..." 13 | cocoapods 14 | end 15 | 16 | desc "Runs all the tests" 17 | lane :test do 18 | run_tests 19 | end 20 | 21 | desc "Submit a new Beta Build to Apple TestFlight" 22 | desc "This will also make sure the profile is up to date" 23 | lane :beta do 24 | # sync_code_signing(type: "appstore") # more information: https://codesigning.guide 25 | build_app(scheme: "Emojilist") # more options available 26 | upload_to_testflight 27 | 28 | # sh "your_script.sh" 29 | # You can also use other beta testing services here (run `fastlane actions`) 30 | end 31 | 32 | desc "Deploy a new version to the App Store" 33 | lane :release do 34 | # sync_code_signing(type: "appstore") 35 | capture_screenshots 36 | build_app(scheme: "Emojilist") # more options available 37 | upload_to_app_store(force: true) 38 | # frame_screenshots 39 | end 40 | 41 | # You can define as many lanes as you want 42 | 43 | after_all do |lane| 44 | # This block is called, only if the executed lane was successful 45 | 46 | # slack( 47 | # message: "Successfully deployed new App Update." 48 | # ) 49 | end 50 | 51 | error do |lane, exception| 52 | # slack( 53 | # message: exception.message, 54 | # success: false 55 | # ) 56 | end 57 | end 58 | 59 | # More information about multiple platforms in fastlane: https://docs.fastlane.tools/advanced/#control-configuration-by-lane-and-by-platform 60 | # All available actions: https://docs.fastlane.tools/actions 61 | 62 | # fastlane reports which actions are used. No personal data is recorded. 63 | # Learn more at https://docs.fastlane.tools/#metrics 64 | -------------------------------------------------------------------------------- /mobile/controllers/common/BaseCollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCollectionViewController.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 11/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class BaseCollectionViewController: BaseViewController, 13 | UICollectionViewDelegate, 14 | UICollectionViewDataSource { 15 | 16 | @IBOutlet weak var collection: UICollectionView! 17 | 18 | override func applyTheme(_ theme: Theme) { 19 | super.applyTheme(theme) 20 | theme.background(collection) 21 | } 22 | 23 | // MARK: - View Rendering 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | collection.delegate = self 28 | collection.dataSource = self 29 | } 30 | 31 | override func viewWillAppear(_ animated: Bool) { 32 | super.viewWillAppear(animated) 33 | 34 | if let viewModel = baseViewModel as? BaseDataViewModel { 35 | viewModel.loadSource() 36 | reload() 37 | } 38 | } 39 | 40 | // MARK: - Collection Delegate 41 | 42 | func reload() { 43 | collection.reloadData() 44 | } 45 | 46 | func collectionView(_ collectionView: UICollectionView, 47 | numberOfItemsInSection section: Int) -> Int { 48 | if let viewModel = baseViewModel as? BaseDataViewModel { 49 | return viewModel.itemsCount 50 | } 51 | return 0 52 | } 53 | 54 | func collectionView(_ collectionView: UICollectionView, 55 | willDisplay cell: UICollectionViewCell, 56 | forItemAt indexPath: IndexPath) { 57 | 58 | if let c = cell as? BaseEmojiCell { 59 | c.applyTheme(baseViewModel.theme) 60 | } 61 | } 62 | 63 | func collectionView(_ collectionView: UICollectionView, 64 | cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 65 | return UICollectionViewCell() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /mobile/controllers/selectPack/SelectPackViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectPackViewController.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/12/2017. 6 | // Copyright © 2017 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol SelectPackDelegate { 12 | func packSelected(pack: EmojiPackViewModel) 13 | func selectedPack() -> EmojiPackViewModel 14 | } 15 | 16 | class SelectPackViewController: BaseTableViewController { 17 | 18 | @IBOutlet weak var newListButton: PrimaryFloatingButton! 19 | @IBOutlet weak var storeButton: PrimaryFloatingButton! 20 | 21 | var viewModel: SelectPackViewModel! 22 | var delegate: SelectPackDelegate! 23 | 24 | override func instantiateDependencies() { 25 | baseViewModel = SelectPackViewModel() 26 | viewModel = baseViewModel as! SelectPackViewModel 27 | } 28 | 29 | override func setViewStyle() { 30 | title = "SelectPack.Title".localized 31 | } 32 | 33 | // MARK: - Table View Boilerplate 34 | 35 | override func tableView(_ tableView: UITableView, 36 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 37 | 38 | let item = viewModel.item(at: indexPath) 39 | let cell: BasePackCell! 40 | 41 | if item.isTextual { 42 | cell = tableView.dequeueReusableCell( 43 | withIdentifier: AsciiPackCell.identifier) as! AsciiPackCell 44 | } else { 45 | cell = tableView.dequeueReusableCell( 46 | withIdentifier: ImagePackCell.identifier) as! ImagePackCell 47 | } 48 | cell.configure(with: item) 49 | 50 | return cell 51 | } 52 | 53 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 54 | let item = viewModel.item(at: indexPath) 55 | viewModel.trackChanged(toPack: item) 56 | delegate.packSelected(pack: item) 57 | } 58 | 59 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 60 | return BasePackCell.cellHeight 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mobile/core/Operators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Operators.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 04/01/18. 6 | // Copyright © 2018 GhostShip. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - REGULAR EXPRESSIONS - 12 | struct Regex { 13 | var pattern: String{ 14 | didSet{ 15 | //updateRegex() 16 | } 17 | } 18 | var expressionOptions: NSRegularExpression.Options{ 19 | didSet{ 20 | 21 | } 22 | } 23 | var matchingOptions: NSRegularExpression.MatchingOptions 24 | var regex : NSRegularExpression? 25 | init(pattern: String, expressionOptions: NSRegularExpression.Options, matchingOptions: NSRegularExpression.MatchingOptions) { 26 | self.pattern = pattern 27 | self.expressionOptions = expressionOptions 28 | self.matchingOptions = matchingOptions 29 | } 30 | init(pattern:String) { 31 | self.pattern = pattern 32 | expressionOptions = NSRegularExpression.Options(rawValue: 0) 33 | matchingOptions = NSRegularExpression.MatchingOptions(rawValue: 0) 34 | } 35 | } 36 | 37 | // MARK: - Operator for Comparing Regex - 38 | precedencegroup ComparingRegexPrecedenceGroup { 39 | associativity: left 40 | higherThan: LogicalConjunctionPrecedence 41 | } 42 | infix operator =~ : ComparingRegexPrecedenceGroup 43 | func =~ (left: String, right: Regex) -> Bool { 44 | let range: NSRange = NSMakeRange(0, left.count) 45 | if (right.regex != nil) { 46 | let matches:[AnyObject] = right.regex!.matches(in: left, options: right.matchingOptions, range: range) 47 | return matches.count > 0 48 | } 49 | return false 50 | } 51 | func =~(left: String, right: String) -> Bool { 52 | return left =~ Regex(pattern: right) 53 | } 54 | 55 | // MARK: - Operator for Replacing Regex - 56 | precedencegroup ReplacingRegexPrecedenceGroup { 57 | associativity: left 58 | higherThan: LogicalConjunctionPrecedence 59 | } 60 | infix operator >< : ReplacingRegexPrecedenceGroup 61 | func >< (left:String, right: (regex:Regex,template:String) ) -> String{ 62 | if left =~ right.regex { 63 | let range: NSRange = NSMakeRange(0, left.count) 64 | if (right.regex.regex != nil) { 65 | return right.regex.regex!.stringByReplacingMatches(in: left, options: right.regex.matchingOptions, range: range, withTemplate: right.template) 66 | } 67 | } 68 | return left 69 | } 70 | func >< (left:String, right: (pattern:String,template:String) ) -> String{ 71 | return left >< (Regex(pattern: right.pattern),right.template) 72 | } 73 | -------------------------------------------------------------------------------- /mobile/controllers/common/EmojiDropView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiDropView.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 13/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import Spring 12 | import SDWebImage 13 | 14 | class EmojiDropView: SpringView { 15 | 16 | @IBOutlet weak var emojiText: UILabel! 17 | @IBOutlet weak var emojiImage: UIImageView! 18 | @IBOutlet weak var protectedArea: UIView! 19 | 20 | override func awakeFromNib() { 21 | emojiText.adjustsFontSizeToFitWidth = true 22 | protectedArea.layer.cornerRadius = protectedArea.bounds.width/2 23 | protectedArea.backgroundColor = UIColorFromRGB(rgb: 0xF1F1F1) 24 | //protectedArea.layer.shadowColor = UIColorFromRGB(rgb: 0x000000).cgColor 25 | //protectedArea.layer.shadowRadius = 3 26 | //protectedArea.layer.shadowOpacity = 0.2 27 | //protectedArea.layer.shadowOffset = CGSize(width: 0, height: 6) 28 | } 29 | 30 | func applyTheme(_ theme: Theme) { 31 | theme.darkBackground(protectedArea) 32 | theme.primaryText(emojiText) 33 | } 34 | 35 | func configure(with emoji: EmojiPackItemViewModel) { 36 | if !emoji.hasImage { 37 | emojiImage.isHidden = true 38 | emojiText.isHidden = false 39 | emojiText.text = emoji.name 40 | } else { 41 | emojiImage.isHidden = false 42 | emojiText.isHidden = true 43 | emojiImage.sd_setImage(with: emoji.imageUrl) 44 | } 45 | } 46 | 47 | func configure(with emoji: EmojiViewModel) { 48 | if !emoji.hasImage { 49 | emojiImage.isHidden = true 50 | emojiText.isHidden = false 51 | emojiText.text = emoji.name 52 | } else { 53 | emojiImage.isHidden = false 54 | emojiText.isHidden = true 55 | emojiImage.sd_setImage(with: emoji.imageUrl) 56 | } 57 | } 58 | 59 | func resize(square itemSize: Int) { 60 | protectedArea.layer.cornerRadius = CGFloat(itemSize/2) 61 | emojiText.font = UIFont(name: emojiText.font.fontName, size: CGFloat(itemSize) * 0.6) 62 | } 63 | 64 | func dropAnimation(toX: CGFloat, toY: CGFloat) { 65 | x = toX 66 | animateToNext { 67 | self.y = toY 68 | self.animateTo() 69 | self.animateToNext { 70 | self.removeFromSuperview() 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mobile/core/Tracker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Analytics.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 15/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Firebase 11 | 12 | class Tracker { 13 | 14 | static func newList(itemsCount: Int) { 15 | Analytics.logEvent("new_list", parameters: [ 16 | "items_count": itemsCount as NSObject 17 | ]) 18 | } 19 | 20 | static func changedPack(packSlug: String) { 21 | Analytics.logEvent("changed_pack", parameters: [ 22 | "slug": packSlug as NSObject 23 | ]) 24 | } 25 | 26 | static func completeList(itemsCount: Int, itemsUsed: Int) { 27 | Analytics.logEvent("complete_list", parameters: [ 28 | "items_count": itemsCount as NSObject, 29 | "items_used": itemsUsed as NSObject 30 | ]) 31 | } 32 | 33 | static func deleteList(itemsCount: Int) { 34 | Analytics.logEvent("delete_list", parameters: [ 35 | "items_count": itemsCount as NSObject, 36 | ]) 37 | } 38 | 39 | static func reusedList(itemsCount: Int) { 40 | Analytics.logEvent("reused_list", parameters: [ 41 | "items_count": itemsCount as NSObject, 42 | ]) 43 | } 44 | 45 | static func changedTheme(themeSlug: String) { 46 | Analytics.logEvent("changed_theme", parameters: [ 47 | "slug": themeSlug as NSObject 48 | ]) 49 | } 50 | 51 | static func shareImage() { 52 | Analytics.logEvent("share_image", parameters: nil) 53 | } 54 | 55 | static func shareApp() { 56 | Analytics.logEvent("share_app", parameters: nil) 57 | } 58 | 59 | static func rateApp() { 60 | Analytics.logEvent("rate_app", parameters: nil) 61 | } 62 | 63 | static func followInstagram() { 64 | Analytics.logEvent("follow_instagram", parameters: nil) 65 | } 66 | 67 | static func followTwitter() { 68 | Analytics.logEvent("follow_twitter", parameters: nil) 69 | } 70 | 71 | static func followFacebook() { 72 | Analytics.logEvent("follow_facebook", parameters: nil) 73 | } 74 | 75 | static func followBlog() { 76 | Analytics.logEvent("follow_blog", parameters: nil) 77 | } 78 | 79 | static func signupNewsletter() { 80 | Analytics.logEvent("signup_newsletter", parameters: nil) 81 | } 82 | 83 | static func contactDeveloper() { 84 | Analytics.logEvent("contact_developer", parameters: nil) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /mobile/core/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Foundation+Extensions.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 04/01/18. 6 | // Copyright © 2018 GhostShip. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | // MARK: - Optionals 13 | extension Optional { 14 | func unwrapOrElse(_ val:Wrapped) -> Wrapped { 15 | if self != nil { 16 | return self! 17 | } else { 18 | return val 19 | } 20 | } 21 | 22 | func isNil() -> Bool { 23 | return self == nil 24 | } 25 | 26 | func isNotNil() -> Bool { 27 | return self != nil 28 | } 29 | } 30 | 31 | extension UIImage { 32 | convenience init(view: UIView) { 33 | UIGraphicsBeginImageContextWithOptions(view.frame.size, false, 0) 34 | view.layer.render(in: UIGraphicsGetCurrentContext()!) 35 | 36 | let image = UIGraphicsGetImageFromCurrentImageContext() 37 | UIGraphicsEndImageContext() 38 | self.init(cgImage: image!.cgImage!) 39 | } 40 | } 41 | 42 | extension Bundle { 43 | 44 | static func loadView(fromNib name: String, withType type: T.Type) -> T { 45 | guard let nibs = Bundle.main.loadNibNamed(name, owner: nil, options: nil) as? [UIView] else { 46 | fatalError("Could not load view with type") 47 | } 48 | 49 | for nib in nibs { 50 | if let view = nib as? T { 51 | return view 52 | } 53 | } 54 | 55 | fatalError("Could not load view with type " + String(describing: type)) 56 | } 57 | } 58 | 59 | // MARK: - Debug 60 | extension NSObject { 61 | 62 | // Print + Name 63 | func pn(_ message: String) { 64 | #if DEBUG 65 | print("[\(type(of: self))] \(message)") 66 | #endif 67 | } 68 | 69 | // Just print 70 | func pr(_ message: String) { 71 | #if DEBUG 72 | print("\(message)") 73 | #endif 74 | } 75 | 76 | // Print + Name + Date 77 | func pd(_ message: String) { 78 | #if DEBUG 79 | let date = Date() 80 | pn("at \(date): \(message)") 81 | #endif 82 | } 83 | } 84 | 85 | // MARK: - Localizable 86 | extension String { 87 | var localized: String { 88 | return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") 89 | } 90 | func localizedWithComment(_ comment:String) -> String { 91 | return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: comment) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /mobile/localized/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "是"; 12 | "No" = "没有"; 13 | "Warning" = "警告!"; 14 | "Success" = "成功!"; 15 | "Dismiss" = "解雇"; 16 | "Cancel" = "取消"; 17 | "Shared" = "共享!"; 18 | 19 | // Errors 20 | "Email.Error" = "不幸的是,您的设备未配置为发送电子邮件。 请发送邮件至buh@ghostship.co与我们联系。"; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "事情'Emojis!"; 24 | "Pack.AllEmojis" = "所有的表情符号!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "新"; 29 | "Lists.Settings" = "设置"; 30 | "Lists.Empty" = "空!"; 31 | "Lists.Empty.Msg" = "通过创建可视化列表,您将节省更多的时间。 使用这些名单为您的杂货,购物项目和更多!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "完成!"; 35 | "UsingList.Settings.Title" = "列表的设置"; 36 | "UsingList.Settings.Msg" = "您可以从设备中删除此列表,也可以通过文字与朋友分享图片或在社交网络上分享。"; 37 | "UsingList.Settings.Redo" = "重新启动此列表。"; 38 | "UsingList.Settings.DeleteList" = "删除这个列表"; 39 | "UsingList.Delete.Msg" = "为防万一,请再次确认您要删除此列表。 此操作无法撤消。"; 40 | "UsingList.Settings.DeleteListConfirmation" = "删除列表"; 41 | "UsingList.Settings.Edit" = "编辑列表"; 42 | 43 | // Share 44 | "Share.Credits1" = "与Mojilist创建"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "新列表"; 49 | "CreateList.Text.Placeholder" = "杂货"; 50 | "CreateList.Label" = "你想怎么称呼它?"; 51 | "CreateList.Hint" = "食品杂货,超市或购物物品可以快速创建视觉。 您将在下一步中选择表情符号项目。 玩的开心!"; 52 | "CreateList.Next" = "下一个"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "什么进入名单?"; 56 | "SelectEmojis.Create" = "创建"; 57 | "SelectEmojis.Update" = "更新"; 58 | "SelectEmojis.Clear" = "明确"; 59 | "SelectEmojis.SelectPack" = "选定的包: "; 60 | "SelectEmojis.ItemsInList" = "列表中的项目。 如果需要,触摸一下将其删除。"; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "包"; 64 | 65 | // About 66 | "About.Title" = "设置"; 67 | "About.Settings" = "自定义您的应用程序!"; 68 | "About.About" = "关于"; 69 | "About.Promo" = "分享"; 70 | "About.Follow" = "跟随"; 71 | "About.MoreApps" = "更多应用程序!"; 72 | "About.Settings.DefaultPack" = "默认包: "; 73 | "About.Settings.Theme" = "使用的主题: "; 74 | "About.Promo.Signup" = "注册我们的名单!"; 75 | "About.Promo.Share" = "与朋友分享这个应用程序"; 76 | "About.Promo.Rate" = "评价应用程序 ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "访问博客"; 81 | "About.About.Contact" = "联系开发人员"; 82 | "About.About.Feature" = "发表您的意见"; 83 | "About.About.Version" = "应用版本"; 84 | "About.Share.Copy" = "下载Mojilist,为您的日常列表的视觉替代!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "主题"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 天"; 91 | "Theme.Dark" = "🌚 晚"; 92 | "Theme.Greeny" = "🍁 绿叶"; 93 | "Theme.Blue" = "🌊 海浪"; 94 | "Theme.Drag" = "💄 口红"; 95 | "Theme.Dracula" = "💥 甜蜜的梦"; 96 | "Theme.Candy" = "🍭 糖抢"; 97 | -------------------------------------------------------------------------------- /mobile/controllers/creating/CreateListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateListViewController.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 09/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CreateListViewController: BaseViewController { 12 | 13 | @IBOutlet weak var listNameField: UITextField! 14 | @IBOutlet weak var listNameLabel: UILabel! 15 | @IBOutlet weak var hintLabel: UILabel! 16 | @IBOutlet weak var nextBarButton: UIBarButtonItem! 17 | 18 | var viewModel: CreateListViewModel! 19 | var passListViewModel: EmojiListViewModel? 20 | 21 | override func instantiateDependencies() { 22 | baseViewModel = CreateListViewModel() 23 | viewModel = baseViewModel as! CreateListViewModel 24 | } 25 | 26 | override func applyTheme(_ theme: Theme) { 27 | super.applyTheme(theme) 28 | theme.darkBackground(listNameField) 29 | theme.primaryText(listNameLabel) 30 | theme.primaryText(listNameField) 31 | theme.secondaryText(hintLabel) 32 | } 33 | 34 | override func setViewStyle() { 35 | title = "CreateList.Title".localized 36 | listNameField.placeholder = "CreateList.Text.Placeholder".localized 37 | listNameLabel.text = "CreateList.Label".localized 38 | hintLabel.text = "CreateList.Hint".localized 39 | nextBarButton.title = "CreateList.Next".localized 40 | } 41 | 42 | override func prepareViewForUser() { 43 | listNameField.becomeFirstResponder() 44 | if let listVM = passListViewModel { 45 | listNameField.text = listVM.name 46 | } 47 | navigationController?.isNavigationBarHidden = false 48 | } 49 | 50 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 51 | if segue.identifier == MainStoryboard.Segue.toSelectEmojis { 52 | let dest = segue.destination as! SelectEmojisViewController 53 | 54 | if let listVM = passListViewModel { 55 | listVM.pendingName = listNameField.text! 56 | dest.passListViewModel = listVM 57 | } else { 58 | dest.passListViewModel = EmojiListViewModel(named: listNameField.text!) 59 | } 60 | } 61 | } 62 | 63 | @IBAction func actionNext(sender: Any?) { 64 | if viewModel.validateInput(listName: listNameField.text) { 65 | goToSelectEmojis() 66 | } 67 | } 68 | 69 | func goToSelectEmojis() { 70 | performSegue(withIdentifier: MainStoryboard.Segue.toSelectEmojis, sender: nil) 71 | } 72 | } 73 | 74 | // MARK: - Text Field Delegate 75 | extension CreateListViewController: UITextFieldDelegate { 76 | 77 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 78 | actionNext(sender: nil) 79 | return true 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.7.1) 3 | - Crashlytics (3.10.1): 4 | - Fabric (~> 1.7.5) 5 | - DateToolsSwift (4.0.0) 6 | - DynamicBlurView (2.0.2) 7 | - Fabric (1.7.6) 8 | - Firebase/Core (4.13.0): 9 | - FirebaseAnalytics (= 4.2.0) 10 | - FirebaseCore (= 4.0.20) 11 | - FirebaseAnalytics (4.2.0): 12 | - FirebaseCore (~> 4.0) 13 | - FirebaseInstanceID (~> 2.0) 14 | - GoogleToolboxForMac/NSData+zlib (~> 2.1) 15 | - nanopb (~> 0.3) 16 | - FirebaseCore (4.0.20): 17 | - GoogleToolboxForMac/NSData+zlib (~> 2.1) 18 | - FirebaseInstanceID (2.0.10): 19 | - FirebaseCore (~> 4.0) 20 | - GoogleToolboxForMac/Defines (2.1.3) 21 | - GoogleToolboxForMac/NSData+zlib (2.1.3): 22 | - GoogleToolboxForMac/Defines (= 2.1.3) 23 | - HTTPStatusCodes (3.1.2) 24 | - MBProgressHUD (1.1.0) 25 | - nanopb (0.3.8): 26 | - nanopb/decode (= 0.3.8) 27 | - nanopb/encode (= 0.3.8) 28 | - nanopb/decode (0.3.8) 29 | - nanopb/encode (0.3.8) 30 | - PopupDialog (0.7.1): 31 | - DynamicBlurView (~> 2.0) 32 | - Realm (3.3.2): 33 | - Realm/Headers (= 3.3.2) 34 | - Realm/Headers (3.3.2) 35 | - RealmSwift (3.3.2): 36 | - Realm (= 3.3.2) 37 | - SDWebImage (3.7.6): 38 | - SDWebImage/Core (= 3.7.6) 39 | - SDWebImage/Core (3.7.6) 40 | - Spring (1.0.5) 41 | 42 | DEPENDENCIES: 43 | - Alamofire 44 | - Crashlytics (~> 3.8) 45 | - DateToolsSwift 46 | - Fabric 47 | - Firebase/Core 48 | - HTTPStatusCodes (~> 3.1.0) 49 | - MBProgressHUD 50 | - PopupDialog (~> 0.6) 51 | - RealmSwift 52 | - SDWebImage (~> 3.7.3) 53 | - Spring (from `https://github.com/MengTo/Spring.git`) 54 | 55 | EXTERNAL SOURCES: 56 | Spring: 57 | :git: https://github.com/MengTo/Spring.git 58 | 59 | CHECKOUT OPTIONS: 60 | Spring: 61 | :commit: 19b04d730d3edabbb02a39e58121d1d3220690ee 62 | :git: https://github.com/MengTo/Spring.git 63 | 64 | SPEC CHECKSUMS: 65 | Alamofire: 68d7d521118d49c615a8d2214d87cdf525599d30 66 | Crashlytics: aee1a064cbbf99b32efa3f056a5f458d846bc8ff 67 | DateToolsSwift: 875d97ff9e3a5d54abdd67a269b3f51c757b71ab 68 | DynamicBlurView: aa6db4defc6e537eb86fc5e58dd3991666178399 69 | Fabric: f8d42c893bb187326a7968b62abe55c36a987a46 70 | Firebase: 5ec5e863d269d82d66b4bf56856726f8fb8f0fb3 71 | FirebaseAnalytics: 7ef69e76a5142f643aeb47c780e1cdce4e23632e 72 | FirebaseCore: 90cb1c53d69b556f112a1bf72b5fcfaad7650790 73 | FirebaseInstanceID: 8d20d890d65c917f9f7d9950b6e10a760ad34321 74 | GoogleToolboxForMac: 2501e2ad72a52eb3dfe7bd9aee7dad11b858bd20 75 | HTTPStatusCodes: 88155ba36826366a898a7b4f34d311f49083d546 76 | MBProgressHUD: e7baa36a220447d8aeb12769bf0585582f3866d9 77 | nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 78 | PopupDialog: 39598b7d41719f5c025b4b70e62728bae7734fef 79 | Realm: d927fbf66df5532cfafc08afb5f7e53ded37b894 80 | RealmSwift: 4e903a494e05d866581d69ad6f7e7b879d446baf 81 | SDWebImage: c325cf02c30337336b95beff20a13df489ec0ec9 82 | Spring: 48aad470f3ead043b2da98b64e0bf0630b7ebc42 83 | 84 | PODFILE CHECKSUM: c803d265eb34fe0432eefc82780c49c4438409d5 85 | 86 | COCOAPODS: 1.4.0 87 | -------------------------------------------------------------------------------- /mobile/localized/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "예"; 12 | "No" = "아니"; 13 | "Warning" = "경고!"; 14 | "Success" = "성공!"; 15 | "Dismiss" = "버리다"; 16 | "Cancel" = "취소"; 17 | "Shared" = "공유 된!"; 18 | 19 | // Errors 20 | "Email.Error" = "안타깝게도 기기가 이메일을 보내도록 설정되어 있지 않습니다. buh@ghostship.co로 연락하여 저희에게 연락하십시오."; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "이모티콘!"; 24 | "Pack.AllEmojis" = "모든 이모티콘!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "새로운"; 29 | "Lists.Settings" = "설정"; 30 | "Lists.Empty" = "목록이 없습니다!"; 31 | "Lists.Empty.Msg" = "시각적 목록을 작성하면 훨씬 더 많은 시간을 절약 할 수 있습니다. 식료품 점, 쇼핑 용품 등을 위해이 목록을 사용하십시오!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "끝난!"; 35 | "UsingList.Settings.Title" = "목록 설정"; 36 | "UsingList.Settings.Msg" = "이 목록을 장치에서 제거하거나 텍스트로 친구와 이미지를 공유하거나 소셜 네트워크에서 공유 할 수 있습니다."; 37 | "UsingList.Settings.Redo" = "이 목록을 다시 시작하십시오."; 38 | "UsingList.Settings.DeleteList" = "이 목록 삭제"; 39 | "UsingList.Delete.Msg" = "이 경우 해당 목록을 삭제할 것인지 다시 확인하십시오. 이 작업은 실행 취소 할 수 없습니다."; 40 | "UsingList.Settings.DeleteListConfirmation" = "목록 삭제"; 41 | "UsingList.Settings.Edit" = "목록 수정"; 42 | 43 | // Share 44 | "Share.Credits1" = "Mojilist로 만들었습니다."; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "새 목록"; 49 | "CreateList.Text.Placeholder" = "식료 잡화류"; 50 | "CreateList.Label" = "어떻게 부를까요?"; 51 | "CreateList.Hint" = "팁 : 식료품 점, 슈퍼마켓 또는 쇼핑 아이템을 시각적으로 신속하게 만들 수 있습니다. 다음 단계에서 그림 이모티콘을 선택합니다. 재미있어!"; 52 | "CreateList.Next" = "다음 것"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "무엇이 목록에 포함됩니까?"; 56 | "SelectEmojis.Create" = "새로운"; 57 | "SelectEmojis.Update" = "최신 정보"; 58 | "SelectEmojis.Clear" = "명확한"; 59 | "SelectEmojis.SelectPack" = "선택된 팩: "; 60 | "SelectEmojis.ItemsInList" = "목록의 항목. 필요한 경우 하나를 터치하여 제거하십시오."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "팩"; 64 | 65 | // About 66 | "About.Title" = "설정"; 67 | "About.Settings" = "앱을 맞춤 설정하십시오!"; 68 | "About.About" = "약"; 69 | "About.Promo" = "몫"; 70 | "About.Follow" = "따르다"; 71 | "About.MoreApps" = "더 많은 애플 리케이션!"; 72 | "About.Settings.DefaultPack" = "기본 팩: "; 73 | "About.Settings.Theme" = "사용한 테마: "; 74 | "About.Promo.Signup" = "우리 명부에 등록하십시오!"; 75 | "About.Promo.Share" = "친구와이 앱 공유"; 76 | "About.Promo.Rate" = "앱 평가하기 ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "블로그 방문"; 81 | "About.About.Contact" = "개발자에게 문의하십시오."; 82 | "About.About.Feature" = "의견 보내기"; 83 | "About.About.Version" = "앱 버전"; 84 | "About.Share.Copy" = "Mojilist를 다운로드하십시오. 일상의 목록에 대한 시각적 대안!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "테마"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 일"; 91 | "Theme.Dark" = "🌚 밤"; 92 | "Theme.Greeny" = "🍁 나무"; 93 | "Theme.Blue" = "🌊 파도"; 94 | "Theme.Drag" = "💄 매력"; 95 | "Theme.Dracula" = "💥 달콤한 꿈"; 96 | "Theme.Candy" = "🍭 설탕 러시"; 97 | -------------------------------------------------------------------------------- /mobile/localized/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "はい"; 12 | "No" = "いいえ"; 13 | "Warning" = "警告!"; 14 | "Success" = "成功!"; 15 | "Dismiss" = "却下する"; 16 | "Cancel" = "キャンセル"; 17 | "Shared" = "共有!"; 18 | 19 | // Errors 20 | "Email.Error" = "残念ながら、お使いの端末は電子メールを送信するように設定されていません。 buh@ghostship.coまでお問い合わせください。"; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "物事エモジス!"; 24 | "Pack.AllEmojis" = "すべてのEmojis!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "新しい"; 29 | "Lists.Settings" = "設定"; 30 | "Lists.Empty" = "空の!"; 31 | "Lists.Empty.Msg" = "ビジュアルリストを作成することで、はるかに多くの時間を節約できます。 あなたの食料品、ショッピングアイテムなどにこれらのリストを使用してください!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "完了!"; 35 | "UsingList.Settings.Title" = "リストの設定"; 36 | "UsingList.Settings.Msg" = "このリストを端末から削除したり、友だちと画像をテキストで共有したり、ソーシャルネットワーク上で共有することができます。"; 37 | "UsingList.Settings.Redo" = "このリストを再起動します。"; 38 | "UsingList.Settings.DeleteList" = "このリストを削除する"; 39 | "UsingList.Delete.Msg" = "その場合は、もう一度このリストを削除することを確認してください。 この操作は元に戻すことはできません。"; 40 | "UsingList.Settings.DeleteListConfirmation" = "リストを削除する"; 41 | "UsingList.Settings.Edit" = "リストを編集する"; 42 | 43 | // Share 44 | "Share.Credits1" = "Mojilistで作成"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "新しいリスト"; 49 | "CreateList.Text.Placeholder" = "食料品"; 50 | "CreateList.Label" = "どのようにそれを呼びたいのですか?"; 51 | "CreateList.Hint" = "食料品、スーパーマーケットまたはショッピングアイテムを視覚的に迅速に作成することができます。 次のステップで、絵文字アイテムを選択します。 楽しむ!"; 52 | "CreateList.Next" = "次"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "何がリストに入りますか?"; 56 | "SelectEmojis.Create" = "作成する"; 57 | "SelectEmojis.Update" = "更新"; 58 | "SelectEmojis.Clear" = "クリア"; 59 | "SelectEmojis.SelectPack" = "選択したパック: "; 60 | "SelectEmojis.ItemsInList" = "リスト内の項目。 必要に応じて1つをタップして削除します."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "パック"; 64 | 65 | // About 66 | "About.Title" = "設定"; 67 | "About.Settings" = "あなたのアプリケーションをカスタマイズ!"; 68 | "About.About" = "約"; 69 | "About.Promo" = "シェア"; 70 | "About.Follow" = "フォローする"; 71 | "About.MoreApps" = "より多くのアプリケーション!"; 72 | "About.Settings.DefaultPack" = "デフォルトパック: "; 73 | "About.Settings.Theme" = "使用テーマ: "; 74 | "About.Promo.Signup" = "私たちのリストにサインアップする!"; 75 | "About.Promo.Share" = "このアプリを友達と共有"; 76 | "About.Promo.Rate" = "アプリを評価する ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "ブログをご覧ください"; 81 | "About.About.Contact" = "開発者に連絡する"; 82 | "About.About.Feature" = "意見を述べる"; 83 | "About.About.Version" = "アプリ版"; 84 | "About.Share.Copy" = "あなたの毎日のリストの視覚的な代替物であるMojilistをダウンロードしてください!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "テーマ"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 日"; 91 | "Theme.Dark" = "🌚 夜"; 92 | "Theme.Greeny" = "🍁 青葉"; 93 | "Theme.Blue" = "🌊 海洋波"; 94 | "Theme.Drag" = "💄 チャーム"; 95 | "Theme.Dracula" = "💥 良い夢を"; 96 | "Theme.Candy" = "🍭 シュガーラッシュ"; 97 | -------------------------------------------------------------------------------- /mobile/controllers/selectEmojis/SelectEmojisViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectEmojisViewModel.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class SelectEmojisViewModel: BaseDataViewModel { 12 | 13 | private var listViewModel: EmojiListViewModel! 14 | private var selectedItems = [EmojiPackItemViewModel]() 15 | 16 | var source: EmojiPackViewModel! 17 | 18 | override var itemsCount: Int! { 19 | return source.items.count 20 | } 21 | var listName: String! { 22 | return listViewModel.pendingName ?? listViewModel.name 23 | } 24 | var selectedCount: Int! { 25 | return selectedItems.count 26 | } 27 | var selectedCountText: String! { 28 | return String(selectedCount) 29 | } 30 | var localizedPackName: String! { 31 | return "SelectEmojis.SelectPack".localized + " \(source.name!.localized)" 32 | } 33 | var shouldUpdateList: Bool! { 34 | return listViewModel.pendingName != nil 35 | } 36 | var createButtonLabel: String! { 37 | return listViewModel.pendingName == nil ? "SelectEmojis.Create" : "SelectEmojis.Update" 38 | } 39 | 40 | init(listViewModel: EmojiListViewModel) { 41 | super.init() 42 | self.listViewModel = listViewModel 43 | 44 | let defaults = UserDefaults.standard 45 | if let pack = defaults.string(forKey: Env.App.defaultPack) { 46 | let predicate = NSPredicate(format: "slug = %@", pack) 47 | let packs = realm.objects(REmojiPack.self).filter(predicate) 48 | if packs.count > 0 { 49 | source = EmojiPackViewModel(with: packs.first!) 50 | } 51 | } else { 52 | source = app.standardEmojiPack() 53 | } 54 | 55 | if listViewModel.items.count > 0 { 56 | selectedItems = listViewModel 57 | .items.map { viewModel -> EmojiPackItemViewModel in 58 | EmojiPackItemViewModel(with: viewModel) 59 | } 60 | } 61 | } 62 | 63 | func item(at indexPath: IndexPath) -> EmojiPackItemViewModel { 64 | return source.items[indexPath.row] 65 | } 66 | 67 | // MARK: - Handle Selection 68 | 69 | func item(selectedAt indexPath: IndexPath) -> EmojiPackItemViewModel { 70 | return selectedItems[indexPath.row] 71 | } 72 | 73 | func select(emojiAt indexPath: IndexPath) { 74 | let emoji = item(at: indexPath) 75 | selectedItems.insert(emoji, at: 0) 76 | } 77 | 78 | func updateList() { 79 | listViewModel.update(withPackItems: selectedItems) 80 | Tracker.newList(itemsCount: selectedItems.count) 81 | } 82 | 83 | func createList() { 84 | listViewModel.create(withPackItems: selectedItems) 85 | Tracker.newList(itemsCount: selectedItems.count) 86 | } 87 | 88 | func remove(emojiAt indexPath: IndexPath) { 89 | selectedItems.remove(at: indexPath.row) 90 | } 91 | 92 | func clearList() { 93 | selectedItems = [] 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /mobile/models/local/REmojiList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiList.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 10/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | 12 | class REmojiList: Object { 13 | 14 | @objc dynamic var name = "" 15 | @objc dynamic var dateCreated: Date? = nil 16 | @objc dynamic var timesUsed = 0 17 | 18 | let emojis = List() 19 | } 20 | 21 | class EmojiListViewModel: BaseViewModel { 22 | 23 | private var model: REmojiList! 24 | 25 | var pendingName: String? 26 | 27 | var name: String! { 28 | return model.name 29 | } 30 | var dateCreated: Date? { 31 | return model.dateCreated 32 | } 33 | var timesUsed: Int! { 34 | return model.timesUsed 35 | } 36 | var items: [EmojiViewModel]! { 37 | return model.emojis.map { EmojiViewModel(with: $0) } 38 | } 39 | var checkedEmojis: [EmojiViewModel]! { 40 | return items.filter { $0.isChecked } 41 | } 42 | var firstEmojis: [EmojiViewModel]! { 43 | let maxEmojis = Env.App.maxEmojisPerRow 44 | return items.count > maxEmojis ? 45 | items[0...maxEmojis].map { $0 } : items 46 | } 47 | var inlineEmojis: String! { 48 | return firstEmojis.reduce("") { soFar, emoji -> String! in 49 | return soFar + " " + emoji.name 50 | } 51 | } 52 | 53 | init(named: String) { 54 | self.model = REmojiList() 55 | self.model.name = named 56 | } 57 | init(with model: REmojiList) { 58 | self.model = model 59 | } 60 | 61 | func uncheckCheckedEmojis() { 62 | let allChecked = model.emojis.filter("checked = true") 63 | try! realm.write { 64 | allChecked.setValue(false, forKey: "checked") 65 | } 66 | } 67 | 68 | func delete() { 69 | try! realm.write { 70 | realm.delete(model) 71 | } 72 | } 73 | 74 | func update(withPackItems itemModels: [EmojiPackItemViewModel]) { 75 | try! realm.write { 76 | model.name = pendingName! 77 | realm.delete(model.emojis) 78 | model.emojis.append(objectsIn: itemModels.map { item -> REmoji in 79 | return self.emojiModel(from: item) 80 | }) 81 | } 82 | } 83 | 84 | func create(withPackItems itemModels: [EmojiPackItemViewModel]) { 85 | try! realm.write { 86 | model.timesUsed = 0 87 | model.dateCreated = Date() 88 | model.emojis.append(objectsIn: itemModels.map { item -> REmoji in 89 | return self.emojiModel(from: item) 90 | }) 91 | realm.add(model) 92 | } 93 | } 94 | 95 | func emojiModel(from viewModel: EmojiPackItemViewModel) -> REmoji { 96 | let emoji = REmoji() 97 | emoji.name = viewModel.name 98 | emoji.checked = false 99 | emoji.imageUrl = viewModel.hasImage ? viewModel.imageUrl.absoluteString : "" 100 | emoji.pack = viewModel.pack 101 | return emoji 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /mobile/localized/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "Yes"; 12 | "No" = "No"; 13 | "Warning" = "Warning!"; 14 | "Success" = "Success!"; 15 | "Dismiss" = "Dismiss"; 16 | "Cancel" = "Cancel"; 17 | "Shared" = "Shared!"; 18 | 19 | // Errors 20 | "Email.Error" = "Unfortunately, your device is not configured to send emails. Please, send a message to buh@ghostship.co to contact us."; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "Things' Emojis!"; 24 | "Pack.AllEmojis" = "All Emojis!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "New"; 29 | "Lists.Settings" = "Settings"; 30 | "Lists.Empty" = "No list!"; 31 | "Lists.Empty.Msg" = "You will save much more time by creating visual list. Use these lists for your groceries, shopping items and more!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "Done!"; 35 | "UsingList.Settings.Title" = "List's Settings"; 36 | "UsingList.Settings.Msg" = "You can remove this list from your device or share an image of it with friends by text or share it on your social networks."; 37 | "UsingList.Settings.Redo" = "Restart this list."; 38 | "UsingList.Settings.DeleteList" = "Delete this list"; 39 | "UsingList.Delete.Msg" = "Just in case, please confirm again you want to delete this list. This action cannot be undone."; 40 | "UsingList.Settings.DeleteListConfirmation" = "Delete the list"; 41 | "UsingList.Settings.Edit" = "Edit the list"; 42 | 43 | // Share 44 | "Share.Credits1" = "Created with Mojilist"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "New List"; 49 | "CreateList.Text.Placeholder" = "Groceries"; 50 | "CreateList.Label" = "How do you want to call it?"; 51 | "CreateList.Hint" = "Tip: groceries, supermarket or shopping items can be quickly created visually. You will select emoji items in the next step. Have fun!"; 52 | "CreateList.Next" = "Next"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "What goes into the list?"; 56 | "SelectEmojis.Create" = "Create"; 57 | "SelectEmojis.Update" = "Update"; 58 | "SelectEmojis.Clear" = "Clear"; 59 | "SelectEmojis.SelectPack" = "Selected Pack: "; 60 | "SelectEmojis.ItemsInList" = "Items in the list. Touch in one to remove it, if needed."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "Packs"; 64 | 65 | // About 66 | "About.Title" = "Settings"; 67 | "About.Settings" = "Customize your App!"; 68 | "About.About" = "About"; 69 | "About.Promo" = "Share"; 70 | "About.Follow" = "Follow"; 71 | "About.MoreApps" = "More apps!"; 72 | "About.Settings.DefaultPack" = "Default pack: "; 73 | "About.Settings.Theme" = "Theme used: "; 74 | "About.Promo.Signup" = "Signup to our list!"; 75 | "About.Promo.Share" = "Share this app with friends"; 76 | "About.Promo.Rate" = "Rate the app ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "Visit the Blog"; 81 | "About.About.Contact" = "Contact the developer"; 82 | "About.About.Feature" = "Send an opinion"; 83 | "About.About.Version" = "App version"; 84 | "About.Share.Copy" = "Download Mojilist, a visual alternative for your everyday's lists!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "Themes"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 Mojilist by Day"; 91 | "Theme.Dark" = "🌚 Mojilist by Night"; 92 | "Theme.Greeny" = "🍁 Green leaves"; 93 | "Theme.Blue" = "🌊 Ocean waves"; 94 | "Theme.Drag" = "💄 Charm"; 95 | "Theme.Dracula" = "💥 Sweet Dreams"; 96 | "Theme.Candy" = "🍭 Sugar Rush"; 97 | -------------------------------------------------------------------------------- /mobile/localized/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "Sì"; 12 | "No" = "No"; 13 | "Warning" = "Attenzione!"; 14 | "Success" = "Successo!"; 15 | "Dismiss" = "Respingere"; 16 | "Cancel" = "Annullare"; 17 | "Shared" = "Condivisa!"; 18 | 19 | // Errors 20 | "Email.Error" = "Il tuo dispositivo non supporta l'invio di email. Si prega di inviare una mail a buh@ghostship.co per entrare in contatto."; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "Emojis delle Cose!"; 24 | "Pack.AllEmojis" = "Tutti gli Emoji!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "Nuova"; 29 | "Lists.Settings" = "Aggiustamenti"; 30 | "Lists.Empty" = "Nessuna lista!"; 31 | "Lists.Empty.Msg" = "Puoi risparmiare molto più tempo creando elenchi visivi. Usa le liste della spesa della settimana, lavoretti e altro!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "Pronto!"; 35 | "UsingList.Settings.Title" = "Aggiustamenti"; 36 | "UsingList.Settings.Msg" = "Puoi rimuovere l'intero elenco dal tuo dispositivo o condividerne un'immagine sui tuoi social network."; 37 | "UsingList.Settings.Redo" = "Riavvia la lista."; 38 | "UsingList.Settings.DeleteList" = "Cancella la lista"; 39 | "UsingList.Delete.Msg" = "Conferma la rimozione della lista"; 40 | "UsingList.Settings.DeleteListConfirmation" = "Rimuovi la lista"; 41 | "UsingList.Settings.Edit" = "Modifica lista"; 42 | 43 | // Share 44 | "Share.Credits1" = "Creato con Mojilist"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "Nuova Lista"; 49 | "CreateList.Text.Placeholder" = "Shopping della settimana"; 50 | "CreateList.Label" = "Come sarà chiamata la nuova lista?"; 51 | "CreateList.Hint" = "Liste di cose da fare, negozi di alimentari o materiale scolastico possono essere creati rapidamente visivamente. Seleziona emoji nel passaggio successivo e divertiti!"; 52 | "CreateList.Next" = "Seguente"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "Cosa c'è nella lista?"; 56 | "SelectEmojis.Create" = "Creare"; 57 | "SelectEmojis.Update" = "Aggiornare"; 58 | "SelectEmojis.Clear" = "Per svuotare"; 59 | "SelectEmojis.SelectPack" = "Pacchetto selezionato:"; 60 | "SelectEmojis.ItemsInList" = "Toccare una volta per rimuovere."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "Pacchetti"; 64 | 65 | // About 66 | "About.Title" = "Aggiustamenti"; 67 | "About.Settings" = "Personalizza la tua app!"; 68 | "About.About" = "Circa"; 69 | "About.Promo" = "Condividi questo"; 70 | "About.Follow" = "Segui sui social network"; 71 | "About.MoreApps" = "Altre apps!"; 72 | "About.Settings.DefaultPack" = "Pacchetto standard: "; 73 | "About.Settings.Theme" = "Tema usato: "; 74 | "About.Promo.Signup" = "Iscriviti alla Newsletter!"; 75 | "About.Promo.Share" = "Condividi questa app"; 76 | "About.Promo.Rate" = "Valuta questa app ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "Visita il blog"; 81 | "About.About.Contact" = "Contatta lo sviluppatore"; 82 | "About.About.Feature" = "Scrivi una recensione"; 83 | "About.About.Version" = "Versione dell'app"; 84 | "About.Share.Copy" = "Scarica Mojilist, un'alternativa visiva alle tue liste giornaliere!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "Temi"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 Giorno"; 91 | "Theme.Dark" = "🌚 Notte"; 92 | "Theme.Greeny" = "🍁 Fogliame"; 93 | "Theme.Blue" = "🌊 Onde del mare"; 94 | "Theme.Drag" = "💄 Bellezza rara"; 95 | "Theme.Dracula" = "💥 Sogni d'oro"; 96 | "Theme.Candy" = "🍭 Più zucchero!"; 97 | -------------------------------------------------------------------------------- /mobile/localized/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "Ja"; 12 | "No" = "Nicht"; 13 | "Warning" = "Achtung!"; 14 | "Success" = "Erfolg!"; 15 | "Dismiss" = "Entlassen"; 16 | "Cancel" = "Abbrechen"; 17 | "Shared" = "Geteilt!"; 18 | 19 | // Errors 20 | "Email.Error" = "Ihr Gerät unterstützt das Senden von E-Mails nicht. Bitte senden Sie eine E-Mail an buh@ghostship.co, um sich zu melden.."; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "Emoji der Dinge!"; 24 | "Pack.AllEmojis" = "Alle Emojis!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "Neu"; 29 | "Lists.Settings" = "Einstellungen"; 30 | "Lists.Empty" = "Keine Liste!"; 31 | "Lists.Empty.Msg" = "Sie sparen viel Zeit, indem Sie visuelle Listen erstellen. Verwenden Sie Ihre Einkaufslisten der Woche, Schulbedarf, Hausarbeiten und vieles mehr!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "Erledigt!"; 35 | "UsingList.Settings.Title" = "Listeneinstellungen"; 36 | "UsingList.Settings.Msg" = "Sie können Ihre gesamte Liste von Ihrem Gerät entfernen oder ein Bild davon in Ihren sozialen Netzwerken teilen."; 37 | "UsingList.Settings.Redo" = "Starten Sie die Liste neu."; 38 | "UsingList.Settings.DeleteList" = "Löschen Sie diese Liste"; 39 | "UsingList.Delete.Msg" = "Bitte bestätigen Sie das Entfernen der folgenden Liste:"; 40 | "UsingList.Settings.DeleteListConfirmation" = "Löschen Sie die Liste"; 41 | "UsingList.Settings.Edit" = "Liste bearbeiten"; 42 | 43 | // Share 44 | "Share.Credits1" = "Erstellt mit Mojilist"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "Neue Liste"; 49 | "CreateList.Text.Placeholder" = "Einkaufen der Woche"; 50 | "CreateList.Label" = "Wie wird die neue Liste heißen?"; 51 | "CreateList.Hint" = "Aufgabenlisten, Lebensmittel oder Schulbedarf können schnell visuell erstellt werden. Wählen Sie Emojis im nächsten Schritt und genießen Sie!"; 52 | "CreateList.Next" = "Weiter"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "Was ist auf der Liste?"; 56 | "SelectEmojis.Create" = "Erstellen"; 57 | "SelectEmojis.Update" = "Aktualisieren"; 58 | "SelectEmojis.Clear" = "Um zu leeren"; 59 | "SelectEmojis.SelectPack" = "Ausgewähltes Paket:"; 60 | "SelectEmojis.ItemsInList" = "Tippen Sie einmal auf, um zu entfernen."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "Pakete"; 64 | 65 | // About 66 | "About.Title" = "Einstellungen"; 67 | "About.Settings" = "Passen Sie Ihre App an!"; 68 | "About.About" = "Über"; 69 | "About.Promo" = "Teilen Sie dies"; 70 | "About.Follow" = "Folge in sozialen Netzwerken"; 71 | "About.MoreApps" = "Mehr Apps!"; 72 | "About.Settings.DefaultPack" = "Standardpaket: "; 73 | "About.Settings.Theme" = "Thema: "; 74 | "About.Promo.Signup" = "Abonniere den Newsletter!"; 75 | "About.Promo.Share" = "Teilen Sie diese App"; 76 | "About.Promo.Rate" = "Bewerten Sie diese App ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "Besuchen Sie den Blog"; 81 | "About.About.Contact" = "Kontakt Entwickler"; 82 | "About.About.Feature" = "Überprüfen Sie die App"; 83 | "About.About.Version" = "App-Version"; 84 | "About.Share.Copy" = "Laden Sie Mojilist herunter, eine visuelle Alternative zu Ihren täglichen Listen!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "Thema"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 Tageszeit"; 91 | "Theme.Dark" = "🌚 Nacht"; 92 | "Theme.Greeny" = "🍁 Blätter"; 93 | "Theme.Blue" = "🌊 Meereswellen"; 94 | "Theme.Drag" = "💄 Fußgängerbrücke"; 95 | "Theme.Dracula" = "💥 Schöne Träume"; 96 | "Theme.Candy" = "🍭 Mehr Zucker, bitte!"; 97 | -------------------------------------------------------------------------------- /mobile/controllers/lists/ListsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/12/2017. 6 | // Copyright © 2017 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ListsViewController: BaseTableViewController { 12 | 13 | @IBOutlet weak var newListButton: PrimaryFloatingButton! 14 | @IBOutlet weak var storeButton: PrimaryFloatingButton! 15 | @IBOutlet weak var settingsBarItem: UIBarButtonItem! 16 | @IBOutlet weak var emptyView: UIView! 17 | @IBOutlet weak var emptyTitle: UILabel! 18 | @IBOutlet weak var emptyMsg: UILabel! 19 | @IBOutlet weak var downDecoration: UIImageView! 20 | @IBOutlet weak var emptyDecoration: UIImageView! 21 | 22 | var viewModel: ListsViewModel! 23 | 24 | override func instantiateDependencies() { 25 | baseViewModel = ListsViewModel() 26 | viewModel = baseViewModel as! ListsViewModel 27 | } 28 | 29 | override func setViewStyle() { 30 | title = "Lists.Title".localized 31 | newListButton.setTitle("Lists.New".localized, for: .normal) 32 | emptyTitle.text = "Lists.Empty".localized 33 | emptyMsg.text = "Lists.Empty.Msg".localized 34 | } 35 | 36 | override func applyTheme(_ theme: Theme) { 37 | super.applyTheme(theme) 38 | theme.actionButton(newListButton) 39 | theme.primaryText(emptyTitle) 40 | theme.secondaryText(emptyMsg) 41 | 42 | downDecoration.image = UIImage(named: theme.downArrowDecoration()) 43 | emptyDecoration.image = UIImage(named: theme.emptyBoxDecoration()) 44 | } 45 | 46 | override func reload() { 47 | super.reload() 48 | emptyView.isHidden = viewModel.itemsCount > 0 49 | } 50 | 51 | // MARK: - Table View 52 | 53 | override func tableView(_ tableView: UITableView, 54 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 55 | 56 | let cell = tableView.dequeueReusableCell( 57 | withIdentifier: AsciiListCell.identifier) as! AsciiListCell 58 | 59 | let item = viewModel.item(at: indexPath) 60 | cell.configure(with: item) 61 | 62 | return cell 63 | } 64 | 65 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 66 | tableView.deselectRow(at: indexPath, animated: true) 67 | 68 | performSegue( 69 | withIdentifier: MainStoryboard.Segue.toUsingList, 70 | sender: viewModel.item(at: indexPath)) 71 | } 72 | 73 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 74 | return AsciiListCell.cellHeight 75 | } 76 | 77 | // MARK: - Segue 78 | 79 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 80 | if segue.identifier == MainStoryboard.Segue.toUsingList { 81 | let dest = segue.destination as! UINavigationController 82 | let vc = dest.viewControllers.first! as! UsingListViewController 83 | vc.passListViewModel = sender as! EmojiListViewModel 84 | } 85 | } 86 | 87 | // MARK: - Button Actions 88 | 89 | @IBAction func actionNewList(_ sender: UIView) { 90 | performSegue(withIdentifier: MainStoryboard.Segue.toCreate, sender: nil) 91 | } 92 | 93 | @IBAction func actionStore(_ sender: UIView) { 94 | successAlert(message: "Open Store!") 95 | } 96 | 97 | @IBAction func actionSettings(_ sender: Any) { 98 | performSegue(withIdentifier: MainStoryboard.Segue.toSettings, sender: nil) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /mobile/localized/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "да"; 12 | "No" = "нет"; 13 | "Warning" = "Предупреждение!"; 14 | "Success" = "Успех!"; 15 | "Dismiss" = "отклонять"; 16 | "Cancel" = "Отмена"; 17 | "Shared" = "Общий!"; 18 | 19 | // Errors 20 | "Email.Error" = "К сожалению, ваше устройство не настроено на отправку писем. Пожалуйста, отправьте сообщение на адрес buh@ghostship.co, чтобы связаться с нами."; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "Эможи!"; 24 | "Pack.AllEmojis" = "Все эмоции!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "новый"; 29 | "Lists.Settings" = "настройки"; 30 | "Lists.Empty" = "Пустое!"; 31 | "Lists.Empty.Msg" = "Вы сможете сэкономить гораздо больше времени, создав визуальный список. Используйте эти списки для своих продуктов, покупок и многое другое!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "Готово!"; 35 | "UsingList.Settings.Title" = "Настройки списка"; 36 | "UsingList.Settings.Msg" = "Вы можете удалить этот список с вашего устройства или поделиться им с друзьями по тексту или поделиться им в своих социальных сетях."; 37 | "UsingList.Settings.Redo" = "Перезагрузите этот список."; 38 | "UsingList.Settings.DeleteList" = "Удалить этот список"; 39 | "UsingList.Delete.Msg" = "На всякий случай, пожалуйста, подтвердите, что хотите удалить этот список. Это действие не может быть отменено."; 40 | "UsingList.Settings.DeleteListConfirmation" = "Удалить список"; 41 | "UsingList.Settings.Edit" = "Редактировать список"; 42 | 43 | // Share 44 | "Share.Credits1" = "Создано с Mojilist"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "Новый список"; 49 | "CreateList.Text.Placeholder" = "бакалейные товары"; 50 | "CreateList.Label" = "Как вы хотите это назвать?"; 51 | "CreateList.Hint" = "бакалея, супермаркет или магазины могут быть быстро созданы визуально. На следующем шаге вы выберете элементы emoji. Повеселись!"; 52 | "CreateList.Next" = "следующий"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "Что входит в список?"; 56 | "SelectEmojis.Create" = "Создайте"; 57 | "SelectEmojis.Update" = "Обновить"; 58 | "SelectEmojis.Clear" = "Очистить"; 59 | "SelectEmojis.SelectPack" = "Выбранный пакет: "; 60 | "SelectEmojis.ItemsInList" = "Нажмите один, чтобы удалить его."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "пакеты"; 64 | 65 | // About 66 | "About.Title" = "настройки"; 67 | "About.Settings" = "Настройте свое приложение!"; 68 | "About.About" = "Около"; 69 | "About.Promo" = "Поделиться"; 70 | "About.Follow" = "следить"; 71 | "About.MoreApps" = "Больше приложений!"; 72 | "About.Settings.DefaultPack" = "Пакет по умолчанию: "; 73 | "About.Settings.Theme" = "Используемая тема: "; 74 | "About.Promo.Signup" = "Зарегистрируйтесь в нашем списке!"; 75 | "About.Promo.Share" = "Поделиться с друзьями"; 76 | "About.Promo.Rate" = "Оценить приложение ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "Посетите блог"; 81 | "About.About.Contact" = "Связаться с разработчиком"; 82 | "About.About.Feature" = "Отправить мнение"; 83 | "About.About.Version" = "Версия приложения"; 84 | "About.Share.Copy" = "Загрузите Mojilist, визуальную альтернативу для ваших ежедневных списков!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "Темы"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 День"; 91 | "Theme.Dark" = "🌚 Ночь"; 92 | "Theme.Greeny" = "🍁 Зеленые листья"; 93 | "Theme.Blue" = "🌊 Океанские волны"; 94 | "Theme.Drag" = "💄 Это очаровательно"; 95 | "Theme.Dracula" = "💥 Сладкие Мечты"; 96 | "Theme.Candy" = "🍭 Сахарная лихорадка."; 97 | -------------------------------------------------------------------------------- /mobile/localized/pt-BR.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "Sim"; 12 | "No" = "Não"; 13 | "Warning" = "Atenção!"; 14 | "Success" = "Sucesso!"; 15 | "Dismiss" = "Dispensar"; 16 | "Cancel" = "Cancelar"; 17 | "Shared" = "Compartilhamento pronto!"; 18 | 19 | // Errors 20 | "Email.Error" = "Aparentemente seu aparelho não suporta envio de e-mail. Por favor, envie um e-mail para buh@ghostship.co para entrar em contato."; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "Emojis de Coisas!"; 24 | "Pack.AllEmojis" = "Todos os Emojis!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "Nova"; 29 | "Lists.Settings" = "Ajustes"; 30 | "Lists.Empty" = "Nenhuma lista!"; 31 | "Lists.Empty.Msg" = "Você economiza muito mais tempo criando listas visuais. Use suas listas para compras da semana, material escolar, afazeres e mais!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "Pronto!"; 35 | "UsingList.Settings.Title" = "Ajustes da lista"; 36 | "UsingList.Settings.Msg" = "Você pode remover sua lista por completo do seu aparelho ou compartilhar uma imagem dela nas suas redes sociais."; 37 | "UsingList.Settings.Redo" = "Começar a lista do zero."; 38 | "UsingList.Settings.DeleteList" = "Deletar esta lista"; 39 | "UsingList.Delete.Msg" = "Só para assegurar-se que você não tocou no botão de deletar sem querer, confirme a remoção da lista a seguir:"; 40 | "UsingList.Settings.DeleteListConfirmation" = "Deletar a lista"; 41 | "UsingList.Settings.Edit" = "Editar a lista"; 42 | 43 | // Share 44 | "Share.Credits1" = "Criado com Mojilist"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "Nova Lista"; 49 | "CreateList.Text.Placeholder" = "Compras da semana"; 50 | "CreateList.Label" = "Como vai se chamar a nova lista?"; 51 | "CreateList.Hint" = "Dica: listas de afazeres, supermercado ou material escolar podem ser criadas rapidamente visualmente. Selecione emojis na próxima etapa e divirta-se!"; 52 | "CreateList.Next" = "Seguinte"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "O que vai na lista?"; 56 | "SelectEmojis.Create" = "Criar"; 57 | "SelectEmojis.Update" = "Atualizar"; 58 | "SelectEmojis.Clear" = "Esvaziar"; 59 | "SelectEmojis.SelectPack" = "Pacote selecionado:"; 60 | "SelectEmojis.ItemsInList" = "Itens na lista. Se precisar, toque uma vez para remover."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "Pacotes"; 64 | 65 | // About 66 | "About.Title" = "Ajustes"; 67 | "About.Settings" = "Personalize seu App!"; 68 | "About.About" = "Sobre"; 69 | "About.Promo" = "Compartilhe"; 70 | "About.Follow" = "Siga nas redes sociais"; 71 | "About.MoreApps" = "Mais apps!"; 72 | "About.Settings.DefaultPack" = "Pacote padrão: "; 73 | "About.Settings.Theme" = "Tema usado: "; 74 | "About.Promo.Signup" = "Inscreva-se na Newsletter!"; 75 | "About.Promo.Share" = "Compartilhe este app"; 76 | "About.Promo.Rate" = "Avalie este app ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "Visite o Blog"; 81 | "About.About.Contact" = "E-mail para o desenvolvedor"; 82 | "About.About.Feature" = "Opine sobre o app"; 83 | "About.About.Version" = "Versão do app"; 84 | "About.Share.Copy" = "Baixe o Mojilist, uma alternativa visual para suas listas do dia-a-dia!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "Temas"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 Mojilist Diurno"; 91 | "Theme.Dark" = "🌚 Mojilist Noturno"; 92 | "Theme.Greeny" = "🍁 Maresia"; 93 | "Theme.Blue" = "🌊 Deu Onda"; 94 | "Theme.Drag" = "💄 Que Tiro Foi Esse?"; 95 | "Theme.Dracula" = "💥 Sweet Dreams"; 96 | "Theme.Candy" = "🍭 Agora Eu Fiquei Doce"; 97 | -------------------------------------------------------------------------------- /mobile/localized/pt-PT.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "Sim"; 12 | "No" = "Não"; 13 | "Warning" = "Atenção!"; 14 | "Success" = "Sucesso!"; 15 | "Dismiss" = "Dispensar"; 16 | "Cancel" = "Cancelar"; 17 | "Shared" = "Compartilhamento pronto!"; 18 | 19 | // Errors 20 | "Email.Error" = "Aparentemente seu aparelho não suporta envio de e-mail. Por favor, envie um e-mail para buh@ghostship.co para entrar em contato."; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "Emojis de Coisas!"; 24 | "Pack.AllEmojis" = "Todos os Emojis!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "Nova"; 29 | "Lists.Settings" = "Ajustes"; 30 | "Lists.Empty" = "Nenhuma lista!"; 31 | "Lists.Empty.Msg" = "Você economiza muito mais tempo criando listas visuais. Use suas listas para compras da semana, material escolar, afazeres e mais!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "Pronto!"; 35 | "UsingList.Settings.Title" = "Ajustes da lista"; 36 | "UsingList.Settings.Msg" = "Você pode remover sua lista por completo do seu aparelho ou compartilhar uma imagem dela nas suas redes sociais."; 37 | "UsingList.Settings.Redo" = "Começar a lista do zero."; 38 | "UsingList.Settings.DeleteList" = "Deletar esta lista"; 39 | "UsingList.Delete.Msg" = "Só para assegurar-se que você não tocou no botão de deletar sem querer, confirme a remoção da lista a seguir:"; 40 | "UsingList.Settings.DeleteListConfirmation" = "Deletar a lista"; 41 | "UsingList.Settings.Edit" = "Editar a lista"; 42 | 43 | // Share 44 | "Share.Credits1" = "Criado com Mojilist"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "Nova Lista"; 49 | "CreateList.Text.Placeholder" = "Compras da semana"; 50 | "CreateList.Label" = "Como vai se chamar a nova lista?"; 51 | "CreateList.Hint" = "Dica: listas de afazeres, supermercado ou material escolar podem ser criadas rapidamente visualmente. Selecione emojis na próxima etapa e divirta-se!"; 52 | "CreateList.Next" = "Seguinte"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "O que vai na lista?"; 56 | "SelectEmojis.Create" = "Criar"; 57 | "SelectEmojis.Update" = "Atualizar"; 58 | "SelectEmojis.Clear" = "Esvaziar"; 59 | "SelectEmojis.SelectPack" = "Pacote selecionado:"; 60 | "SelectEmojis.ItemsInList" = "Itens na lista. Se precisar, toque uma vez para remover."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "Pacotes"; 64 | 65 | // About 66 | "About.Title" = "Ajustes"; 67 | "About.Settings" = "Personalize seu App!"; 68 | "About.About" = "Sobre"; 69 | "About.Promo" = "Compartilhe"; 70 | "About.Follow" = "Siga nas redes sociais"; 71 | "About.MoreApps" = "Mais apps!"; 72 | "About.Settings.DefaultPack" = "Pacote padrão: "; 73 | "About.Settings.Theme" = "Tema usado: "; 74 | "About.Promo.Signup" = "Inscreva-se na Newsletter!"; 75 | "About.Promo.Share" = "Compartilhe este app"; 76 | "About.Promo.Rate" = "Avalie este app ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "Visite o Blog"; 81 | "About.About.Contact" = "E-mail para o desenvolvedor"; 82 | "About.About.Feature" = "Opine sobre o app"; 83 | "About.About.Version" = "Versão do app"; 84 | "About.Share.Copy" = "Baixe o Mojilist, uma alternativa visual para suas listas do dia-a-dia!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "Temas"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 Mojilist Diurno"; 91 | "Theme.Dark" = "🌚 Mojilist Noturno"; 92 | "Theme.Greeny" = "🍁 Maresia"; 93 | "Theme.Blue" = "🌊 Deu Onda"; 94 | "Theme.Drag" = "💄 Que Tiro Foi Esse?"; 95 | "Theme.Dracula" = "💥 Sweet Dreams"; 96 | "Theme.Candy" = "🍭 Agora Eu Fiquei Doce"; 97 | -------------------------------------------------------------------------------- /mobile/localized/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "Sí"; 12 | "No" = "No"; 13 | "Warning" = "¡Atención!"; 14 | "Success" = "¡Éxito!"; 15 | "Dismiss" = "Descartar"; 16 | "Cancel" = "Cancelar"; 17 | "Shared" = "Compartir listo!"; 18 | 19 | // Errors 20 | "Email.Error" = "Aparentemente su aparato no soporta el envío de correo electrónico. Por favor, envíe un correo electrónico a buh@ghostship.co para ponerse en contacto."; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "¡Emojis de cosas!"; 24 | "Pack.AllEmojis" = "¡Todos los Emojis!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "Nueva"; 29 | "Lists.Settings" = "Configuraciones"; 30 | "Lists.Empty" = "¡Sin listas!"; 31 | "Lists.Empty.Msg" = "Usted ahorra mucho más tiempo creando listas visuales. ¡Utilice sus listas para las compras de la semana, tareas y más!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "¡Listo!"; 35 | "UsingList.Settings.Title" = "Configuraciones"; 36 | "UsingList.Settings.Msg" = "Usted puede quitar su lista por completo de su dispositivo o compartir una imagen de ella en sus redes sociales."; 37 | "UsingList.Settings.Redo" = "Comenzar la lista de cero."; 38 | "UsingList.Settings.DeleteList" = "Borrar esta lista"; 39 | "UsingList.Delete.Msg" = "Solo para estar seguro, confirme nuevamente que desea eliminar esta lista."; 40 | "UsingList.Settings.DeleteListConfirmation" = "Borrar lista"; 41 | "UsingList.Settings.Edit" = "Editar la lista"; 42 | 43 | // Share 44 | "Share.Credits1" = "Creado con Mojilist"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "Nueva Lista"; 49 | "CreateList.Text.Placeholder" = "Lista de compras"; 50 | "CreateList.Label" = "¿Cómo se llamará la nueva lista?"; 51 | "CreateList.Hint" = "Sugerencia: listas de tareas, supermercados o material escolar se pueden crear rápidamente visualmente. Selecciona emojis en el siguiente paso y diviértete!"; 52 | "CreateList.Next" = "Siguiente"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "¿Qué va en la lista?"; 56 | "SelectEmojis.Create" = "Crear"; 57 | "SelectEmojis.Update" = "Actualizar"; 58 | "SelectEmojis.Clear" = "Vaciar"; 59 | "SelectEmojis.SelectPack" = "Paquete seleccionado:"; 60 | "SelectEmojis.ItemsInList" = "Elementos en la lista. Si lo necesita, toque una vez para quitar."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "Paquetes"; 64 | 65 | // About 66 | "About.Title" = "Configuraciones"; 67 | "About.Settings" = "¡Personalice su App!"; 68 | "About.About" = "Acerca"; 69 | "About.Promo" = "Comparte"; 70 | "About.Follow" = "Sigue en las redes sociales"; 71 | "About.MoreApps" = "¡Más apps!"; 72 | "About.Settings.DefaultPack" = "Paquete estándar: "; 73 | "About.Settings.Theme" = "Tema usado: "; 74 | "About.Promo.Signup" = "Suscríbete a nuestro boletín!"; 75 | "About.Promo.Share" = "Compartir esta app"; 76 | "About.Promo.Rate" = "Evaluar esta app ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "Visita el Blog"; 81 | "About.About.Contact" = "E-mail para el desarrollador"; 82 | "About.About.Feature" = "Opina sobre la aplicación"; 83 | "About.About.Version" = "Versión de la app"; 84 | "About.Share.Copy" = "¡Descargue o Mojilist, una alternativa visual para tus listas del día a día!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "Temas"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 Mojilist Día"; 91 | "Theme.Dark" = "🌚 Mojilist Noche"; 92 | "Theme.Greeny" = "🍁 Hojas verdes"; 93 | "Theme.Blue" = "🌊 Ola Ola"; 94 | "Theme.Drag" = "💄 Encantador"; 95 | "Theme.Dracula" = "💥 Dulces sueños"; 96 | "Theme.Candy" = "🍭 Pico de azúcar"; 97 | -------------------------------------------------------------------------------- /mobile/localized/es-419.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "Sí"; 12 | "No" = "No"; 13 | "Warning" = "¡Atención!"; 14 | "Success" = "¡Éxito!"; 15 | "Dismiss" = "Descartar"; 16 | "Cancel" = "Cancelar"; 17 | "Shared" = "Compartir listo!"; 18 | 19 | // Errors 20 | "Email.Error" = "Aparentemente su aparato no soporta el envío de correo electrónico. Por favor, envíe un correo electrónico a buh@ghostship.co para ponerse en contacto."; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "¡Emojis de cosas!"; 24 | "Pack.AllEmojis" = "¡Todos los Emojis!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "Nueva"; 29 | "Lists.Settings" = "Configuraciones"; 30 | "Lists.Empty" = "¡Sin listas!"; 31 | "Lists.Empty.Msg" = "Usted ahorra mucho más tiempo creando listas visuales. ¡Utilice sus listas para las compras de la semana, tareas y más!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "¡Listo!"; 35 | "UsingList.Settings.Title" = "Configuraciones"; 36 | "UsingList.Settings.Msg" = "Usted puede quitar su lista por completo de su dispositivo o compartir una imagen de ella en sus redes sociales."; 37 | "UsingList.Settings.Redo" = "Comenzar la lista de cero."; 38 | "UsingList.Settings.DeleteList" = "Borrar esta lista"; 39 | "UsingList.Delete.Msg" = "Solo para estar seguro, confirme nuevamente que desea eliminar esta lista."; 40 | "UsingList.Settings.DeleteListConfirmation" = "Borrar lista"; 41 | "UsingList.Settings.Edit" = "Editar la lista"; 42 | 43 | // Share 44 | "Share.Credits1" = "Creado con Mojilist"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "Nueva Lista"; 49 | "CreateList.Text.Placeholder" = "Lista de compras"; 50 | "CreateList.Label" = "¿Cómo se llamará la nueva lista?"; 51 | "CreateList.Hint" = "Sugerencia: listas de tareas, supermercados o material escolar se pueden crear rápidamente visualmente. Selecciona emojis en el siguiente paso y diviértete!"; 52 | "CreateList.Next" = "Siguiente"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "¿Qué va en la lista?"; 56 | "SelectEmojis.Create" = "Crear"; 57 | "SelectEmojis.Update" = "Actualizar"; 58 | "SelectEmojis.Clear" = "Vaciar"; 59 | "SelectEmojis.SelectPack" = "Paquete seleccionado:"; 60 | "SelectEmojis.ItemsInList" = "Elementos en la lista. Si lo necesita, toque una vez para quitar."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "Paquetes"; 64 | 65 | // About 66 | "About.Title" = "Configuraciones"; 67 | "About.Settings" = "¡Personalice su App!"; 68 | "About.About" = "Acerca"; 69 | "About.Promo" = "Comparte"; 70 | "About.Follow" = "Sigue en las redes sociales"; 71 | "About.MoreApps" = "¡Más apps!"; 72 | "About.Settings.DefaultPack" = "Paquete estándar: "; 73 | "About.Settings.Theme" = "Tema usado: "; 74 | "About.Promo.Signup" = "Suscríbete a nuestro boletín!"; 75 | "About.Promo.Share" = "Compartir esta app"; 76 | "About.Promo.Rate" = "Evaluar esta app ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "Visita el Blog"; 81 | "About.About.Contact" = "E-mail para el desarrollador"; 82 | "About.About.Feature" = "Opina sobre la aplicación"; 83 | "About.About.Version" = "Versión de la app"; 84 | "About.Share.Copy" = "¡Descargue o Mojilist, una alternativa visual para tus listas del día a día!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "Temas"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 Mojilist Día"; 91 | "Theme.Dark" = "🌚 Mojilist Noche"; 92 | "Theme.Greeny" = "🍁 Hojas verdes"; 93 | "Theme.Blue" = "🌊 Ola Ola"; 94 | "Theme.Drag" = "💄 Encantador"; 95 | "Theme.Dracula" = "💥 Dulces sueños"; 96 | "Theme.Candy" = "🍭 Pico de azúcar"; 97 | 98 | -------------------------------------------------------------------------------- /mobile/localized/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Emojilist 4 | 5 | Created by Thiago Ricieri on 10/01/2018. 6 | Copyright © 2018 Ghost Ship. All rights reserved. 7 | */ 8 | 9 | // Base 10 | "OK" = "OK"; 11 | "Yes" = "Oui"; 12 | "No" = "Non"; 13 | "Warning" = "Attention!"; 14 | "Success" = "Succès!"; 15 | "Dismiss" = "Rejeter"; 16 | "Cancel" = "Annuler"; 17 | "Shared" = "Partagé!"; 18 | 19 | // Errors 20 | "Email.Error" = "Votre appareil ne peut pas envoi d'e-mails. S'il vous plaît, envoyez un courriel à buh@ghostship.co pour entrer en contact."; 21 | 22 | // Pack 23 | "Pack.EmojiThings" = "Emojis des Choses!"; 24 | "Pack.AllEmojis" = "Tous les Emojis!"; 25 | 26 | // Lists 27 | "Lists.Title" = "MOJILIST"; 28 | "Lists.New" = "Nouvelle"; 29 | "Lists.Settings" = "Paramètres"; 30 | "Lists.Empty" = "Pas de liste!"; 31 | "Lists.Empty.Msg" = "Vous économisez beaucoup plus de temps en créant des listes visuelles. Utilisez vos listes de courses, les affaires scolaires et plus encore!"; 32 | 33 | // Using List 34 | "UsingList.Done" = "Terminé!"; 35 | "UsingList.Settings.Title" = "Paramètres de liste"; 36 | "UsingList.Settings.Msg" = "Vous pouvez supprimer votre liste de votre appareil ou partager une photo de celui-ci sur vos réseaux sociaux."; 37 | "UsingList.Settings.Redo" = "Démarrer la liste."; 38 | "UsingList.Settings.DeleteList" = "Supprimer la liste."; 39 | "UsingList.Delete.Msg" = "Confirmez la suppression de la liste suivante:"; 40 | "UsingList.Settings.DeleteListConfirmation" = "Supprimer la liste"; 41 | "UsingList.Settings.Edit" = "Modifier la liste"; 42 | 43 | // Share 44 | "Share.Credits1" = "Créé avec Mojilist"; 45 | "Share.Credits2" = "https://ghostship.co/mojilist"; 46 | 47 | // Create List 48 | "CreateList.Title" = "Nouvelle Liste"; 49 | "CreateList.Text.Placeholder" = "Les Courses"; 50 | "CreateList.Label" = "Quelle sera la nouvelle liste appelée?"; 51 | "CreateList.Hint" = "Les listes de choses à faire, les épiceries ou les affaires scolaires peuvent être créées rapidement et visuellement. Sélectionnez emojis dans la prochaine étape et profitez-en!"; 52 | "CreateList.Next" = "Prochain"; 53 | 54 | // Select emojis 55 | "SelectEmojis.Title" = "Qu'est-ce qui est sur la liste?"; 56 | "SelectEmojis.Create" = "Créer"; 57 | "SelectEmojis.Update" = "Mettre à jour"; 58 | "SelectEmojis.Clear" = "Vider"; 59 | "SelectEmojis.SelectPack" = "Package sélectionné:"; 60 | "SelectEmojis.ItemsInList" = "Articles dans la liste. Si nécessaire, appuyez une fois pour supprimer."; 61 | 62 | // Select Pack 63 | "SelectPack.Title" = "Packages"; 64 | 65 | // About 66 | "About.Title" = "Paramètres"; 67 | "About.Settings" = "Personnalisez votre application!"; 68 | "About.About" = "Environ"; 69 | "About.Promo" = "Partager"; 70 | "About.Follow" = "Suivez sur les réseaux sociaux"; 71 | "About.MoreApps" = "Plus des apps!"; 72 | "About.Settings.DefaultPack" = "Package sélectionné: "; 73 | "About.Settings.Theme" = "Thème utilisé: "; 74 | "About.Promo.Signup" = "Abonnez-vous à la newsletter!"; 75 | "About.Promo.Share" = "Partagez cette application"; 76 | "About.Promo.Rate" = "Noter cette application ❤️"; 77 | "About.Follow.Instagram" = "Instagram"; 78 | "About.Follow.Facebook" = "Facebook"; 79 | "About.Follow.Twitter" = "Twitter"; 80 | "About.Follow.Blog" = "Visitez le blog"; 81 | "About.About.Contact" = "Contacter le développeur"; 82 | "About.About.Feature" = "Donnez votre avis"; 83 | "About.About.Version" = "Version de l'application"; 84 | "About.Share.Copy" = "Téléchargez Mojilist, une alternative visuelle à vos listes des cours!"; 85 | 86 | // Change Theme 87 | "ChangeTheme.Title" = "Thème"; 88 | 89 | // Themes 90 | "Theme.Basic" = "🌞 Mojilist du Jour"; 91 | "Theme.Dark" = "🌚 Mojilist de la Nuit"; 92 | "Theme.Greeny" = "🍁 Feuilles"; 93 | "Theme.Blue" = "🌊 Vagues de la mer"; 94 | "Theme.Drag" = "💄 La mode est choquante"; 95 | "Theme.Dracula" = "💥 Beaux Rêves"; 96 | "Theme.Candy" = "🍭 Plus de sucre, svp!"; 97 | -------------------------------------------------------------------------------- /mobile/resources/assets.xcassets/Mojilist.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "57x57", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-57x57@1x.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "57x57", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-57x57@2x.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "60x60", 59 | "idiom" : "iphone", 60 | "filename" : "Icon-App-60x60@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "60x60", 65 | "idiom" : "iphone", 66 | "filename" : "Icon-App-60x60@3x.png", 67 | "scale" : "3x" 68 | }, 69 | { 70 | "size" : "20x20", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-20x20@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "20x20", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-20x20@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-29x29@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "29x29", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-29x29@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "40x40", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-40x40@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "40x40", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-40x40@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "50x50", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-Small-50x50@1x.png", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "50x50", 113 | "idiom" : "ipad", 114 | "filename" : "Icon-Small-50x50@2x.png", 115 | "scale" : "2x" 116 | }, 117 | { 118 | "size" : "72x72", 119 | "idiom" : "ipad", 120 | "filename" : "Icon-App-72x72@1x.png", 121 | "scale" : "1x" 122 | }, 123 | { 124 | "size" : "72x72", 125 | "idiom" : "ipad", 126 | "filename" : "Icon-App-72x72@2x.png", 127 | "scale" : "2x" 128 | }, 129 | { 130 | "size" : "76x76", 131 | "idiom" : "ipad", 132 | "filename" : "Icon-App-76x76@1x.png", 133 | "scale" : "1x" 134 | }, 135 | { 136 | "size" : "76x76", 137 | "idiom" : "ipad", 138 | "filename" : "Icon-App-76x76@2x.png", 139 | "scale" : "2x" 140 | }, 141 | { 142 | "size" : "83.5x83.5", 143 | "idiom" : "ipad", 144 | "filename" : "Icon-App-83.5x83.5@2x.png", 145 | "scale" : "2x" 146 | }, 147 | { 148 | "size" : "1024x1024", 149 | "idiom" : "ios-marketing", 150 | "filename" : "ItunesArtwork@2x.png", 151 | "scale" : "1x" 152 | } 153 | ], 154 | "info" : { 155 | "version" : 1, 156 | "author" : "xcode" 157 | } 158 | } -------------------------------------------------------------------------------- /mobile/views/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /mobile/core/Emojis.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Emojis.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 14/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func thingsEmoji() -> [Character] { 12 | return "🍏🍎🍐🍊🍋🍌🍉🍇🍓🍈🍒🍑🍍🥥🥝🍅🍆🥑🥦🥒🌶🌽🥕🥔🍠🥐🍞🥖🥨🧀🥚🥞🥓🥩🍗🍖🌭🍔🍟🍕🥪🥙🌮🌯🥗🥘🥫🍝🍜🍲🍛🍣🍱🥟🍤🍙🍚🍘🍥🥠🍢🍡🍧🍨🍦🥧🍰🎂🍮🍭🍬🍫🍿🍩🍪🌰🥜🍯🥛☕️🍵🥤🍶🍺🍷🥃🍸🍹🥂🍾🍴🥣💧☂️🔥🎄🌲🌹🌻🌸🍄🍁🦀🐠🦑🐙🐟🐌👔👖👚👕👢👗👙🧦🧤🧣🎩🧢🎒👛🌂👞👟👡👠👓🕶💄👀👅⚽️🏀🏈⚾️🎾🏐🏉🎱🏓🏸🏒🏑🏏🎣🥊⛸🛷🎿🎫🎟🎭🎤🎧🎹🥁🎷🎺🎸🎻🎲🎯🎳🎮🛴🛵🚲🚗🚕🚙🏍✈️🗺⛱🏝⌚️📱💻⌨️🖥🖱🖨🕹📷📹📞☎️⏰🔦🕯🗑💵💶💴💷💎🔧🔨🔪🔩⚙️💊🛍🎁🖼🎈✉️📁🗞📔📎📌✂️🖊🖌✏️🖍".map { return $0 } 13 | } 14 | 15 | func allEmojis() -> [Character] { 16 | return "😀😃😄😁😆😅😂🤣☺️😊😇🙂🙃😉😌😍😘😗😙😚😋😜😝😛🤑🤗🤓😎🤡🤠😏😒😞😔😟😕🙁☹️😣😖😫😩😤😠😡😶😐😑😯😦😧😮😲😵😳😱😨😰😢😥🤤😭😓😪😴🙄🤔🤥😬🤐🤢🤧😷🤒🤕😈👿👹👺💩👻💀☠️👽👾🤖🎃😺😸😹😻😼😽🙀😿😾👐🙌👏🙏🤝👍👎👊✊🤛🤜🤞✌️🤘👌👈👉👆👇☝️✋🤚🖐🖖👋🤙💪🖕✍️🤳💅💍💄💋👄👅👂👃👣👁👀🗣👤👥👶👦👧👨👩👱‍♀️👱👴👵👲👳‍♀️👳👮‍♀️👮👷‍♀️👷💂‍♀️💂🕵️‍♀️🕵️👩‍⚕️👨‍⚕️👩‍🌾👨‍🌾👩‍🍳👨‍🍳👩‍🎓👨‍🎓👩‍🎤👨‍🎤👩‍🏫👨‍🏫👩‍🏭👨‍🏭👩‍💻👨‍💻👩‍💼👨‍💼👩‍🔧👨‍🔧👩‍🔬👨‍🔬👩‍🎨👨‍🎨👩‍🚒👨‍🚒👩‍✈️👨‍✈️👩‍🚀👨‍🚀👩‍⚖️👨‍⚖️🤶🎅👸🤴👰🤵👼🤰🙇‍♀️🙇💁💁‍♂️🙅🙅‍♂️🙆🙆‍♂️🙋🙋‍♂️🤦‍♀️🤦‍♂️🤷‍♀️🤷‍♂️🙎🙎‍♂️🙍🙍‍♂️💇💇‍♂️💆💆‍♂️🕴💃🕺👯👯‍♂️🚶‍♀️🚶🏃‍♀️🏃👫👭👬💑👩‍❤️‍👩👨‍❤️‍👨💏👩‍❤️‍💋‍👩👨‍❤️‍💋‍👨👪👨‍👩‍👧👨‍👩‍👧‍👦👨‍👩‍👦‍👦👨‍👩‍👧‍👧👩‍👩‍👦👩‍👩‍👧👩‍👩‍👧‍👦👩‍👩‍👦‍👦👩‍👩‍👧‍👧👨‍👨‍👦👨‍👨‍👧👨‍👨‍👧‍👦👨‍👨‍👦‍👦👨‍👨‍👧‍👧👩‍👦👩‍👧👩‍👧‍👦👩‍👦‍👦👩‍👧‍👧👨‍👦👨‍👧👨‍👧‍👦👨‍👦‍👦👨‍👧‍👧👚👕👖👔👗👙👘👠👡👢👞👟👒🎩🎓👑⛑🎒👝👛👜💼👓🕶🌂☂️🐶🐱🐭🐹🐰🦊🐻🐼🐨🐯🦁🐮🐷🐽🐸🐵🙈🙉🙊🐒🐔🐧🐦🐤🐣🐥🦆🦅🦉🦇🐺🐗🐴🦄🐝🐛🦋🐌🐚🐞🐜🕷🕸🐢🐍🦎🦂🦀🦑🐙🦐🐠🐟🐡🐬🦈🐳🐋🐊🐆🐅🐃🐂🐄🦌🐪🐫🐘🦏🦍🐎🐖🐐🐏🐑🐕🐩🐈🐓🦃🕊🐇🐁🐀🐿🐾🐉🐲🌵🎄🌲🌳🌴🌱🌿☘️🍀🎍🎋🍃🍂🍁🍄🌾💐🌷🌹🥀🌻🌼🌸🌺🌎🌍🌏🌕🌖🌗🌘🌑🌒🌓🌔🌚🌝🌞🌛🌜🌙💫⭐️🌟✨⚡️🔥💥☄️☀️🌤⛅️🌥🌦🌈☁️🌧⛈🌩🌨☃️⛄️❄️🌬💨🌪🌫🌊💧💦☔️🍏🍎🍐🍊🍋🍌🍉🍇🍓🍈🍒🍑🍍🥝🥑🍅🍆🥒🥕🌽🌶🥔🍠🌰🥜🍯🥐🍞🥖🧀🥚🍳🥓🥞🍤🍗🍖🍕🌭🍔🍟🥙🌮🌯🥗🥘🍝🍜🍲🍥🍣🍱🍛🍚🍙🍘🍢🍡🍧🍨🍦🍰🎂🍮🍭🍬🍫🍿🍩🍪🥛🍼☕️🍵🍶🍺🍻🥂🍷🥃🍸🍹🍾🥄🍴🍽⚽️🏀🏈⚾️🎾🏐🏉🎱🏓🏸🥅🏒🏑🏏⛳️🏹🎣🥊🥋⛸🎿⛷🏂🏋️‍♀️🏋️🤺🤼‍♀️🤼‍♂️🤸‍♀️🤸‍♂️⛹️‍♀️⛹️🤾‍♀️🤾‍♂️🏌️‍♀️🏌️🏄‍♀️🏄🏊‍♀️🏊🤽‍♀️🤽‍♂️🚣‍♀️🚣🏇🚴‍♀️🚴🚵‍♀️🚵🎽🏅🎖🥇🥈🥉🏆🏵🎗🎫🎟🎪🤹‍♀️🤹‍♂️🎭🎨🎬🎤🎧🎼🎹🥁🎷🎺🎸🎻🎲🎯🎳🎮🎰🚗🚕🚙🚌🚎🏎🚓🚑🚒🚐🚚🚛🚜🛴🚲🛵🏍🚨🚔🚍🚘🚖🚡🚠🚟🚃🚋🚞🚝🚄🚅🚈🚂🚆🚇🚊🚉🚁🛩✈️🛫🛬🚀🛰💺🛶⛵️🛥🚤🛳⛴🚢⚓️🚧⛽️🚏🚦🚥🗺🗿🗽⛲️🗼🏰🏯🏟🎡🎢🎠⛱🏖🏝⛰🏔🗻🌋🏜🏕⛺️🛤🛣🏗🏭🏠🏡🏘🏚🏢🏬🏣🏤🏥🏦🏨🏪🏫🏩💒🏛⛪️🕌🕍🕋⛩🗾🎑🏞🌅🌄🌠🎇🎆🌇🌆🏙🌃🌌🌉🌁⌚️📱📲💻⌨️🖥🖨🖱🖲🕹🗜💽💾💿📀📼📷📸📹🎥📽🎞📞☎️📟📠📺📻🎙🎚🎛⏱⏲⏰🕰⌛️⏳📡🔋🔌💡🔦🕯🗑🛢💸💵💴💶💷💰💳💎⚖️🔧🔨⚒🛠⛏🔩⚙️⛓🔫💣🔪🗡⚔️🛡🚬⚰️⚱️🏺🔮📿💈⚗️🔭🔬🕳💊💉🌡🚽🚰🚿🛁🛀🛎🔑🗝🚪🛋🛏🛌🖼🛍🛒🎁🎈🎏🎀🎊🎉🎎🏮🎐✉️📩📨📧💌📥📤📦🏷📪📫📬📭📮📯📜📃📄📑📊📈📉🗒🗓📆📅📇🗃🗳🗄📋📁📂🗂🗞📰📓📔📒📕📗📘📙📚📖🔖🔗📎🖇📐📏📌📍✂️🖊🖋✒️🖌🖍📝✏️🔍🔎🔏🔐🔒🔓❤️💛💚💙💜🖤💔❣️💕💞💓💗💖💘💝💟☮️✝️☪️🕉☸️✡️🔯🕎☯️☦️🛐⛎♈️♉️♊️♋️♌️♍️♎️♏️♐️♑️♒️♓️🆔⚛️🉑☢️☣️📴📳🈶🈚️🈸🈺🈷️✴️🆚💮🉐㊙️㊗️🈴🈵🈹🈲🅰️🅱️🆎🆑🅾️🆘❌⭕️🛑⛔️📛🚫💯💢♨️🚷🚯🚳🚱🔞📵🚭❗️❕❓❔‼️⁉️🔅🔆〽️⚠️🚸🔱⚜️🔰♻️✅🈯️💹❇️✳️❎🌐💠Ⓜ️🌀💤🏧🚾♿️🅿️🈳🈂️🛂🛃🛄🛅🚹🚺🚼🚻🚮🎦📶🈁🔣ℹ️🔤🔡🔠🆖🆗🆙🆒🆕🆓0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣🔟🔢#️⃣*️⃣▶️⏸⏯⏹⏺⏭⏮⏩⏪⏫⏬◀️🔼🔽➡️⬅️⬆️⬇️↗️↘️↙️↖️↕️↔️↪️↩️⤴️⤵️🔀🔁🔂🔄🔃🎵🎶➕➖➗✖️💲💱™️©️®️〰️➰➿🔚🔙🔛🔝🔜✔️☑️🔘⚪️⚫️🔴🔵🔺🔻🔸🔹🔶🔷🔳🔲▪️▫️◾️◽️◼️◻️⬛️⬜️🔈🔇🔉🔊🔔🔕📣📢👁‍🗨💬💭🗯♠️♣️♥️♦️🃏🎴🀄️🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚🕛🕜🕝🕞🕟🕠🕡🕢🕣🕤🕥🕦🕧🏳️🏴🏁🚩🏳️‍🌈🇦🇫🇦🇽🇦🇱🇩🇿🇦🇸🇦🇩🇦🇴🇦🇮🇦🇶🇦🇬🇦🇷🇦🇲🇦🇼🇦🇺🇦🇹🇦🇿🇧🇸🇧🇭🇧🇩🇧🇧🇧🇾🇧🇪🇧🇿🇧🇯🇧🇲🇧🇹🇧🇴🇧🇶🇧🇦🇧🇼🇧🇷🇮🇴🇻🇬🇧🇳🇧🇬🇧🇫🇧🇮🇨🇻🇰🇭🇨🇲🇨🇦🇮🇨🇰🇾🇨🇫🇹🇩🇨🇱🇨🇳🇨🇽🇨🇨🇨🇴🇰🇲🇨🇬🇨🇩🇨🇰🇨🇷🇨🇮🇭🇷🇨🇺🇨🇼🇨🇾🇨🇿🇩🇰🇩🇯🇩🇲🇩🇴🇪🇨🇪🇬🇸🇻🇬🇶🇪🇷🇪🇪🇪🇹🇪🇺🇫🇰🇫🇴🇫🇯🇫🇮🇫🇷🇬🇫🇵🇫🇹🇫🇬🇦🇬🇲🇬🇪🇩🇪🇬🇭🇬🇮🇬🇷🇬🇱🇬🇩🇬🇵🇬🇺🇬🇹🇬🇬🇬🇳🇬🇼🇬🇾🇭🇹🇭🇳🇭🇰🇭🇺🇮🇸🇮🇳🇮🇩🇮🇷🇮🇶🇮🇪🇮🇲🇮🇱🇮🇹🇯🇲🇯🇵🎌🇯🇪🇯🇴🇰🇿🇰🇪🇰🇮🇽🇰🇰🇼🇰🇬🇱🇦🇱🇻🇱🇧🇱🇸🇱🇷🇱🇾🇱🇮🇱🇹🇱🇺🇲🇴🇲🇰🇲🇬🇲🇼🇲🇾🇲🇻🇲🇱🇲🇹🇲🇭🇲🇶🇲🇷🇲🇺🇾🇹🇲🇽🇫🇲🇲🇩🇲🇨🇲🇳🇲🇪🇲🇸🇲🇦🇲🇿🇲🇲🇳🇦🇳🇷🇳🇵🇳🇱🇳🇨🇳🇿🇳🇮🇳🇪🇳🇬🇳🇺🇳🇫🇲🇵🇰🇵🇳🇴🇴🇲🇵🇰🇵🇼🇵🇸🇵🇦🇵🇬🇵🇾🇵🇪🇵🇭🇵🇳🇵🇱🇵🇹🇵🇷🇶🇦🇷🇪🇷🇴🇷🇺🇷🇼🇧🇱🇸🇭🇰🇳🇱🇨🇵🇲🇻🇨🇼🇸🇸🇲🇸🇹🇸🇦🇸🇳🇷🇸🇸🇨🇸🇱🇸🇬🇸🇽🇸🇰🇸🇮🇸🇧🇸🇴🇿🇦🇬🇸🇰🇷🇸🇸🇪🇸🇱🇰🇸🇩🇸🇷🇸🇿🇸🇪🇨🇭🇸🇾🇹🇼🇹🇯🇹🇿🇹🇭🇹🇱🇹🇬🇹🇰🇹🇴🇹🇹🇹🇳🇹🇷🇹🇲🇹🇨🇹🇻🇺🇬🇺🇦🇦🇪🇬🇧🇺🇸🇻🇮🇺🇾🇺🇿🇻🇺🇻🇦🇻🇪🇻🇳🇼🇫🇪🇭🇾🇪🇿🇲🇿🇼".map { $0 } 17 | } 18 | -------------------------------------------------------------------------------- /mobile/controllers/common/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 10/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import MBProgressHUD 12 | import RealmSwift 13 | 14 | class BaseViewController : UIViewController { 15 | 16 | var baseViewModel: BaseViewModel! 17 | var currentTheme: String? 18 | 19 | fileprivate(set) var currentHud: MBProgressHUD? 20 | 21 | @IBOutlet weak var scrollView: UIScrollView? 22 | 23 | // MARK: - View Boilerplate 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | instantiateDependencies() 28 | checkTheme() 29 | setViewStyle() 30 | } 31 | 32 | override func viewWillAppear(_ animated: Bool) { 33 | super.viewWillAppear(animated) 34 | checkTheme() 35 | prepareViewForUser() 36 | } 37 | 38 | // MARK: - Workflow 39 | 40 | func instantiateDependencies() { 41 | } 42 | 43 | func checkTheme() { 44 | let appTheme = baseViewModel.theme.identifier() 45 | if currentTheme == nil || currentTheme != appTheme { 46 | currentTheme = appTheme 47 | applyTheme(baseViewModel.theme) 48 | } 49 | } 50 | 51 | func applyTheme(_ theme: Theme) { 52 | theme.background(self.view) 53 | theme.tintAccent(self.view) 54 | 55 | if let nc = navigationController { 56 | theme.styleNavigationBar(nc.navigationBar) 57 | } 58 | setNeedsStatusBarAppearanceUpdate() 59 | } 60 | 61 | func setViewStyle() { 62 | } 63 | 64 | func prepareViewForUser() { 65 | } 66 | 67 | override var preferredStatusBarStyle: UIStatusBarStyle { 68 | return baseViewModel == nil ? .default : baseViewModel.theme.statusBarStyle() 69 | } 70 | } 71 | 72 | // MARK: - HUD 73 | extension BaseViewController { 74 | 75 | func hudShow (message: String? = nil) { 76 | currentHud = MBProgressHUD.showAdded(to: self.view, animated: true) 77 | currentHud?.mode = .indeterminate 78 | 79 | if message != nil { 80 | currentHud?.label.text = message! 81 | } 82 | } 83 | 84 | func hudDismiss() { 85 | if currentHud != nil { 86 | currentHud?.hide(animated: true) 87 | currentHud = nil 88 | } 89 | } 90 | } 91 | 92 | // MARK: - Impact Generator 93 | extension BaseViewController { 94 | 95 | func heavyImpact() { 96 | let generator = UIImpactFeedbackGenerator(style: .heavy) 97 | generator.impactOccurred() 98 | } 99 | 100 | func mediumImpact() { 101 | let generator = UIImpactFeedbackGenerator(style: .medium) 102 | generator.impactOccurred() 103 | } 104 | 105 | func lightImpact() { 106 | let generator = UIImpactFeedbackGenerator(style: .light) 107 | generator.impactOccurred() 108 | } 109 | } 110 | 111 | // MARK: - Observing Keyboard 112 | extension BaseViewController { 113 | 114 | func keyboardAppear(notification: Notification) { 115 | if let safeScroll = self.scrollView { 116 | var contentInset = UIEdgeInsets.zero 117 | if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { 118 | contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0) 119 | } 120 | safeScroll.contentInset = contentInset 121 | } 122 | } 123 | 124 | func keyboardDisappear(notification: Notification) { 125 | if let safeScroll = self.scrollView { 126 | safeScroll.contentInset = UIEdgeInsets.zero 127 | } 128 | } 129 | 130 | func alertViewWithTitle(title: String, message: String) { 131 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 132 | alert.addAction(UIAlertAction(title: "OK".localized, style: .default, handler: nil)) 133 | self.present(alert, animated: true, completion: nil) 134 | } 135 | 136 | func errorAlert(message: String) { 137 | alertViewWithTitle(title: "Warning".localized, message: message) 138 | } 139 | 140 | func successAlert(message: String) { 141 | alertViewWithTitle(title: "Success".localized, message: message) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /fastlane/report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /mobile/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Emojilist 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIcons 12 | 13 | CFBundlePrimaryIcon 14 | 15 | CFBundleIconFiles 16 | 17 | Mojilist 18 | 19 | UIPrerenderedIcon 20 | 21 | 22 | CFBundleAlternateIcons 23 | 24 | MojilistBlue 25 | 26 | CFBundleIconFiles 27 | 28 | MojilistBlue 29 | 30 | UIPrerenderedIcon 31 | 32 | 33 | MojilistCandy 34 | 35 | CFBundleIconFiles 36 | 37 | MojilistCandy 38 | 39 | UIPrerenderedIcon 40 | 41 | 42 | MojilistDark 43 | 44 | CFBundleIconFiles 45 | 46 | MojilistDark 47 | 48 | UIPrerenderedIcon 49 | 50 | 51 | MojilistDracula 52 | 53 | CFBundleIconFiles 54 | 55 | MojilistDracula 56 | 57 | UIPrerenderedIcon 58 | 59 | 60 | MojilistDrag 61 | 62 | CFBundleIconFiles 63 | 64 | MojilistDrag 65 | 66 | UIPrerenderedIcon 67 | 68 | 69 | MojilistGreeny 70 | 71 | CFBundleIconFiles 72 | 73 | MojilistGreeny 74 | 75 | UIPrerenderedIcon 76 | 77 | 78 | 79 | 80 | CFBundleIdentifier 81 | $(PRODUCT_BUNDLE_IDENTIFIER) 82 | CFBundleInfoDictionaryVersion 83 | 6.0 84 | CFBundleName 85 | $(PRODUCT_NAME) 86 | CFBundlePackageType 87 | APPL 88 | CFBundleShortVersionString 89 | 1.0 90 | CFBundleVersion 91 | 1 92 | LSRequiresIPhoneOS 93 | 94 | NSAppTransportSecurity 95 | 96 | NSAllowsArbitraryLoads 97 | 98 | NSAllowsArbitraryLoadsInWebContent 99 | 100 | 101 | NSPhotoLibraryAddUsageDescription 102 | We need to access your photos to save image of your list to share. 103 | NSPhotoLibraryUsageDescription 104 | We need to access your photos to save image of your list to share. 105 | UILaunchStoryboardName 106 | LaunchScreen 107 | UIMainStoryboardFile 108 | Main 109 | UIRequiredDeviceCapabilities 110 | 111 | armv7 112 | 113 | UISupportedInterfaceOrientations 114 | 115 | UIInterfaceOrientationPortrait 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /Emojilist.xcodeproj/xcshareddata/xcschemes/Emojilist.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 44 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 76 | 78 | 84 | 85 | 86 | 87 | 90 | 91 | 92 | 93 | 94 | 95 | 101 | 103 | 109 | 110 | 111 | 112 | 114 | 115 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /mobile/controllers/share/ShareSnippetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShareStudioViewController.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 14/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import RealmSwift 12 | 13 | class ShareSnippetView: UIView, 14 | UICollectionViewDelegate, 15 | UICollectionViewDataSource { 16 | 17 | @IBOutlet weak var listName: UILabel! 18 | @IBOutlet weak var collection: UICollectionView! 19 | @IBOutlet weak var layout: UICollectionViewFlowLayout! 20 | @IBOutlet weak var creditsName: UILabel! 21 | @IBOutlet weak var creditsUrl: UILabel! 22 | 23 | var list: EmojiListViewModel? 24 | 25 | var maxWidth = 686 26 | var maxHeight = 514 27 | let margin = 8 28 | var itemSize = 0 29 | 30 | override func awakeFromNib() { 31 | let nib = UINib(nibName: ShareCell.identifier, bundle: nil) 32 | 33 | collection.register(nib, forCellWithReuseIdentifier: ShareCell.identifier) 34 | collection.delegate = self 35 | collection.dataSource = self 36 | 37 | let del = UIApplication.shared.delegate as! AppDelegate 38 | applyTheme(del.app.theme) 39 | } 40 | 41 | func applyTheme(_ theme: Theme) { 42 | theme.primaryText(listName) 43 | theme.secondaryText(creditsName) 44 | theme.secondaryText(creditsUrl) 45 | theme.background(self) 46 | theme.background(collection) 47 | } 48 | 49 | func configure(with emojiList: EmojiListViewModel) { 50 | list = emojiList 51 | 52 | creditsName.text = "Share.Credits1".localized 53 | creditsUrl.text = "Share.Credits2".localized 54 | listName.text = list?.name 55 | 56 | maxWidth = Int(collection.bounds.width) 57 | maxHeight = Int(collection.bounds.height) 58 | 59 | itemSize = recalculateBasedOnItems(list!.items) - margin 60 | layout.itemSize = CGSize(width: itemSize, height: itemSize) 61 | 62 | collection.reloadData() 63 | } 64 | 65 | func recalculateBasedOnItems(_ emojis: [EmojiViewModel]) -> Int { 66 | let minSize = 50 67 | let maxSize = 220 68 | 69 | let maxItemsMinSize = itemsToFit( 70 | inWidth: maxWidth, inHeight: maxHeight, 71 | withMargin: margin, withSize: minSize) 72 | 73 | let maxItemsMaxSize = itemsToFit( 74 | inWidth: maxWidth, inHeight: maxHeight, 75 | withMargin: margin, withSize: maxSize) 76 | 77 | if emojis.count >= maxItemsMinSize.2 { 78 | return minSize 79 | } 80 | 81 | if emojis.count <= maxItemsMaxSize.2 { 82 | return maxSize 83 | } 84 | 85 | for i in minSize...maxSize { 86 | let dynamicSize = itemsToFit( 87 | inWidth: maxWidth, inHeight: maxHeight, 88 | withMargin: margin, withSize: i) 89 | 90 | if emojis.count > dynamicSize.2 { 91 | return i 92 | } 93 | } 94 | 95 | // Never used 96 | return maxSize 97 | } 98 | 99 | func itemsToFit(inWidth: Int, inHeight: Int, withMargin: Int, withSize: Int) -> (Int, Int, Int) { 100 | let rows = (inHeight - withMargin) / withSize 101 | let columns = (inWidth - withMargin * 2) / withSize 102 | return (columns, rows, columns * rows) 103 | } 104 | 105 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 106 | return list != nil ? list!.items.count : 0 107 | } 108 | 109 | func collectionView(_ collectionView: UICollectionView, 110 | cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 111 | 112 | let cell = collectionView.dequeueReusableCell( 113 | withReuseIdentifier: ShareCell.identifier, 114 | for: indexPath) as! ShareCell 115 | let del = UIApplication.shared.delegate as! AppDelegate 116 | 117 | if let item = list?.items[indexPath.row] { 118 | cell.configure(with: item, squareSize: itemSize) 119 | cell.applyTheme(del.app.theme) 120 | } 121 | 122 | return cell 123 | } 124 | } 125 | 126 | class ShareCell: UICollectionViewCell { 127 | 128 | static let identifier = "ShareCell" 129 | 130 | @IBOutlet weak var emojiView: EmojiDropView! 131 | 132 | override func awakeFromNib() { 133 | clipsToBounds = false 134 | } 135 | 136 | func configure(with emoji: EmojiViewModel, squareSize: Int) { 137 | emojiView.configure(with: emoji) 138 | emojiView.resize(square: squareSize) 139 | } 140 | 141 | func applyTheme(_ theme: Theme) { 142 | emojiView.applyTheme(theme) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /mobile/core/Launcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Launcher.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 09/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import RealmSwift 12 | import Firebase 13 | 14 | class Launcher { 15 | 16 | // Vars 17 | private var launchParams: LaunchParams? 18 | private var provideCredentials = false 19 | private var app: App! 20 | 21 | // Weak references 22 | private weak var window: UIWindow? 23 | 24 | func startWith(app usingApp: App) { 25 | app = usingApp 26 | window = UIWindow(frame: UIScreen.main.bounds) 27 | window?.makeKeyAndVisible() 28 | } 29 | 30 | // MARK: - CHAINED CALLS 31 | 32 | // MARK: - UI 33 | 34 | func setWindow(_ window: UIWindow?) -> Self { 35 | self.window = window 36 | return self 37 | } 38 | 39 | func initialViewController(vc: UIViewController) -> Self { 40 | window?.rootViewController = vc 41 | return self 42 | } 43 | 44 | // MARK: - Configurable options 45 | 46 | func shouldProvideCredentials(_ requirement: Bool) -> Self { 47 | provideCredentials = requirement 48 | return self 49 | } 50 | 51 | func setDefaultPack() -> Self { 52 | let defaults = UserDefaults.standard 53 | if defaults.string(forKey: Env.App.defaultPack) == nil { 54 | defaults.set("things", forKey: Env.App.defaultPack) 55 | } 56 | return self 57 | } 58 | 59 | // MARK: - Deeplink 60 | 61 | func setLaunchOptions(_ launchOptions: LaunchParams?) -> Self { 62 | self.launchParams = launchOptions 63 | return self 64 | } 65 | 66 | // MARK: - Migrate Realm 67 | 68 | func migrateRealm() -> Self { 69 | 70 | var config = Realm.Configuration( 71 | // Set the new schema version. This must be greater than the previously used 72 | // version (if you've never set a schema version before, the version is 0). 73 | schemaVersion: 0, 74 | 75 | // Set the block which will be called automatically when opening a Realm with 76 | // a schema version lower than the one set above 77 | migrationBlock: { migration, oldSchemaVersion in 78 | // We haven’t migrated anything yet, so oldSchemaVersion == 0 79 | if (oldSchemaVersion < 0) { 80 | // Nothing to do! 81 | // Realm will automatically detect new properties and removed properties 82 | // And will update the schema on disk automatically 83 | } 84 | }) 85 | 86 | config.fileURL = config.fileURL! 87 | .deletingLastPathComponent() 88 | .appendingPathComponent("Mojilist.realm") 89 | 90 | // Tell Realm to use this new configuration object for the default Realm 91 | Realm.Configuration.defaultConfiguration = config 92 | return self 93 | } 94 | 95 | func includeStandardPack() -> Self { 96 | let realm = try! Realm() 97 | let query = realm.objects(REmojiPack.self).filter("ascii = true") 98 | 99 | guard query.count < 1 else { 100 | print("Standard pack is already included") 101 | return self 102 | } 103 | 104 | try! realm.write { 105 | [ 106 | ( 107 | name: "Pack.EmojiThings", 108 | emojis: thingsEmoji(), 109 | slug: "things" 110 | ), ( 111 | name: "Pack.AllEmojis", 112 | emojis: allEmojis(), 113 | slug: "all" 114 | ) 115 | ].forEach { emojiPack in 116 | let pack = REmojiPack() 117 | pack.name = emojiPack.name 118 | pack.ascii = true 119 | pack.slug = emojiPack.slug 120 | pack.url = "" 121 | 122 | for ec in emojiPack.emojis { 123 | let e = REmojiPackItem() 124 | e.name = String(ec) 125 | e.pack = pack.slug 126 | pack.emojis.append(e) 127 | } 128 | 129 | realm.add(pack) 130 | } 131 | } 132 | 133 | return self 134 | } 135 | 136 | // MARK: - Third-Party Integrations 137 | 138 | func setFirebase() -> Self { 139 | FirebaseApp.configure() 140 | return self 141 | } 142 | 143 | func setFacebook() -> Self { 144 | // TODO: Add credentials 145 | return self 146 | } 147 | 148 | func setFabric() -> Self { 149 | // TODO: Add credentials 150 | return self 151 | } 152 | 153 | func setTwitter() -> Self { 154 | // TODO: Add credentials 155 | return self 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /mobile/controllers/about/SettingsViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewModel.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 20/01/2018. 6 | // Copyright © 2018 Ghost Ship. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class SettingsViewModel: BaseDataViewModel { 12 | 13 | var source = [SettingsGroup]() 14 | 15 | var defaultPackName: String! { 16 | let defaults = UserDefaults.standard 17 | if let slug = defaults.string(forKey: Env.App.defaultPack) { 18 | let predicate = NSPredicate(format: "slug = %@", slug) 19 | let packs = realm.objects(REmojiPack.self).filter(predicate) 20 | if packs.count > 0 { 21 | return packs.first!.name 22 | } 23 | } 24 | return app.standardEmojiPack().name! 25 | } 26 | override var itemsCount: Int! { 27 | return source.count 28 | } 29 | 30 | override func loadSource() { 31 | 32 | // Settings 33 | var settings = SettingsGroup() 34 | settings.title = "About.Settings" 35 | 36 | var theme = SettingsOption() 37 | theme.name = "About.Settings.Theme" 38 | theme.icon = "theme" 39 | 40 | var defaultPack = SettingsOption() 41 | defaultPack.name = "About.Settings.DefaultPack" 42 | defaultPack.icon = "pack" 43 | 44 | settings.items = [theme, defaultPack] 45 | 46 | // Promotion 47 | var promo = SettingsGroup() 48 | promo.title = "About.Promo" 49 | 50 | var signup = SettingsOption() 51 | signup.name = "About.Promo.Signup" 52 | signup.icon = "inbox" 53 | signup.metadata = [ 54 | "url": "https://ghostship.us17.list-manage.com/subscribe?u=c95fc7c29b150bc1b79053748&id=ddd4ee4e1f" as AnyObject 55 | ] 56 | 57 | var share = SettingsOption() 58 | share.name = "About.Promo.Share" 59 | share.icon = "like" 60 | 61 | var rate = SettingsOption() 62 | rate.name = "About.Promo.Rate" 63 | rate.icon = "rate" 64 | 65 | promo.items = [signup, share, rate] 66 | 67 | // Follow 68 | var follow = SettingsGroup() 69 | follow.title = "About.Follow" 70 | 71 | var instagram = SettingsOption() 72 | instagram.name = "About.Follow.Instagram" 73 | instagram.icon = "instagram" 74 | instagram.metadata = [ 75 | "url": "https://instagram.com/_ghostship_" as AnyObject 76 | ] 77 | 78 | var twitter = SettingsOption() 79 | twitter.name = "About.Follow.Twitter" 80 | twitter.icon = "twitter" 81 | twitter.metadata = [ 82 | "url": "https://twitter.com/ghostship__" as AnyObject 83 | ] 84 | 85 | var facebook = SettingsOption() 86 | facebook.name = "About.Follow.Facebook" 87 | facebook.icon = "facebook" 88 | facebook.metadata = [ 89 | "url": "https://facebook.com/ghostshiptech" as AnyObject 90 | ] 91 | 92 | var blog = SettingsOption() 93 | blog.name = "About.Follow.Blog" 94 | blog.icon = "safari" 95 | blog.metadata = [ 96 | "url": "https://ghostship.co" as AnyObject 97 | ] 98 | 99 | follow.items = [instagram, twitter, facebook, blog] 100 | 101 | // About 102 | var about = SettingsGroup() 103 | about.title = "About.About" 104 | 105 | var feature = SettingsOption() 106 | feature.name = "About.About.Feature" 107 | feature.icon = "mail" 108 | feature.metadata = ["subject": "" as AnyObject] 109 | 110 | var contact = SettingsOption() 111 | contact.name = "About.About.Contact" 112 | contact.icon = "contact" 113 | contact.metadata = ["subject": "" as AnyObject] 114 | 115 | var version = SettingsOption() 116 | version.name = "About.About.Version" 117 | version.cellIdentifier = .simple 118 | let nsObject = Bundle.main.infoDictionary?["CFBundleShortVersionString"] 119 | let appVersion = nsObject as! String 120 | version.metadata = ["version": appVersion as AnyObject] 121 | 122 | //var moreApps = SettingsOption() 123 | //moreApps.name = "About.MoreApps".localized 124 | //moreApps.cellIdentifier = "" 125 | //moreApps.icon = "" 126 | 127 | about.items = [feature, contact, version] 128 | 129 | // Assemble 130 | source = [settings, promo, follow, about] 131 | } 132 | 133 | func item(at indexPath: IndexPath) -> SettingsOption { 134 | return source[indexPath.section].items[indexPath.row] 135 | } 136 | 137 | func section(at: Int) -> SettingsGroup { 138 | return source[at] 139 | } 140 | } 141 | 142 | enum SettingsCellTypes: String { 143 | case simple = "SettingsSimple" 144 | case withIcon = "SettingsIcon" 145 | case actionable = "SettingsActionable" 146 | } 147 | 148 | struct SettingsGroup { 149 | 150 | var title = "" 151 | var items = [SettingsOption]() 152 | } 153 | 154 | struct SettingsOption { 155 | 156 | var name = "" 157 | var icon: String? = nil 158 | var cellIdentifier: SettingsCellTypes = .withIcon 159 | var metadata: [String: AnyObject]? = nil 160 | } 161 | -------------------------------------------------------------------------------- /mobile/core/App.swift: -------------------------------------------------------------------------------- 1 | // 2 | // App.swift 3 | // Emojilist 4 | // 5 | // Created by Thiago Ricieri on 04/01/18. 6 | // Copyright © 2018 GhostShip. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import DateToolsSwift 11 | import RealmSwift 12 | 13 | protocol App { 14 | 15 | var config: AppConfig { get } 16 | var documentPath: String { get } 17 | var realm: Realm { get } 18 | var theme: Theme { get set } 19 | 20 | var humamFormatter: DateFormatter { get } 21 | var posixFormatter: DateFormatter { get } 22 | var sqlFormatter: DateFormatter { get } 23 | var currencyFormatter: NumberFormatter { get } 24 | 25 | func convertToDate(fromSql: String) -> Date? 26 | func convertToDate(fromSimpleSql: String) -> Date? 27 | func dateToSql(fromHuman: String) -> String 28 | func dateToSimpleSql(fromHuman: String) -> String 29 | func dateToHuman(fromSql: String) -> String 30 | func dateToHuman(from: String, toFormat: String) -> String 31 | func dateToHuman(fromSimpleSql: String) -> String 32 | 33 | func changeVisuals(_ visuals: Visuals) 34 | func standardEmojiPack() -> EmojiPackViewModel 35 | } 36 | 37 | // MARK: - Production App 38 | class ProductionAppImpl: App { 39 | 40 | lazy var theme: Theme = self.initTheme() 41 | 42 | fileprivate(set) var config: AppConfig 43 | fileprivate(set) public var documentPath: String 44 | 45 | fileprivate(set) lazy var realm: Realm = self.initRealm() 46 | fileprivate(set) lazy var humamFormatter: DateFormatter = self.initHumanFormatter() 47 | fileprivate(set) lazy var posixFormatter: DateFormatter = self.initPosixFormatter() 48 | fileprivate(set) lazy var sqlFormatter: DateFormatter = self.initSqlFormatter() 49 | fileprivate(set) lazy var simpleSqlFormatter: DateFormatter = self.initSimpleSqlFormatter() 50 | fileprivate(set) lazy var currencyFormatter: NumberFormatter = self.initCurrencyFormatter() 51 | 52 | init() { 53 | self.config = ProductionAppConfigImpl() 54 | self.documentPath = "" 55 | } 56 | 57 | fileprivate func initRealm() -> Realm { 58 | return try! Realm() 59 | } 60 | 61 | fileprivate func initTheme() -> Theme { 62 | let defaults = UserDefaults.standard 63 | if let theme = defaults.string(forKey: Env.App.theming) { 64 | return Theme(visualString: theme) 65 | } 66 | return Theme(visuals: BasicVisual()) 67 | } 68 | 69 | // Init formatters 70 | fileprivate func initHumanFormatter() -> DateFormatter { 71 | let df = DateFormatter() 72 | df.dateFormat = "dd/MM/yyyy" 73 | return df 74 | } 75 | 76 | fileprivate func initPosixFormatter() -> DateFormatter { 77 | let df = DateFormatter() 78 | df.dateFormat = "yyyyMMdd'T'HHmmssZZZ" 79 | df.locale = Locale(identifier:"en_US_POSIX") 80 | return df 81 | } 82 | 83 | fileprivate func initSqlFormatter() -> DateFormatter { 84 | let df = DateFormatter() 85 | df.dateFormat = "yyyy-MM-dd HH:mm:ss" 86 | return df 87 | } 88 | 89 | fileprivate func initSimpleSqlFormatter() -> DateFormatter { 90 | let df = DateFormatter() 91 | df.dateFormat = "yyyy-MM-dd" 92 | return df 93 | } 94 | 95 | fileprivate func initCurrencyFormatter() -> NumberFormatter { 96 | let df = NumberFormatter() 97 | df.numberStyle = .currency 98 | df.locale = Locale(identifier: "pt_BR") 99 | return df 100 | } 101 | 102 | func dateToSql(fromHuman: String) -> String { 103 | let date = humamFormatter.date(from: fromHuman) 104 | return sqlFormatter.string(from: date!) 105 | } 106 | 107 | func dateToSimpleSql(fromHuman: String) -> String { 108 | let date = humamFormatter.date(from: fromHuman) 109 | return simpleSqlFormatter.string(from: date!) 110 | } 111 | 112 | func dateToHuman(fromSql: String) -> String { 113 | let date = sqlFormatter.date(from: fromSql) 114 | return humamFormatter.string(from: date!) 115 | } 116 | 117 | func convertToDate(fromSql: String) -> Date? { 118 | return sqlFormatter.date(from: fromSql) 119 | } 120 | 121 | func dateToHuman(fromSimpleSql: String) -> String { 122 | let date = simpleSqlFormatter.date(from: fromSimpleSql) 123 | return humamFormatter.string(from: date!) 124 | } 125 | 126 | func dateToHuman(from: String, toFormat: String) -> String { 127 | let df = DateFormatter() 128 | df.dateFormat = toFormat 129 | if let date = sqlFormatter.date(from: from) { 130 | return df.string(from: date) 131 | } 132 | return "" 133 | } 134 | 135 | func convertToDate(fromSimpleSql: String) -> Date? { 136 | return simpleSqlFormatter.date(from: fromSimpleSql) 137 | } 138 | 139 | // MARK: - Others 140 | 141 | func changeVisuals(_ visuals: Visuals) { 142 | theme.visuals = visuals 143 | let defaults = UserDefaults.standard 144 | defaults.set(visuals.identifier, forKey: Env.App.theming) 145 | 146 | if UIApplication.shared.supportsAlternateIcons { 147 | UIApplication.shared.setAlternateIconName(visuals.icon) 148 | } 149 | } 150 | 151 | func standardEmojiPack() -> EmojiPackViewModel { 152 | let pack = realm.objects(REmojiPack.self).filter("ascii = true").first! 153 | return EmojiPackViewModel(with: pack) 154 | } 155 | } 156 | 157 | // MARK: - Staging App 158 | class StagingAppImpl: ProductionAppImpl { 159 | 160 | public override init() { 161 | super.init() 162 | self.config = StagingAppConfigImpl() 163 | } 164 | } 165 | --------------------------------------------------------------------------------