Skip to content

Commit

Permalink
Initialize Mart
Browse files Browse the repository at this point in the history
  • Loading branch information
InteligenceQ authored Feb 23, 2025
0 parents commit 546c776
Show file tree
Hide file tree
Showing 49 changed files with 2,876 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"image": "mcr.microsoft.com/devcontainers/universal:2",
"features": {
}
}

22 changes: 22 additions & 0 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.# This workflow will build a Swift project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift

name: Swift

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: macos-latest

steps:
- uses: actions/checkout@v4
- name: Build
run: swift build -v
- name: Run tests
run: swift test -v
Binary file added 1024.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 102 additions & 0 deletions Album.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
See the LICENSE.txt file for this sample’s licensing information.

Abstract:
The model object for an album of media assets.
*/

import Foundation
import Photos

@Observable
final class Album {

// MARK: Properties

let collection: PHAssetCollection

var title: String
var assets = [Asset]()

var creationDate: Date? {
collection.startDate
}

// MARK: Lifecycle

init(collection: PHAssetCollection) {
self.collection = collection
self.title = collection.localizedTitle ?? ""
}

// MARK: Methods

func setTitle(_ title: String) async throws {
try await PHPhotoLibrary.shared().performChanges {
let request = PHAssetCollectionChangeRequest(for: self.collection)
request?.title = title
}
self.title = title
}

func setAssets(_ assets: Set<Asset>) async throws {
let current = Set(self.assets)

let toInsert = assets.subtracting(current).map(\.phAsset)
let toRemove = current.subtracting(assets).map(\.phAsset)

try await PHPhotoLibrary.shared().performChanges {
let request = PHAssetCollectionChangeRequest(for: self.collection)
request?.addAssets(toInsert as NSFastEnumeration)
request?.removeAssets(toRemove as NSFastEnumeration)
}
self.assets = Array(assets)
}

func fetchAssets() async throws {
// Fetch all assets from this collection.
let fetchResult = PHAsset.fetchAssets(in: collection, options: nil)

// Enumerate and insert assets.
var phAssets = [PHAsset]()
fetchResult.enumerateObjects { (object, count, stop) in
phAssets.append(object)
}

// Process the assets.
let assets = phAssets.map { phAsset in
Asset(phAsset: phAsset)
}

// Update the assets on the main thread.
await MainActor.run {
self.assets = assets
}
}
}

extension Album: Identifiable, Hashable {

var id: String {
collection.localIdentifier
}

func hash(into hasher: inout Hasher) {
hasher.combine(id)
}

static func == (lhs: Album, rhs: Album) -> Bool {
lhs.id == rhs.id
}
}

extension Album: @unchecked Sendable {

var entity: AlbumEntity {
let entity = AlbumEntity(id: id)
entity.name = title
entity.albumType = .custom
entity.creationDate = creationDate
return entity
}
}
101 changes: 101 additions & 0 deletions AlbumAssetPicker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
See the LICENSE.txt file for this sample’s licensing information.

Abstract:
A view that allows a person to choose a media asset.
*/

import SwiftUI

struct AlbumAssetPicker: View {

// MARK: Properties

let album: Album

@State private var selection: Set<Asset>

@Environment(MediaLibrary.self) private var library
@Environment(\.dismiss) private var dismiss

private let targetSize = CGSize(width: 100, height: 100)

private let columns = [
GridItem(.adaptive(minimum: 110))
]

// MARK: Lifecycle

init(album: Album) {
self.album = album
self._selection = State(wrappedValue: Set(album.assets))
}

var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(library.assets) { asset in
let isSelected = selection.contains(asset)
ZStack(alignment: .bottomLeading) {
Button {
didSelect(asset)
} label: {
AssetView(asset: asset)
.opacity(isSelected ? 0.5 : 1)
}

if isSelected {
Image(systemName: "checkmark.circle")
.font(.headline)
.foregroundStyle(.white)
.background(Circle().fill(Color.blue))
.shadow(radius: 4)
.padding(4)
}
}
}
}
#if os(iOS)
// The search bar on iOS already comes with spacing at the top,
// so only add it on the sides and bottom.
.padding([.horizontal, .bottom])
#else
.padding()
#endif
}
.navigationTitle(album.title)
.toolbarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel", role: .cancel) {
dismiss()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Done", action: saveAction)
}
}
}
}

// MARK: Methods

private func saveAction() {
Task {
// Save changes.
try await album.setAssets(selection)

// Close.
dismiss()
}
}

private func didSelect(_ asset: Asset) {
if selection.contains(asset) {
selection.remove(asset)
} else {
selection.insert(asset)
}
}
}
57 changes: 57 additions & 0 deletions AlbumDetailView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
See the LICENSE.txt file for this sample’s licensing information.

Abstract:
A view that displays detail information and interactions for a media album.
*/

import AppIntents
import SwiftUI

struct AlbumDetailView: View {

// MARK: Properties

let album: Album

@State private var isNameSheetPresented = false
@State private var isAssetPickerPresented = false

@Environment(MediaLibrary.self) private var library
@Environment(\.dismiss) private var dismiss

// MARK: Lifecycle

var body: some View {
VStack {
AssetGrid(assets: album.assets, album: album)
}
.toolbar {
ToolbarItem {
Menu {
Button("Select Photos", systemImage: "photo.badge.checkmark") {
isAssetPickerPresented = true
}
Button("Rename Album", systemImage: "pencil") {
isNameSheetPresented = true
}
Divider()
Button("Delete", systemImage: "trash", role: .destructive) {
Task {
try await library.deleteAlbums([album])
dismiss()
}
}
} label: {
Image(systemName: "ellipsis")
}
}
}
.sheet(isPresented: $isNameSheetPresented) {
AlbumNameView(album: album)
}
.sheet(isPresented: $isAssetPickerPresented) {
AlbumAssetPicker(album: album)
}
}
}
51 changes: 51 additions & 0 deletions AlbumEntity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
See the LICENSE.txt file for this sample’s licensing information.

Abstract:
An entity that describes a photo album.
*/

import AppIntents
import CoreLocation

@AssistantEntity(schema: .photos.album)
struct AlbumEntity: IndexedEntity {

// MARK: Static

static let defaultQuery = AlbumQuery()

// MARK: Properties

let id: String

var name: String
var creationDate: Date?
var albumType: AlbumType

var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)",
subtitle: albumType.localizedStringResource
)
}
}

extension AlbumEntity {

struct AlbumQuery: EntityQuery {

@Dependency
var library: MediaLibrary

@MainActor
func entities(for identifiers: [AlbumEntity.ID]) async throws -> [AlbumEntity] {
library.albums(for: identifiers).map(\.entity)
}

@MainActor
func suggestedEntities() async throws -> [AlbumEntity] {
library.albums.prefix(3).map(\.entity)
}
}
}
Loading

0 comments on commit 546c776

Please sign in to comment.