Rebranded the app for iOS

This commit is contained in:
Matheus Gomes
2025-06-09 19:26:12 +02:00
committed by Konstantin Pastbin
parent 51b669cfd1
commit d4e1d53c40
85 changed files with 506 additions and 1309 deletions

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "logo.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,9 @@
import WidgetKit
import SwiftUI
@main
struct CoMapsWidgetExtensionBundle: WidgetBundle {
var body: some Widget {
TrackRecordingLiveActivityConfiguration()
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,9 @@
import SwiftUI
struct AppLogo: View {
var body: some View {
Image(.logo)
.resizable()
.aspectRatio(contentMode: .fit)
}
}

View File

@@ -0,0 +1,26 @@
import ActivityKit
import SwiftUI
@available(iOS 16.2, *)
final class LiveActivityManager {
static func startActivity<T: ActivityAttributes>(_ attributes: T, content: ActivityContent<T.ContentState>) throws -> Activity<T> {
return try Activity.request(attributes: attributes, content: content, pushType: nil)
}
static func update<T: ActivityAttributes>(_ activity: Activity<T>, content: ActivityContent<T.ContentState>) {
Task {
await activity.update(content)
}
}
static func stop(_ activity: Activity<some ActivityAttributes>) {
// semaphore is used for removing the activity during the app termination
let semaphore = DispatchSemaphore(value: 0)
Task {
await activity.end(nil, dismissalPolicy: .immediate)
semaphore.signal()
}
semaphore.wait()
}
}

View File

@@ -0,0 +1,28 @@
import SwiftUI
struct StatisticDetailView: View {
private let title: String
private let subtitle: String
init(title: String, subtitle: String) {
self.title = title
self.subtitle = subtitle
}
var body: some View {
VStack(alignment: .leading) {
Text(title)
.contentTransition(.numericText())
.font(.system(size: 14, weight: .bold).monospacedDigit())
.minimumScaleFactor(0.5)
.lineLimit(1)
.foregroundStyle(.white)
Text(subtitle)
.font(.system(size: 10))
.minimumScaleFactor(0.5)
.lineLimit(2)
.multilineTextAlignment(.center)
.foregroundStyle(.white)
}
}
}

View File

@@ -0,0 +1,17 @@
import SwiftUI
struct StatisticValueView: View {
private let value: String
init(_ value: String) {
self.value = value
}
var body: some View {
Text(value)
.contentTransition(.numericText())
.minimumScaleFactor(0.1)
.font(.title3.bold().monospacedDigit())
.foregroundStyle(.white)
}
}

View File

@@ -0,0 +1,17 @@
import ActivityKit
import AppIntents
struct TrackRecordingLiveActivityAttributes: ActivityAttributes {
struct ContentState: Codable, Hashable {
struct StatisticsViewModel: Codable, Hashable {
let key: String
let value: String
}
let duration: StatisticsViewModel
let distance: StatisticsViewModel
let ascent: StatisticsViewModel
let descent: StatisticsViewModel
let maxElevation: StatisticsViewModel
let minElevation: StatisticsViewModel
}
}

View File

@@ -0,0 +1,26 @@
import WidgetKit
import SwiftUI
struct TrackRecordingLiveActivityConfiguration: Widget {
@Environment(\.colorScheme) private var colorScheme
var body: some WidgetConfiguration {
ActivityConfiguration(for: TrackRecordingLiveActivityAttributes.self) { context in
TrackRecordingLiveActivityView(state: context.state)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.center) {
// TODO: Implement the expanded view
}
} compactLeading: {
AppLogo()
} compactTrailing: {
StatisticValueView(context.state.duration.value)
} minimal: {
// TODO: Implement the minimal view
}
}
}
}

View File

@@ -0,0 +1,73 @@
import SwiftUI
import WidgetKit
#if canImport(ActivityKit)
struct TrackRecordingLiveActivityView: View {
let state: TrackRecordingLiveActivityAttributes.ContentState
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack(alignment: .top, spacing: 24) {
StatisticValueView(state.duration.value)
StatisticValueView(state.distance.value)
Spacer()
AppLogo()
.frame(width: 20, height: 20)
}
.padding([.top, .leading, .trailing], 16)
HStack(alignment: .top, spacing: 20) {
StatisticDetailView(title: state.ascent.value, subtitle: state.ascent.key)
StatisticDetailView(title: state.descent.value, subtitle: state.descent.key)
StatisticDetailView(title: state.maxElevation.value, subtitle: state.maxElevation.key)
StatisticDetailView(title: state.minElevation.value, subtitle: state.minElevation.key)
}
.padding([.leading, .trailing, .bottom], 16)
}
.activityBackgroundTint(.black.opacity(0.2))
// Uncomment the line below to simulate the background color in Preview because the `activityBackgroundTint` can only displayed on the device or simulator.
//.background(.black.opacity(0.85))
}
}
#if DEBUG
struct TrackRecordingLiveActivityWidget_Previews: PreviewProvider {
static var previews: some View {
Group {
let activityAttributes = TrackRecordingLiveActivityAttributes()
let activityState = TrackRecordingLiveActivityAttributes.ContentState(
duration: .init(
key: "Duration",
value: "1h 12min"
),
distance: .init(
key: "Distance",
value: "12 km"
),
ascent: .init(
key: "Ascent",
value: "100 m"
),
descent: .init(
key: "Descent",
value: "100 m"
),
maxElevation: .init(
key: "Max Elevation",
value: "100 m"
),
minElevation: .init(
key: "Min Elevation",
value: "10 m"
)
)
activityAttributes
.previewContext(activityState, viewKind: .content)
.previewDisplayName("Notification")
}
}
}
#endif
#endif