mirror of https://github.com/buresdv/Cork
Merge branch 'adoptable-discoverability.outdated-package-display-fixes'
This commit is contained in:
commit
2514db5e47
|
|
@ -12,6 +12,7 @@ import SwiftUI
|
|||
import CorkShared
|
||||
import Defaults
|
||||
import DefaultsMacros
|
||||
import CorkModels
|
||||
|
||||
@Observable
|
||||
class AppDelegate: NSObject, NSApplicationDelegate
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"symbols" : [
|
||||
{
|
||||
"filename" : "custom.shippingbox.2.badge.arrow.down.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
<!DOCTYPE svg
|
||||
PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 3300 2200">
|
||||
<!--glyph: "", point size: 100.0, font version: "21.0d6e2", template writer version: "138.0.0"-->
|
||||
<style>.defaults {-sfsymbols-variable-value-mode:color;-sfsymbols-draw-reverses-motion-groups:true}
|
||||
|
||||
.monochrome-0 {-sfsymbols-motion-group:1;-sfsymbols-layer-tags:3be001059164cad3 6d5e10b98f62d4b7 shippingbox}
|
||||
.monochrome-1 {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0;-sfsymbols-layer-tags:3be001059164cad3 _badge arrow.down}
|
||||
.monochrome-2 {-sfsymbols-motion-group:0;-sfsymbols-always-pulses:true;-sfsymbols-layer-tags:3be001059164cad3 _badge arrow.down}
|
||||
.monochrome-3 {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0;-sfsymbols-always-pulses:true;-sfsymbols-layer-tags:3be001059164cad3 _badge arrow.down}
|
||||
|
||||
.multicolor-0:tintColor {-sfsymbols-motion-group:1;-sfsymbols-layer-tags:3be001059164cad3 6d5e10b98f62d4b7 shippingbox}
|
||||
.multicolor-1:tintColor {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0;-sfsymbols-layer-tags:3be001059164cad3 _badge arrow.down}
|
||||
.multicolor-2:tintColor {-sfsymbols-motion-group:0;-sfsymbols-always-pulses:true;-sfsymbols-layer-tags:3be001059164cad3 _badge arrow.down}
|
||||
.multicolor-3:white {-sfsymbols-motion-group:0;-sfsymbols-always-pulses:true;-sfsymbols-layer-tags:3be001059164cad3 _badge arrow.down}
|
||||
|
||||
.hierarchical-0:secondary {-sfsymbols-motion-group:1;-sfsymbols-layer-tags:3be001059164cad3 6d5e10b98f62d4b7 shippingbox}
|
||||
.hierarchical-1:primary {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0;-sfsymbols-layer-tags:3be001059164cad3 _badge arrow.down}
|
||||
.hierarchical-2:primary {-sfsymbols-motion-group:0;-sfsymbols-always-pulses:true;-sfsymbols-layer-tags:3be001059164cad3 _badge arrow.down}
|
||||
.hierarchical-3:primary {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0;-sfsymbols-always-pulses:true;-sfsymbols-layer-tags:3be001059164cad3 _badge arrow.down}
|
||||
|
||||
.SFSymbolsPreviewWireframe {fill:none;opacity:1.0;stroke:black;stroke-width:0.5}
|
||||
</style>
|
||||
<g id="Notes">
|
||||
<rect height="2200" id="artboard" style="fill:white;opacity:1" width="3300" x="0" y="0"/>
|
||||
<line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="292" y2="292"/>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 322)">Weight/Scale Variations</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 559.711 322)">Ultralight</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 856.422 322)">Thin</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1153.13 322)">Light</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1449.84 322)">Regular</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1746.56 322)">Medium</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2043.27 322)">Semibold</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2339.98 322)">Bold</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2636.69 322)">Heavy</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2933.4 322)">Black</text>
|
||||
<line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1903" y2="1903"/>
|
||||
<g transform="matrix(0.2 0 0 0.2 263 1933)">
|
||||
<path d="m46.2402 4.15039c21.7773 0 39.4531-17.627 39.4531-39.4043s-17.6758-39.4043-39.4531-39.4043c-21.7285 0-39.4043 17.627-39.4043 39.4043s17.6758 39.4043 39.4043 39.4043Zm0-7.42188c-17.6758 0-31.9336-14.3066-31.9336-31.9824s14.2578-31.9824 31.9336-31.9824 31.9824 14.3066 31.9824 31.9824-14.3066 31.9824-31.9824 31.9824Zm3.61328-17.7734v-28.4668c0-2.24609-1.46484-3.75977-3.71094-3.75977-2.14844 0-3.61328 1.51367-3.61328 3.75977v28.4668c0 2.19727 1.46484 3.71094 3.61328 3.71094 2.24609 0 3.71094-1.51367 3.71094-3.71094Zm-17.8223-10.5957h28.418c2.19727 0 3.71094-1.46484 3.71094-3.61328 0-2.19727-1.51367-3.71094-3.71094-3.71094h-28.418c-2.24609 0-3.75977 1.51367-3.75977 3.71094 0 2.14844 1.51367 3.61328 3.75977 3.61328Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.2 0 0 0.2 281.506 1933)">
|
||||
<path d="m58.5449 14.5508c27.4902 0 49.8047-22.3145 49.8047-49.8047s-22.3145-49.8047-49.8047-49.8047-49.8047 22.3145-49.8047 49.8047 22.3145 49.8047 49.8047 49.8047Zm0-8.30078c-22.9492 0-41.5039-18.5547-41.5039-41.5039s18.5547-41.5039 41.5039-41.5039 41.5039 18.5547 41.5039 41.5039-18.5547 41.5039-41.5039 41.5039Zm4.05273-23.0957v-36.9141c0-2.49023-1.70898-4.19922-4.15039-4.19922-2.39258 0-4.05273 1.70898-4.05273 4.19922v36.9141c0 2.44141 1.66016 4.15039 4.05273 4.15039 2.44141 0 4.15039-1.66016 4.15039-4.15039Zm-22.5586-14.4043h36.9629c2.44141 0 4.15039-1.61133 4.15039-4.00391 0-2.44141-1.70898-4.15039-4.15039-4.15039h-36.9629c-2.49023 0-4.15039 1.70898-4.15039 4.15039 0 2.39258 1.66016 4.00391 4.15039 4.00391Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.2 0 0 0.2 304.924 1933)">
|
||||
<path d="m74.8535 28.3203c35.1074 0 63.623-28.4668 63.623-63.5742s-28.5156-63.623-63.623-63.623-63.5742 28.5156-63.5742 63.623 28.4668 63.5742 63.5742 63.5742Zm0-9.08203c-30.127 0-54.4922-24.3652-54.4922-54.4922s24.3652-54.4922 54.4922-54.4922 54.4922 24.3652 54.4922 54.4922-24.3652 54.4922-54.4922 54.4922Zm4.44336-30.3223v-48.4863c0-2.73438-1.85547-4.63867-4.54102-4.63867-2.58789 0-4.44336 1.9043-4.44336 4.63867v48.4863c0 2.68555 1.85547 4.58984 4.44336 4.58984 2.68555 0 4.54102-1.85547 4.54102-4.58984Zm-28.7109-19.7754h48.4863c2.68555 0 4.58984-1.80664 4.58984-4.39453 0-2.73438-1.85547-4.58984-4.58984-4.58984h-48.4863c-2.73438 0-4.58984 1.85547-4.58984 4.58984 0 2.58789 1.85547 4.39453 4.58984 4.39453Z"/>
|
||||
</g>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 1953)">Design Variations</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1971)">Symbols are supported in up to nine weights and three scales.</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1989)">For optimal layout with text and other symbols, vertically align</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 2007)">symbols with the adjacent text.</text>
|
||||
<line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="776" x2="776" y1="1919" y2="1933"/>
|
||||
<g transform="matrix(0.2 0 0 0.2 776 1933)">
|
||||
<path d="m16.5527 0.78125c2.58789 0 3.85742-0.976562 4.78516-3.71094l20.5566-57.5195h0.244141l20.6055 57.5195c0.927734 2.73438 2.19727 3.71094 4.73633 3.71094 2.58789 0 4.24805-1.5625 4.24805-4.00391 0-0.830078-0.146484-1.61133-0.537109-2.63672l-22.9004-60.9863c-1.12305-2.97852-3.125-4.49219-6.25-4.49219-3.02734 0-5.07812 1.46484-6.15234 4.44336l-22.9004 61.084c-0.390625 1.02539-0.537109 1.80664-0.537109 2.63672 0 2.44141 1.5625 3.95508 4.10156 3.95508Zm10.2051-20.9473h30.6641c2.00195 0 3.66211-1.66016 3.66211-3.66211 0-2.05078-1.66016-3.66211-3.66211-3.66211h-30.6641c-2.00195 0-3.66211 1.61133-3.66211 3.66211 0 2.00195 1.66016 3.66211 3.66211 3.66211Z"/>
|
||||
</g>
|
||||
<line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="792.836" x2="792.836" y1="1919" y2="1933"/>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 776 1953)">Margins</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1971)">Leading and trailing margins on the left and right side of each symbol</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1989)">can be adjusted by modifying the x-location of the margin guidelines.</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2007)">Modifications are automatically applied proportionally to all</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2025)">scales and weights.</text>
|
||||
<g transform="matrix(0.2 0 0 0.2 1289 1933)">
|
||||
<path d="m14.209 13.1348 7.86133 7.86133c4.29688 4.39453 9.32617 4.10156 13.8672-1.02539l60.6934-68.2129-4.88281-4.88281-60.2539 67.6758c-1.80664 1.95312-3.4668 2.44141-5.81055 0.0976562l-5.17578-5.12695c-2.29492-2.29492-1.80664-3.95508 0.195312-5.81055l67.4805-62.1582-4.88281-4.83398-68.0664 62.5977c-4.98047 4.58984-5.32227 9.47266-1.02539 13.8184Zm44.873-97.4609c-2.05078 2.00195-2.24609 4.88281-1.07422 6.78711 1.12305 1.80664 3.4668 3.02734 6.5918 2.24609 5.85938-1.66016 12.5977-2.39258 18.8965 0.927734l-2.68555 7.12891c-1.61133 4.00391-0.732422 6.88477 1.70898 9.42383l10.2539 10.3027c2.34375 2.39258 4.54102 2.44141 7.08008 1.95312l4.44336-0.732422 2.58789 2.53906-0.195312 2.24609c-0.0976562 2.29492 0.537109 4.29688 2.7832 6.49414l3.36914 3.32031c2.29492 2.29492 5.51758 2.49023 7.8125 0.195312l12.9883-13.0371c2.29492-2.34375 2.14844-5.37109-0.195312-7.66602l-3.41797-3.41797c-2.19727-2.19727-4.05273-3.02734-6.34766-2.88086l-2.34375 0.244141-2.44141-2.44141 1.02539-4.6875c0.634766-2.73438-0.244141-4.98047-2.88086-7.61719l-11.2793-11.1816c-12.9395-12.8418-35.5957-11.0352-46.6797-0.146484Zm7.08008 2.05078c8.78906-6.39648 25.9766-5.66406 33.6914 1.95312l12.3047 12.207c1.02539 1.02539 1.2207 1.80664 0.927734 3.32031l-1.46484 6.64062 6.73828 6.68945 4.39453-0.244141c1.12305-0.0488281 1.51367 0.0488281 2.34375 0.878906l2.53906 2.49023-10.8398 10.8398-2.49023-2.49023c-0.830078-0.878906-0.976562-1.2207-0.927734-2.39258l0.292969-4.3457-6.68945-6.73828-6.83594 1.17188c-1.41602 0.292969-2.05078 0.195312-3.17383-0.878906l-8.93555-8.88672c-1.07422-1.02539-1.17188-1.70898-0.488281-3.36914l4.58984-11.4746c-6.10352-6.34766-17.041-7.51953-25.5859-4.58984-0.683594 0.244141-0.927734-0.390625-0.390625-0.78125Z"/>
|
||||
</g>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 1289 1953)">Exporting</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1971)">Symbols should be outlined when exporting to ensure the</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1989)">design is preserved when submitting to Xcode.</text>
|
||||
<text id="template-version" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1933)">Template v.6.0</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1951)">Requires Xcode 16 or greater</text>
|
||||
<text id="descriptive-name" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1969)">Generated from </text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1987)">Typeset at 100.0 points</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 726)">Small</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1156)">Medium</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1586)">Large</text>
|
||||
</g>
|
||||
<g id="Guides">
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 696)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="696" y2="696"/>
|
||||
<line id="Capline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="625.541" y2="625.541"/>
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1126)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1126" y2="1126"/>
|
||||
<line id="Capline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1055.54" y2="1055.54"/>
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1556)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1556" y2="1556"/>
|
||||
<line id="Capline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1485.54" y2="1485.54"/>
|
||||
<line id="right-margin-Black-S" style="fill:none;stroke:#FF3B30;stroke-width:0.5;opacity:1.0;" x1="2983.79" x2="2983.79" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Black-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="2883.01" x2="2883.01" y1="600.785" y2="720.121"/>
|
||||
<line id="right-margin-Regular-S" style="fill:none;stroke:#FF3B30;stroke-width:0.5;opacity:1.0;" x1="1498.5" x2="1498.5" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Regular-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="1401.19" x2="1401.19" y1="600.785" y2="720.121"/>
|
||||
<line id="right-margin-Ultralight-S" style="fill:none;stroke:#FF3B30;stroke-width:0.5;opacity:1.0;" x1="606.961" x2="606.961" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Ultralight-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="512.462" x2="512.462" y1="600.785" y2="720.121"/>
|
||||
</g>
|
||||
<g id="Symbols">
|
||||
<g id="Black-S" transform="matrix(1 0 0 1 2883.01 696)">
|
||||
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:secondary SFSymbolsPreviewWireframe" d="M15.9668-9.375L47.168 6.78711C49.0234 7.76367 51.7578 7.76367 53.6133 6.78711L84.8145-9.375C89.3066-11.7188 91.0156-14.5508 91.0156-19.5312L91.0156-53.6621C91.0156-58.2031 89.2578-61.2793 84.9121-63.5254L58.8379-77.1484C53.6621-79.8828 47.1191-79.8828 41.9434-77.1484L15.8691-63.5254C11.5234-61.2793 9.76562-58.2031 9.76562-53.6621L9.76562-19.5312C9.76562-14.5508 11.4746-11.7188 15.9668-9.375ZM23.584-19.3359C22.0703-20.166 21.4844-21.0938 21.4844-22.6074L21.4844-45.6055L44.2383-33.7891L44.2383-8.30078ZM77.1973-19.3359L56.543-8.30078L56.543-33.7891L79.2969-45.6055L79.2969-22.6074C79.2969-21.0938 78.7109-20.166 77.1973-19.3359ZM50.3906-44.4824L28.2227-56.0547L33.252-58.6426L55.5664-47.168ZM68.1152-53.7598L46.0449-65.332L47.4121-66.0156C49.3164-66.9922 51.416-67.041 53.3691-66.0156L72.5586-56.0547Z"/>
|
||||
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:primary SFSymbolsPreviewWireframe" d="M90.7019 20.249C105.643 20.249 117.997 7.84668 117.997-7.0947C117.997-22.0361 105.643-34.3408 90.7019-34.3408C75.7605-34.3408 63.407-22.0361 63.407-7.0947C63.407 7.84668 75.7605 20.249 90.7019 20.249Z"/>
|
||||
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M90.7019 13.9014C102.177 13.9014 111.649 4.33105 111.649-7.0947C111.649-18.5205 102.177-27.9932 90.7019-27.9932C79.2273-27.9932 69.7546-18.5205 69.7546-7.0947C69.7546 4.33105 79.2273 13.9014 90.7019 13.9014Z"/>
|
||||
<path class="monochrome-3 multicolor-3:white hierarchical-3:primary SFSymbolsPreviewWireframe" d="M90.7019 6.08886C89.53 6.08886 88.4558 5.69824 87.3816 4.62402L78.2507-4.16504C77.4695-4.8975 77.03-5.874 77.03-6.9482C77.03-9.2432 78.8855-11.1475 81.2292-11.1475C82.3523-11.1475 83.5242-10.708 84.1101-9.8779L86.2585-6.9482L86.3074-6.8994L86.1609-9.5361L86.1609-15.7861C86.1609-18.4229 87.9675-20.1807 90.7019-20.1807C93.4363-20.1807 95.2429-18.4229 95.2429-15.7861L95.2429-9.5361L95.0964-6.8994L95.1453-6.9482L97.2937-9.8779C97.8796-10.708 99.0515-11.1475 100.175-11.1475C102.518-11.1475 104.374-9.2432 104.374-6.9482C104.374-5.874 103.934-4.8975 103.153-4.16504L94.0222 4.62402C92.948 5.69824 91.8738 6.08886 90.7019 6.08886Z"/>
|
||||
</g>
|
||||
<g id="Regular-S" transform="matrix(1 0 0 1 1401.19 696)">
|
||||
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:secondary SFSymbolsPreviewWireframe" d="M15.5273-10.5469L45.3125 6.49414C47.6074 7.8125 49.707 7.8125 51.9531 6.49414L81.7383-10.5469C85.4492-12.6465 87.5488-14.7461 87.5488-20.8496L87.5488-50.3418C87.5488-54.7852 85.9375-57.5684 82.3242-59.668L56.2012-74.6582C51.0742-77.5879 46.2402-77.5879 41.1133-74.6582L14.9902-59.668C11.3281-57.5684 9.76562-54.7852 9.76562-50.3418L9.76562-20.8496C9.76562-14.7461 11.8652-12.6465 15.5273-10.5469ZM19.2871-16.3574C17.1875-17.5781 16.3574-18.8965 16.3574-21.0449L16.3574-48.9746L45.2148-32.2754L45.2148-1.41602ZM78.0273-16.3574L52.0996-1.41602L52.0996-32.2754L80.957-48.9746L80.957-21.0449C80.957-18.8965 80.0781-17.5781 78.0273-16.3574ZM48.6328-38.3789L20.2148-54.6387L31.0547-60.9375L59.4238-44.5801ZM66.4551-48.584L37.8906-64.8438L43.8477-68.2617C47.168-70.166 50.1465-70.2148 53.418-68.2617L77.0996-54.6387Z"/>
|
||||
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:primary SFSymbolsPreviewWireframe" d="M87.5791 18.3448C101.446 18.3448 112.969 6.82131 112.969-7.04585C112.969-20.9618 101.544-32.4365 87.5791-32.4365C73.6631-32.4365 62.1885-20.9618 62.1885-7.04585C62.1885 6.91896 73.6142 18.3448 87.5791 18.3448Z"/>
|
||||
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M87.5791 12.6319C98.2236 12.6319 107.306 3.69631 107.306-7.04585C107.306-17.8368 98.3701-26.7236 87.5791-26.7236C76.7881-26.7236 67.9013-17.8368 67.9013-7.04585C67.9013 3.79396 76.7881 12.6319 87.5791 12.6319Z"/>
|
||||
<path class="monochrome-3 multicolor-3:white hierarchical-3:primary SFSymbolsPreviewWireframe" d="M87.6279 5.25881C86.9443 5.25881 86.3584 4.96584 85.626 4.28225L75.7138-4.84865C75.1279-5.33685 74.8838-5.97165 74.8349-6.70405C74.7373-8.16895 75.9092-9.19435 77.374-9.19435C78.0576-9.24315 78.79-8.85255 79.2783-8.36425L83.2822-4.31151L85.0888-2.45604L84.8447-7.14355L84.8447-16.665C84.8447-18.1298 86.1142-19.3505 87.6279-19.3505C89.1416-19.3505 90.4599-18.1298 90.4599-16.665L90.4599-7.14355L90.2158-2.45604L91.9736-4.31151L95.9775-8.36425C96.5146-8.85255 97.1982-9.14545 97.8818-9.19435C99.2978-9.29195 100.421-8.16895 100.421-6.70405C100.421-5.97165 100.177-5.33685 99.5908-4.84865L89.6299 4.28225C88.8974 4.91701 88.3603 5.25881 87.6279 5.25881Z"/>
|
||||
</g>
|
||||
<g id="Ultralight-S" transform="matrix(1 0 0 1 512.462 696)">
|
||||
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:secondary SFSymbolsPreviewWireframe" d="M13.3023-13.6347L44.7676 3.49709C46.4268 4.36136 47.9815 4.40677 49.728 3.49709L81.1934-13.6347C83.8145-15.0986 84.7334-16.6987 84.7334-19.5781L84.7334-53.4297C84.7334-55.103 83.7578-56.2515 82.3242-57.0796L51.1153-74.3403C48.6675-75.6807 45.8315-75.6807 43.3838-74.3403L12.1748-57.0796C10.7378-56.2515 9.76562-55.103 9.76562-53.4297L9.76562-19.5781C9.76562-16.6987 10.6392-15.0986 13.3023-13.6347ZM15.2002-15.0859C12.5103-16.5791 12.1343-17.5796 12.1343-19.7734L12.1343-52.789L46.0776-34.3642L46.0776 1.53562ZM79.2988-15.0859L48.376 1.53562L48.376-34.3642L82.3647-52.789L82.3647-19.7734C82.3647-17.5796 81.9853-16.5791 79.2988-15.0859ZM47.2251-36.2901L12.7676-54.9565L29.1475-63.98L63.7832-45.2612ZM66.0918-46.4951L31.4424-65.2524L44.9829-72.7119C46.4414-73.5718 48.0576-73.5752 49.5127-72.7119L81.686-54.9565Z"/>
|
||||
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:primary SFSymbolsPreviewWireframe" d="M85.0492 14.3033C96.7367 14.3033 106.398 4.68705 106.398-7.04587C106.398-18.7822 96.7888-28.3951 85.0492-28.3951C73.3582-28.3951 63.7-18.7368 63.7-7.04587C63.7 4.69389 73.3548 14.3033 85.0492 14.3033Z"/>
|
||||
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M85.0492 11.2242C95.0578 11.2242 103.368 3.01517 103.368-7.04587C103.368-17.1104 95.1135-25.3159 85.0492-25.3159C75.0755-25.3159 66.7791-17.065 66.7791-7.04587C66.7791 3.02201 75.0755 11.2242 85.0492 11.2242Z"/>
|
||||
<path class="monochrome-3 multicolor-3:white hierarchical-3:primary SFSymbolsPreviewWireframe" d="M85.098 5.07717C84.7777 5.07717 84.4642 4.96584 84.0949 4.60012L74.3646-4.98487C73.824-5.51857 73.5799-5.83547 73.5764-6.38617C73.5696-7.16987 74.1966-7.74117 74.9803-7.74117C75.3914-7.74457 75.7606-7.53567 76.0218-7.27437L77.9823-5.21967L83.8304 0.631837L83.7225-5.05467L83.7225-18.209C83.7225-18.9019 84.3563-19.5322 85.098-19.5322C85.8851-19.5322 86.4769-18.9019 86.4769-18.209L86.4769-5.05467L86.4144 0.631837L92.2136-5.21967L94.2195-7.27437C94.3934-7.53567 94.8045-7.73777 95.2157-7.74117C96.0413-7.74807 96.6194-7.16987 96.6194-6.38617C96.6194-5.88087 96.3753-5.51857 95.8348-4.98487L86.1008 4.60012C85.8226 4.87161 85.5125 5.07717 85.098 5.07717Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -11,6 +11,8 @@ import ButtonKit
|
|||
import CorkShared
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
struct ContentView: View, Sendable
|
||||
{
|
||||
|
|
@ -426,6 +428,9 @@ private extension View
|
|||
case .tapAddition:
|
||||
AddTapView()
|
||||
|
||||
case .massAppAdoption(let appsToAdopt):
|
||||
MassAppAdoptionView(appsToAdopt: appsToAdopt)
|
||||
|
||||
case .fullUpdate:
|
||||
UpdatePackagesView()
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import Defaults
|
|||
import SwiftData
|
||||
import SwiftUI
|
||||
import UserNotifications
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
@main
|
||||
struct CorkApp: App
|
||||
|
|
@ -90,7 +92,8 @@ struct CorkApp: App
|
|||
.environment(outdatedPackagesTracker)
|
||||
.environment(topPackagesTracker)
|
||||
.modelContainer(for: [
|
||||
SavedTaggedPackage.self
|
||||
SavedTaggedPackage.self,
|
||||
ExcludedAdoptableApp.self
|
||||
])
|
||||
.task
|
||||
{
|
||||
|
|
@ -131,146 +134,19 @@ struct CorkApp: App
|
|||
}
|
||||
.onAppear
|
||||
{
|
||||
print("Licensing state: \(appDelegate.appState.licensingState)")
|
||||
|
||||
#if SELF_COMPILED
|
||||
AppConstants.shared.logger.debug("Will set licensing state to Self Compiled")
|
||||
appDelegate.appState.licensingState = .selfCompiled
|
||||
#else
|
||||
if !hasValidatedEmail
|
||||
{
|
||||
if appDelegate.appState.licensingState != .selfCompiled
|
||||
{
|
||||
if let demoActivatedAt
|
||||
{
|
||||
let timeDemoWillRunOutAt: Date = demoActivatedAt + AppConstants.shared.demoLengthInSeconds
|
||||
|
||||
AppConstants.shared.logger.debug("There is \(demoActivatedAt.timeIntervalSinceNow.formatted()) to go on the demo")
|
||||
|
||||
AppConstants.shared.logger.debug("Demo will time out at \(timeDemoWillRunOutAt.formatted(date: .complete, time: .complete))")
|
||||
|
||||
if ((demoActivatedAt.timeIntervalSinceNow) + AppConstants.shared.demoLengthInSeconds) > 0
|
||||
{ // Check if there is still time on the demo
|
||||
/// do stuff if there is
|
||||
}
|
||||
else
|
||||
{
|
||||
hasFinishedLicensingWorkflow = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
handleLicensing()
|
||||
}
|
||||
.onAppear
|
||||
{
|
||||
// Start the background update scheduler when the app starts
|
||||
backgroundUpdateTimer.schedule
|
||||
{ (completion: NSBackgroundActivityScheduler.CompletionHandler) in
|
||||
AppConstants.shared.logger.log("Scheduled event fired at \(Date(), privacy: .auto)")
|
||||
|
||||
Task
|
||||
{
|
||||
var updateResult: TerminalOutput = await shell(AppConstants.shared.brewExecutablePath, ["update"])
|
||||
|
||||
AppConstants.shared.logger.debug("Update result:\nStandard output: \(updateResult.standardOutput, privacy: .public)\nStandard error: \(updateResult.standardError, privacy: .public)")
|
||||
|
||||
do
|
||||
{
|
||||
let temporaryOutdatedPackageTracker: OutdatedPackagesTracker = await .init()
|
||||
|
||||
try await temporaryOutdatedPackageTracker.getOutdatedPackages(brewPackagesTracker: brewPackagesTracker)
|
||||
|
||||
var newOutdatedPackages: Set<OutdatedPackage> = await temporaryOutdatedPackageTracker.outdatedPackages
|
||||
|
||||
AppConstants.shared.logger.debug("Outdated packages checker output: \(newOutdatedPackages, privacy: .public)")
|
||||
|
||||
defer
|
||||
{
|
||||
AppConstants.shared.logger.log("Will purge temporary update trackers")
|
||||
|
||||
updateResult = .init(standardOutput: "", standardError: "")
|
||||
newOutdatedPackages = .init()
|
||||
}
|
||||
|
||||
if await newOutdatedPackages.count > outdatedPackagesTracker.outdatedPackages.count
|
||||
{
|
||||
AppConstants.shared.logger.log("New updates found")
|
||||
|
||||
/// Set this to `true` so the normal notification doesn't get sent
|
||||
await setWhetherToSendStandardUpdatesAvailableNotification(to: false)
|
||||
|
||||
let differentPackages: Set<OutdatedPackage> = await newOutdatedPackages.subtracting(outdatedPackagesTracker.displayableOutdatedPackages)
|
||||
AppConstants.shared.logger.debug("Changed packages: \(differentPackages, privacy: .auto)")
|
||||
|
||||
sendNotification(title: String(localized: "notification.new-outdated-packages-found.title"), subtitle: differentPackages.map(\.package.name).formatted(.list(type: .and)))
|
||||
|
||||
await outdatedPackagesTracker.setOutdatedPackages(to: newOutdatedPackages)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1)
|
||||
{
|
||||
sendStandardUpdatesAvailableNotification = true
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.log("No new updates found")
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
AppConstants.shared.logger.error("Something got fucked up about checking for outdated packages")
|
||||
}
|
||||
}
|
||||
|
||||
completion(NSBackgroundActivityScheduler.Result.finished)
|
||||
}
|
||||
handleBackgroundUpdating()
|
||||
}
|
||||
.onChange(of: demoActivatedAt) // React to when the user activates the demo
|
||||
{ _, newValue in
|
||||
if let newValue
|
||||
{ // If the demo has not been activated, `demoActivatedAt` is nil. So, when it's not nil anymore, it means the user activated it
|
||||
AppConstants.shared.logger.debug("The user activated the demo at \(newValue.formatted(date: .complete, time: .complete), privacy: .public)")
|
||||
hasFinishedLicensingWorkflow = true
|
||||
}
|
||||
handleDemoTiming(newValue: newValue)
|
||||
}
|
||||
.onChange(of: outdatedPackagesTracker.displayableOutdatedPackages.count)
|
||||
{ _, outdatedPackageCount in
|
||||
AppConstants.shared.logger.debug("Number of displayable outdated packages changed (\(outdatedPackageCount))")
|
||||
|
||||
// TODO: Remove this once I figure out why the updating spinner sometimes doesn't disappear
|
||||
withAnimation
|
||||
{
|
||||
outdatedPackagesTracker.isCheckingForPackageUpdates = false
|
||||
}
|
||||
|
||||
if outdatedPackageCount == 0
|
||||
{
|
||||
NSApp.dockTile.badgeLabel = ""
|
||||
}
|
||||
else
|
||||
{
|
||||
if areNotificationsEnabled
|
||||
{
|
||||
if outdatedPackageNotificationType == .badge || outdatedPackageNotificationType == .both
|
||||
{
|
||||
NSApp.dockTile.badgeLabel = String(outdatedPackageCount)
|
||||
}
|
||||
|
||||
// TODO: Changing the package display type sends a notificaiton, which is not visible since the app is in the foreground. Once macOS 15 comes out, move `sendStandardUpdatesAvailableNotification` into the AppState and suppress it
|
||||
if outdatedPackageNotificationType == .notification || outdatedPackageNotificationType == .both
|
||||
{
|
||||
AppConstants.shared.logger.log("Will try to send notification")
|
||||
|
||||
/// This needs to be checked because when the background update system finds an update, we don't want to send this normal notification.
|
||||
/// Instead, we want to send a more succinct notification that includes only the new package
|
||||
if sendStandardUpdatesAvailableNotification
|
||||
{
|
||||
sendNotification(title: String(localized: "notification.outdated-packages-found.title"), subtitle: String(localized: "notification.outdated-packages-found.body-\(outdatedPackageCount)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
handleOutdatedPackageChangeAppBadge(outdatedPackageCount: outdatedPackageCount)
|
||||
}
|
||||
.onChange(of: outdatedPackageNotificationType) // Set the correct app badge number when the user changes their notification settings
|
||||
{ _, newValue in
|
||||
|
|
@ -415,7 +291,7 @@ struct CorkApp: App
|
|||
WindowGroup(id: .previewWindowID, for: MinimalHomebrewPackage.self)
|
||||
{ $packageToPreview in
|
||||
|
||||
let convertedMinimalPackage: BrewPackage? = .init(from: packageToPreview)
|
||||
let convertedMinimalPackage: BrewPackage? = BrewPackage(using: packageToPreview)
|
||||
|
||||
PackagePreview(packageToPreview: convertedMinimalPackage)
|
||||
.navigationTitle(packageToPreview?.name ?? "")
|
||||
|
|
@ -657,13 +533,8 @@ struct CorkApp: App
|
|||
.environment(appDelegate.appState)
|
||||
.environment(outdatedPackagesTracker)
|
||||
|
||||
Button
|
||||
{
|
||||
appDelegate.appState.showSheet(ofType: .fullUpdate)
|
||||
} label: {
|
||||
Text("navigation.menu.packages.update")
|
||||
}
|
||||
.keyboardShortcut("r", modifiers: [.control, .command])
|
||||
UpgradePackagesButton(appState: appDelegate.appState)
|
||||
.keyboardShortcut("r", modifiers: [.control, .command])
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
@ -721,7 +592,7 @@ struct CorkApp: App
|
|||
{
|
||||
Button
|
||||
{
|
||||
openWindow(id: .errorInspectorWindowID, value: PackageLoadingError.packageIsNotAFolder("Hello I am an error", packageURL: .applicationDirectory).localizedDescription)
|
||||
openWindow(id: .errorInspectorWindowID, value: BrewPackage.PackageLoadingError.packageIsNotAFolder("Hello I am an error", packageURL: .applicationDirectory).localizedDescription)
|
||||
} label: {
|
||||
Text("debug.action.show-error-inspector")
|
||||
}
|
||||
|
|
@ -729,9 +600,10 @@ struct CorkApp: App
|
|||
Text("debug.action.ui")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Functions
|
||||
|
||||
|
||||
// MARK: - App badge
|
||||
func setAppBadge(outdatedPackageNotificationType: OutdatedPackageNotificationType)
|
||||
{
|
||||
if outdatedPackageNotificationType == .badge || outdatedPackageNotificationType == .both
|
||||
|
|
@ -746,9 +618,160 @@ struct CorkApp: App
|
|||
NSApp.dockTile.badgeLabel = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func setWhetherToSendStandardUpdatesAvailableNotification(to newValue: Bool)
|
||||
{
|
||||
self.sendStandardUpdatesAvailableNotification = newValue
|
||||
}
|
||||
|
||||
func handleOutdatedPackageChangeAppBadge(outdatedPackageCount: Int)
|
||||
{
|
||||
AppConstants.shared.logger.debug("Number of displayable outdated packages changed (\(outdatedPackageCount))")
|
||||
|
||||
// TODO: Remove this once I figure out why the updating spinner sometimes doesn't disappear
|
||||
withAnimation
|
||||
{
|
||||
outdatedPackagesTracker.isCheckingForPackageUpdates = false
|
||||
}
|
||||
|
||||
if outdatedPackageCount == 0
|
||||
{
|
||||
NSApp.dockTile.badgeLabel = ""
|
||||
}
|
||||
else
|
||||
{
|
||||
if areNotificationsEnabled
|
||||
{
|
||||
if outdatedPackageNotificationType == .badge || outdatedPackageNotificationType == .both
|
||||
{
|
||||
NSApp.dockTile.badgeLabel = String(outdatedPackageCount)
|
||||
}
|
||||
|
||||
// TODO: Changing the package display type sends a notificaiton, which is not visible since the app is in the foreground. Once macOS 15 comes out, move `sendStandardUpdatesAvailableNotification` into the AppState and suppress it
|
||||
if outdatedPackageNotificationType == .notification || outdatedPackageNotificationType == .both
|
||||
{
|
||||
AppConstants.shared.logger.log("Will try to send notification")
|
||||
|
||||
/// This needs to be checked because when the background update system finds an update, we don't want to send this normal notification.
|
||||
/// Instead, we want to send a more succinct notification that includes only the new package
|
||||
if sendStandardUpdatesAvailableNotification
|
||||
{
|
||||
sendNotification(title: String(localized: "notification.outdated-packages-found.title"), subtitle: String(localized: "notification.outdated-packages-found.body-\(outdatedPackageCount)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Background updating
|
||||
|
||||
func handleBackgroundUpdating()
|
||||
{
|
||||
// Start the background update scheduler when the app starts
|
||||
backgroundUpdateTimer.schedule
|
||||
{ (completion: NSBackgroundActivityScheduler.CompletionHandler) in
|
||||
AppConstants.shared.logger.log("Scheduled event fired at \(Date(), privacy: .auto)")
|
||||
|
||||
Task
|
||||
{
|
||||
var updateResult: TerminalOutput = await shell(AppConstants.shared.brewExecutablePath, ["update"])
|
||||
|
||||
AppConstants.shared.logger.debug("Update result:\nStandard output: \(updateResult.standardOutput, privacy: .public)\nStandard error: \(updateResult.standardError, privacy: .public)")
|
||||
|
||||
do
|
||||
{
|
||||
let temporaryOutdatedPackageTracker: OutdatedPackagesTracker = await .init()
|
||||
|
||||
try await temporaryOutdatedPackageTracker.getOutdatedPackages(brewPackagesTracker: brewPackagesTracker)
|
||||
|
||||
var newOutdatedPackages: Set<OutdatedPackage> = await temporaryOutdatedPackageTracker.outdatedPackages
|
||||
|
||||
AppConstants.shared.logger.debug("Outdated packages checker output: \(newOutdatedPackages, privacy: .public)")
|
||||
|
||||
defer
|
||||
{
|
||||
AppConstants.shared.logger.log("Will purge temporary update trackers")
|
||||
|
||||
updateResult = .init(standardOutput: "", standardError: "")
|
||||
newOutdatedPackages = .init()
|
||||
}
|
||||
|
||||
if await newOutdatedPackages.count > outdatedPackagesTracker.outdatedPackages.count
|
||||
{
|
||||
AppConstants.shared.logger.log("New updates found")
|
||||
|
||||
/// Set this to `true` so the normal notification doesn't get sent
|
||||
await setWhetherToSendStandardUpdatesAvailableNotification(to: false)
|
||||
|
||||
let differentPackages: Set<OutdatedPackage> = await newOutdatedPackages.subtracting(outdatedPackagesTracker.displayableOutdatedPackages)
|
||||
AppConstants.shared.logger.debug("Changed packages: \(differentPackages, privacy: .auto)")
|
||||
|
||||
sendNotification(title: String(localized: "notification.new-outdated-packages-found.title"), subtitle: differentPackages.map(\.package.name).formatted(.list(type: .and)))
|
||||
|
||||
await outdatedPackagesTracker.setOutdatedPackages(to: newOutdatedPackages)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1)
|
||||
{
|
||||
sendStandardUpdatesAvailableNotification = true
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.log("No new updates found")
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
AppConstants.shared.logger.error("Something got fucked up about checking for outdated packages")
|
||||
}
|
||||
}
|
||||
|
||||
completion(NSBackgroundActivityScheduler.Result.finished)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Licensing
|
||||
func handleLicensing()
|
||||
{
|
||||
print("Licensing state: \(appDelegate.appState.licensingState)")
|
||||
|
||||
#if SELF_COMPILED
|
||||
AppConstants.shared.logger.debug("Will set licensing state to Self Compiled")
|
||||
appDelegate.appState.licensingState = .selfCompiled
|
||||
#else
|
||||
if !hasValidatedEmail
|
||||
{
|
||||
if appDelegate.appState.licensingState != .selfCompiled
|
||||
{
|
||||
if let demoActivatedAt
|
||||
{
|
||||
let timeDemoWillRunOutAt: Date = demoActivatedAt + AppConstants.shared.demoLengthInSeconds
|
||||
|
||||
AppConstants.shared.logger.debug("There is \(demoActivatedAt.timeIntervalSinceNow.formatted()) to go on the demo")
|
||||
|
||||
AppConstants.shared.logger.debug("Demo will time out at \(timeDemoWillRunOutAt.formatted(date: .complete, time: .complete))")
|
||||
|
||||
if ((demoActivatedAt.timeIntervalSinceNow) + AppConstants.shared.demoLengthInSeconds) > 0
|
||||
{ // Check if there is still time on the demo
|
||||
/// do stuff if there is
|
||||
}
|
||||
else
|
||||
{
|
||||
hasFinishedLicensingWorkflow = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func handleDemoTiming(newValue: Date?)
|
||||
{
|
||||
if let newValue
|
||||
{ // If the demo has not been activated, `demoActivatedAt` is nil. So, when it's not nil anymore, it means the user activated it
|
||||
AppConstants.shared.logger.debug("The user activated the demo at \(newValue.formatted(date: .complete, time: .complete), privacy: .public)")
|
||||
hasFinishedLicensingWorkflow = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
//
|
||||
// Brewfile Import Stage.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 11.11.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum BrewfileImportStage
|
||||
{
|
||||
case importing, finished
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// Licensing State.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 18.03.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum LicensingState
|
||||
{
|
||||
case notBoughtOrHasNotActivatedDemo
|
||||
|
||||
case demo
|
||||
case bought
|
||||
|
||||
case selfCompiled
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CorkModels
|
||||
|
||||
enum NavigationTargetMainWindow: Hashable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
//
|
||||
// Outdated Package Type.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 17.05.2024.
|
||||
//
|
||||
|
||||
import Charts
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum CachedDownloadType: String, CustomStringConvertible, Plottable
|
||||
{
|
||||
case formula
|
||||
case cask
|
||||
case other
|
||||
case unknown
|
||||
|
||||
var description: String
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return String(localized: "package-details.type.formula")
|
||||
case .cask:
|
||||
return String(localized: "package-details.type.cask")
|
||||
case .other:
|
||||
return String(localized: "start-page.cached-downloads.graph.other-smaller-packages")
|
||||
default:
|
||||
return String(localized: "cached-downloads.type.unknown")
|
||||
}
|
||||
}
|
||||
|
||||
var color: Color
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return .purple
|
||||
case .cask:
|
||||
return .orange
|
||||
case .other:
|
||||
return .mint
|
||||
default:
|
||||
return .gray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
//
|
||||
// Package Types.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 05.02.2023.
|
||||
//
|
||||
|
||||
import AppIntents
|
||||
import Charts
|
||||
import CorkShared
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum PackageType: String, CustomStringConvertible, Plottable, AppEntity, Codable
|
||||
{
|
||||
case formula
|
||||
case cask
|
||||
|
||||
/// User-readable description of the package type
|
||||
var description: String
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return String(localized: "package-details.type.formula")
|
||||
case .cask:
|
||||
return String(localized: "package-details.type.cask")
|
||||
}
|
||||
}
|
||||
|
||||
/// Localization keys for description of the package type
|
||||
var localizableDescription: LocalizedStringKey
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return "package-details.type.formula"
|
||||
case .cask:
|
||||
return "package-details.type.cask"
|
||||
}
|
||||
}
|
||||
|
||||
/// Parent folder for this package type
|
||||
var parentFolder: URL
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return AppConstants.shared.brewCellarPath
|
||||
case .cask:
|
||||
return AppConstants.shared.brewCaskPath
|
||||
}
|
||||
}
|
||||
|
||||
/// Accessibility representation
|
||||
var accessibilityLabel: LocalizedStringKey
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return "accessibility.label.package-type.formula"
|
||||
case .cask:
|
||||
return "accessibility.label.package-type.cask"
|
||||
}
|
||||
}
|
||||
|
||||
static let typeDisplayRepresentation: TypeDisplayRepresentation = .init(name: "package-details.type")
|
||||
|
||||
var displayRepresentation: DisplayRepresentation
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
DisplayRepresentation(title: "package-details.type.formula")
|
||||
case .cask:
|
||||
DisplayRepresentation(title: "package-details.type.cask")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// JSON Parsing Error.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 21.06.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum JSONParsingError: LocalizedError
|
||||
{
|
||||
case couldNotConvertStringToData(failureReason: String?), couldNotDecode(failureReason: String)
|
||||
|
||||
var errorDescription: String?
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .couldNotConvertStringToData(let failureReason):
|
||||
return String(localized: "error.json-parsing.could-not-convert-string-to-data.\(failureReason ?? "")")
|
||||
case .couldNotDecode(let failureReason):
|
||||
return String(localized: "error.json-parsing.could-not-decode.\(failureReason)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
//
|
||||
// Package Loading Error.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 10.11.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Error representing failures while loading
|
||||
enum PackageLoadingError: LocalizedError, Hashable, Identifiable
|
||||
{
|
||||
/// Tried to treat the folder `Cellar` or `Caskroom` itself as a package - means Homebrew itself is broken
|
||||
case triedToThreatFolderContainingPackagesAsPackage(packageType: PackageType)
|
||||
|
||||
/// The `Cellar` and `Caskroom` folder itself couldn't be loaded
|
||||
case couldNotReadContentsOfParentFolder(failureReason: String, folderURL: URL)
|
||||
|
||||
/// Failed while trying to read contents of package folder
|
||||
case failedWhileReadingContentsOfPackageFolder(folderURL: URL, reportedError: String)
|
||||
|
||||
case failedWhileTryingToDetermineIntentionalInstallation(folderURL: URL, associatedIntentionalDiscoveryError: IntentionalInstallationDiscoveryError)
|
||||
|
||||
/// The package root folder exists, but the package itself doesn't have any versions
|
||||
case packageDoesNotHaveAnyVersionsInstalled(packageURL: URL)
|
||||
|
||||
/// A folder that should have contained the package is not actually a folder
|
||||
case packageIsNotAFolder(String, packageURL: URL)
|
||||
|
||||
/// The number of loaded packages does not match the number of package parent folders
|
||||
case numberOLoadedPackagesDosNotMatchNumberOfPackageFolders
|
||||
|
||||
var errorDescription: String?
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .couldNotReadContentsOfParentFolder(let failureReason, _):
|
||||
return String(localized: "error.package-loading.could-not-read-contents-of-parent-folder.\(failureReason)")
|
||||
|
||||
case .triedToThreatFolderContainingPackagesAsPackage(let packageType):
|
||||
switch packageType
|
||||
{
|
||||
case .formula:
|
||||
return "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.formulae"
|
||||
case .cask:
|
||||
return "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.casks"
|
||||
}
|
||||
|
||||
case .failedWhileReadingContentsOfPackageFolder(let folderURL, let reportedError):
|
||||
return String(localized: "error.package-loading.could-not-load-\(folderURL.packageNameFromURL())-at-\(folderURL.absoluteString)-because-\(reportedError)", comment: "Couldn't load package (package name) at (package URL) because (failure reason)")
|
||||
|
||||
case .failedWhileTryingToDetermineIntentionalInstallation(_, let associatedIntentionalDiscoveryError):
|
||||
return associatedIntentionalDiscoveryError.localizedDescription
|
||||
|
||||
case .packageDoesNotHaveAnyVersionsInstalled(let packageURL):
|
||||
return String(localized: "error.package-loading.\(packageURL.packageNameFromURL())-does-not-have-any-versions-installed")
|
||||
|
||||
case .packageIsNotAFolder(let string, _):
|
||||
return String(localized: "error.package-loading.\(string)-not-a-folder", comment: "Package folder in this context means a folder that encloses package versions. Every package has its own folder, and this error occurs when the provided URL does not point to a folder that encloses package versions")
|
||||
|
||||
case .numberOLoadedPackagesDosNotMatchNumberOfPackageFolders:
|
||||
return String(localized: "error.package-loading.number-of-loaded-poackages-does-not-match-number-of-package-folders", comment: "This error occurs when there's a mismatch between the number of loaded packages, and the number of package folders in the package folders")
|
||||
}
|
||||
}
|
||||
|
||||
var id: UUID
|
||||
{
|
||||
return UUID()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// Adopt Package.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 07.10.2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
extension MassAppAdoptionView.MassAppAdoptionTacker
|
||||
{
|
||||
@MainActor
|
||||
func adoptApp(
|
||||
_ appToAdopt: BrewPackagesTracker.AdoptableApp
|
||||
) async -> AdoptionProcessResult
|
||||
{
|
||||
let (stream, process): (AsyncStream<StreamedTerminalOutput>, Process) = shell(AppConstants.shared.brewExecutablePath, ["install", "--cask", "--adopt", appToAdopt.caskName])
|
||||
adoptionProcess = process
|
||||
|
||||
var consolidatedOutput: (standardOutput: [String], standardError: [String]) = (standardOutput: .init(), standardError: .init())
|
||||
|
||||
for await output in stream
|
||||
{
|
||||
switch output {
|
||||
case .standardOutput(let string):
|
||||
self.outputLines.append(.init(line: string))
|
||||
|
||||
consolidatedOutput.standardOutput.append(string)
|
||||
|
||||
case .standardError(let string):
|
||||
self.outputLines.append(.init(line: string))
|
||||
|
||||
consolidatedOutput.standardError.append(string)
|
||||
}
|
||||
}
|
||||
|
||||
AppConstants.shared.logger.debug("""
|
||||
Finished mass adoption process for cask \(appToAdopt.caskName) with this result:
|
||||
Output: \(consolidatedOutput.standardOutput.joined())
|
||||
Error: \(consolidatedOutput.standardError.joined())
|
||||
""")
|
||||
|
||||
if consolidatedOutput.standardError.isEmpty
|
||||
{
|
||||
AppConstants.shared.logger.info("Adoption process for cask \(appToAdopt.caskName) was successful")
|
||||
|
||||
return .success(appToAdopt)
|
||||
}
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.error("Adoption process for cask \(appToAdopt.caskName) failed")
|
||||
|
||||
return .failure(
|
||||
.failedWithError(
|
||||
failedAdoptionCandidate: appToAdopt,
|
||||
error: consolidatedOutput.standardError.joined()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,16 +8,20 @@
|
|||
import AppIntents
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkIntents
|
||||
|
||||
struct GetInstalledCasksIntent: AppIntent
|
||||
public struct GetInstalledCasksIntent: AppIntent
|
||||
{
|
||||
static let title: LocalizedStringResource = "intent.get-installed-casks.title"
|
||||
static let description: LocalizedStringResource = "intent.get-installed-casks.description"
|
||||
public init() {}
|
||||
|
||||
public static let title: LocalizedStringResource = "intent.get-installed-casks.title"
|
||||
public static let description: LocalizedStringResource = "intent.get-installed-casks.description"
|
||||
|
||||
static let isDiscoverable: Bool = true
|
||||
static let openAppWhenRun: Bool = false
|
||||
public static let isDiscoverable: Bool = true
|
||||
public static let openAppWhenRun: Bool = false
|
||||
|
||||
func perform() async throws -> some ReturnsValue<[MinimalHomebrewPackage]>
|
||||
public func perform() async throws -> some ReturnsValue<[MinimalHomebrewPackage]>
|
||||
{
|
||||
let allowAccessToFile: Bool = AppConstants.shared.brewCaskPath.startAccessingSecurityScopedResource()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import AppIntents
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
|
||||
enum FolderAccessingError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import AppIntents
|
||||
import Foundation
|
||||
import CorkModels
|
||||
|
||||
struct GetInstalledPackagesIntent: AppIntent
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import AppIntents
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum RefreshIntentResult: String, AppEnum
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum BrewfileDumpingError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum BrewfileReadingError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
|
||||
enum TopPackageLoadingError: LocalizedError
|
||||
{
|
||||
|
|
@ -99,7 +100,15 @@ extension TopPackagesTracker
|
|||
|
||||
if normalizedDownloadNumber > downloadsCutoff
|
||||
{
|
||||
return .init(name: rawTopFormula.formula, type: .formula, installedOn: nil, versions: .init(), sizeInBytes: nil, downloadCount: normalizedDownloadNumber)
|
||||
return .init(
|
||||
name: rawTopFormula.formula,
|
||||
type: .formula,
|
||||
installedOn: nil,
|
||||
versions: .init(),
|
||||
url: nil,
|
||||
sizeInBytes: nil,
|
||||
downloadCount: normalizedDownloadNumber
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -155,7 +164,15 @@ extension TopPackagesTracker
|
|||
|
||||
if normalizedDownloadNumber > downloadsCutoff
|
||||
{
|
||||
return .init(name: rawTopCask.cask, type: .cask, installedOn: nil, versions: .init(), sizeInBytes: nil, downloadCount: normalizedDownloadNumber)
|
||||
return .init(
|
||||
name: rawTopCask.cask,
|
||||
type: .cask,
|
||||
installedOn: nil,
|
||||
versions: .init(),
|
||||
url: nil,
|
||||
sizeInBytes: nil,
|
||||
downloadCount: normalizedDownloadNumber
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,177 +0,0 @@
|
|||
//
|
||||
// Get Contents of Folder.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 03.07.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
|
||||
/*
|
||||
func getContentsOfFolder(targetFolder: URL) async throws -> Set<BrewPackage>
|
||||
{
|
||||
do
|
||||
{
|
||||
guard let items = targetFolder.validPackageURLs
|
||||
else
|
||||
{
|
||||
throw PackageLoadingError.failedWhileLoadingPackages(failureReason: String(localized: "alert.fatal.could-not-filter-invalid-packages"))
|
||||
}
|
||||
|
||||
let loadedPackages: Set<BrewPackage> = try await withThrowingTaskGroup(of: BrewPackage.self, returning: Set<BrewPackage>.self)
|
||||
{ taskGroup in
|
||||
for item in items
|
||||
{
|
||||
let fullURLToPackageFolderCurrentlyBeingProcessed: URL = targetFolder.appendingPathComponent(item, conformingTo: .folder)
|
||||
|
||||
taskGroup.addTask(priority: .high)
|
||||
{
|
||||
guard let versionURLs: [URL] = fullURLToPackageFolderCurrentlyBeingProcessed.packageVersionURLs
|
||||
else
|
||||
{
|
||||
if targetFolder.appendingPathComponent(item, conformingTo: .fileURL).isDirectory
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while getting package version for package \(fullURLToPackageFolderCurrentlyBeingProcessed.lastPathComponent). Package does not have any version installed.")
|
||||
throw PackageLoadingError.packageDoesNotHaveAnyVersionsInstalled(item)
|
||||
}
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while getting package version for package \(fullURLToPackageFolderCurrentlyBeingProcessed.lastPathComponent). Package is not a folder")
|
||||
throw PackageLoadingError.packageIsNotAFolder(item, targetFolder.appendingPathComponent(item, conformingTo: .fileURL))
|
||||
}
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if versionURLs.isEmpty
|
||||
{
|
||||
throw PackageLoadingError.packageDoesNotHaveAnyVersionsInstalled(item)
|
||||
}
|
||||
|
||||
let wasPackageInstalledIntentionally: Bool = try await targetFolder.checkIfPackageWasInstalledIntentionally(versionURLs)
|
||||
|
||||
let foundPackage: BrewPackage = .init(
|
||||
name: item,
|
||||
type: targetFolder.packageType,
|
||||
installedOn: fullURLToPackageFolderCurrentlyBeingProcessed.creationDate,
|
||||
versions: versionURLs.versions,
|
||||
installedIntentionally: wasPackageInstalledIntentionally,
|
||||
sizeInBytes: fullURLToPackageFolderCurrentlyBeingProcessed.directorySize
|
||||
)
|
||||
|
||||
return foundPackage
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var loadedPackages: Set<BrewPackage> = .init()
|
||||
for try await package in taskGroup
|
||||
{
|
||||
loadedPackages.insert(package)
|
||||
}
|
||||
return loadedPackages
|
||||
}
|
||||
|
||||
return loadedPackages
|
||||
}
|
||||
catch
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while accessing folder: \(error)")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// MARK: - Sub-functions
|
||||
|
||||
private extension URL
|
||||
{
|
||||
/// ``[URL]`` to packages without hidden files or symlinks.
|
||||
/// e.g. only actual package URLs
|
||||
var validPackageURLs: [String]?
|
||||
{
|
||||
let items: [String]? = try? FileManager.default.contentsOfDirectory(atPath: path).filter { !$0.hasPrefix(".") }.filter
|
||||
{ item in
|
||||
/// Filter out all symlinks from the folder
|
||||
let completeURLtoItem: URL = self.appendingPathComponent(item, conformingTo: .folder)
|
||||
|
||||
guard let isSymlink = completeURLtoItem.isSymlink()
|
||||
else
|
||||
{
|
||||
return false
|
||||
}
|
||||
|
||||
return !isSymlink
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
/// Get URLs to a package's versions
|
||||
var packageVersionURLs: [URL]?
|
||||
{
|
||||
AppConstants.shared.logger.debug("Will check URL \(self)")
|
||||
do
|
||||
{
|
||||
let versions: [URL] = try FileManager.default.contentsOfDirectory(at: self, includingPropertiesForKeys: [.isHiddenKey], options: .skipsHiddenFiles)
|
||||
|
||||
if versions.isEmpty
|
||||
{
|
||||
AppConstants.shared.logger.warning("Package URL \(self, privacy: .public) has no versions installed")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
AppConstants.shared.logger.debug("URL \(self) has these versions: \(versions))")
|
||||
|
||||
return versions
|
||||
}
|
||||
catch
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while loading version for package \(lastPathComponent, privacy: .public) at URL \(self, privacy: .public)")
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension [URL]
|
||||
{
|
||||
/// Returns an array of versions from an array of URLs to available versions
|
||||
var versions: [String]
|
||||
{
|
||||
return map
|
||||
{ versionURL in
|
||||
versionURL.lastPathComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Getting list of URLs in folder
|
||||
|
||||
func getContentsOfFolder(targetFolder: URL, options: FileManager.DirectoryEnumerationOptions? = nil) throws -> [URL]
|
||||
{
|
||||
do
|
||||
{
|
||||
if let options
|
||||
{
|
||||
return try FileManager.default.contentsOfDirectory(at: targetFolder, includingPropertiesForKeys: nil, options: options)
|
||||
}
|
||||
else
|
||||
{
|
||||
return try FileManager.default.contentsOfDirectory(at: targetFolder, includingPropertiesForKeys: nil)
|
||||
}
|
||||
}
|
||||
catch let folderReadingError
|
||||
{
|
||||
AppConstants.shared.logger.error("\(folderReadingError.localizedDescription)")
|
||||
|
||||
throw folderReadingError
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
|
||||
func deleteCachedDownloads() throws(CachedDownloadDeletionError)
|
||||
{
|
||||
|
|
@ -15,7 +16,7 @@ func deleteCachedDownloads() throws(CachedDownloadDeletionError)
|
|||
/// This folder has the symlinks, so we have do **delete ONLY THE SYMLINKS**
|
||||
do
|
||||
{
|
||||
for url in try getContentsOfFolder(targetFolder: AppConstants.shared.brewCachedFormulaeDownloadsPath)
|
||||
for url in try AppConstants.shared.brewCachedFormulaeDownloadsPath.getContents()
|
||||
{
|
||||
if let isSymlink = url.isSymlink()
|
||||
{
|
||||
|
|
@ -47,7 +48,7 @@ func deleteCachedDownloads() throws(CachedDownloadDeletionError)
|
|||
/// This folder has the symlinks, so we have to **delete ONLY THE SYMLINKS**
|
||||
do
|
||||
{
|
||||
for url in try getContentsOfFolder(targetFolder: AppConstants.shared.brewCachedCasksDownloadsPath)
|
||||
for url in try AppConstants.shared.brewCachedCasksDownloadsPath.getContents()
|
||||
{
|
||||
if let isSymlink = url.isSymlink()
|
||||
{
|
||||
|
|
@ -79,7 +80,7 @@ func deleteCachedDownloads() throws(CachedDownloadDeletionError)
|
|||
/// This folder has the downloads themselves, so we have do **DELETE EVERYTHING THAT IS NOT A SYMLINK**
|
||||
do
|
||||
{
|
||||
for url in try getContentsOfFolder(targetFolder: AppConstants.shared.brewCachedDownloadsPath)
|
||||
for url in try AppConstants.shared.brewCachedDownloadsPath.getContents()
|
||||
{
|
||||
if let isSymlink = url.isSymlink()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum HealthCheckError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
/* enum CachePurgeError: Error
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum OrphanUninstallationError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum HomebrewCachePurgeError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum OrphanRemovalError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// Load up Installed Packages.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 11.02.2023.
|
||||
//
|
||||
|
||||
import CorkShared
|
||||
import Foundation
|
||||
|
||||
/*
|
||||
@MainActor
|
||||
func loadUpPackages(whatToLoad: PackageType, appState: AppState) async -> Set<BrewPackage>
|
||||
{
|
||||
AppConstants.shared.logger.info("Started \(whatToLoad == .formula ? "Formula" : "Cask", privacy: .public) loading task at \(Date(), privacy: .public)")
|
||||
|
||||
var contentsOfFolder: Set<BrewPackage> = .init()
|
||||
|
||||
do
|
||||
{
|
||||
switch whatToLoad
|
||||
{
|
||||
case .formula:
|
||||
contentsOfFolder = try await getContentsOfFolder(targetFolder: AppConstants.shared.brewCellarPath)
|
||||
case .cask:
|
||||
contentsOfFolder = try await getContentsOfFolder(targetFolder: AppConstants.shared.brewCaskPath)
|
||||
}
|
||||
}
|
||||
catch let packageLoadingError as PackageLoadingError
|
||||
{
|
||||
switch packageLoadingError
|
||||
{
|
||||
case .couldNotReadContentsOfParentFolder(let failureReason):
|
||||
appState.showAlert(errorToShow: .couldNotGetContentsOfPackageFolder(failureReason))
|
||||
case .failedWhileLoadingPackages:
|
||||
appState.showAlert(errorToShow: .couldNotLoadAnyPackages(packageLoadingError))
|
||||
case .failedWhileLoadingCertainPackage(let offendingPackage, let offendingPackageURL, let failureReason):
|
||||
appState.showAlert(errorToShow: .couldNotLoadCertainPackage(offendingPackage, offendingPackageURL, failureReason: failureReason))
|
||||
case .packageDoesNotHaveAnyVersionsInstalled(let offendingPackage):
|
||||
appState.showAlert(errorToShow: .installedPackageHasNoVersions(corruptedPackageName: offendingPackage))
|
||||
case .packageIsNotAFolder(let offendingFile, let offendingFileURL):
|
||||
appState.showAlert(errorToShow: .installedPackageIsNotAFolder(itemName: offendingFile, itemURL: offendingFileURL))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Something got completely fucked up while loading packages")
|
||||
}
|
||||
|
||||
AppConstants.shared.logger.info("Finished \(whatToLoad == .formula ? "Formula" : "Cask", privacy: .public) loading task at \(Date(), privacy: .auto)")
|
||||
|
||||
return contentsOfFolder
|
||||
}
|
||||
*/
|
||||
|
|
@ -7,8 +7,10 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
import CorkModels
|
||||
|
||||
func searchForPackage(packageName: String, packageType: PackageType) async -> [String]
|
||||
func searchForPackage(packageName: String, packageType: BrewPackage.PackageType) async -> [String]
|
||||
{
|
||||
var finalPackageArray: [String]
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum HomebrewServiceLoadingError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
extension ServicesTracker
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
extension ServicesTracker
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum ServiceStoppingError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
extension HomebrewService
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
//
|
||||
// Parse Tap Info.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 21.06.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
|
||||
func parseTapInfo(from rawJSON: String) async throws -> TapInfo?
|
||||
{
|
||||
let decoder: JSONDecoder = {
|
||||
let decoder: JSONDecoder = .init()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
|
||||
return decoder
|
||||
}()
|
||||
|
||||
do
|
||||
{
|
||||
guard let jsonAsData: Data = rawJSON.data(using: .utf8, allowLossyConversion: false)
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.error("Could not convert tap JSON string into data")
|
||||
throw JSONParsingError.couldNotConvertStringToData(failureReason: nil)
|
||||
}
|
||||
|
||||
return try decoder.decode([TapInfo].self, from: jsonAsData).first
|
||||
}
|
||||
catch let decodingError
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while decoding tap info: \(decodingError.localizedDescription, privacy: .public)\n-\(decodingError, privacy: .public)")
|
||||
|
||||
throw JSONParsingError.couldNotDecode(failureReason: decodingError.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
import CorkModels
|
||||
|
||||
@MainActor
|
||||
func refreshPackages(_ updateProgressTracker: UpdateProgressTracker, outdatedPackagesTracker: OutdatedPackagesTracker) async -> PackageUpdateAvailability
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
@MainActor
|
||||
func updatePackages(updateProgressTracker: UpdateProgressTracker, detailStage: UpdatingProcessDetails) async
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
//
|
||||
// Brew Tap.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 10.02.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct BrewTap: Identifiable, Hashable
|
||||
{
|
||||
let id: UUID = .init()
|
||||
let name: String
|
||||
|
||||
var isBeingModified: Bool = false
|
||||
|
||||
mutating func changeBeingModifiedStatus()
|
||||
{
|
||||
isBeingModified.toggle()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
//
|
||||
// Cached Download.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 04.11.2023.
|
||||
//
|
||||
|
||||
import Charts
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct CachedDownload: Identifiable, Hashable
|
||||
{
|
||||
var id: UUID = .init()
|
||||
|
||||
let packageName: String
|
||||
let sizeInBytes: Int
|
||||
|
||||
var packageType: CachedDownloadType?
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// Corrupted Package.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 28.03.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct CorruptedPackage: Identifiable, Equatable
|
||||
{
|
||||
let id: UUID = .init()
|
||||
let name: String
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import SwiftUI
|
|||
import CorkShared
|
||||
import Defaults
|
||||
import DefaultsMacros
|
||||
import CorkModels
|
||||
|
||||
@Observable @MainActor
|
||||
class TopPackagesTracker
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
//
|
||||
// Minimal Homebrew Package.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 25.05.2024.
|
||||
//
|
||||
|
||||
import AppIntents
|
||||
import Foundation
|
||||
|
||||
struct MinimalHomebrewPackage: Identifiable, Hashable, AppEntity, Codable
|
||||
{
|
||||
var id: UUID = .init()
|
||||
|
||||
var name: String
|
||||
|
||||
var type: PackageType
|
||||
|
||||
var installDate: Date?
|
||||
|
||||
var installedIntentionally: Bool
|
||||
|
||||
static let typeDisplayRepresentation: TypeDisplayRepresentation = .init(name: "intents.type.minimal-homebrew-package")
|
||||
|
||||
var displayRepresentation: DisplayRepresentation
|
||||
{
|
||||
DisplayRepresentation(
|
||||
title: "\(name)",
|
||||
subtitle: "intents.type.minimal-homebrew-package.representation.subtitle"
|
||||
)
|
||||
}
|
||||
|
||||
static let defaultQuery: MinimalHomebrewPackageIntentQuery = .init()
|
||||
}
|
||||
|
||||
extension MinimalHomebrewPackage
|
||||
{
|
||||
init?(from homebrewPackage: BrewPackage?)
|
||||
{
|
||||
guard let homebrewPackage = homebrewPackage
|
||||
else
|
||||
{
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(
|
||||
name: homebrewPackage.name,
|
||||
type: homebrewPackage.type,
|
||||
installedIntentionally: homebrewPackage.installedIntentionally
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct MinimalHomebrewPackageIntentQuery: EntityQuery
|
||||
{
|
||||
func entities(for _: [UUID]) async throws -> [MinimalHomebrewPackage]
|
||||
{
|
||||
return .init()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
//
|
||||
// Tap Codable Model.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 21.06.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Decodable tap info
|
||||
struct TapInfo: Codable
|
||||
{
|
||||
/// The name of the tap
|
||||
let name: String
|
||||
|
||||
/// The user responsible for the tap
|
||||
let user: String
|
||||
|
||||
/// Name of the upstream repo
|
||||
let repo: String
|
||||
|
||||
/// Path to the tap
|
||||
let path: URL
|
||||
|
||||
/// Whether the tap is currently added
|
||||
let installed: Bool
|
||||
|
||||
/// Whether the tap is from the Homebrew developers
|
||||
let official: Bool
|
||||
|
||||
// MARK: - The contents of the tap
|
||||
|
||||
/// The formulae included in the tap
|
||||
let formulaNames: [String]
|
||||
|
||||
/// The casks included in the tap
|
||||
let caskTokens: [String]
|
||||
|
||||
/// The paths to the formula files
|
||||
let formulaFiles: [URL]?
|
||||
|
||||
/// The paths to the cask files
|
||||
let caskFiles: [URL]?
|
||||
|
||||
/// No idea, honestly
|
||||
let commandFiles: [String]?
|
||||
|
||||
/// Link to the actual repo
|
||||
let remote: URL?
|
||||
|
||||
/// IDK
|
||||
let customRemote: Bool?
|
||||
|
||||
var numberOfPackages: Int
|
||||
{
|
||||
return self.formulaNames.count + self.caskTokens.count
|
||||
}
|
||||
|
||||
/// Formulae that include the package type. Useful for rpeviewing packages.
|
||||
var includedFormulaeWithAdditionalMetadata: [MinimalHomebrewPackage]
|
||||
{
|
||||
return formulaNames.map
|
||||
{ formulaName in
|
||||
.init(name: formulaName, type: .formula, installedIntentionally: false)
|
||||
}
|
||||
}
|
||||
|
||||
var includedCasksWithAdditionalMetadata: [MinimalHomebrewPackage]
|
||||
{
|
||||
return caskTokens.map
|
||||
{ caskName in
|
||||
.init(name: caskName, type: .cask, installedIntentionally: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
//
|
||||
// Outdated Package.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 05.04.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct OutdatedPackage: Identifiable, Equatable, Hashable
|
||||
{
|
||||
enum PackageUpdatingType
|
||||
{
|
||||
/// The package is updating through Homebrew
|
||||
case homebrew
|
||||
|
||||
/// The package updates itself
|
||||
case selfUpdating
|
||||
|
||||
var argument: String
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .homebrew:
|
||||
return .init()
|
||||
case .selfUpdating:
|
||||
return "--greedy"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id: UUID = .init()
|
||||
|
||||
let package: BrewPackage
|
||||
|
||||
let installedVersions: [String]
|
||||
let newerVersion: String
|
||||
|
||||
var isMarkedForUpdating: Bool = true
|
||||
|
||||
var updatingManagedBy: PackageUpdatingType
|
||||
|
||||
static func == (lhs: OutdatedPackage, rhs: OutdatedPackage) -> Bool
|
||||
{
|
||||
return lhs.package.name == rhs.package.name
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(package.name)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
@Observable
|
||||
class InstallationProgressTracker
|
||||
{
|
||||
var packageBeingInstalled: PackageInProgressOfBeingInstalled = .init(package: .init(name: "", type: .formula, installedOn: nil, versions: [], sizeInBytes: 0, downloadCount: nil), installationStage: .downloadingCask, packageInstallationProgress: 0)
|
||||
var packageBeingInstalled: PackageInProgressOfBeingInstalled = .init(package: .init(name: "", type: .formula, installedOn: nil, versions: [], url: nil, sizeInBytes: 0, downloadCount: nil), installationStage: .downloadingCask, packageInstallationProgress: 0)
|
||||
|
||||
var numberOfPackageDependencies: Int = 0
|
||||
var numberInLineOfPackageCurrentlyBeingFetched: Int = 0
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CorkModels
|
||||
|
||||
struct RealTimeTerminalLine: Identifiable, Hashable, Equatable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
//
|
||||
// Brew Package Details.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 18.07.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
|
||||
enum PinningUnpinningError: LocalizedError
|
||||
{
|
||||
case failedWhileChangingPinnedStatus
|
||||
|
||||
var errorDescription: String?
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .failedWhileChangingPinnedStatus:
|
||||
return String(localized: "error.package-details.couldnt-pin-unpin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Observable @MainActor
|
||||
class BrewPackageDetails
|
||||
{
|
||||
// MARK: - Immutable properties
|
||||
|
||||
/// Name of the package
|
||||
let name: String
|
||||
|
||||
let description: String?
|
||||
|
||||
let homepage: URL
|
||||
let tap: BrewTap
|
||||
let installedAsDependency: Bool
|
||||
let dependencies: [BrewPackageDependency]?
|
||||
let outdated: Bool
|
||||
let caveats: String?
|
||||
|
||||
let deprecated: Bool
|
||||
let deprecationReason: String?
|
||||
|
||||
let isCompatible: Bool?
|
||||
|
||||
var dependents: [String]?
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(name: String, description: String?, homepage: URL, tap: BrewTap, installedAsDependency: Bool, dependents: [String]? = nil, dependencies: [BrewPackageDependency]? = nil, outdated: Bool, caveats: String? = nil, deprecated: Bool, deprecationReason: String? = nil, isCompatible: Bool?)
|
||||
{
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.homepage = homepage
|
||||
self.tap = tap
|
||||
self.installedAsDependency = installedAsDependency
|
||||
self.dependents = dependents
|
||||
self.dependencies = dependencies
|
||||
self.outdated = outdated
|
||||
self.deprecated = deprecated
|
||||
self.deprecationReason = deprecationReason
|
||||
self.caveats = caveats
|
||||
self.isCompatible = isCompatible
|
||||
}
|
||||
|
||||
// MARK: - Functions
|
||||
|
||||
func loadDependents() async
|
||||
{
|
||||
AppConstants.shared.logger.debug("Will load dependents for \(self.name)")
|
||||
let packageDependentsRaw: String = await shell(AppConstants.shared.brewExecutablePath, ["uses", "--installed", name]).standardOutput
|
||||
|
||||
let finalDependents: [String] = packageDependentsRaw.components(separatedBy: "\n").dropLast()
|
||||
|
||||
AppConstants.shared.logger.debug("Dependents loaded: \(finalDependents)")
|
||||
|
||||
dependents = finalDependents
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
//
|
||||
// Package Dependency.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 27.02.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct BrewPackageDependency: Identifiable, Hashable
|
||||
{
|
||||
let id: UUID = .init()
|
||||
let name: String
|
||||
let version: String
|
||||
let directlyDeclared: Bool
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CorkModels
|
||||
|
||||
@Observable
|
||||
class SearchResultTracker
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
//
|
||||
// Terminal Output.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 12.02.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TerminalOutput
|
||||
{
|
||||
var standardOutput: String
|
||||
var standardError: String
|
||||
}
|
||||
|
||||
enum StreamedTerminalOutput
|
||||
{
|
||||
case standardOutput(String)
|
||||
case standardError(String)
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
protocol DismissablePane: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct BrewfileImportProgressView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import CorkShared
|
|||
import SwiftUI
|
||||
import ButtonKit
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct AddFormulaView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import CorkShared
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct SearchResultRow: View, Sendable
|
||||
{
|
||||
|
|
@ -126,11 +127,11 @@ struct SearchResultRow: View, Sendable
|
|||
|
||||
do
|
||||
{
|
||||
let searchedForPackage: BrewPackage = .init(name: searchedForPackage.name, type: searchedForPackage.type, installedOn: Date(), versions: [], sizeInBytes: nil, downloadCount: nil)
|
||||
let searchedForPackage: BrewPackage = .init(name: searchedForPackage.name, type: searchedForPackage.type, installedOn: Date(), versions: [], url: nil, sizeInBytes: nil, downloadCount: nil)
|
||||
|
||||
do
|
||||
{
|
||||
let parsedPackageInfo: BrewPackageDetails = try await searchedForPackage.loadDetails()
|
||||
let parsedPackageInfo: BrewPackage.BrewPackageDetails = try await searchedForPackage.loadDetails()
|
||||
|
||||
description = parsedPackageInfo.description
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import CorkShared
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct InstallationInitialView: View
|
||||
{
|
||||
|
|
@ -116,6 +117,7 @@ struct InstallationInitialView: View
|
|||
))
|
||||
}
|
||||
.disabled(foundPackageSelection == nil)
|
||||
.labelStyle(.titleOnly)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import CorkShared
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct TopPackagesSection: View
|
||||
{
|
||||
|
|
@ -15,7 +16,7 @@ struct TopPackagesSection: View
|
|||
|
||||
let packageTracker: TopPackagesTracker
|
||||
|
||||
let trackerType: PackageType
|
||||
let trackerType: BrewPackage.PackageType
|
||||
|
||||
private var packages: [BrewPackage]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
struct InstallingPackageView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct PresentingSearchResultsView: View
|
||||
{
|
||||
|
|
@ -129,6 +130,7 @@ struct PresentingSearchResultsView: View
|
|||
AppConstants.shared.logger.debug("Would preview package \(selectedPackage.name)")
|
||||
}
|
||||
.disabled(foundPackageSelection == nil)
|
||||
.labelStyle(.titleOnly)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
@ -185,7 +187,7 @@ struct PresentingSearchResultsView: View
|
|||
|
||||
private struct SearchResultsSection: View
|
||||
{
|
||||
let sectionType: PackageType
|
||||
let sectionType: BrewPackage.PackageType
|
||||
|
||||
let packageList: [BrewPackage]
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct InstallationSearchingView: View, Sendable
|
||||
{
|
||||
|
|
@ -28,11 +29,11 @@ struct InstallationSearchingView: View, Sendable
|
|||
|
||||
for formula in await foundFormulae
|
||||
{
|
||||
searchResultTracker.foundFormulae.append(BrewPackage(name: formula, type: .formula, installedOn: nil, versions: [], sizeInBytes: nil, downloadCount: nil))
|
||||
searchResultTracker.foundFormulae.append(BrewPackage(name: formula, type: .formula, installedOn: nil, versions: [], url: nil, sizeInBytes: nil, downloadCount: nil))
|
||||
}
|
||||
for cask in await foundCasks
|
||||
{
|
||||
searchResultTracker.foundCasks.append(BrewPackage(name: cask, type: .cask, installedOn: nil, versions: [], sizeInBytes: nil, downloadCount: nil))
|
||||
searchResultTracker.foundCasks.append(BrewPackage(name: cask, type: .cask, installedOn: nil, versions: [], url: nil, sizeInBytes: nil, downloadCount: nil))
|
||||
}
|
||||
|
||||
packageInstallationProcessStep = .presentingSearchResults
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
struct AdoptingAlreadyInstalledCaskView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct AnotherProcessAlreadyRunningView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct BinaryAlreadyExistsView: View, Sendable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct InstallationFatalErrorView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import CorkNotifications
|
|||
import CorkShared
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct InstallationFinishedSuccessfullyView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct SudoRequiredView: View, Sendable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct WrongArchitectureView: View, Sendable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import CorkShared
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct LicensingView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct Licensing_BoughtView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import CorkShared
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct Licensing_DemoView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import SwiftUI
|
|||
import CorkShared
|
||||
import ButtonKit
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct Licensing_NotBoughtOrActivatedView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct Licensing_SelfCompiledView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
enum MaintenanceSteps
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import CorkShared
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct MaintenanceFinishedView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
struct MaintenanceRunningView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Adoption Results List.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 08.10.2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AdoptionResultsList: View
|
||||
{
|
||||
@Environment(\.openWindow) var openWindow: OpenWindowAction
|
||||
|
||||
@Environment(MassAppAdoptionView.MassAppAdoptionTacker.self) var massAppAdoptionTracker: MassAppAdoptionView.MassAppAdoptionTacker
|
||||
|
||||
var body: some View
|
||||
{
|
||||
DisclosureGroup("mass-adoption.failed.details-dropdown.label")
|
||||
{
|
||||
List(massAppAdoptionTracker.unsuccessfullyAdoptedApps)
|
||||
{ unsuccessfullyAdoptedApp in
|
||||
if case .failedWithError(let failedAdoptionCandidate, let error) = unsuccessfullyAdoptedApp
|
||||
{
|
||||
HStack(alignment: .center)
|
||||
{
|
||||
Text(failedAdoptionCandidate.caskName)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
openWindow(id: .errorInspectorWindowID, value: error)
|
||||
} label: {
|
||||
Label("action.inspect-error", systemImage: "info.circle")
|
||||
}
|
||||
.labelStyle(.iconOnly)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.bordered(alternatesRowBackgrounds: true))
|
||||
.frame(minHeight: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// Mass Adoption Stage - Ready.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 07.10.2025.
|
||||
//
|
||||
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MassAdoptionStage_Adopting: View
|
||||
{
|
||||
@Environment(MassAppAdoptionView.MassAppAdoptionTacker.self) var massAppAdoptionTracker: MassAppAdoptionView.MassAppAdoptionTacker
|
||||
|
||||
let appsToAdopt: [BrewPackagesTracker.AdoptableApp]
|
||||
|
||||
@State private var currentAdoptionIndex: Double = 0
|
||||
|
||||
var body: some View
|
||||
{
|
||||
ProgressView(value: currentAdoptionIndex, total: Double(appsToAdopt.count))
|
||||
{
|
||||
Text("app-adoption.currently-being-adopted.\(massAppAdoptionTracker.appCurrentlyBeingAdopted.appExecutable)")
|
||||
}
|
||||
.progressViewStyle(.linear)
|
||||
.task
|
||||
{
|
||||
for appToAdopt in appsToAdopt
|
||||
{
|
||||
AppConstants.shared.logger.info("Will start adoption process for \(appToAdopt.caskName)")
|
||||
|
||||
await massAppAdoptionTracker.adoptNextApp(appToAdopt: appToAdopt)
|
||||
|
||||
currentAdoptionIndex += 1
|
||||
}
|
||||
|
||||
if massAppAdoptionTracker.unsuccessfullyAdoptedApps.isEmpty
|
||||
{
|
||||
AppConstants.shared.logger.info("All selected apps were adopted successfully!")
|
||||
|
||||
massAppAdoptionTracker.massAdoptionStage = .finished(result: .success)
|
||||
}
|
||||
else if !massAppAdoptionTracker.unsuccessfullyAdoptedApps.isEmpty && !massAppAdoptionTracker.successfullyAdoptedApps.isEmpty
|
||||
{
|
||||
AppConstants.shared.logger.warning("Some selected apps were adoptes successfully, some unsuccessfully")
|
||||
|
||||
massAppAdoptionTracker.massAdoptionStage = .finished(result: .someSuccessSomeFailure)
|
||||
}
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.error("No selected apps were adopted successfully")
|
||||
|
||||
massAppAdoptionTracker.massAdoptionStage = .finished(result: .failure)
|
||||
}
|
||||
}
|
||||
.onDisappear
|
||||
{
|
||||
AppConstants.shared.logger.info("Cancelled the app adoption - will also cancel the app adoption process")
|
||||
|
||||
massAppAdoptionTracker.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Mass Adoption Stage - Failure.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 07.10.2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MassAdoptionStage_Failure: View
|
||||
{
|
||||
@Environment(\.openWindow) var openWindow: OpenWindowAction
|
||||
|
||||
@Environment(MassAppAdoptionView.MassAppAdoptionTacker.self) var massAppAdoptionTracker: MassAppAdoptionView.MassAppAdoptionTacker
|
||||
|
||||
var body: some View
|
||||
{
|
||||
ComplexWithIcon(systemName: "seal")
|
||||
{
|
||||
VStack(alignment: .leading, spacing: 10)
|
||||
{
|
||||
HeadlineWithSubheadline(
|
||||
headline: "mass-adoption.some-failed",
|
||||
subheadline: "mass-adoption.some-failed.message",
|
||||
alignment: .leading
|
||||
)
|
||||
|
||||
AdoptionResultsList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Mass Adoption Stage - Some Success Some Failure.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 08.10.2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MassAdoptionStage_SomeSuccessSomeFailure: View
|
||||
{
|
||||
@Environment(MassAppAdoptionView.MassAppAdoptionTacker.self) var massAppAdoptionTracker: MassAppAdoptionView.MassAppAdoptionTacker
|
||||
|
||||
var body: some View
|
||||
{
|
||||
ComplexWithIcon(systemName: "xmark.seal")
|
||||
{
|
||||
VStack(alignment: .leading, spacing: 10)
|
||||
{
|
||||
HeadlineWithSubheadline(
|
||||
headline: "mass-adoption.failed",
|
||||
subheadline: "mass-adoption.failed.message",
|
||||
alignment: .leading
|
||||
)
|
||||
|
||||
AdoptionResultsList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// Mass Adoption Stage - Success.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 07.10.2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkNotifications
|
||||
|
||||
struct MassAdoptionStage_Success: View
|
||||
{
|
||||
@Default(.notifyAboutMassAdoptionResults) var notifyAboutMassAdoptionResults: Bool
|
||||
|
||||
var body: some View
|
||||
{
|
||||
DisappearableSheet
|
||||
{
|
||||
ComplexWithIcon(systemName: "checkmark.seal")
|
||||
{
|
||||
HeadlineWithSubheadline(
|
||||
headline: "mass-adoption.finished",
|
||||
subheadline: "mass-adoption.finished.description",
|
||||
alignment: .leading
|
||||
)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if notifyAboutMassAdoptionResults
|
||||
{
|
||||
sendNotification(
|
||||
title: "mass-adoption.finished",
|
||||
body: "mass-adoption.finished-successfully.message"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
//
|
||||
// Mass App Adoption View.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 07.10.2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
typealias AdoptionProcessResult = Result<BrewPackagesTracker.AdoptableApp, MassAppAdoptionView.AdoptionAttemptFailure>
|
||||
|
||||
struct MassAppAdoptionView: View
|
||||
{
|
||||
@Observable
|
||||
final class MassAppAdoptionTacker
|
||||
{
|
||||
@ObservationIgnored
|
||||
var adoptionProcess: Process?
|
||||
|
||||
var outputLines: [RealTimeTerminalLine] = .init()
|
||||
|
||||
var massAdoptionStage: MassAdoptionStage = .adopting
|
||||
|
||||
private(set) var appCurrentlyBeingAdopted: BrewPackagesTracker.AdoptableApp
|
||||
private(set) var currentAdoptionIndex: Int
|
||||
|
||||
private(set) var appAdoptionResults: [AdoptionProcessResult] = .init()
|
||||
|
||||
var successfullyAdoptedApps: [BrewPackagesTracker.AdoptableApp]
|
||||
{
|
||||
return appAdoptionResults.compactMap
|
||||
{ rawResult in
|
||||
if case .success(let success) = rawResult {
|
||||
return success
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unsuccessfullyAdoptedApps: [MassAppAdoptionView.AdoptionAttemptFailure]
|
||||
{
|
||||
return appAdoptionResults.compactMap
|
||||
{ rawResult in
|
||||
if case .failure(let failure) = rawResult {
|
||||
return failure
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(appsToAdopt: [BrewPackagesTracker.AdoptableApp])
|
||||
{
|
||||
self.appCurrentlyBeingAdopted = appsToAdopt.first!
|
||||
self.currentAdoptionIndex = 0
|
||||
}
|
||||
|
||||
deinit
|
||||
{
|
||||
cancel()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func adoptNextApp(appToAdopt: BrewPackagesTracker.AdoptableApp) async
|
||||
{
|
||||
self.appCurrentlyBeingAdopted = appToAdopt
|
||||
self.currentAdoptionIndex += 1
|
||||
|
||||
self.appAdoptionResults.append(await self.adoptApp(appToAdopt))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func cancel() -> Bool
|
||||
{
|
||||
guard let adoptionProcess else { return false }
|
||||
adoptionProcess.terminate()
|
||||
self.adoptionProcess = nil
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
|
||||
@Environment(BrewPackagesTracker.self) var brewPackagesTracker: BrewPackagesTracker
|
||||
@Environment(CachedDownloadsTracker.self) var cachedDownloadsTracker: CachedDownloadsTracker
|
||||
|
||||
let appsToAdopt: [BrewPackagesTracker.AdoptableApp]
|
||||
|
||||
@State private var massAdoptionTracker: MassAppAdoptionTacker
|
||||
|
||||
init(appsToAdopt: [BrewPackagesTracker.AdoptableApp])
|
||||
{
|
||||
self.appsToAdopt = appsToAdopt
|
||||
self.massAdoptionTracker = .init(appsToAdopt: appsToAdopt)
|
||||
}
|
||||
|
||||
enum AdoptionAttemptFailure: Identifiable, Error
|
||||
{
|
||||
case failedWithError(failedAdoptionCandidate: BrewPackagesTracker.AdoptableApp, error: String)
|
||||
|
||||
var id: UUID
|
||||
{
|
||||
switch self {
|
||||
case .failedWithError(let failedAdoptionCandidate, let error):
|
||||
return failedAdoptionCandidate.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum MassAdoptionStage
|
||||
{
|
||||
case adopting, finished(result: AdoptionResult)
|
||||
|
||||
enum AdoptionResult
|
||||
{
|
||||
case success
|
||||
case someSuccessSomeFailure
|
||||
case failure
|
||||
}
|
||||
|
||||
var isDismissable: Bool
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .adopting:
|
||||
return true
|
||||
case .finished:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View
|
||||
{
|
||||
NavigationStack
|
||||
{
|
||||
SheetTemplate(isShowingTitle: true)
|
||||
{
|
||||
switch massAdoptionTracker.massAdoptionStage
|
||||
{
|
||||
case .adopting:
|
||||
MassAdoptionStage_Adopting(appsToAdopt: appsToAdopt)
|
||||
case .finished(let result):
|
||||
switch result
|
||||
{
|
||||
case .success:
|
||||
MassAdoptionStage_Success()
|
||||
case .someSuccessSomeFailure:
|
||||
MassAdoptionStage_SomeSuccessSomeFailure()
|
||||
case .failure:
|
||||
MassAdoptionStage_Failure()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("mass-adoption.title")
|
||||
.toolbar
|
||||
{
|
||||
if massAdoptionTracker.massAdoptionStage.isDismissable
|
||||
{
|
||||
ToolbarItem(placement: .cancellationAction)
|
||||
{
|
||||
DismissSheetButton(dismiss: _dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.environment(massAdoptionTracker)
|
||||
.onDisappear
|
||||
{
|
||||
Task
|
||||
{
|
||||
try? await brewPackagesTracker.synchronizeInstalledPackages(cachedDownloadsTracker: cachedDownloadsTracker)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MenuBarItem: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkNotifications
|
||||
import CorkModels
|
||||
|
||||
struct MenuBar_CachedDownloadsCleanup: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import ButtonKit
|
|||
import CorkNotifications
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MenuBar_OrphanCleanup: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MenuBar_PackageInstallation: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MenuBar_PackageOverview: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MenuBar_PackageUpdating: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct PackagePreview: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
import ApplicationInspector
|
||||
|
||||
struct PackageDetailView: View, Sendable, DismissablePane
|
||||
{
|
||||
|
|
@ -39,7 +41,9 @@ struct PackageDetailView: View, Sendable, DismissablePane
|
|||
|
||||
var isInPreviewWindow: Bool = false
|
||||
|
||||
@State private var packageDetails: BrewPackageDetails? = nil
|
||||
@State private var packageDetails: BrewPackage.BrewPackageDetails? = nil
|
||||
|
||||
@State private var caskExecutable: Application? = nil
|
||||
|
||||
@Environment(BrewPackagesTracker.self) var brewPackagesTracker: BrewPackagesTracker
|
||||
|
||||
|
|
@ -92,9 +96,15 @@ struct PackageDetailView: View, Sendable, DismissablePane
|
|||
isShowingExpandedCaveats: $isShowingExpandedCaveats
|
||||
)
|
||||
|
||||
PackageDependencies(dependencies: packageDetails?.dependencies, isDependencyDisclosureGroupExpanded: $isShowingExpandedDependencies)
|
||||
PackageDependencies(
|
||||
dependencies: packageDetails?.dependencies,
|
||||
isDependencyDisclosureGroupExpanded: $isShowingExpandedDependencies
|
||||
)
|
||||
|
||||
PackageSystemInfo(package: packageStructureToUse)
|
||||
PackageSystemInfo(
|
||||
package: packageStructureToUse,
|
||||
caskExecutable: caskExecutable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -157,6 +167,19 @@ struct PackageDetailView: View, Sendable, DismissablePane
|
|||
erroredOut = (true, packageInfoDecodingError.localizedDescription)
|
||||
}
|
||||
}
|
||||
.task(id: package.id)
|
||||
{ // For casks, try to load the application executable
|
||||
if package.type == .cask
|
||||
{
|
||||
AppConstants.shared.logger.info("Package is cask, will see what the app's location is for url \(package.url as NSObject?)")
|
||||
|
||||
if let packageURL = package.url
|
||||
{
|
||||
AppConstants.shared.logger.info("Will try to load app icon for URL \(packageURL)")
|
||||
caskExecutable = try? .init(from: packageURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -217,10 +240,10 @@ private extension BrewPackagesTracker
|
|||
struct FastPackageComparableRepresentation: Hashable
|
||||
{
|
||||
let name: String
|
||||
let type: PackageType
|
||||
let type: BrewPackage.PackageType
|
||||
let versions: [String]
|
||||
|
||||
init(name: String, type: PackageType, versions: [String])
|
||||
init(name: String, type: BrewPackage.PackageType, versions: [String])
|
||||
{
|
||||
self.name = name
|
||||
self.type = type
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@
|
|||
import CorkShared
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct BasicPackageInfoView: View
|
||||
{
|
||||
@Default(.caveatDisplayOptions) var caveatDisplayOptions: PackageCaveatDisplay
|
||||
|
||||
let package: BrewPackage
|
||||
let packageDetails: BrewPackageDetails
|
||||
let packageDetails: BrewPackage.BrewPackageDetails
|
||||
|
||||
let isLoadingDetails: Bool
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct DependencyList: View
|
||||
{
|
||||
|
|
@ -43,7 +44,7 @@ struct DependencyList: View
|
|||
{
|
||||
TableColumn("package-details.dependencies.results.name")
|
||||
{ dependency in
|
||||
SanitizedPackageName(package: .init(name: dependency.name, type: .formula, installedOn: nil, versions: [dependency.version], sizeInBytes: nil, downloadCount: nil), shouldShowVersion: false)
|
||||
SanitizedPackageName(package: .init(name: dependency.name, type: .formula, installedOn: nil, versions: [dependency.version], url: nil, sizeInBytes: nil, downloadCount: nil), shouldShowVersion: false)
|
||||
}
|
||||
TableColumn("package-details.dependencies.results.version")
|
||||
{ dependency in
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct PackageDependencies: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
|
||||
struct PackageDetailHeaderComplex: View
|
||||
{
|
||||
|
|
@ -22,7 +23,7 @@ struct PackageDetailHeaderComplex: View
|
|||
|
||||
var isInPreviewWindow: Bool
|
||||
|
||||
@Bindable var packageDetails: BrewPackageDetails
|
||||
@Bindable var packageDetails: BrewPackage.BrewPackageDetails
|
||||
|
||||
let isLoadingDetails: Bool
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import SwiftUI
|
|||
import CorkShared
|
||||
import ButtonKit
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct PackageModificationButtons: View
|
||||
{
|
||||
|
|
@ -21,7 +22,7 @@ struct PackageModificationButtons: View
|
|||
@Environment(OutdatedPackagesTracker.self) var outdatedPackagesTracker: OutdatedPackagesTracker
|
||||
|
||||
let package: BrewPackage
|
||||
@Bindable var packageDetails: BrewPackageDetails
|
||||
@Bindable var packageDetails: BrewPackage.BrewPackageDetails
|
||||
|
||||
let isLoadingDetails: Bool
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ struct PackageModificationButtons: View
|
|||
if package.type == .formula
|
||||
{
|
||||
PinUnpinButton(package: package)
|
||||
.labelStyle(.titleOnly)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
|
@ -52,12 +54,14 @@ struct PackageModificationButtons: View
|
|||
if !allowMoreCompleteUninstallations
|
||||
{
|
||||
UninstallPackageButton(package: package)
|
||||
.labelStyle(.titleOnly)
|
||||
}
|
||||
else
|
||||
{
|
||||
Menu
|
||||
{
|
||||
PurgePackageButton(package: package)
|
||||
.labelStyle(.titleOnly)
|
||||
} label: {
|
||||
Text("action.uninstall-\(package.name)")
|
||||
} primaryAction: {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import ButtonKit
|
||||
import CorkModels
|
||||
|
||||
struct PinUnpinButton: View
|
||||
{
|
||||
|
|
@ -23,7 +24,9 @@ struct PinUnpinButton: View
|
|||
{
|
||||
await package.performPinnedStatusChangeAction(appState: appState, brewPackagesTracker: brewPackagesTracker)
|
||||
} label: {
|
||||
Text(package.isPinned ? "package-details.action.unpin-version-\(package.versions.formatted(.list(type: .and)))" : "package-details.action.pin-version-\(package.versions.formatted(.list(type: .and)))")
|
||||
let labelText: LocalizedStringKey = package.isPinned ? "package-details.action.unpin-version-\(package.versions.formatted(.list(type: .and)))" : "package-details.action.pin-version-\(package.versions.formatted(.list(type: .and)))"
|
||||
|
||||
Label(labelText, systemImage: "pin.fill")
|
||||
}
|
||||
.asyncButtonStyle(.leading)
|
||||
.disabledWhenLoading()
|
||||
|
|
|
|||
|
|
@ -6,10 +6,14 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import ApplicationInspector
|
||||
import CorkModels
|
||||
|
||||
struct PackageSystemInfo: View
|
||||
{
|
||||
let package: BrewPackage
|
||||
|
||||
let caskExecutable: Application?
|
||||
|
||||
@State private var isShowingCaskSizeHelpPopover: Bool = false
|
||||
|
||||
|
|
@ -19,47 +23,57 @@ struct PackageSystemInfo: View
|
|||
{
|
||||
Section
|
||||
{
|
||||
LabeledContent
|
||||
{
|
||||
Text(installedOnDate.formatted(.packageInstallationStyle))
|
||||
} label: {
|
||||
Text("package-details.install-date")
|
||||
}
|
||||
caskInstalledAsLine
|
||||
|
||||
installedOnDateLine(installedOnDate)
|
||||
|
||||
if let packageSize = package.sizeInBytes
|
||||
{
|
||||
LabeledContent
|
||||
{
|
||||
HStack
|
||||
{
|
||||
Text(packageSize.formatted(.byteCount(style: .file)))
|
||||
|
||||
if package.type == .cask
|
||||
{
|
||||
HelpButton
|
||||
{
|
||||
isShowingCaskSizeHelpPopover.toggle()
|
||||
}
|
||||
.help("package-details.size.help")
|
||||
.popover(isPresented: $isShowingCaskSizeHelpPopover)
|
||||
{
|
||||
VStack(alignment: .leading, spacing: 10)
|
||||
{
|
||||
Text("package-details.size.help")
|
||||
.font(.headline)
|
||||
Text("package-details.size.help.body-1")
|
||||
Text("package-details.size.help.body-2")
|
||||
}
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding()
|
||||
.frame(width: 300)
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("package-details.size")
|
||||
}
|
||||
}
|
||||
packageSizeLine(package.sizeInBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var caskInstalledAsLine: some View
|
||||
{
|
||||
|
||||
if let caskExecutable
|
||||
{
|
||||
LabeledContent
|
||||
{
|
||||
AppIconDisplay(
|
||||
displayType: .asIconWithAppNameDisplayed(
|
||||
usingApp: caskExecutable,
|
||||
namePosition: .besideAppIcon
|
||||
),
|
||||
allowRevealingInFinderFromIcon: true
|
||||
)
|
||||
} label: {
|
||||
Text("package-details.installed-as")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func installedOnDateLine(_ installedOnDate: Date) -> some View
|
||||
{
|
||||
LabeledContent
|
||||
{
|
||||
Text(installedOnDate.formatted(.packageInstallationStyle))
|
||||
} label: {
|
||||
Text("package-details.install-date")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func packageSizeLine(_ packageSize: Int64?) -> some View
|
||||
{
|
||||
if let packageSize = package.sizeInBytes
|
||||
{
|
||||
LabeledContent
|
||||
{
|
||||
Text(packageSize.formatted(.byteCount(style: .file)))
|
||||
} label: {
|
||||
Text("package-details.size")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct PackageListItem: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
struct ReinstallCorruptedPackageView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import ButtonKit
|
||||
import CorkModels
|
||||
|
||||
struct SudoRequiredForRemovalSheet: View, Sendable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
//
|
||||
// App Icon Display.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 22.10.2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ApplicationInspector
|
||||
|
||||
/// Show the icon of a linked app
|
||||
struct AppIconDisplay: View
|
||||
{
|
||||
enum DisplayType
|
||||
{
|
||||
case asIcon(
|
||||
usingApp: Application
|
||||
)
|
||||
case asIconWithAppNameDisplayed(
|
||||
usingApp: Application,
|
||||
namePosition: AppNamePosition
|
||||
)
|
||||
case asPathControl(
|
||||
usingURL: URL
|
||||
)
|
||||
|
||||
enum AppNamePosition
|
||||
{
|
||||
case besideAppIcon
|
||||
case underAppIcon
|
||||
}
|
||||
}
|
||||
|
||||
let displayType: DisplayType
|
||||
|
||||
let allowRevealingInFinderFromIcon: Bool
|
||||
|
||||
var body: some View
|
||||
{
|
||||
switch displayType
|
||||
{
|
||||
case .asIcon(let usingApp):
|
||||
ApplicationIconImage(
|
||||
app: usingApp,
|
||||
allowRevealingInFinderThroughIcon: allowRevealingInFinderFromIcon
|
||||
)
|
||||
case .asIconWithAppNameDisplayed(let usingApp, let namePosition):
|
||||
switch namePosition
|
||||
{
|
||||
case .besideAppIcon:
|
||||
HStack(alignment: .center, spacing: 5)
|
||||
{
|
||||
ApplicationIconImage(
|
||||
app: usingApp,
|
||||
allowRevealingInFinderThroughIcon: allowRevealingInFinderFromIcon
|
||||
)
|
||||
|
||||
applicationName(app: usingApp)
|
||||
}
|
||||
case .underAppIcon:
|
||||
|
||||
VStack(alignment: .center, spacing: 5)
|
||||
{
|
||||
ApplicationIconImage(
|
||||
app: usingApp,
|
||||
allowRevealingInFinderThroughIcon: allowRevealingInFinderFromIcon
|
||||
)
|
||||
|
||||
applicationName(app: usingApp)
|
||||
}
|
||||
}
|
||||
case .asPathControl(let usingURL):
|
||||
AppIconDisplay_AsPathControl(urlToApp: usingURL)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func applicationName(app: Application) -> some View
|
||||
{
|
||||
Text(app.name)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ApplicationIconImage: View
|
||||
{
|
||||
let app: Application
|
||||
|
||||
let allowRevealingInFinderThroughIcon: Bool
|
||||
|
||||
var body: some View
|
||||
{
|
||||
if let appIconImage = app.iconImage
|
||||
{
|
||||
appIconImage
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 35)
|
||||
.contextMenu {
|
||||
Button
|
||||
{
|
||||
app.url.revealInFinder(.openParentDirectoryAndHighlightTarget)
|
||||
} label: {
|
||||
Label("action.reveal-\(app.name)-in-finder", systemImage: "finder")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct AppIconDisplay_AsPathControl: NSViewRepresentable
|
||||
{
|
||||
typealias NSViewType = NSPathControl
|
||||
|
||||
let urlToApp: URL
|
||||
|
||||
func makeNSView(context _: Context) -> NSPathControl
|
||||
{
|
||||
let pathControl: NSPathControl = .init()
|
||||
|
||||
pathControl.url = urlToApp
|
||||
|
||||
if let lastPathItem = pathControl.pathItems.last
|
||||
{
|
||||
pathControl.pathItems = [lastPathItem]
|
||||
}
|
||||
|
||||
return pathControl
|
||||
}
|
||||
|
||||
func updateNSView(_: NSPathControl, context _: Context)
|
||||
{}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue