Skip to content

Commit

Permalink
Merge branch 'release/1.1.1' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
mouredev committed Aug 2, 2021
2 parents 74f0e92 + 1c8cc4f commit 0a2614b
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 128 deletions.
8 changes: 6 additions & 2 deletions Twitimer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
AC294770268324F900B28A46 /* TextFieldPlaceholderStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC29476F268324F900B28A46 /* TextFieldPlaceholderStyle.swift */; };
AC2E2DFD267E31440091D5F5 /* UserSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2E2DFC267E31440091D5F5 /* UserSearch.swift */; };
AC2E2DFF267E362E0091D5F5 /* SearchQueryRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2E2DFE267E362E0091D5F5 /* SearchQueryRowView.swift */; };
AC4FBBA726B031140045C63B /* ActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC4FBBA626B031140045C63B /* ActionButton.swift */; };
AC5FC0A3268EBC8B0001406B /* UserScheduleSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5FC0A2268EBC8B0001406B /* UserScheduleSegment.swift */; };
AC7023BE26A6E7840040EDCA /* IQKeyboardManagerSwift in Frameworks */ = {isa = PBXBuildFile; productRef = AC7023BD26A6E7840040EDCA /* IQKeyboardManagerSwift */; };
AC83999A263150A500F0E8F1 /* TwitimerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC839999263150A500F0E8F1 /* TwitimerApp.swift */; };
Expand Down Expand Up @@ -126,6 +127,7 @@
AC29476F268324F900B28A46 /* TextFieldPlaceholderStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldPlaceholderStyle.swift; sourceTree = "<group>"; };
AC2E2DFC267E31440091D5F5 /* UserSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSearch.swift; sourceTree = "<group>"; };
AC2E2DFE267E362E0091D5F5 /* SearchQueryRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchQueryRowView.swift; sourceTree = "<group>"; };
AC4FBBA626B031140045C63B /* ActionButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionButton.swift; sourceTree = "<group>"; };
AC5FC0A2268EBC8B0001406B /* UserScheduleSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserScheduleSegment.swift; sourceTree = "<group>"; };
AC839996263150A500F0E8F1 /* Twitimer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Twitimer.app; sourceTree = BUILT_PRODUCTS_DIR; };
AC839999263150A500F0E8F1 /* TwitimerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitimerApp.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -498,6 +500,7 @@
AC839A162631533C00F0E8F1 /* MainButton.swift */,
AC275D38267B93F400C92508 /* NavigationButton.swift */,
ACD3ABE5263A0DE70066160E /* ChannelButton.swift */,
AC4FBBA626B031140045C63B /* ActionButton.swift */,
);
path = Controls;
sourceTree = "<group>";
Expand Down Expand Up @@ -859,6 +862,7 @@
AC839A362631533C00F0E8F1 /* AccountView.swift in Sources */,
AC839A302631533C00F0E8F1 /* UserRouter.swift in Sources */,
AC839A332631533C00F0E8F1 /* MainButton.swift in Sources */,
AC4FBBA726B031140045C63B /* ActionButton.swift in Sources */,
AC127C48267A6DA900B45CF1 /* Onboarding.swift in Sources */,
AC839A222631533C00F0E8F1 /* DateExtension.swift in Sources */,
AC8CED5326334E3B0079DCBE /* TimerRowView.swift in Sources */,
Expand Down Expand Up @@ -1080,7 +1084,7 @@
"@executable_path/Frameworks",
);
MACH_O_TYPE = mh_execute;
MARKETING_VERSION = 1.1;
MARKETING_VERSION = 1.1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.mouredev.Twitimer;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand All @@ -1106,7 +1110,7 @@
"@executable_path/Frameworks",
);
MACH_O_TYPE = mh_execute;
MARKETING_VERSION = 1.1;
MARKETING_VERSION = 1.1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.mouredev.Twitimer;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand Down
31 changes: 26 additions & 5 deletions Twitimer/Model/Domain/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ struct User: Codable {

let id: String?
let login: String?
let displayName: String?
let broadcasterType: BroadcasterType?
let descr: String?
let profileImageUrl: String?
let offlineImageUrl: String?
private(set) var displayName: String?
private(set) var broadcasterType: BroadcasterType?
private(set) var descr: String?
private(set) var profileImageUrl: String?
private(set) var offlineImageUrl: String?

// Optional
var streamer: Bool?
Expand Down Expand Up @@ -65,6 +65,27 @@ struct User: Codable {
return scheduleJSON
}

// Actualiza datos mutables del usuario. Esto ocurre cuando recuperamos de nuevo el usuario de Twitch para actualizarlo en Twitimer.
mutating func override(user: User) -> Bool {

var override = false
if displayName != user.displayName
|| broadcasterType?.rawValue != user.broadcasterType?.rawValue
|| descr != user.descr
|| profileImageUrl != user.profileImageUrl
|| offlineImageUrl != user.offlineImageUrl {
override = true
}

displayName = user.displayName
broadcasterType = user.broadcasterType
descr = user.descr
profileImageUrl = user.profileImageUrl
offlineImageUrl = user.offlineImageUrl

return override
}

// Actualiza el calendario del usuario a fechas disponibles a futuro
mutating func updateToAvailableSchedule() {

Expand Down
48 changes: 31 additions & 17 deletions Twitimer/Model/Session/Session.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,26 @@ final class Session {
}
}
}

func save(followedUser: String) {

func save(followedUser: User) {

let login = followedUser.login ?? ""

if let index = user?.followedUsers?.firstIndex(of: followedUser) {
if let index = user?.followedUsers?.firstIndex(of: login) {
user?.followedUsers?.remove(at: index)
streamers?.removeAll(where: { user in
return user.login == login
})

setupNotification(add: false, topic: followedUser)
setupNotification(add: false, topic: login)
} else {
if user?.followedUsers == nil {
user?.followedUsers = []
}
user?.followedUsers?.append(followedUser)
user?.followedUsers?.append(login)
streamers?.append(followedUser)

setupNotification(add: true, topic: followedUser)
setupNotification(add: true, topic: login)
}

if let user = user {
Expand Down Expand Up @@ -143,27 +149,23 @@ final class Session {
self.user?.followedUsers = followedUsers
self.user?.streamer = streamer

self.reloadUser(completion: completion)
self.reloadUser(override: true, completion: completion)

} failure: { (_) in
self.reloadUser(completion: completion)
}
}
}

func reloadUser(completion: @escaping () -> Void) {
func reloadUser(override: Bool = false, completion: @escaping () -> Void) {

firebaseAuth {
if let user = self.user {
FirebaseRDBService.shared.user(user: user) { user in
self.user = user
UserDefaultsProvider.setCodable(key: .authUser, value: user)
self.reloadStreamers(completion: completion)
if let currentUser = self.user {
FirebaseRDBService.shared.user(user: currentUser) { remoteUser in
self.saveNewUserAndReloadStreamers(currentUser: currentUser, newUser: remoteUser, override: override, completion: completion)
} failure: { _ in
FirebaseRDBService.shared.user(user: user, forceStreamer: true) { user in
self.user = user
UserDefaultsProvider.setCodable(key: .authUser, value: user)
self.reloadStreamers(completion: completion)
FirebaseRDBService.shared.user(user: currentUser, forceStreamer: true) { remoteUser in
self.saveNewUserAndReloadStreamers(currentUser: currentUser, newUser: remoteUser, override: override, completion: completion)
} failure: { _ in
self.reloadStreamers(completion: completion)
}
Expand Down Expand Up @@ -319,6 +321,18 @@ final class Session {
}
}

private func saveNewUserAndReloadStreamers(currentUser: User, newUser: User, override: Bool, completion: @escaping () -> Void) {
var user = newUser
if override, user.override(user: currentUser) {
self.user = user
save()
} else {
self.user = user
UserDefaultsProvider.setCodable(key: .authUser, value: user)
}
reloadStreamers(completion: completion)
}

private func mergeUsers(user: User, oldFollowers: Set<String>, success: @escaping () -> Void) {

self.user?.schedule = user.schedule
Expand Down
38 changes: 38 additions & 0 deletions Twitimer/UseCases/Common/Controls/ActionButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// ActionButton.swift
// Twitimer
//
// Created by Brais Moure on 27/7/21.
//

import SwiftUI

struct ActionButton: View {

let image: Image
let action: () -> Void

var body: some View {
Button(action: {
action()
}) {
image.template.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: Size.mediumBig.rawValue, height: Size.mediumBig.rawValue)
.foregroundColor(.lightColor)
}
.buttonStyle(BorderlessButtonStyle())
.padding(Size.small.rawValue)
.background(Color.primaryColor)
.clipShape(Circle())
.shadow(color: Color.darkColor.opacity(UIConstants.kShadowOpacity), radius: Size.verySmall.rawValue)
}
}

struct ActionButton_Previews: PreviewProvider {
static var previews: some View {
ActionButton(image: Image("calendar-add"), action: {
print("ActionButton primary action")
})
}
}
157 changes: 80 additions & 77 deletions Twitimer/UseCases/Common/Rows/ScheduleRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,95 +29,98 @@ struct ScheduleRowView: View {

var body: some View {

HStack(spacing: Size.small.rawValue) {
ZStack(alignment: .leading) {

if !readOnly {

Button(action: {
enable.toggle()
}) {
Image(enable ? "check-circle" : "cursor-select-circle").templateIcon(size: .mediumBig).foregroundColor(.lightColor)
}.buttonStyle(BorderlessButtonStyle())

}

VStack(alignment: .trailing, spacing: Size.small.rawValue) {
HStack(spacing: Size.small.rawValue) {

HStack(spacing: Size.small.rawValue) {
VStack(alignment: .trailing, spacing: Size.small.rawValue) {

if type == .custom {
Image("calendar-favorite").template
.resizable()
.foregroundColor(.lightColor)
.padding(Size.small.rawValue)
.frame(width: Size.veryBig.rawValue, height: Size.veryBig.rawValue)
.background(Color.primaryColor)
.clipShape(Circle())
.shadow(radius: Size.verySmall.rawValue)
HStack(spacing: Size.small.rawValue) {

} else {
Text(type.name.first?.uppercased() ?? "").foregroundColor(.textColor)
.font(size: .title, type: .light)
.frame(width: Size.veryBig.rawValue, height: Size.veryBig.rawValue)
.background(Color.backgroundColor)
.clipShape(Circle())
.shadow(radius: Size.verySmall.rawValue)
}

Text(type.name).font(size: .head).foregroundColor(.lightColor)

Spacer()
}

HStack(spacing: Size.verySmall.rawValue) {

if type == .custom {
DatePicker("", selection: $date, displayedComponents: [.date, .hourAndMinute]).labelsHidden().datePickerStyle(CompactDatePickerStyle())
.accentColor(readOnly ? .lightColor : .primaryColor)
.padding(Size.verySmall.rawValue).disabled(!enable || readOnly)
} else {

Image("time-clock-circle").templateIcon().foregroundColor(Color.lightColor)
if type == .custom {
Image("calendar-favorite").template
.resizable()
.foregroundColor(.lightColor)
.padding(Size.small.rawValue)
.frame(width: Size.veryBig.rawValue, height: Size.veryBig.rawValue)
.background(Color.primaryColor)
.clipShape(Circle())
.shadow(radius: Size.verySmall.rawValue)

} else {
Text(type.name.first?.uppercased() ?? "").foregroundColor(.textColor)
.font(size: .title, type: .light)
.frame(width: Size.veryBig.rawValue, height: Size.veryBig.rawValue)
.background(Color.backgroundColor)
.clipShape(Circle())
.shadow(radius: Size.verySmall.rawValue)
}

Text(type.name).font(size: .head).foregroundColor(.lightColor)

DatePicker("", selection: $date, displayedComponents: .hourAndMinute).labelsHidden().datePickerStyle(CompactDatePickerStyle()).accentColor(.lightColor).disabled(!enable || readOnly)
Spacer()
}

Image("hourglass").templateIcon().foregroundColor(Color.lightColor)
HStack(spacing: Size.verySmall.rawValue) {

if type == .custom {
DatePicker("", selection: $date, displayedComponents: [.date, .hourAndMinute]).labelsHidden().datePickerStyle(CompactDatePickerStyle())
.accentColor(readOnly ? .lightColor : .primaryColor)
.padding(Size.verySmall.rawValue).disabled(!enable || readOnly)
} else {

Image("time-clock-circle").templateIcon().foregroundColor(Color.lightColor)

DatePicker("", selection: $date, displayedComponents: .hourAndMinute).labelsHidden().datePickerStyle(CompactDatePickerStyle()).accentColor(.lightColor).disabled(!enable || readOnly)
}

Image("hourglass").templateIcon().foregroundColor(Color.lightColor)

Picker("\(readOnly ? "" : "+")\(duration)h", selection: $duration) {
ForEach((1...24), id: \.self) {
Text("+\($0)h").foregroundColor(.textColor)
}
}.font(size: .body)
.foregroundColor(.lightColor).pickerStyle(MenuPickerStyle()).disabled(!enable || readOnly)
.opacity(readOnly ? UIConstants.kViewOpacity : 1)

}.frame(height: Size.big.rawValue)

Picker("\(readOnly ? "" : "+")\(duration)h", selection: $duration) {
ForEach((1...24), id: \.self) {
Text("+\($0)h").foregroundColor(.textColor)
if !readOnly || (readOnly && !title.isEmpty) {

TextField("", text: $title).onReceive(Just(title)) { _ in
limitText(kTextLimit)
}
}.font(size: .body)
.foregroundColor(.lightColor).pickerStyle(MenuPickerStyle()).disabled(!enable || readOnly)
.opacity(readOnly ? UIConstants.kViewOpacity : 1)
.modifier(TextFieldPlaceholderStyle(showPlaceHolder: title.isEmpty, placeholder: eventPlaceholderText))
.font(size:.body)
.foregroundColor(readOnly ? .lightColor : .textColor)
.accentColor(readOnly ? .lightColor : .textColor)
.padding(Size.small.rawValue)
.background(readOnly ? Color.secondaryColor : Color.backgroundColor)
.clipShape(Capsule())
.disabled(!enable || readOnly)
}

}.frame(height: Size.big.rawValue)
}.padding(.vertical, Size.medium.rawValue)
}
.padding(.horizontal, Size.medium.rawValue)
.if(!readOnly) {
$0.padding(.leading, Size.big.rawValue)
}
.background(Color.secondaryColor)
.opacity(enable ? 1 : UIConstants.kViewOpacity)
.cornerRadius(Size.big.rawValue)
.shadow(radius: Size.verySmall.rawValue)
.padding(.vertical, Size.small.rawValue)

if !readOnly {

if !readOnly || (readOnly && !title.isEmpty) {

TextField("", text: $title).onReceive(Just(title)) { _ in
limitText(kTextLimit)
}
.modifier(TextFieldPlaceholderStyle(showPlaceHolder: title.isEmpty, placeholder: eventPlaceholderText))
.font(size:.body)
.foregroundColor(readOnly ? .lightColor : .textColor)
.accentColor(readOnly ? .lightColor : .textColor)
.padding(Size.small.rawValue)
.background(readOnly ? Color.secondaryColor : Color.backgroundColor)
.clipShape(Capsule())
.disabled(!enable || readOnly)
}

}.padding(.vertical, Size.medium.rawValue)

ActionButton(image: Image(enable ? "check-circle" : "cursor-select-circle"), action: {
enable.toggle()
})
.padding(.leading, Size.medium.rawValue)
}
}
.padding(.horizontal, Size.medium.rawValue)
.background(Color.secondaryColor)
.opacity(enable ? 1 : UIConstants.kViewOpacity)
.cornerRadius(Size.big.rawValue)
.shadow(radius: Size.verySmall.rawValue)
.padding(.vertical, Size.small.rawValue)
.listRowInsets(EdgeInsets())
.padding(.horizontal, Size.medium.rawValue)
.background(Color.secondaryBackgroundColor)
Expand Down
Loading

0 comments on commit 0a2614b

Please sign in to comment.