Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public extension View {
private struct PodcastMiniPlayerModifier: ViewModifier {
@Environment(PodcastPlayerManager.self) private var manager
@Environment(\.shouldUseSidebar) private var shouldUseSidebar
@Environment(\.colorScheme) private var appColorScheme
@Namespace private var animation

public init() {}
Expand All @@ -22,7 +23,8 @@ private struct PodcastMiniPlayerModifier: ViewModifier {
.safeAreaInset(edge: .bottom, spacing: 16) {
MiniPlayerView(
playerManager: manager,
currentPodcast: current
currentPodcast: current,
appColorScheme: appColorScheme
)
.frame(maxWidth: 480, alignment: .center)
.frame(height: 60)
Expand All @@ -35,7 +37,8 @@ private struct PodcastMiniPlayerModifier: ViewModifier {
.tabViewBottomAccessory {
MiniPlayerView(
playerManager: manager,
currentPodcast: current
currentPodcast: current,
appColorScheme: appColorScheme
)
.matchedTransitionSource(id: "MINIPLAYER", in: animation)
.padding(.horizontal, 8)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,27 @@ struct MiniPlayerView: View {
@Bindable var playerManager: PodcastPlayerManager

let currentPodcast: PodcastDB
let appColorScheme: ColorScheme

private var controlsColor: Color {
switch appColorScheme {
case .dark:
return .white
case .light:
return .black
@unknown default:
return .primary
}
}

init(
playerManager: PodcastPlayerManager,
currentPodcast: PodcastDB
currentPodcast: PodcastDB,
appColorScheme: ColorScheme
) {
self.playerManager = playerManager
self.currentPodcast = currentPodcast
self.appColorScheme = appColorScheme
}

var body: some View {
Expand Down Expand Up @@ -66,6 +80,7 @@ private extension MiniPlayerView {
Image(systemName: "gobackward.15")
.font(.system(size: 20))
}
.buttonStyle(.plain)

Button {
playerManager.togglePlayPause()
Expand All @@ -74,18 +89,21 @@ private extension MiniPlayerView {
Image(systemName: playerManager.isPlaying ? "pause.fill" : "play.fill")
.font(.system(size: 24))
}
.buttonStyle(.plain)

Button {
playerManager.skip(by: 15)
} label: {
Image(systemName: "goforward.15")
.font(.system(size: 20))
}
.buttonStyle(.plain)

}
}
.foregroundStyle(controlsColor)
.contentShape(Rectangle())
}
.foregroundColor(.primary)
}

@ViewBuilder
Expand Down
6 changes: 4 additions & 2 deletions MacMagazine/MacMagazine.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -627,9 +627,10 @@
DEVELOPMENT_TEAM = A5VW9QUF9L;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = WatchkApp;
INFOPLIST_KEY_CFBundleDisplayName = MacMagazine;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.brit.beta.macmagazine;
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = NO;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -661,9 +662,10 @@
DEVELOPMENT_TEAM = A5VW9QUF9L;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = WatchkApp;
INFOPLIST_KEY_CFBundleDisplayName = MacMagazine;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.brit.beta.macmagazine;
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = NO;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
66 changes: 66 additions & 0 deletions MacMagazine/WatchApp/Extension/FeedDB.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import FeedLibrary
import Foundation

extension FeedDB {
var linkURL: URL? {
guard !link.isEmpty else { return nil }
return URL(string: link)
}

var dateText: String {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "pt_BR")
formatter.dateFormat = "dd/MM/yyyy 'Γ s' HH:mm"
return formatter.string(from: pubDate)
}

var displaySubtitle: String? {
let subtitle = subtitle.trimmingCharacters(in: .whitespacesAndNewlines)
return subtitle.isEmpty ? nil : subtitle
}

var displayBody: String? {
let full = fullContent.trimmingCharacters(in: .whitespacesAndNewlines)
if !full.isEmpty { return full }

let excerpt = excerpt.trimmingCharacters(in: .whitespacesAndNewlines)
return excerpt.isEmpty ? nil : excerpt
}

var artworkRemoteURL: URL? {
guard !artworkURL.isEmpty else { return nil }
return URL(string: artworkURL)
}

static var previewItem: FeedDB {
FeedDB(
postId: UUID().uuidString,
title: "Apple lanΓ§a atualizaΓ§Γ£o do watchOS",
subtitle: "MudanΓ§as importantes para o Apple Watch",
pubDate: Date().addingTimeInterval(-3600),
artworkURL: "https://picsum.photos/400/400",
link: "https://macmagazine.com.br",
categories: ["watchos", "news", "teste1", "teste2", "teste 3"],
excerpt: "Resumo curto para teste no relΓ³gio…",
fullContent: "",
favorite: false
)
}

static var previewItems: [FeedDB] {
(1...10).map { index in
FeedDB(
postId: UUID().uuidString,
title: "NotΓ­cia \(index): tΓ­tulo de teste para o Watch",
subtitle: "SubtΓ­tulo \(index)",
pubDate: Date().addingTimeInterval(TimeInterval(-index * 900)),
artworkURL: "https://picsum.photos/seed/\(index)/600/600",
link: "https://macmagazine.com.br",
categories: ["news", "teste1", "teste2", "teste 3"],
excerpt: "Excerpt \(index) – texto curto para validar layout.",
fullContent: "",
favorite: index % 3 == 0
)
}
}
}
65 changes: 65 additions & 0 deletions MacMagazine/WatchApp/Helper/FeedDotsIndicatorView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import SwiftUI

enum IndicatorAxis {
case horizontal
case vertical
}

// MARK: - Dots Indicator

public struct FeedDotsIndicatorView: View {
let axis: IndicatorAxis
let count: Int
let selectedIndex: Int

public var body: some View {
Group {
switch axis {
case .vertical:
VStack(spacing: 5) {
dots
}

case .horizontal:
HStack(spacing: 6) {
dots
}
}
}
.padding(6)
.glassEffect(.clear)
.allowsHitTesting(false)
}

private var dots: some View {
ForEach(0..<count, id: \.self) { index in
Circle()
.fill(dotColor(for: index))
.frame(width: dotSize(for: index), height: dotSize(for: index))
.shadow(color: index == selectedIndex ? .white.opacity(0.5) : .clear, radius: 2)
.animation(.easeInOut(duration: 0.2), value: selectedIndex)
}
}

private func dotColor(for index: Int) -> Color {
index == selectedIndex ? .white : .white.opacity(0.4)
}

private func dotSize(for index: Int) -> CGFloat {
index == selectedIndex ? 8 : 5
}
}

#Preview("Vertical") {
ZStack {
Color.gray
FeedDotsIndicatorView(axis: .vertical, count: 10, selectedIndex: 3)
}
}

#Preview("Horizontal") {
ZStack {
Color.gray
FeedDotsIndicatorView(axis: .horizontal, count: 5, selectedIndex: 2)
}
}
106 changes: 106 additions & 0 deletions MacMagazine/WatchApp/Helper/FlowTagsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import SwiftUI

struct FlowTagsView<Tag: Hashable, Content: View>: View {

// MARK: - Properties

let tags: [Tag]
let horizontalSpacing: CGFloat
let verticalSpacing: CGFloat
let content: (Tag) -> Content

@State private var measuredHeight: CGFloat = 0

// MARK: - Init

init(
tags: [Tag],
horizontalSpacing: CGFloat,
verticalSpacing: CGFloat,
@ViewBuilder content: @escaping (Tag) -> Content
) {
self.tags = tags
self.horizontalSpacing = horizontalSpacing
self.verticalSpacing = verticalSpacing
self.content = content
}

// MARK: - Body

var body: some View {
GeometryReader { proxy in
let rows = makeRows(availableWidth: proxy.size.width)

VStack(alignment: .leading, spacing: verticalSpacing) {
ForEach(rows.indices, id: \.self) { rowIndex in
HStack(spacing: horizontalSpacing) {
ForEach(rows[rowIndex], id: \.self) { tag in
content(tag)
.fixedSize(horizontal: true, vertical: true)
}
}
}
}
.background(
GeometryReader { innerProxy in
Color.clear
.preference(key: HeightPreferenceKey.self, value: innerProxy.size.height)
}
)
}
.frame(height: measuredHeight)
.onPreferenceChange(HeightPreferenceKey.self) { height in
if height != measuredHeight {
measuredHeight = height
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.fixedSize(horizontal: false, vertical: true)
}

// MARK: - Layout

private func makeRows(availableWidth: CGFloat) -> [[Tag]] {
var rows: [[Tag]] = [[]]
var currentRowWidth: CGFloat = 0

for tag in tags {
let tagWidth = estimatedTagWidth(tag: tag)

if rows[rows.count - 1].isEmpty {
rows[rows.count - 1].append(tag)
currentRowWidth = tagWidth
continue
}

let nextWidth = currentRowWidth + horizontalSpacing + tagWidth

if nextWidth <= availableWidth {
rows[rows.count - 1].append(tag)
currentRowWidth = nextWidth
} else {
rows.append([tag])
currentRowWidth = tagWidth
}
}

return rows
}

private func estimatedTagWidth(tag: Tag) -> CGFloat {
let text = String(describing: tag)
let estimatedCharacterWidth: CGFloat = 6
let horizontalPadding: CGFloat = 16
return CGFloat(text.count) * estimatedCharacterWidth + horizontalPadding
}
}

// MARK: - Height Preference

private struct HeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0

static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
14 changes: 14 additions & 0 deletions MacMagazine/WatchApp/Model/SelectedPost.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import SwiftUI
import FeedLibrary

// MARK: - Navigation Payload

struct SelectedPost: Hashable, Identifiable {
let id: String
let post: FeedDB

init(post: FeedDB) {
id = post.postId
self.post = post
}
}
22 changes: 22 additions & 0 deletions MacMagazine/WatchApp/Resources/WatchApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import FeedLibrary
import StorageLibrary
import SwiftUI

@main
struct WatchApp: App {

private let database = Database(models: [FeedDB.self], inMemory: false)

var body: some Scene {
WindowGroup {
FeedRootView(
viewModel: FeedRootViewModel(
feedViewModel: FeedViewModel(
network: nil,
storage: database
)
)
)
}
}
}
Loading
Loading