Commit 7b560730 by Dmitriy Stepanets

Finished Shorts UI

parent 5c505122
No preview for this file type
......@@ -155,6 +155,8 @@
CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1E72572429E00CF5017 /* AppFont.swift */; };
CDD0F1EE25725BCF00CF5017 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */; };
CDD75F0D25DE68B10099ACDB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CDD75F0F25DE68B10099ACDB /* Localizable.strings */; };
CDDCD50726809F6D00E089AD /* ShortsSwipeHelperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDCD50626809F6D00E089AD /* ShortsSwipeHelperView.swift */; };
CDDCD5092680C18B00E089AD /* ShortsUnreadNudgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDCD5082680C18B00E089AD /* ShortsUnreadNudgeView.swift */; };
CDDE8D7C262EED3C00267931 /* MapLegendSevereView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDE8D7B262EED3C00267931 /* MapLegendSevereView.swift */; };
CDDE8D7F262EED4D00267931 /* MapLegendWeatherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDE8D7E262EED4D00267931 /* MapLegendWeatherView.swift */; };
CDE18DCD25D1666700C80ED9 /* ForecastCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE18DCC25D1666700C80ED9 /* ForecastCoordinator.swift */; };
......@@ -410,6 +412,8 @@
CDD0F1E72572429E00CF5017 /* AppFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFont.swift; sourceTree = "<group>"; };
CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
CDD75F0E25DE68B10099ACDB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
CDDCD50626809F6D00E089AD /* ShortsSwipeHelperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortsSwipeHelperView.swift; sourceTree = "<group>"; };
CDDCD5082680C18B00E089AD /* ShortsUnreadNudgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortsUnreadNudgeView.swift; sourceTree = "<group>"; };
CDDE8D7B262EED3C00267931 /* MapLegendSevereView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLegendSevereView.swift; sourceTree = "<group>"; };
CDDE8D7E262EED4D00267931 /* MapLegendWeatherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLegendWeatherView.swift; sourceTree = "<group>"; };
CDE18DCC25D1666700C80ED9 /* ForecastCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastCoordinator.swift; sourceTree = "<group>"; };
......@@ -870,6 +874,7 @@
CD8579662671F0A600CC4CDA /* Shorts */ = {
isa = PBXGroup;
children = (
CDDCD50526809F5200E089AD /* Views */,
CD85796B2671FCD000CC4CDA /* Cells */,
CD8579672671F10C00CC4CDA /* ShortsViewController.swift */,
);
......@@ -1076,6 +1081,15 @@
path = Helpers;
sourceTree = "<group>";
};
CDDCD50526809F5200E089AD /* Views */ = {
isa = PBXGroup;
children = (
CDDCD50626809F6D00E089AD /* ShortsSwipeHelperView.swift */,
CDDCD5082680C18B00E089AD /* ShortsUnreadNudgeView.swift */,
);
path = Views;
sourceTree = "<group>";
};
CDE18DCF25D166DD00C80ED9 /* Forecast */ = {
isa = PBXGroup;
children = (
......@@ -1572,6 +1586,7 @@
CE13B814262480B3007CBD4D /* BRNativeBannerView.swift in Sources */,
CD18728B2624763000AFEDAA /* MapLegendView.swift in Sources */,
CDB0D4CA2670CAD00081C773 /* ShortsCollectionViewCell.swift in Sources */,
CDDCD5092680C18B00E089AD /* ShortsUnreadNudgeView.swift in Sources */,
CE13B818262480B3007CBD4D /* A9BidObject.swift in Sources */,
CD85796A2671FA8100CC4CDA /* ShortsCoordinator.swift in Sources */,
CDB0D4CE2670DABF0081C773 /* UIImage+AverageColor.swift in Sources */,
......@@ -1682,6 +1697,7 @@
CDE18DCD25D1666700C80ED9 /* ForecastCoordinator.swift in Sources */,
CE895F0F26393FD800214175 /* WeatherImageProvider.swift in Sources */,
CD85797526721DE500CC4CDA /* UITabBarController+Append.swift in Sources */,
CDDCD50726809F6D00E089AD /* ShortsSwipeHelperView.swift in Sources */,
CD85797D267221AC00CC4CDA /* UITabBarController+Hide.swift in Sources */,
CDC6126625E9085600188DA7 /* GraphLine.swift in Sources */,
CE13B819262480B3007CBD4D /* A9Cache.swift in Sources */,
......
......@@ -12,7 +12,7 @@
<key>OneWeatherNotificationServiceExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>60</integer>
<integer>59</integer>
</dict>
<key>PG (Playground) 1.xcscheme</key>
<dict>
......
......@@ -17,12 +17,18 @@ public struct AppConfig: Codable {
public let adConfig: AdConfig
public let ccpaUpdateInterval: TimeInterval?
public let nwsAlertsViaMoEngageEnabled: Bool
public let shortsLeftBelowCount: Int
public let shortsLastNudgeEnabled: Bool
public let shortsSwipeUpNudgeCount: Int
public init(popularCities: [GeoNamesPlace]?, adConfig: AdConfig, ccpaUpdateInterval: TimeInterval?, nwsAlertsViaMoEngageEnabled: Bool) {
public init(popularCities: [GeoNamesPlace]?, adConfig: AdConfig, ccpaUpdateInterval: TimeInterval?, nwsAlertsViaMoEngageEnabled: Bool, shortsLeftBelowCountKey: Int, shortsLastNudgeEnabledKey: Bool, shortsSwipeUpNudgeCountKey: Int) {
self.popularCities = popularCities
self.adConfig = adConfig
self.ccpaUpdateInterval = ccpaUpdateInterval
self.nwsAlertsViaMoEngageEnabled = nwsAlertsViaMoEngageEnabled
self.shortsLeftBelowCount = shortsLeftBelowCountKey
self.shortsLastNudgeEnabled = shortsLastNudgeEnabledKey
self.shortsSwipeUpNudgeCount = shortsSwipeUpNudgeCountKey
}
}
......@@ -35,6 +41,9 @@ public class ConfigManager {
private static let popularCitiesConfigKey = "search_screen_popular_cities"
private static let ccpaUpdateIntervalConfigKey = "ccpa_update_interval_ios"
private static let nwsAlertsViaMoEngageEnabledKey = "ios_nws_alerts_via_moengage_enabled"
private static let shortsLeftBelowCountKey = "shorts_left_below_nudge_every_x_cards"
private static let shortsLastNudgeEnabledKey = "shorts_swipe_down_nudge_enabled"
private static let shortsSwipeUpNudgeCountKey = "shorts_swipe_up_nudge_on_x_cards"
private let delegates = MulticastDelegate<ConfigManagerDelegate>()
......@@ -50,7 +59,13 @@ public class ConfigManager {
public static let shared = ConfigManager()
public var config: AppConfig = AppConfig(popularCities: nil, adConfig: AdConfig(), ccpaUpdateInterval: nil, nwsAlertsViaMoEngageEnabled: true)
public var config: AppConfig = AppConfig(popularCities: nil,
adConfig: AdConfig(),
ccpaUpdateInterval: nil,
nwsAlertsViaMoEngageEnabled: true,
shortsLeftBelowCountKey: 0,
shortsLastNudgeEnabledKey: false,
shortsSwipeUpNudgeCountKey: 0)
public func updateConfig() {
log.info("update config")
......@@ -122,9 +137,23 @@ public class ConfigManager {
let nwsAlertsViaMoEngageEnabledValue = remoteConfig.configValue(forKey: ConfigManager.nwsAlertsViaMoEngageEnabledKey)
let nwsAlertsViaMoEngageEnabled = nwsAlertsViaMoEngageEnabledValue.boolValue
let shortsLeftBelowCountValue = remoteConfig.configValue(forKey: ConfigManager.shortsLeftBelowCountKey)
let shortsLeftBelowCount = shortsLeftBelowCountValue.numberValue.intValue
let shortsLastNudgeEnabledValue = remoteConfig.configValue(forKey: ConfigManager.shortsLastNudgeEnabledKey)
let shortsLastNudgeEnabled = shortsLastNudgeEnabledValue.boolValue
let shortsSwipeNudgeCountValue = remoteConfig.configValue(forKey: ConfigManager.shortsSwipeUpNudgeCountKey)
let shortsSwipeNudgeCount = shortsSwipeNudgeCountValue.numberValue.intValue
DispatchQueue.main.async {
self.config = AppConfig(popularCities: popularCities, adConfig: adConfig, ccpaUpdateInterval: ccpaUpdateInterval, nwsAlertsViaMoEngageEnabled: nwsAlertsViaMoEngageEnabled)
self.config = AppConfig(popularCities: popularCities,
adConfig: adConfig,
ccpaUpdateInterval:ccpaUpdateInterval,
nwsAlertsViaMoEngageEnabled: nwsAlertsViaMoEngageEnabled,
shortsLeftBelowCountKey: shortsLeftBelowCount,
shortsLastNudgeEnabledKey: shortsLastNudgeEnabled,
shortsSwipeUpNudgeCountKey: shortsSwipeNudgeCount)
self.notifyAboutConfigUpdate()
DispatchQueue.global().async {
let encoder = JSONEncoder()
......
{
"images" : [
{
"filename" : "shorts_swipe_down.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
......@@ -157,6 +157,9 @@
//Shorts
"shorts.source" = "Source";
"shorts.unread" = "%d unread Shorts left below";
"shorts.viewedAll" = "You're viewed all Shorts\nSwipe down to revisit";
"shorts.swipeUp" = "Swipe up for more Shorts";
//Radar
"radar.layers.base" = "Base layer";
......
......@@ -40,6 +40,10 @@ public struct AppFont {
font(weigth: .regular, size: size)
}
static func light(size: CGFloat) -> UIFont {
font(weigth: .light, size: size)
}
static func bold(size: CGFloat) -> UIFont {
font(weigth: .bold, size: size)
}
......
......@@ -10,8 +10,8 @@ import OneWeatherCore
import pop
private enum ScrollDirection {
case toTop
case toBottom
case swipeDown //toTop
case swipeUp //toBottom
}
class ShortsViewController: UIViewController {
......@@ -20,9 +20,12 @@ class ShortsViewController: UIViewController {
private let coordinator:ShortsCoordinator
private let viewModel = ShortsViewModel(shortsManager: ShortsManager.shared)
private let tableView = UITableView()
private let swipeHelperView = ShortsSwipeHelperView()
private let unreadShortsView = ShortsUnreadNudgeView()
private var averageColorCache = [AnyHashable:UIColor]()
private var lastOffset: CGFloat = 0
private var itemIndexToScroll: Int?
private var swipeUpCounter = 0
deinit {
print("[ShortsViewController] deinit")
......@@ -43,32 +46,33 @@ class ShortsViewController: UIViewController {
viewModel.delegate = self
prepareTableView()
prepareSwipeHelperView()
prepareUnreadView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard !viewModel.shorts.isEmpty else { return }
if let indexToScroll = itemIndexToScroll {
tableView.scrollToRow(at: [0, indexToScroll], at: .top, animated: false)
lastOffset = tableView.contentOffset.y
updateView(basedOnDirection: .swipeUp, willShowRowAt: indexToScroll)
itemIndexToScroll = nil
viewModel.markAsViewed(atIndex: indexToScroll)
}
else {
lastOffset = 0
updateView(basedOnDirection: .swipeDown, willShowRowAt: 0)
tableView.scrollToRow(at: [0, 0], at: .top, animated: false)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if lastOffset == 0 {
viewModel.markAsViewed(atIndex: 0)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
swipeUpCounter = 0
ShortsManager.shared.reordering()
}
......@@ -81,6 +85,66 @@ class ShortsViewController: UIViewController {
tableView.pop_add(animation, forKey: kAnimationKey)
}
private func updateView(basedOnDirection directon: ScrollDirection, willShowRowAt rowIndex:Int) {
//Increment swipe up counter
if directon == .swipeUp {
if rowIndex == viewModel.shorts.count - 1 {
swipeUpCounter = 0
}
else {
swipeUpCounter += 1
}
}
//If counter equal to config value, showing the view
if swipeUpCounter == ConfigManager.shared.config.shortsLeftBelowCount {
self.showUnreadShortsView(willShowRowIndex: rowIndex)
}
//Show the swipe helper view if needed
if ConfigManager.shared.config.shortsLastNudgeEnabled {
//Check for the last row
if rowIndex == viewModel.shorts.count - 1 {
swipeHelperView.configure(forState: .downViewedAll)
UIView.animate(withDuration: 0.3) {
self.swipeHelperView.alpha = 1
}
return
}
}
if Settings.shared.shortsSwipeUpNudgeShowedCount <= ConfigManager.shared.config.shortsSwipeUpNudgeCount {
swipeHelperView.configure(forState: .upNext)
UIView.animate(withDuration: 0.3) {
self.swipeHelperView.alpha = 1
}
return
}
//Hide swipe helper view
UIView.animate(withDuration: 0.3) {
self.swipeHelperView.alpha = 0
}
}
private func showUnreadShortsView(willShowRowIndex index:Int) {
swipeUpCounter = 0
let unreadCount = max(0, viewModel.shorts.count - index)
guard unreadCount > 0 else { return }
unreadShortsView.configure(withCount: unreadCount)
UIView.animate(withDuration: 0.3) {
self.unreadShortsView.alpha = 1
} completion: { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
UIView.animate(withDuration: 0.3) {
self.unreadShortsView.alpha = 0
}
}
}
}
func set(indexToScroll: Int) {
itemIndexToScroll = indexToScroll
}
......@@ -101,6 +165,26 @@ private extension ShortsViewController {
make.edges.equalToSuperview()
}
}
func prepareSwipeHelperView() {
swipeHelperView.alpha = 0
view.addSubview(swipeHelperView)
swipeHelperView.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(8)
make.centerX.equalToSuperview()
}
}
func prepareUnreadView() {
unreadShortsView.alpha = 0
view.addSubview(unreadShortsView)
unreadShortsView.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(8)
make.centerX.equalToSuperview()
}
}
}
//MARK:- UITableView Data Source
......@@ -132,10 +216,10 @@ extension ShortsViewController: UITableViewDelegate {
//Get direction
let direction:ScrollDirection
if velocity.y != 0 {
direction = velocity.y > 0 ? .toBottom : .toTop
direction = velocity.y > 0 ? .swipeUp : .swipeDown
}
else {
direction = targetContentOffset.pointee.y - lastOffset > 0 ? .toBottom : .toTop
direction = targetContentOffset.pointee.y - lastOffset > 0 ? .swipeUp : .swipeDown
}
//Save last offset
......@@ -156,7 +240,7 @@ extension ShortsViewController: UITableViewDelegate {
//Calculate next row
let nextRowIndexPath:IndexPath
switch direction {
case .toBottom:
case .swipeUp:
let rowsCount = tableView.numberOfRows(inSection: topRowIndexPath.section)
let nextIndex = min(topRowIndexPath.row + 1, rowsCount - 1)
nextRowIndexPath = IndexPath(row: nextIndex, section: topRowIndexPath.section)
......@@ -164,14 +248,16 @@ extension ShortsViewController: UITableViewDelegate {
//Check for the last row
if nextRowIndexPath.row == rowsCount - 1 {
viewModel.markAsViewed(atIndex: nextRowIndexPath.row)
updateView(basedOnDirection: direction, willShowRowAt: nextRowIndexPath.row)
let offset = tableView.contentSize.height - tableView.frame.height
self.scrollTo(newOffset: .init(x: tableView.contentOffset.x, y: offset), velocity: velocity)
return
}
case .toTop:
case .swipeDown:
if topRowIndexPath.row == 0 {
self.scrollTo(newOffset: .zero, velocity: velocity)
viewModel.markAsViewed(atIndex: 0)
updateView(basedOnDirection: direction, willShowRowAt: 0)
return
}
......@@ -180,6 +266,7 @@ extension ShortsViewController: UITableViewDelegate {
let nextRowRect = tableView.rectForRow(at: nextRowIndexPath)
viewModel.markAsViewed(atIndex: nextRowIndexPath.row)
updateView(basedOnDirection: direction, willShowRowAt: nextRowIndexPath.row)
self.scrollTo(newOffset: nextRowRect.origin, velocity: velocity)
}
......
//
// ShortsSwipeHelperView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 21.06.2021.
//
import UIKit
enum ShortsSwipeState {
case downViewedAll
case upNext
}
class ShortsSwipeHelperView: UIView {
//Private
private let label = UILabel()
private let imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
prepareLabel()
prepareImageView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(forState state:ShortsSwipeState) {
switch state {
case .downViewedAll:
imageView.transform = .identity
label.text = "shorts.viewedAll".localized()
case .upNext:
imageView.transform = CGAffineTransform(rotationAngle: 180 * .pi / 180)
label.text = "shorts.swipeUp".localized()
}
}
}
//MARK:- Prepare
private extension ShortsSwipeHelperView {
func prepareLabel() {
label.font = AppFont.SFPro.regular(size: 12)
label.numberOfLines = 0
label.textAlignment = .center
label.lineBreakMode = .byWordWrapping
label.textColor = .white
addSubview(label)
label.snp.makeConstraints { make in
make.left.bottom.right.equalToSuperview()
}
}
func prepareImageView() {
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(named: "shorts_swipe_down")
addSubview(imageView)
imageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview()
make.width.equalTo(14)
make.height.equalTo(30)
make.bottom.equalTo(label.snp.top).offset(-8)
}
}
}
//
// ShortsUnreadNudgeView.swift
// 1Weather
//
// Created by Dmitry Stepanets on 21.06.2021.
//
import UIKit
class ShortsUnreadNudgeView: UIView {
//Private
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
prepareLabel()
updateUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = (bounds.height / 2).rounded(.down)
}
func configure(withCount count:Int) {
label.text = "shorts.unread".localizedFormat(count)
}
private func updateUI() {
switch interfaceStyle {
case .light:
label.textColor = ThemeManager.currentTheme.secondaryTextColor
case .dark:
label.textColor = ThemeManager.currentTheme.primaryTextColor
}
backgroundColor = ThemeManager.currentTheme.containerBackgroundColor
}
}
private extension ShortsUnreadNudgeView {
func prepareLabel() {
label.font = AppFont.SFPro.regular(size: 12)
label.textAlignment = .center
label.text = "shorts.unread".localizedFormat(0)
addSubview(label)
label.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(26)
make.top.bottom.equalToSuperview().inset(12)
}
}
}
......@@ -41,7 +41,9 @@ class ShortsViewModel: ViewModelProtocol {
func markAsViewed(atIndex index: Int) {
guard index < shorts.count else { return }
print("[ShortsViewModel mark as viewed \(shorts[index].title)]")
if Settings.shared.shortsSwipeUpNudgeShowedCount <= ConfigManager.shared.config.shortsSwipeUpNudgeCount {
Settings.shared.shortsSwipeUpNudgeShowedCount += 1
}
shortsManager.markAsViewed(item: shorts[index])
}
}
......
......@@ -95,6 +95,9 @@ public class Settings {
@UserDefaultsBasicValue(key: "locationDidAdded")
public var locationDidAdded:Bool = false
@UserDefaultsBasicValue(key: "shorts_showed_swipeUp_count")
public var shortsSwipeUpNudgeShowedCount = 0
#warning("Not implemented!")
//TODO: implement store in UserDefaults and configure via UI in debug builds.
public var adLogging: Bool = true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment