mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-04 11:09:01 +08:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc2b90daa6 | |||
| 74819f6141 | |||
| ab758b01b3 | |||
| a505e65f32 | |||
| cbcc708073 | |||
| 876ea3a987 | |||
| 92887791fc | |||
| 1e861e54ee | |||
| c19ef05534 | |||
| ea1e7076d7 | |||
| 8065fa4bf4 | |||
| 1aca314299 | |||
| f6b281d12f | |||
| 6d0516175d | |||
| d3591dcdc1 | |||
| 333edab3b6 | |||
| deeca60efa | |||
| a1abb60dec | |||
| 04f3d88e2c |
@@ -605,6 +605,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
'BFZk3c2ERp-sliRvA7MQ_p3NdkdCLt2Ze0DQ9i21dpA=',
|
||||
],
|
||||
blockId: 'lcZphIJe63',
|
||||
content: '',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:image',
|
||||
parentBlockId: '6x7ALjUDjj',
|
||||
@@ -619,6 +620,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
'HWvCItS78DzPGbwcuaGcfkpVDUvL98IvH5SIK8-AcL8=',
|
||||
],
|
||||
blockId: 'JlgVJdWU12',
|
||||
content: '',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:image',
|
||||
parentBlockId: '6x7ALjUDjj',
|
||||
@@ -633,6 +635,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
'ZRKpsBoC88qEMmeiXKXqywfA1rLvWoLa5rpEh9x9Oj0=',
|
||||
],
|
||||
blockId: 'lht7AqBqnF',
|
||||
content: '',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:image',
|
||||
parentBlockId: '6x7ALjUDjj',
|
||||
@@ -1236,6 +1239,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
'BFZk3c2ERp-sliRvA7MQ_p3NdkdCLt2Ze0DQ9i21dpA=',
|
||||
],
|
||||
blockId: 'lcZphIJe63',
|
||||
content: '',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:image',
|
||||
parentBlockId: '6x7ALjUDjj',
|
||||
@@ -1250,6 +1254,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
'HWvCItS78DzPGbwcuaGcfkpVDUvL98IvH5SIK8-AcL8=',
|
||||
],
|
||||
blockId: 'JlgVJdWU12',
|
||||
content: '',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:image',
|
||||
parentBlockId: '6x7ALjUDjj',
|
||||
@@ -1264,6 +1269,7 @@ Generated by [AVA](https://avajs.dev).
|
||||
'ZRKpsBoC88qEMmeiXKXqywfA1rLvWoLa5rpEh9x9Oj0=',
|
||||
],
|
||||
blockId: 'lht7AqBqnF',
|
||||
content: '',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:image',
|
||||
parentBlockId: '6x7ALjUDjj',
|
||||
|
||||
Binary file not shown.
@@ -158,6 +158,7 @@ export class SelfhostTeamSubscriptionManager extends SubscriptionManager {
|
||||
'stripeScheduleId',
|
||||
'nextBillAt',
|
||||
'canceledAt',
|
||||
'end',
|
||||
]),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -243,6 +243,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
'stripeScheduleId',
|
||||
'nextBillAt',
|
||||
'canceledAt',
|
||||
'end',
|
||||
]),
|
||||
create: {
|
||||
targetId: userId,
|
||||
|
||||
@@ -166,6 +166,7 @@ export class WorkspaceSubscriptionManager extends SubscriptionManager {
|
||||
'nextBillAt',
|
||||
'canceledAt',
|
||||
'quantity',
|
||||
'end',
|
||||
]),
|
||||
},
|
||||
create: {
|
||||
|
||||
@@ -15,7 +15,7 @@ export const passwordLimitsFragment = `fragment PasswordLimits on PasswordLimits
|
||||
minLength
|
||||
maxLength
|
||||
}`;
|
||||
export const licenseFragment = `fragment license on License {
|
||||
export const licenseBodyFragment = `fragment licenseBody on License {
|
||||
expiredAt
|
||||
installedAt
|
||||
quantity
|
||||
@@ -1443,10 +1443,10 @@ export const activateLicenseMutation = {
|
||||
op: 'activateLicense',
|
||||
query: `mutation activateLicense($workspaceId: String!, $license: String!) {
|
||||
activateLicense(workspaceId: $workspaceId, license: $license) {
|
||||
...license
|
||||
...licenseBody
|
||||
}
|
||||
}
|
||||
${licenseFragment}`,
|
||||
${licenseBodyFragment}`,
|
||||
};
|
||||
|
||||
export const deactivateLicenseMutation = {
|
||||
@@ -1463,11 +1463,11 @@ export const getLicenseQuery = {
|
||||
query: `query getLicense($workspaceId: String!) {
|
||||
workspace(id: $workspaceId) {
|
||||
license {
|
||||
...license
|
||||
...licenseBody
|
||||
}
|
||||
}
|
||||
}
|
||||
${licenseFragment}`,
|
||||
${licenseBodyFragment}`,
|
||||
};
|
||||
|
||||
export const installLicenseMutation = {
|
||||
@@ -1475,10 +1475,10 @@ export const installLicenseMutation = {
|
||||
op: 'installLicense',
|
||||
query: `mutation installLicense($workspaceId: String!, $license: Upload!) {
|
||||
installLicense(workspaceId: $workspaceId, license: $license) {
|
||||
...license
|
||||
...licenseBody
|
||||
}
|
||||
}
|
||||
${licenseFragment}`,
|
||||
${licenseBodyFragment}`,
|
||||
file: true,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#import './license.gql'
|
||||
#import './license-body.gql'
|
||||
|
||||
mutation activateLicense($workspaceId: String!, $license: String!) {
|
||||
activateLicense(workspaceId: $workspaceId, license: $license) {
|
||||
...license
|
||||
...licenseBody
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#import './license.gql'
|
||||
#import './license-body.gql'
|
||||
|
||||
query getLicense($workspaceId: String!) {
|
||||
workspace(id: $workspaceId) {
|
||||
license {
|
||||
...license
|
||||
...licenseBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#import './license.gql'
|
||||
#import './license-body.gql'
|
||||
|
||||
mutation installLicense($workspaceId: String!, $license: Upload!) {
|
||||
installLicense(workspaceId: $workspaceId, license: $license) {
|
||||
...license
|
||||
...licenseBody
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
fragment license on License {
|
||||
fragment licenseBody on License {
|
||||
expiredAt
|
||||
installedAt
|
||||
quantity
|
||||
@@ -4401,7 +4401,7 @@ export type InstallLicenseMutation = {
|
||||
};
|
||||
};
|
||||
|
||||
export type LicenseFragment = {
|
||||
export type LicenseBodyFragment = {
|
||||
__typename?: 'License';
|
||||
expiredAt: string | null;
|
||||
installedAt: string;
|
||||
|
||||
@@ -1673,6 +1673,7 @@ exports[`should read doc blocks work 1`] = `
|
||||
"BFZk3c2ERp-sliRvA7MQ_p3NdkdCLt2Ze0DQ9i21dpA=",
|
||||
],
|
||||
"blockId": "lcZphIJe63",
|
||||
"content": "",
|
||||
"docId": "test-doc",
|
||||
"flavour": "affine:image",
|
||||
"parentBlockId": "6x7ALjUDjj",
|
||||
@@ -1702,6 +1703,7 @@ exports[`should read doc blocks work 1`] = `
|
||||
"HWvCItS78DzPGbwcuaGcfkpVDUvL98IvH5SIK8-AcL8=",
|
||||
],
|
||||
"blockId": "JlgVJdWU12",
|
||||
"content": "",
|
||||
"docId": "test-doc",
|
||||
"flavour": "affine:image",
|
||||
"parentBlockId": "6x7ALjUDjj",
|
||||
@@ -1731,6 +1733,7 @@ exports[`should read doc blocks work 1`] = `
|
||||
"ZRKpsBoC88qEMmeiXKXqywfA1rLvWoLa5rpEh9x9Oj0=",
|
||||
],
|
||||
"blockId": "lht7AqBqnF",
|
||||
"content": "",
|
||||
"docId": "test-doc",
|
||||
"flavour": "affine:image",
|
||||
"parentBlockId": "6x7ALjUDjj",
|
||||
@@ -3361,6 +3364,7 @@ exports[`should read doc blocks work without root doc 1`] = `
|
||||
"BFZk3c2ERp-sliRvA7MQ_p3NdkdCLt2Ze0DQ9i21dpA=",
|
||||
],
|
||||
"blockId": "lcZphIJe63",
|
||||
"content": "",
|
||||
"docId": "test-doc",
|
||||
"flavour": "affine:image",
|
||||
"parentBlockId": "6x7ALjUDjj",
|
||||
@@ -3390,6 +3394,7 @@ exports[`should read doc blocks work without root doc 1`] = `
|
||||
"HWvCItS78DzPGbwcuaGcfkpVDUvL98IvH5SIK8-AcL8=",
|
||||
],
|
||||
"blockId": "JlgVJdWU12",
|
||||
"content": "",
|
||||
"docId": "test-doc",
|
||||
"flavour": "affine:image",
|
||||
"parentBlockId": "6x7ALjUDjj",
|
||||
@@ -3419,6 +3424,7 @@ exports[`should read doc blocks work without root doc 1`] = `
|
||||
"ZRKpsBoC88qEMmeiXKXqywfA1rLvWoLa5rpEh9x9Oj0=",
|
||||
],
|
||||
"blockId": "lht7AqBqnF",
|
||||
"content": "",
|
||||
"docId": "test-doc",
|
||||
"flavour": "affine:image",
|
||||
"parentBlockId": "6x7ALjUDjj",
|
||||
|
||||
@@ -648,12 +648,24 @@ export async function readAllBlocksFromDoc({
|
||||
parentBlockId,
|
||||
});
|
||||
}
|
||||
} else if (flavour === 'affine:attachment' || flavour === 'affine:image') {
|
||||
} else if (flavour === 'affine:attachment') {
|
||||
const blobId = block.get('prop:sourceId');
|
||||
if (typeof blobId === 'string') {
|
||||
blockDocuments.push({
|
||||
...commonBlockProps,
|
||||
blob: [blobId],
|
||||
content: block.get('prop:name')?.toString() ?? '',
|
||||
parentFlavour,
|
||||
parentBlockId,
|
||||
});
|
||||
}
|
||||
} else if (flavour === 'affine:image') {
|
||||
const blobId = block.get('prop:sourceId');
|
||||
if (typeof blobId === 'string') {
|
||||
blockDocuments.push({
|
||||
...commonBlockProps,
|
||||
blob: [blobId],
|
||||
content: block.get('prop:caption')?.toString() ?? '',
|
||||
parentFlavour,
|
||||
parentBlockId,
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -13,7 +13,6 @@
|
||||
50802D612D112F8700694021 /* Intelligents in Frameworks */ = {isa = PBXBuildFile; productRef = 50802D602D112F8700694021 /* Intelligents */; };
|
||||
50A285D72D112A5E000D5A6D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 50A285D62D112A5E000D5A6D /* Localizable.xcstrings */; };
|
||||
50A285D82D112A5E000D5A6D /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 50A285D52D112A5E000D5A6D /* InfoPlist.xcstrings */; };
|
||||
50A285DC2D112B24000D5A6D /* Intelligents in Frameworks */ = {isa = PBXBuildFile; productRef = 50A285DB2D112B24000D5A6D /* Intelligents */; };
|
||||
50FF428A2D2E757E0050AA83 /* ApplicationBridgedWindowScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50FF42892D2E757E0050AA83 /* ApplicationBridgedWindowScript.swift */; };
|
||||
50FF428C2D2E77CC0050AA83 /* AffineViewController+AIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50FF428B2D2E77CC0050AA83 /* AffineViewController+AIButton.swift */; };
|
||||
9D52FC432D26CDBF00105D0A /* JSValueContainerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D52FC422D26CDB600105D0A /* JSValueContainerExt.swift */; };
|
||||
@@ -78,6 +77,8 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
C45499AB2D140B5000E21978 /* NBStore */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
path = NBStore;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -88,7 +89,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
50A285DC2D112B24000D5A6D /* Intelligents in Frameworks */,
|
||||
9DFCD1462D27D1D70028C92B /* libaffine_mobile_native.a in Frameworks */,
|
||||
50802D612D112F8700694021 /* Intelligents in Frameworks */,
|
||||
2E0DD47B57B994A104B25EED /* Pods_AFFiNE.framework in Frameworks */,
|
||||
@@ -326,13 +326,9 @@
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AFFiNE/Pods-AFFiNE-frameworks.sh\"\n";
|
||||
@@ -518,14 +514,13 @@
|
||||
baseConfigurationReference = 3256F4410D881A03FE77D092 /* Pods-AFFiNE.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: TOEVERYTHING PTE. LTD. (73YMMDVT2M)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
DEVELOPMENT_TEAM = 73YMMDVT2M;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 73YMMDVT2M;
|
||||
DEVELOPMENT_TEAM = 964G86XT2P;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -563,7 +558,7 @@
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 73YMMDVT2M;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -576,8 +571,7 @@
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.affine.pro;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "AppStore app.affine.pro";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AppStore app.affine.pro";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
@@ -617,10 +611,6 @@
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Intelligents;
|
||||
};
|
||||
50A285DB2D112B24000D5A6D /* Intelligents */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Intelligents;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 504EC2FC1FED79650016851F /* Project object */;
|
||||
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "apollo-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apollographql/apollo-ios",
|
||||
"state" : {
|
||||
"revision" : "9aa748d6f0526a744d49d59a2383dc7fdf9d645b",
|
||||
"version" : "1.18.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sqlite.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/stephencelis/SQLite.swift.git",
|
||||
"state" : {
|
||||
"revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8",
|
||||
"version" : "0.15.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-collections",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-collections",
|
||||
"state" : {
|
||||
"revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0",
|
||||
"version" : "1.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-eventsource",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/LaunchDarkly/swift-eventsource.git",
|
||||
"state" : {
|
||||
"revision" : "57051701c58a93603ffa2051f8e9cf0c8cff7814",
|
||||
"version" : "3.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
+34
-7
@@ -3,19 +3,28 @@
|
||||
{
|
||||
"identity" : "apollo-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apollographql/apollo-ios.git",
|
||||
"location" : "https://github.com/apollographql/apollo-ios",
|
||||
"state" : {
|
||||
"revision" : "9aa748d6f0526a744d49d59a2383dc7fdf9d645b",
|
||||
"version" : "1.18.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "snapkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SnapKit/SnapKit.git",
|
||||
"state" : {
|
||||
"revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4",
|
||||
"version" : "5.7.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sqlite.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/stephencelis/SQLite.swift.git",
|
||||
"state" : {
|
||||
"revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8",
|
||||
"version" : "0.15.3"
|
||||
"revision" : "392dd6058624d9f6c5b4c769d165ddd8c7293394",
|
||||
"version" : "0.15.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -28,12 +37,30 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-eventsource",
|
||||
"identity" : "swift-toolchain-sqlite",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/LaunchDarkly/swift-eventsource.git",
|
||||
"location" : "https://github.com/swiftlang/swift-toolchain-sqlite",
|
||||
"state" : {
|
||||
"revision" : "57051701c58a93603ffa2051f8e9cf0c8cff7814",
|
||||
"version" : "3.3.0"
|
||||
"revision" : "b626d3002773b1a1304166643e7f118f724b2132",
|
||||
"version" : "1.0.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swifterswift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SwifterSwift/SwifterSwift.git",
|
||||
"state" : {
|
||||
"revision" : "39fa28c90a3ebe3d53f80289304fd880cf2c42d0",
|
||||
"version" : "6.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "then",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/devxoul/Then",
|
||||
"state" : {
|
||||
"revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a",
|
||||
"version" : "3.0.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -5,123 +5,18 @@
|
||||
// Created by 秋星桥 on 2025/1/8.
|
||||
//
|
||||
|
||||
import ChidoriMenu
|
||||
import Intelligents
|
||||
import UIKit
|
||||
|
||||
extension AFFiNEViewController: IntelligentsButtonDelegate, IntelligentsFocusApertureViewDelegate {
|
||||
extension AFFiNEViewController: IntelligentsButtonDelegate {
|
||||
func onIntelligentsButtonTapped(_ button: IntelligentsButton) {
|
||||
guard let webView else {
|
||||
assertionFailure() // ? wdym ?
|
||||
return
|
||||
}
|
||||
|
||||
IntelligentContext.shared.webView = webView!
|
||||
button.beginProgress()
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
group.enter()
|
||||
webView.evaluateScript(.getCurrentServerBaseUrl) { result in
|
||||
self.baseUrl = result as? String
|
||||
print("[*] setting baseUrl: \(self.baseUrl ?? "")")
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.enter()
|
||||
webView.evaluateScript(.getCurrentDocId) { result in
|
||||
self.documentID = result as? String
|
||||
print("[*] setting documentID: \(self.documentID ?? "")")
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.enter()
|
||||
webView.evaluateScript(.getCurrentWorkspaceId) { result in
|
||||
self.workspaceID = result as? String
|
||||
print("[*] setting workspaceID: \(self.workspaceID ?? "")")
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.enter()
|
||||
webView.evaluateScript(.getCurrentDocContentInMarkdown) { input in
|
||||
self.documentContent = input as? String
|
||||
print("[*] setting documentContent: \(self.documentContent?.count ?? 0) chars")
|
||||
group.leave()
|
||||
}
|
||||
|
||||
DispatchQueue.global().asyncAfter(deadline: .now()) {
|
||||
group.wait()
|
||||
DispatchQueue.main.async {
|
||||
button.stopProgress()
|
||||
webView.resignFirstResponder()
|
||||
self.openIntelligentsSheet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func openIntelligentsSheet() -> IntelligentsFocusApertureView? {
|
||||
dismissIntelligentsButton()
|
||||
view.resignFirstResponder()
|
||||
// stop scroll on webview
|
||||
if let contentOffset = webView?.scrollView.contentOffset {
|
||||
webView?.scrollView.contentOffset = contentOffset
|
||||
}
|
||||
let focus = IntelligentsFocusApertureView()
|
||||
focus.prepareAnimationWith(
|
||||
capturingTargetContentView: webView ?? .init(),
|
||||
coveringRootViewController: self
|
||||
)
|
||||
focus.delegate = self
|
||||
focus.executeAnimationKickIn()
|
||||
dismissIntelligentsButton()
|
||||
return focus
|
||||
}
|
||||
|
||||
func openSimpleChat() {
|
||||
let targetController = IntelligentsChatController()
|
||||
presentIntoCurrentContext(withTargetController: targetController)
|
||||
}
|
||||
|
||||
func focusApertureRequestAction(
|
||||
from view: IntelligentsFocusApertureView,
|
||||
actionType: IntelligentsFocusApertureViewActionType
|
||||
) {
|
||||
switch actionType {
|
||||
case .translateTo:
|
||||
var actions: [UIAction] = []
|
||||
for lang in IntelligentsEphemeralActionController.EphemeralAction.Language.allCases {
|
||||
actions.append(.init(title: lang.rawValue) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
let controller = IntelligentsEphemeralActionController(
|
||||
action: .translate(to: lang)
|
||||
)
|
||||
controller.workspaceID = workspaceID ?? ""
|
||||
controller.documentID = documentID ?? ""
|
||||
controller.documentContent = documentContent ?? ""
|
||||
controller.configure(previewImage: view.capturedImage ?? .init())
|
||||
presentIntoCurrentContext(withTargetController: controller)
|
||||
})
|
||||
}
|
||||
view.present(menu: .init(children: actions)) { controller in
|
||||
controller.overrideUserInterfaceStyle = .dark
|
||||
} controllerDidPresent: { _ in }
|
||||
case .summary:
|
||||
let controller = IntelligentsEphemeralActionController(
|
||||
action: .summarize
|
||||
)
|
||||
controller.configure(previewImage: view.capturedImage ?? .init())
|
||||
controller.workspaceID = workspaceID ?? ""
|
||||
controller.documentID = documentID ?? ""
|
||||
controller.documentContent = documentContent ?? ""
|
||||
presentIntoCurrentContext(withTargetController: controller)
|
||||
case .chatWithAI:
|
||||
let controller = IntelligentsChatController()
|
||||
controller.metadata[.documentID] = documentID
|
||||
controller.metadata[.workspaceID] = workspaceID
|
||||
controller.metadata[.content] = documentContent
|
||||
presentIntoCurrentContext(withTargetController: controller)
|
||||
case .dismiss:
|
||||
presentIntelligentsButton()
|
||||
|
||||
IntelligentContext.shared.preparePresent() {
|
||||
button.stopProgress()
|
||||
let controller = IntelligentsController()
|
||||
self.present(controller, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,6 @@ import Intelligents
|
||||
import UIKit
|
||||
|
||||
class AFFiNEViewController: CAPBridgeViewController {
|
||||
var baseUrl: String? {
|
||||
didSet { Intelligents.setUpstreamEndpoint(baseUrl ?? "") }
|
||||
}
|
||||
var documentID: String?
|
||||
var workspaceID: String?
|
||||
var documentContent: String?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
webView?.allowsBackForwardNavigationGestures = true
|
||||
@@ -36,7 +29,7 @@ class AFFiNEViewController: CAPBridgeViewController {
|
||||
CookiePlugin(),
|
||||
HashcashPlugin(),
|
||||
NavigationGesturePlugin(),
|
||||
IntelligentsPlugin(representController: self),
|
||||
// IntelligentsPlugin(representController: self), // no longer put in use
|
||||
NbStorePlugin(),
|
||||
]
|
||||
plugins.forEach { bridge?.registerPluginInstance($0) }
|
||||
@@ -45,16 +38,11 @@ class AFFiNEViewController: CAPBridgeViewController {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
navigationController?.setNavigationBarHidden(false, animated: animated)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
||||
super.motionEnded(motion, with: event)
|
||||
if motion == .motionShake {
|
||||
presentIntelligentsButton()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
self.presentIntelligentsButton()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>AFFiNE requires access to select photos from your photo library and insert them into your documents</string>
|
||||
<key>NSUserTrackingUsageDescription</key>
|
||||
<string>Rest assured, enabling this permission won't access your private info on other sites. It's only used to identify your device and improve security and product experience.</string>
|
||||
<string>Rest assured, enabling this permission won't access your private info on other sites. It's only used to identify your device and improve security and product experience.</string>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict>
|
||||
<key>UIImageName</key>
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import Capacitor
|
||||
import Foundation
|
||||
|
||||
@objc(IntelligentsPlugin)
|
||||
public class IntelligentsPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
public let identifier = "IntelligentsPlugin"
|
||||
public let jsName = "Intelligents"
|
||||
public let pluginMethods: [CAPPluginMethod] = [
|
||||
CAPPluginMethod(name: "presentIntelligentsButton", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "dismissIntelligentsButton", returnType: CAPPluginReturnPromise),
|
||||
]
|
||||
public private(set) weak var representController: UIViewController?
|
||||
|
||||
init(representController: UIViewController) {
|
||||
self.representController = representController
|
||||
super.init()
|
||||
}
|
||||
|
||||
deinit {
|
||||
representController = nil
|
||||
}
|
||||
|
||||
@objc public func presentIntelligentsButton(_ call: CAPPluginCall) {
|
||||
DispatchQueue.main.async {
|
||||
self.representController?.presentIntelligentsButton()
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func dismissIntelligentsButton(_ call: CAPPluginCall) {
|
||||
DispatchQueue.main.async {
|
||||
self.representController?.dismissIntelligentsButton()
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
//@objc(IntelligentsPlugin)
|
||||
//public class IntelligentsPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
// public let identifier = "IntelligentsPlugin"
|
||||
// public let jsName = "Intelligents"
|
||||
// public let pluginMethods: [CAPPluginMethod] = [
|
||||
// CAPPluginMethod(name: "presentIntelligentsButton", returnType: CAPPluginReturnPromise),
|
||||
// CAPPluginMethod(name: "dismissIntelligentsButton", returnType: CAPPluginReturnPromise),
|
||||
// ]
|
||||
// public private(set) weak var representController: UIViewController?
|
||||
//
|
||||
// init(representController: UIViewController) {
|
||||
// self.representController = representController
|
||||
// super.init()
|
||||
// }
|
||||
//
|
||||
// deinit {
|
||||
// representController = nil
|
||||
// }
|
||||
//
|
||||
// @objc public func presentIntelligentsButton(_ call: CAPPluginCall) {
|
||||
// DispatchQueue.main.async {
|
||||
// self.representController?.presentIntelligentsButton()
|
||||
// call.resolve()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @objc public func dismissIntelligentsButton(_ call: CAPPluginCall) {
|
||||
// DispatchQueue.main.async {
|
||||
// self.representController?.dismissIntelligentsButton()
|
||||
// call.resolve()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -1711,6 +1711,7 @@ extension UniffiError: Equatable, Hashable {}
|
||||
|
||||
|
||||
|
||||
|
||||
extension UniffiError: Foundation.LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
String(reflecting: self)
|
||||
@@ -1718,6 +1719,8 @@ extension UniffiError: Foundation.LocalizedError {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
module affine_mobile_nativeFFI {
|
||||
header "affine_mobile_nativeFFI.h"
|
||||
export *
|
||||
use "Darwin"
|
||||
use "_Builtin_stdbool"
|
||||
use "_Builtin_stdint"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "apollo-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apollographql/apollo-ios.git",
|
||||
"state" : {
|
||||
"revision" : "6ecc75281dab2fa231cb0d5fed3a3713826fecae",
|
||||
"version" : "1.21.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
+2
-2
@@ -3,9 +3,9 @@
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public struct License: AffineGraphQL.SelectionSet, Fragment {
|
||||
public struct LicenseBody: AffineGraphQL.SelectionSet, Fragment {
|
||||
public static var fragmentDefinition: StaticString {
|
||||
#"fragment license on License { __typename expiredAt installedAt quantity recurring validatedAt variant }"#
|
||||
#"fragment licenseBody on License { __typename expiredAt installedAt quantity recurring validatedAt variant }"#
|
||||
}
|
||||
|
||||
public let __data: DataDict
|
||||
+4
-4
@@ -7,8 +7,8 @@ public class ActivateLicenseMutation: GraphQLMutation {
|
||||
public static let operationName: String = "activateLicense"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"mutation activateLicense($workspaceId: String!, $license: String!) { activateLicense(workspaceId: $workspaceId, license: $license) { __typename ...license } }"#,
|
||||
fragments: [License.self]
|
||||
#"mutation activateLicense($workspaceId: String!, $license: String!) { activateLicense(workspaceId: $workspaceId, license: $license) { __typename ...licenseBody } }"#,
|
||||
fragments: [LicenseBody.self]
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
@@ -51,7 +51,7 @@ public class ActivateLicenseMutation: GraphQLMutation {
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.License }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.fragment(License.self),
|
||||
.fragment(LicenseBody.self),
|
||||
] }
|
||||
|
||||
public var expiredAt: AffineGraphQL.DateTime? { __data["expiredAt"] }
|
||||
@@ -65,7 +65,7 @@ public class ActivateLicenseMutation: GraphQLMutation {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public var license: License { _toFragment() }
|
||||
public var licenseBody: LicenseBody { _toFragment() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -7,8 +7,8 @@ public class InstallLicenseMutation: GraphQLMutation {
|
||||
public static let operationName: String = "installLicense"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"mutation installLicense($workspaceId: String!, $license: Upload!) { installLicense(workspaceId: $workspaceId, license: $license) { __typename ...license } }"#,
|
||||
fragments: [License.self]
|
||||
#"mutation installLicense($workspaceId: String!, $license: Upload!) { installLicense(workspaceId: $workspaceId, license: $license) { __typename ...licenseBody } }"#,
|
||||
fragments: [LicenseBody.self]
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
@@ -51,7 +51,7 @@ public class InstallLicenseMutation: GraphQLMutation {
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.License }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.fragment(License.self),
|
||||
.fragment(LicenseBody.self),
|
||||
] }
|
||||
|
||||
public var expiredAt: AffineGraphQL.DateTime? { __data["expiredAt"] }
|
||||
@@ -65,7 +65,7 @@ public class InstallLicenseMutation: GraphQLMutation {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public var license: License { _toFragment() }
|
||||
public var licenseBody: LicenseBody { _toFragment() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class InviteBatchMutation: GraphQLMutation {
|
||||
public static let operationName: String = "inviteBatch"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"mutation inviteBatch($workspaceId: String!, $emails: [String!]!, $sendInviteMail: Boolean) { inviteBatch( workspaceId: $workspaceId emails: $emails sendInviteMail: $sendInviteMail ) { __typename email inviteId sentSuccess } }"#
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
public var emails: [String]
|
||||
public var sendInviteMail: GraphQLNullable<Bool>
|
||||
|
||||
public init(
|
||||
workspaceId: String,
|
||||
emails: [String],
|
||||
sendInviteMail: GraphQLNullable<Bool>
|
||||
) {
|
||||
self.workspaceId = workspaceId
|
||||
self.emails = emails
|
||||
self.sendInviteMail = sendInviteMail
|
||||
}
|
||||
|
||||
public var __variables: Variables? { [
|
||||
"workspaceId": workspaceId,
|
||||
"emails": emails,
|
||||
"sendInviteMail": sendInviteMail
|
||||
] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||
#warning("Argument 'sendInviteMail' of field 'inviteBatch' is deprecated. Reason: 'never used'")
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("inviteBatch", [InviteBatch].self, arguments: [
|
||||
"workspaceId": .variable("workspaceId"),
|
||||
"emails": .variable("emails"),
|
||||
"sendInviteMail": .variable("sendInviteMail")
|
||||
]),
|
||||
] }
|
||||
|
||||
public var inviteBatch: [InviteBatch] { __data["inviteBatch"] }
|
||||
|
||||
/// InviteBatch
|
||||
///
|
||||
/// Parent Type: `InviteResult`
|
||||
public struct InviteBatch: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.InviteResult }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("email", String.self),
|
||||
.field("inviteId", String?.self),
|
||||
.field("sentSuccess", Bool.self),
|
||||
] }
|
||||
|
||||
public var email: String { __data["email"] }
|
||||
/// Invite id, null if invite record create failed
|
||||
public var inviteId: String? { __data["inviteId"] }
|
||||
/// Invite email sent success
|
||||
public var sentSuccess: Bool { __data["sentSuccess"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class InviteByEmailMutation: GraphQLMutation {
|
||||
public static let operationName: String = "inviteByEmail"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"mutation inviteByEmail($workspaceId: String!, $email: String!, $sendInviteMail: Boolean) { invite( workspaceId: $workspaceId email: $email sendInviteMail: $sendInviteMail ) }"#
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
public var email: String
|
||||
public var sendInviteMail: GraphQLNullable<Bool>
|
||||
|
||||
public init(
|
||||
workspaceId: String,
|
||||
email: String,
|
||||
sendInviteMail: GraphQLNullable<Bool>
|
||||
) {
|
||||
self.workspaceId = workspaceId
|
||||
self.email = email
|
||||
self.sendInviteMail = sendInviteMail
|
||||
}
|
||||
|
||||
public var __variables: Variables? { [
|
||||
"workspaceId": workspaceId,
|
||||
"email": email,
|
||||
"sendInviteMail": sendInviteMail
|
||||
] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Mutation }
|
||||
#warning("Argument 'sendInviteMail' of field 'invite' is deprecated. Reason: 'never used'")
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("invite", String.self, arguments: [
|
||||
"workspaceId": .variable("workspaceId"),
|
||||
"email": .variable("email"),
|
||||
"sendInviteMail": .variable("sendInviteMail")
|
||||
]),
|
||||
] }
|
||||
|
||||
public var invite: String { __data["invite"] }
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class GetIsAdminQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getIsAdmin"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getIsAdmin($workspaceId: String!) { isAdmin(workspaceId: $workspaceId) }"#
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
|
||||
public init(workspaceId: String) {
|
||||
self.workspaceId = workspaceId
|
||||
}
|
||||
|
||||
public var __variables: Variables? { ["workspaceId": workspaceId] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("isAdmin", Bool.self, arguments: ["workspaceId": .variable("workspaceId")]),
|
||||
] }
|
||||
|
||||
/// Get is admin of workspace
|
||||
@available(*, deprecated, message: "use WorkspaceType[role] instead")
|
||||
public var isAdmin: Bool { __data["isAdmin"] }
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class GetIsOwnerQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getIsOwner"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getIsOwner($workspaceId: String!) { isOwner(workspaceId: $workspaceId) }"#
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
|
||||
public init(workspaceId: String) {
|
||||
self.workspaceId = workspaceId
|
||||
}
|
||||
|
||||
public var __variables: Variables? { ["workspaceId": workspaceId] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("isOwner", Bool.self, arguments: ["workspaceId": .variable("workspaceId")]),
|
||||
] }
|
||||
|
||||
/// Get is owner of workspace
|
||||
@available(*, deprecated, message: "use WorkspaceType[role] instead")
|
||||
public var isOwner: Bool { __data["isOwner"] }
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -7,8 +7,8 @@ public class GetLicenseQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getLicense"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getLicense($workspaceId: String!) { workspace(id: $workspaceId) { __typename license { __typename ...license } } }"#,
|
||||
fragments: [License.self]
|
||||
#"query getLicense($workspaceId: String!) { workspace(id: $workspaceId) { __typename license { __typename ...licenseBody } } }"#,
|
||||
fragments: [LicenseBody.self]
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
@@ -57,7 +57,7 @@ public class GetLicenseQuery: GraphQLQuery {
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.License }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.fragment(License.self),
|
||||
.fragment(LicenseBody.self),
|
||||
] }
|
||||
|
||||
public var expiredAt: AffineGraphQL.DateTime? { __data["expiredAt"] }
|
||||
@@ -71,7 +71,7 @@ public class GetLicenseQuery: GraphQLQuery {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public var license: License { _toFragment() }
|
||||
public var licenseBody: LicenseBody { _toFragment() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class GetUsersCountQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getUsersCount"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getUsersCount { usersCount }"#
|
||||
))
|
||||
|
||||
public init() {}
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("usersCount", Int.self),
|
||||
] }
|
||||
|
||||
/// Get users count
|
||||
public var usersCount: Int { __data["usersCount"] }
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import ApolloAPI
|
||||
|
||||
public extension Objects {
|
||||
static let WorkspacePageMeta = ApolloAPI.Object(
|
||||
typename: "WorkspacePageMeta",
|
||||
implementedInterfaces: [],
|
||||
keyFields: nil
|
||||
)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "apollo-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apollographql/apollo-ios.git",
|
||||
"state" : {
|
||||
"revision" : "c3f48d45ec1300bc95243bf19f67284f9dc0d14a",
|
||||
"version" : "1.15.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "msdisplaylink",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Lakr233/MSDisplayLink",
|
||||
"state" : {
|
||||
"revision" : "6e92b5513e3473e064685e64056c4ac46470e7b0",
|
||||
"version" : "2.0.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "networkimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/gonzalezreal/NetworkImage",
|
||||
"state" : {
|
||||
"revision" : "2849f5323265386e200484b0d0f896e73c3411b9",
|
||||
"version" : "6.0.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "springinterpolation",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Lakr233/SpringInterpolation",
|
||||
"state" : {
|
||||
"revision" : "f9d1ee3d2466bdb00fd0ade7f256ed20229c8413",
|
||||
"version" : "1.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sqlite.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/stephencelis/SQLite.swift.git",
|
||||
"state" : {
|
||||
"revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8",
|
||||
"version" : "0.15.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-cmark",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/swiftlang/swift-cmark",
|
||||
"state" : {
|
||||
"revision" : "3ccff77b2dc5b96b77db3da0d68d28068593fa53",
|
||||
"version" : "0.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-eventsource",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/LaunchDarkly/swift-eventsource.git",
|
||||
"state" : {
|
||||
"revision" : "57051701c58a93603ffa2051f8e9cf0c8cff7814",
|
||||
"version" : "3.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-markdown-ui",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/gonzalezreal/swift-markdown-ui",
|
||||
"state" : {
|
||||
"revision" : "5f613358148239d0292c0cef674a3c2314737f9e",
|
||||
"version" : "2.4.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -7,7 +7,7 @@ let package = Package(
|
||||
name: "Intelligents",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v17),
|
||||
.iOS(.v16),
|
||||
],
|
||||
products: [
|
||||
.library(name: "Intelligents", targets: ["Intelligents"]),
|
||||
@@ -15,15 +15,22 @@ let package = Package(
|
||||
dependencies: [
|
||||
.package(path: "../AffineGraphQL"),
|
||||
.package(url: "https://github.com/apollographql/apollo-ios.git", from: "1.18.0"),
|
||||
.package(url: "https://github.com/LaunchDarkly/swift-eventsource.git", from: "3.3.0"),
|
||||
.package(url: "https://github.com/apple/swift-collections", from: "1.2.0"),
|
||||
.package(url: "https://github.com/devxoul/Then", from: "3.0.0"),
|
||||
.package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.0.1"),
|
||||
.package(url: "https://github.com/SwifterSwift/SwifterSwift.git", from: "6.0.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "Intelligents", dependencies: [
|
||||
"AffineGraphQL",
|
||||
"SnapKit",
|
||||
"Then",
|
||||
"SwifterSwift",
|
||||
.product(name: "Apollo", package: "apollo-ios"),
|
||||
.product(name: "LDSwiftEventSource", package: "swift-eventsource"),
|
||||
.product(name: "OrderedCollections", package: "swift-collections"),
|
||||
], resources: [
|
||||
.process("Resources/main.metal"),
|
||||
.process("Interface/View/InputBox/InputBox.xcassets"),
|
||||
]),
|
||||
]
|
||||
)
|
||||
|
||||
+1
-27
@@ -5,19 +5,7 @@ import AffineGraphQL
|
||||
import Apollo
|
||||
import Foundation
|
||||
|
||||
public enum Intelligents {
|
||||
public private(set) static var qlClient: ApolloClient = createQlClient()
|
||||
|
||||
public static func setUpstreamEndpoint(_ upstream: String) {
|
||||
guard let url = URL(string: upstream) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
print("[*] setting up upstream endpoint to \(url.absoluteString)")
|
||||
Constant.affineUpstreamURL = url
|
||||
qlClient = createQlClient()
|
||||
}
|
||||
}
|
||||
public enum Intelligents {}
|
||||
|
||||
private extension Intelligents {
|
||||
private final class URLSessionCookieClient: URLSessionClient {
|
||||
@@ -29,18 +17,4 @@ private extension Intelligents {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func createQlClient() -> ApolloClient {
|
||||
let store = ApolloStore(cache: InMemoryNormalizedCache())
|
||||
let provider = DefaultInterceptorProvider(
|
||||
client: URLSessionCookieClient(),
|
||||
shouldInvalidateClientOnDeinit: true,
|
||||
store: store
|
||||
)
|
||||
let transport = RequestChainNetworkTransport(
|
||||
interceptorProvider: provider,
|
||||
endpointURL: Constant.affineUpstreamURL.appendingPathComponent("graphql")
|
||||
)
|
||||
return .init(networkTransport: transport, store: store)
|
||||
}
|
||||
}
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// BlurTransition.swift
|
||||
// BlurTransition
|
||||
//
|
||||
// Created by 秋星桥 on 6/16/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIViewController {
|
||||
func presentWithFullScreenBlurTransition(_ viewController: UIViewController) {
|
||||
viewController.modalPresentationStyle = .custom
|
||||
viewController.transitioningDelegate = BlurTransitioningDelegate.shared
|
||||
present(viewController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
class BlurTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
|
||||
static let shared = BlurTransitioningDelegate()
|
||||
|
||||
func animationController(
|
||||
forPresented _: UIViewController,
|
||||
presenting _: UIViewController,
|
||||
source _: UIViewController
|
||||
) -> UIViewControllerAnimatedTransitioning? {
|
||||
BlurTransitionAnimator(presenting: true)
|
||||
}
|
||||
|
||||
func animationController(
|
||||
forDismissed _: UIViewController
|
||||
) -> UIViewControllerAnimatedTransitioning? {
|
||||
BlurTransitionAnimator(presenting: false)
|
||||
}
|
||||
}
|
||||
|
||||
class BlurTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
private let presenting: Bool
|
||||
|
||||
private let snapshotViewTag = "snapshotView".hashValue
|
||||
private let blurViewTag = "blurView".hashValue
|
||||
|
||||
init(presenting: Bool) {
|
||||
self.presenting = presenting
|
||||
super.init()
|
||||
}
|
||||
|
||||
func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
0.5
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
if presenting {
|
||||
animatePresentation(using: transitionContext)
|
||||
} else {
|
||||
animateDismissal(using: transitionContext)
|
||||
}
|
||||
}
|
||||
|
||||
private func animatePresentation(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let toViewController = transitionContext.viewController(forKey: .to),
|
||||
let fromViewController = transitionContext.viewController(forKey: .from)
|
||||
else {
|
||||
transitionContext.completeTransition(false)
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let toView = toViewController.view!
|
||||
let fromView = fromViewController.view!
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
|
||||
guard let fromViewSnapshot = fromView.snapshotView(afterScreenUpdates: false) else {
|
||||
transitionContext.completeTransition(false)
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
fromViewSnapshot.frame = fromView.frame
|
||||
fromViewSnapshot.tag = snapshotViewTag
|
||||
containerView.addSubview(fromViewSnapshot)
|
||||
fromView.isHidden = true
|
||||
|
||||
let blurEffectView = UIVisualEffectView()
|
||||
blurEffectView.frame = containerView.bounds
|
||||
blurEffectView.tag = blurViewTag
|
||||
containerView.addSubview(blurEffectView)
|
||||
|
||||
toView.frame = containerView.bounds
|
||||
toView.alpha = 0
|
||||
toView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
|
||||
containerView.addSubview(toView)
|
||||
|
||||
toView.layoutIfNeeded()
|
||||
|
||||
performWithAnimation(animations: {
|
||||
blurEffectView.effect = UIBlurEffect(style: .systemMaterial)
|
||||
fromViewSnapshot.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
||||
toView.alpha = 1
|
||||
toView.transform = .identity
|
||||
fromView.layoutIfNeeded()
|
||||
toView.layoutIfNeeded()
|
||||
}) { _ in
|
||||
let success = !transitionContext.transitionWasCancelled
|
||||
if !success {
|
||||
assertionFailure()
|
||||
fromView.isHidden = false
|
||||
fromViewSnapshot.removeFromSuperview()
|
||||
blurEffectView.removeFromSuperview()
|
||||
toView.removeFromSuperview()
|
||||
}
|
||||
transitionContext.completeTransition(success)
|
||||
}
|
||||
}
|
||||
|
||||
private func animateDismissal(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard let fromViewController = transitionContext.viewController(forKey: .from),
|
||||
let toViewController = transitionContext.viewController(forKey: .to)
|
||||
else {
|
||||
transitionContext.completeTransition(false)
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let fromView = fromViewController.view!
|
||||
let toView = toViewController.view!
|
||||
let containerView = transitionContext.containerView
|
||||
|
||||
guard let fromViewSnapshot = containerView.viewWithTag(snapshotViewTag),
|
||||
let blurEffectView = containerView.viewWithTag(blurViewTag) as? UIVisualEffectView
|
||||
else {
|
||||
toView.isHidden = false
|
||||
assertionFailure()
|
||||
transitionContext.completeTransition(true)
|
||||
return
|
||||
}
|
||||
|
||||
performWithAnimation(animations: {
|
||||
fromViewSnapshot.transform = .identity
|
||||
blurEffectView.effect = nil
|
||||
fromView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
|
||||
fromView.alpha = 0
|
||||
}) { _ in
|
||||
let success = !transitionContext.transitionWasCancelled
|
||||
if success {
|
||||
toView.isHidden = false
|
||||
fromViewSnapshot.removeFromSuperview()
|
||||
blurEffectView.removeFromSuperview()
|
||||
fromView.layoutIfNeeded()
|
||||
toView.layoutIfNeeded()
|
||||
} else {
|
||||
assertionFailure()
|
||||
fromView.transform = .identity
|
||||
fromView.alpha = 1
|
||||
}
|
||||
transitionContext.completeTransition(success)
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// IntelligentsController.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/17/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public class IntelligentsController: UINavigationController {
|
||||
public init() {
|
||||
super.init(rootViewController: MainViewController())
|
||||
modalPresentationStyle = .custom
|
||||
transitioningDelegate = BlurTransitioningDelegate.shared
|
||||
setNavigationBarHidden(true, animated: false)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .systemBackground
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// MainViewController+Header.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/19/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension MainViewController: MainHeaderViewDelegate {
|
||||
func mainHeaderViewDidTapClose() {
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
func mainHeaderViewDidTapDropdown() {
|
||||
print(#function)
|
||||
}
|
||||
|
||||
func mainHeaderViewDidTapMenu() {
|
||||
print(#function)
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// MainViewController+Input.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/19/25.
|
||||
//
|
||||
|
||||
import PhotosUI
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension MainViewController: InputBoxDelegate {
|
||||
func inputBoxDidSelectTakePhoto(_: InputBox) {
|
||||
let imagePickerController = UIImagePickerController()
|
||||
imagePickerController.delegate = self
|
||||
imagePickerController.sourceType = .camera
|
||||
imagePickerController.allowsEditing = false
|
||||
present(imagePickerController, animated: true)
|
||||
}
|
||||
|
||||
func inputBoxDidSelectPhotoLibrary(_: InputBox) {
|
||||
var configuration = PHPickerConfiguration()
|
||||
configuration.filter = .images
|
||||
configuration.selectionLimit = 0 // 0 means no limit
|
||||
|
||||
let picker = PHPickerViewController(configuration: configuration)
|
||||
picker.delegate = self
|
||||
present(picker, animated: true)
|
||||
}
|
||||
|
||||
func inputBoxDidSelectAttachFiles(_: InputBox) {
|
||||
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [
|
||||
.pdf, .plainText, .commaSeparatedText, .data,
|
||||
])
|
||||
documentPicker.delegate = self
|
||||
documentPicker.allowsMultipleSelection = false
|
||||
present(documentPicker, animated: true)
|
||||
}
|
||||
|
||||
func inputBoxDidSelectEmbedDocs(_ inputBox: InputBox) {
|
||||
print(#function, inputBox)
|
||||
}
|
||||
|
||||
func inputBoxDidSelectAttachment(_ inputBox: InputBox) {
|
||||
print(#function, inputBox)
|
||||
}
|
||||
|
||||
func inputBoxDidSend(_ inputBox: InputBox) {
|
||||
print(#function, inputBox, inputBox.viewModel)
|
||||
}
|
||||
|
||||
func inputBoxTextDidChange(_ text: String) {
|
||||
print(#function, text)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIImagePickerControllerDelegate
|
||||
|
||||
extension MainViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
|
||||
defer { picker.dismiss(animated: true) }
|
||||
|
||||
guard let image = info[.originalImage] as? UIImage else { return }
|
||||
inputBox.addImageAttachment(image)
|
||||
}
|
||||
|
||||
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
||||
picker.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PHPickerViewControllerDelegate
|
||||
|
||||
extension MainViewController: PHPickerViewControllerDelegate {
|
||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||
defer { picker.dismiss(animated: true) }
|
||||
|
||||
for result in results {
|
||||
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
|
||||
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] object, error in
|
||||
guard let image = object as? UIImage, error == nil else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self?.inputBox.addImageAttachment(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIDocumentPickerDelegate
|
||||
|
||||
extension MainViewController: UIDocumentPickerDelegate {
|
||||
func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
for url in urls {
|
||||
// Start accessing security-scoped resource
|
||||
guard url.startAccessingSecurityScopedResource() else { continue }
|
||||
defer { url.stopAccessingSecurityScopedResource() }
|
||||
|
||||
// Copy file to temporary directory
|
||||
let context = IntelligentContext.shared
|
||||
context.prepareTemporaryDirectory()
|
||||
|
||||
let tempURL = context.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
|
||||
|
||||
do {
|
||||
// Remove existing file if it exists
|
||||
if FileManager.default.fileExists(atPath: tempURL.path) {
|
||||
try FileManager.default.removeItem(at: tempURL)
|
||||
}
|
||||
|
||||
// Copy file to temporary directory
|
||||
try FileManager.default.copyItem(at: url, to: tempURL)
|
||||
|
||||
// Add file attachment using the temporary URL
|
||||
inputBox.addFileAttachment(tempURL)
|
||||
} catch {
|
||||
print("Failed to copy file: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import Combine
|
||||
import SnapKit
|
||||
import Then
|
||||
import UIKit
|
||||
|
||||
class MainViewController: UIViewController {
|
||||
// MARK: - UI Components
|
||||
|
||||
lazy var headerView = MainHeaderView().then {
|
||||
$0.delegate = self
|
||||
}
|
||||
|
||||
lazy var inputBox = InputBox().then {
|
||||
$0.delegate = self
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private let intelligentContext = IntelligentContext.shared
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .systemBackground
|
||||
let inputBox = InputBox().then {
|
||||
$0.delegate = self
|
||||
}
|
||||
self.inputBox = inputBox
|
||||
|
||||
view.addSubview(headerView)
|
||||
view.addSubview(inputBox)
|
||||
|
||||
headerView.snp.makeConstraints { make in
|
||||
make.top.equalTo(view.safeAreaLayoutGuide)
|
||||
make.leading.trailing.equalToSuperview()
|
||||
}
|
||||
|
||||
inputBox.snp.makeConstraints { make in
|
||||
make.leading.trailing.equalToSuperview()
|
||||
make.bottom.equalTo(view.keyboardLayoutGuide.snp.top)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
navigationController!.setNavigationBarHidden(true, animated: animated)
|
||||
DispatchQueue.main.async {
|
||||
self.inputBox.textView.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
navigationController!.setNavigationBarHidden(false, animated: animated)
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// ParticleView+Removal.swift
|
||||
// UIEffectKit
|
||||
//
|
||||
// Created by 秋星桥 on 6/13/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIView {
|
||||
func removeFromSuperviewWithExplodeEffect() {
|
||||
guard let superview else { return }
|
||||
guard let window else {
|
||||
removeFromSuperview()
|
||||
return
|
||||
}
|
||||
guard MTLCreateSystemDefaultDevice() != nil else {
|
||||
removeFromSuperview()
|
||||
return
|
||||
}
|
||||
|
||||
let image = createViewSnapshot()
|
||||
guard let cgImage = image.cgImage else {
|
||||
removeFromSuperview()
|
||||
return
|
||||
}
|
||||
|
||||
let frameInWindow = superview.convert(frame, to: window)
|
||||
let particleView = ParticleView(frame: frameInWindow)
|
||||
|
||||
window.addSubview(particleView)
|
||||
particleView.layer.zPosition = 1000
|
||||
particleView.frame = frameInWindow
|
||||
particleView.setNeedsLayout()
|
||||
particleView.layoutIfNeeded()
|
||||
|
||||
particleView.beginWith(cgImage, targetFrame: frameInWindow, onComplete: {
|
||||
particleView.removeFromSuperview()
|
||||
}, onFirstFrameRendered: { [weak self] in
|
||||
DispatchQueue.main.async {
|
||||
self?.removeFromSuperview()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
//
|
||||
// ParticleView+Renderer.swift
|
||||
// UIEffectKit
|
||||
//
|
||||
// Created by 秋星桥 on 6/13/25.
|
||||
//
|
||||
|
||||
import MetalKit
|
||||
|
||||
extension ParticleView {
|
||||
class Renderer: NSObject, MTKViewDelegate {
|
||||
private struct Particle {
|
||||
var position: simd_float2
|
||||
var velocity: simd_float2
|
||||
var life: simd_float1
|
||||
var duration: simd_float1
|
||||
}
|
||||
|
||||
private struct Vertex {
|
||||
var position: simd_float4
|
||||
var uv: simd_float2
|
||||
var opacity: simd_float1
|
||||
}
|
||||
|
||||
private var isPrepared = false
|
||||
private var renderPipeline: MTLRenderPipelineState!
|
||||
private var computePipeline: MTLComputePipelineState!
|
||||
private var vertexBuffer: MTLBuffer!
|
||||
private var particleBuffer: MTLBuffer!
|
||||
private var particleCount: Int = 0
|
||||
private var texture: MTLTexture!
|
||||
private var targetFrameSize: simd_float2 = .zero
|
||||
private var stepSize: Float = 0
|
||||
private var commandQueue: MTLCommandQueue!
|
||||
private var maxLife: Float = 0
|
||||
private var onComplete: (() -> Void)?
|
||||
private var onFirstFrameRendered: (() -> Void)?
|
||||
private var hasRenderedFirstFrame = false
|
||||
private var device: MTLDevice!
|
||||
|
||||
func prepareResources(
|
||||
with device: MTLDevice,
|
||||
image: CGImage,
|
||||
targetFrame: CGRect,
|
||||
onComplete: @escaping () -> Void,
|
||||
onFirstFrameRendered: @escaping () -> Void
|
||||
) {
|
||||
guard !isPrepared else { return }
|
||||
|
||||
self.device = device
|
||||
self.onComplete = onComplete
|
||||
self.onFirstFrameRendered = onFirstFrameRendered
|
||||
let integralTargetFrame = targetFrame.integral
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
||||
guard let self else { return }
|
||||
|
||||
setupPipelineStates(with: device)
|
||||
setupVertexBuffer(with: device)
|
||||
setupParticleSystem(targetFrame: integralTargetFrame, device: device)
|
||||
setupTexture(from: image, device: device)
|
||||
finalizeSetup(targetFrame: integralTargetFrame, device: device)
|
||||
|
||||
DispatchQueue.main.async { self.isPrepared = true }
|
||||
}
|
||||
}
|
||||
|
||||
private func setupPipelineStates(with device: MTLDevice) {
|
||||
let library = try! device.makeDefaultLibrary(bundle: .module)
|
||||
|
||||
let particleVertexFunction = library.makeFunction(name: "PTS_ParticleVertex")!
|
||||
let particleFragmentFunction = library.makeFunction(name: "PTS_ParticleFragment")!
|
||||
let updateParticlesFunction = library.makeFunction(name: "PTS_UpdateParticles")!
|
||||
|
||||
let renderPipelineDescriptor = createRenderPipelineDescriptor(
|
||||
vertexFunction: particleVertexFunction,
|
||||
fragmentFunction: particleFragmentFunction
|
||||
)
|
||||
|
||||
do {
|
||||
renderPipeline = try device.makeRenderPipelineState(descriptor: renderPipelineDescriptor)
|
||||
computePipeline = try device.makeComputePipelineState(function: updateParticlesFunction)
|
||||
} catch {
|
||||
fatalError("failed to create pipeline states: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func createRenderPipelineDescriptor(
|
||||
vertexFunction: MTLFunction,
|
||||
fragmentFunction: MTLFunction
|
||||
) -> MTLRenderPipelineDescriptor {
|
||||
let descriptor = MTLRenderPipelineDescriptor()
|
||||
descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
|
||||
descriptor.colorAttachments[0].isBlendingEnabled = true
|
||||
descriptor.colorAttachments[0].rgbBlendOperation = .add
|
||||
descriptor.colorAttachments[0].alphaBlendOperation = .add
|
||||
descriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
|
||||
descriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha
|
||||
descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
|
||||
descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha
|
||||
descriptor.vertexFunction = vertexFunction
|
||||
descriptor.fragmentFunction = fragmentFunction
|
||||
return descriptor
|
||||
}
|
||||
|
||||
func mtkView(_: MTKView, drawableSizeWillChange _: CGSize) {
|
||||
// No-op since view is not subject to resize
|
||||
}
|
||||
|
||||
func draw(in view: MTKView) {
|
||||
guard isPrepared else { return }
|
||||
|
||||
updateParticles()
|
||||
|
||||
if checkAllParticlesDead() {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onComplete?()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
renderParticles(in: view)
|
||||
|
||||
if !hasRenderedFirstFrame {
|
||||
hasRenderedFirstFrame = true
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onFirstFrameRendered?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateParticles() {
|
||||
let maxThreadsPerThreadgroup = computePipeline.maxTotalThreadsPerThreadgroup
|
||||
let threadgroupSize = min(maxThreadsPerThreadgroup, 2048)
|
||||
let threadgroupCount = (particleCount + threadgroupSize - 1) / threadgroupSize
|
||||
|
||||
let computeCommandBuffer = commandQueue.makeCommandBuffer()!
|
||||
|
||||
let computeCommandEncoder = computeCommandBuffer.makeComputeCommandEncoder()!
|
||||
computeCommandEncoder.setComputePipelineState(computePipeline)
|
||||
computeCommandEncoder.setBuffer(particleBuffer, offset: 0, index: 0)
|
||||
computeCommandEncoder.dispatchThreadgroups(
|
||||
.init(width: threadgroupCount, height: 1, depth: 1),
|
||||
threadsPerThreadgroup: .init(width: threadgroupSize, height: 1, depth: 1)
|
||||
)
|
||||
computeCommandEncoder.endEncoding()
|
||||
|
||||
computeCommandBuffer.commit()
|
||||
}
|
||||
|
||||
private func checkAllParticlesDead() -> Bool {
|
||||
let particleData = particleBuffer
|
||||
.contents()
|
||||
.bindMemory(to: Particle.self, capacity: particleCount)
|
||||
|
||||
for i in 0 ..< particleCount {
|
||||
if particleData[i].life >= 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func renderParticles(in view: MTKView) {
|
||||
let viewCGSize = view.bounds.size
|
||||
var viewSize = simd_float2(Float(viewCGSize.width), Float(viewCGSize.height))
|
||||
|
||||
let renderCommandBuffer = commandQueue.makeCommandBuffer()!
|
||||
|
||||
guard let renderPassDescriptor = view.currentRenderPassDescriptor else { return }
|
||||
renderPassDescriptor.colorAttachments[0].loadAction = .clear
|
||||
renderPassDescriptor.colorAttachments[0].clearColor = .init(red: 0, green: 0, blue: 0, alpha: 0)
|
||||
|
||||
let renderCommandEncoder = renderCommandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!
|
||||
renderCommandEncoder.setRenderPipelineState(renderPipeline)
|
||||
renderCommandEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
||||
|
||||
withUnsafeBytes(of: &viewSize) { pointer in
|
||||
renderCommandEncoder.setVertexBytes(
|
||||
pointer.baseAddress!,
|
||||
length: MemoryLayout<simd_float2>.size,
|
||||
index: 1
|
||||
)
|
||||
}
|
||||
renderCommandEncoder.setVertexBuffer(particleBuffer, offset: 0, index: 2)
|
||||
withUnsafeBytes(of: &targetFrameSize) { pointer in
|
||||
renderCommandEncoder.setVertexBytes(
|
||||
pointer.baseAddress!,
|
||||
length: MemoryLayout<simd_float2>.size,
|
||||
index: 3
|
||||
)
|
||||
}
|
||||
withUnsafeBytes(of: &stepSize) { pointer in
|
||||
renderCommandEncoder.setVertexBytes(
|
||||
pointer.baseAddress!,
|
||||
length: MemoryLayout<Float>.size,
|
||||
index: 4
|
||||
)
|
||||
}
|
||||
renderCommandEncoder.setFragmentTexture(texture, index: 0)
|
||||
|
||||
setupSampler(renderCommandEncoder: renderCommandEncoder)
|
||||
|
||||
renderCommandEncoder.drawPrimitives(
|
||||
type: .triangleStrip,
|
||||
vertexStart: 0,
|
||||
vertexCount: 4,
|
||||
instanceCount: particleCount
|
||||
)
|
||||
renderCommandEncoder.endEncoding()
|
||||
|
||||
renderCommandBuffer.present(view.currentDrawable!)
|
||||
renderCommandBuffer.commit()
|
||||
}
|
||||
|
||||
private func setupSampler(renderCommandEncoder: MTLRenderCommandEncoder) {
|
||||
let samplerDescriptor = MTLSamplerDescriptor()
|
||||
samplerDescriptor.minFilter = .linear
|
||||
samplerDescriptor.magFilter = .linear
|
||||
samplerDescriptor.mipFilter = .notMipmapped
|
||||
samplerDescriptor.sAddressMode = .clampToEdge
|
||||
samplerDescriptor.tAddressMode = .clampToEdge
|
||||
let samplerState = device.makeSamplerState(descriptor: samplerDescriptor)
|
||||
renderCommandEncoder.setFragmentSamplerState(samplerState, index: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ParticleView.Renderer {
|
||||
private func setupVertexBuffer(with device: MTLDevice) {
|
||||
let vertices: [Vertex] = [
|
||||
.init(position: .init(0, 0, 0, 1), uv: .init(0, 0), opacity: .zero),
|
||||
.init(position: .init(1, 0, 0, 1), uv: .init(1, 0), opacity: .zero),
|
||||
.init(position: .init(0, 1, 0, 1), uv: .init(0, 1), opacity: .zero),
|
||||
.init(position: .init(1, 1, 0, 1), uv: .init(1, 1), opacity: .zero),
|
||||
]
|
||||
let vertexBuffer = vertices.withUnsafeBytes { pointer in
|
||||
device.makeBuffer(
|
||||
bytes: pointer.baseAddress!,
|
||||
length: MemoryLayout<Vertex>.stride * vertices.count,
|
||||
options: .storageModeShared
|
||||
)
|
||||
}
|
||||
self.vertexBuffer = vertexBuffer!
|
||||
}
|
||||
|
||||
private func setupParticleSystem(targetFrame: CGRect, device: MTLDevice) {
|
||||
var particles = [Particle]()
|
||||
let targetFrameHeight = Float(targetFrame.height)
|
||||
let targetFrameWidth = Float(targetFrame.width)
|
||||
let particleStep = 1
|
||||
|
||||
let estimatedParticleCount = 1
|
||||
* Int(targetFrameWidth / Float(particleStep))
|
||||
* Int(targetFrameHeight / Float(particleStep))
|
||||
let pixelMultiplier = 1
|
||||
particles.reserveCapacity(estimatedParticleCount * pixelMultiplier)
|
||||
|
||||
for y in stride(from: 0, to: Int(targetFrameHeight), by: particleStep) {
|
||||
for x in stride(from: 0, to: Int(targetFrameWidth), by: particleStep) {
|
||||
let particle = createParticle(x: x, y: y, step: particleStep)
|
||||
for _ in 0 ..< pixelMultiplier {
|
||||
particles.append(particle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
particleCount = particles.count
|
||||
let particleBuffer = particles.withUnsafeBytes { pointer in
|
||||
device.makeBuffer(
|
||||
bytes: pointer.baseAddress!,
|
||||
length: MemoryLayout<Particle>.stride * particles.count,
|
||||
options: .storageModeShared
|
||||
)
|
||||
}
|
||||
self.particleBuffer = particleBuffer!
|
||||
stepSize = Float(particleStep)
|
||||
}
|
||||
|
||||
private func createParticle(x: Int, y: Int, step: Int) -> Particle {
|
||||
let particleDuration: Float = .random(in: 20 ... 60)
|
||||
let initialX = Float(x) + Float(step) / 2.0
|
||||
let initialY = Float(y) + Float(step) / 2.0
|
||||
return .init(
|
||||
position: .init(initialX, initialY),
|
||||
velocity: .init(
|
||||
cos(Float.random(in: 0 ... (2 * Float.pi))) * Float.random(in: 1 ... 4),
|
||||
sin(Float.random(in: 0 ... (2 * Float.pi))) * Float.random(in: 1 ... 4) - 2.5
|
||||
),
|
||||
life: simd_float1(particleDuration),
|
||||
duration: simd_float1(particleDuration)
|
||||
)
|
||||
}
|
||||
|
||||
private func setupTexture(from image: CGImage, device: MTLDevice) {
|
||||
let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)!
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
|
||||
|
||||
guard let context = CGContext(
|
||||
data: nil,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
bitsPerComponent: 8,
|
||||
bytesPerRow: 0,
|
||||
space: colorSpace,
|
||||
bitmapInfo: bitmapInfo.rawValue
|
||||
) else { return }
|
||||
context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
|
||||
|
||||
guard let convertedImage = context.makeImage() else { return }
|
||||
|
||||
let textureLoader = MTKTextureLoader(device: device)
|
||||
let textureLoaderOptions: [MTKTextureLoader.Option: Any] = [
|
||||
.textureStorageMode: MTLStorageMode.private.rawValue,
|
||||
.SRGB: false,
|
||||
]
|
||||
guard let texture = try? textureLoader.newTexture(
|
||||
cgImage: convertedImage,
|
||||
options: textureLoaderOptions
|
||||
) else { return }
|
||||
|
||||
self.texture = texture
|
||||
}
|
||||
|
||||
private func finalizeSetup(targetFrame: CGRect, device: MTLDevice) {
|
||||
let targetFrameWidth = Float(targetFrame.width)
|
||||
let targetFrameHeight = Float(targetFrame.height)
|
||||
targetFrameSize = .init(targetFrameWidth, targetFrameHeight)
|
||||
commandQueue = device.makeCommandQueue()!
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// ParticleView.swift
|
||||
// TrollNFC
|
||||
//
|
||||
// Created by 砍砍 on 6/8/25.
|
||||
//
|
||||
|
||||
import MetalKit
|
||||
import simd
|
||||
|
||||
import UIKit
|
||||
|
||||
class ParticleView: UIView {
|
||||
private var device: MTLDevice!
|
||||
private var metalView: MTKView!
|
||||
private var renderer = Renderer()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupMetalDevice()
|
||||
setupMetalView()
|
||||
setupViewProperties()
|
||||
}
|
||||
|
||||
private func setupMetalDevice() {
|
||||
guard let device = Self.createSystemDefaultDevice() else {
|
||||
fatalError("failed to create Metal device")
|
||||
}
|
||||
self.device = device
|
||||
}
|
||||
|
||||
private func setupMetalView() {
|
||||
metalView = MTKView(frame: .zero, device: device)
|
||||
configureMetalView()
|
||||
addSubview(metalView)
|
||||
}
|
||||
|
||||
private func configureMetalView() {
|
||||
metalView.layer.isOpaque = false
|
||||
metalView.backgroundColor = UIColor.clear
|
||||
metalView.delegate = renderer
|
||||
}
|
||||
|
||||
private func setupViewProperties() {
|
||||
clipsToBounds = false
|
||||
metalView.clipsToBounds = false
|
||||
}
|
||||
|
||||
private static func createSystemDefaultDevice() -> MTLDevice? {
|
||||
MTLCreateSystemDefaultDevice()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func beginWith(
|
||||
_ image: CGImage,
|
||||
targetFrame: CGRect,
|
||||
onComplete: @escaping () -> Void,
|
||||
onFirstFrameRendered: @escaping () -> Void
|
||||
) {
|
||||
renderer.prepareResources(
|
||||
with: device,
|
||||
image: image,
|
||||
targetFrame: targetFrame,
|
||||
onComplete: onComplete,
|
||||
onFirstFrameRendered: onFirstFrameRendered
|
||||
)
|
||||
metalView.draw()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
let expandedBounds = bounds.insetBy(dx: -bounds.width, dy: -bounds.height)
|
||||
metalView.frame = expandedBounds
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// UIView+createViewSnapshot.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/19/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIView {
|
||||
func createViewSnapshot() -> UIImage {
|
||||
let renderer = UIGraphicsImageRenderer(bounds: bounds)
|
||||
return renderer.image { context in
|
||||
// clear the background
|
||||
context.cgContext.setFillColor(UIColor.clear.cgColor)
|
||||
context.cgContext.fill(bounds)
|
||||
|
||||
// MUST USE DRAW HIERARCHY TO RENDER VISUAL EFFECT VIEW
|
||||
self.drawHierarchy(in: bounds, afterScreenUpdates: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// AccentColor.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/19/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
static let accent: UIColor = .init(red: 30 / 255, green: 150 / 255, blue: 235 / 255, alpha: 1.0)
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Animation.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/19/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
func performWithAnimation(
|
||||
animations: @escaping () -> Void,
|
||||
completion: @escaping (Bool) -> Void = { _ in }
|
||||
) {
|
||||
UIView.animate(
|
||||
withDuration: 0.5,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 0.8,
|
||||
initialSpringVelocity: 0.8,
|
||||
options: [.beginFromCurrentState, .allowAnimatedContent, .curveEaseInOut],
|
||||
animations: animations,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
//
|
||||
// UIHostingView.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 2024/12/13.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
class UIHostingView<Content: View>: UIView {
|
||||
private let hostingViewController: UIHostingController<Content>
|
||||
|
||||
var rootView: Content {
|
||||
get { hostingViewController.rootView }
|
||||
set { hostingViewController.rootView = newValue }
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
hostingViewController.view.intrinsicContentSize
|
||||
}
|
||||
|
||||
init(rootView: Content) {
|
||||
hostingViewController = UIHostingController(rootView: rootView)
|
||||
hostingViewController.edgesForExtendedLayout = []
|
||||
hostingViewController.extendedLayoutIncludesOpaqueBars = false
|
||||
super.init(frame: .zero)
|
||||
|
||||
hostingViewController.view?.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(hostingViewController.view)
|
||||
if let view = hostingViewController.view {
|
||||
view.removeFromSuperview()
|
||||
view.backgroundColor = .clear
|
||||
view.isOpaque = false
|
||||
addSubview(view)
|
||||
let constraints = [
|
||||
view.topAnchor.constraint(equalTo: topAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
view.leftAnchor.constraint(equalTo: leftAnchor),
|
||||
view.rightAnchor.constraint(equalTo: rightAnchor),
|
||||
]
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func sizeThatFits(_ size: CGSize) -> CGSize {
|
||||
hostingViewController.sizeThatFits(in: size)
|
||||
}
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
import Combine
|
||||
import SnapKit
|
||||
import Then
|
||||
import UIKit
|
||||
|
||||
class InputBox: UIView {
|
||||
weak var delegate: InputBoxDelegate?
|
||||
|
||||
|
||||
private lazy var containerView = UIView().then {
|
||||
$0.backgroundColor = UIColor.affineLayerBackgroundPrimary
|
||||
$0.layer.cornerRadius = 12
|
||||
$0.layer.borderWidth = 0.5
|
||||
$0.layer.borderColor = UIColor.affineLayerBorder.cgColor
|
||||
|
||||
$0.layer.shadowColor = UIColor.black.cgColor
|
||||
$0.layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
$0.layer.shadowRadius = 12
|
||||
$0.layer.shadowOpacity = 0.075
|
||||
$0.clipsToBounds = false
|
||||
}
|
||||
|
||||
lazy var textView = UITextView().then {
|
||||
$0.backgroundColor = .clear
|
||||
$0.font = .systemFont(ofSize: 16)
|
||||
$0.textColor = .label
|
||||
$0.isScrollEnabled = false
|
||||
$0.textContainer.lineFragmentPadding = 0
|
||||
$0.textContainerInset = .zero
|
||||
$0.delegate = self
|
||||
$0.text = ""
|
||||
}
|
||||
|
||||
lazy var placeholderLabel = UILabel().then {
|
||||
$0.text = "Write your message..."
|
||||
$0.font = .systemFont(ofSize: 16)
|
||||
$0.textColor = .systemGray3
|
||||
$0.isHidden = true
|
||||
}
|
||||
|
||||
private lazy var addButton = UIButton(type: .system).then {
|
||||
$0.backgroundColor = UIColor.affineLayerBackgroundPrimary
|
||||
$0.layer.cornerRadius = 6
|
||||
$0.layer.borderWidth = 0.5
|
||||
$0.layer.borderColor = UIColor.affineLayerBorder.cgColor
|
||||
$0.setImage(UIImage.affinePlus, for: .normal)
|
||||
$0.tintColor = UIColor.affineIconPrimary
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
private lazy var toolButton = UIButton(type: .system).then {
|
||||
$0.setImage(UIImage.affineTools, for: .normal)
|
||||
$0.tintColor = UIColor.affineIconPrimary
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.addTarget(self, action: #selector(toolButtonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
private lazy var webButton = UIButton(type: .system).then {
|
||||
$0.setImage(UIImage.affineWeb, for: .normal)
|
||||
$0.tintColor = UIColor.affineIconPrimary
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.addTarget(self, action: #selector(webButtonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
private lazy var reactButton = UIButton(type: .system).then {
|
||||
$0.setImage(UIImage.affineThink, for: .normal)
|
||||
$0.tintColor = UIColor.affineIconPrimary
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.addTarget(self, action: #selector(reactButtonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
private lazy var sendButton = UIButton(type: .system).then {
|
||||
$0.backgroundColor = UIColor.affineButtonPrimary
|
||||
$0.layer.cornerRadius = 19
|
||||
$0.setImage(UIImage.affineArrowUpBig, for: .normal)
|
||||
$0.tintColor = UIColor.affineLayerPureWhite
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.addTarget(self, action: #selector(sendButtonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
private lazy var leftButtonsStackView = UIStackView().then {
|
||||
$0.axis = .horizontal
|
||||
$0.spacing = 16
|
||||
$0.alignment = .center
|
||||
$0.addArrangedSubview(addButton)
|
||||
}
|
||||
|
||||
private lazy var rightButtonsStackView = UIStackView().then {
|
||||
$0.axis = .horizontal
|
||||
$0.spacing = 16
|
||||
$0.alignment = .center
|
||||
$0.addArrangedSubview(toolButton)
|
||||
$0.addArrangedSubview(webButton)
|
||||
$0.addArrangedSubview(reactButton)
|
||||
$0.addArrangedSubview(sendButton)
|
||||
|
||||
}
|
||||
|
||||
lazy var imageBar = InputBoxImageBar().then {
|
||||
$0.imageBarDelegate = self
|
||||
}
|
||||
|
||||
lazy var mainStackView = UIStackView().then {
|
||||
$0.axis = .vertical
|
||||
$0.spacing = 16
|
||||
$0.alignment = .fill
|
||||
$0.addArrangedSubview(imageBar)
|
||||
$0.addArrangedSubview(textView)
|
||||
$0.addArrangedSubview(functionBar)
|
||||
}
|
||||
|
||||
|
||||
private var textViewHeightConstraint: Constraint?
|
||||
private let minTextViewHeight: CGFloat = 48
|
||||
private let maxTextViewHeight: CGFloat = 140
|
||||
|
||||
|
||||
var text: String {
|
||||
get { textView.text ?? "" }
|
||||
set {
|
||||
textView.text = newValue
|
||||
updatePlaceholderVisibility()
|
||||
updateTextViewHeight()
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect = .zero) {
|
||||
super.init(frame: frame)
|
||||
|
||||
backgroundColor = .clear
|
||||
addSubview(containerView)
|
||||
containerView.addSubview(mainStackView)
|
||||
containerView.addSubview(placeholderLabel)
|
||||
imageBar.isHidden = true
|
||||
|
||||
containerView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(8)
|
||||
}
|
||||
|
||||
mainStackView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(8)
|
||||
}
|
||||
|
||||
imageBar.snp.makeConstraints { make in
|
||||
make.left.right.equalToSuperview()
|
||||
}
|
||||
|
||||
textView.snp.makeConstraints { make in
|
||||
textViewHeightConstraint = make.height.equalTo(minTextViewHeight).constraint
|
||||
}
|
||||
|
||||
placeholderLabel.snp.makeConstraints { make in
|
||||
make.left.right.equalTo(textView)
|
||||
make.top.equalTo(textView)
|
||||
}
|
||||
|
||||
setupBindings()
|
||||
updatePlaceholderVisibility()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setupBindings() {
|
||||
// 绑定 ViewModel 到 UI
|
||||
viewModel.$inputText
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] text in
|
||||
if self?.textView.text != text {
|
||||
self?.textView.text = text
|
||||
self?.updatePlaceholderVisibility()
|
||||
self?.updateTextViewHeight()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$isToolEnabled
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] enabled in
|
||||
self?.functionBar.updateToolState(isEnabled: enabled)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$isNetworkEnabled
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] enabled in
|
||||
self?.functionBar.updateNetworkState(isEnabled: enabled)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$isDeepThinkingEnabled
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] enabled in
|
||||
self?.functionBar.updateDeepThinkingState(isEnabled: enabled)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$canSend
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] canSend in
|
||||
self?.functionBar.updateSendState(canSend: canSend)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$hasAttachments
|
||||
.dropFirst() // for view setup
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] hasAttachments in
|
||||
performWithAnimation {
|
||||
self?.updateImageBarVisibility(hasAttachments)
|
||||
self?.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$attachments
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] attachments in
|
||||
self?.updateImageBarContent(attachments)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func updateTextViewHeight() {
|
||||
let size = textView.sizeThatFits(CGSize(width: textView.frame.width, height: CGFloat.greatestFiniteMagnitude))
|
||||
let newHeight = max(minTextViewHeight, min(maxTextViewHeight, size.height))
|
||||
|
||||
let height = textView.frame.height
|
||||
guard height != newHeight else { return }
|
||||
|
||||
textViewHeightConstraint?.update(offset: newHeight)
|
||||
textView.isScrollEnabled = size.height > maxTextViewHeight
|
||||
|
||||
if height == 0 || superview == nil || window == nil || isHidden { return }
|
||||
|
||||
performWithAnimation {
|
||||
self.layoutIfNeeded()
|
||||
self.superview?.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
func updatePlaceholderVisibility() {
|
||||
placeholderLabel.isHidden = !textView.text.isEmpty
|
||||
}
|
||||
|
||||
func updateImageBarVisibility(_ hasAttachments: Bool) {
|
||||
imageBar.isHidden = !hasAttachments
|
||||
}
|
||||
|
||||
func updateImageBarContent(_ attachments: [InputAttachment]) {
|
||||
imageBar.updateImageBarContent(attachments)
|
||||
}
|
||||
|
||||
// MARK: - Public Methods
|
||||
|
||||
public func addImageAttachment(_ image: UIImage) {
|
||||
guard let imageData = image.jpegData(compressionQuality: 0.8) else { return }
|
||||
|
||||
let attachment = InputAttachment(
|
||||
type: .image,
|
||||
data: imageData,
|
||||
name: "image.jpg",
|
||||
size: Int64(imageData.count)
|
||||
)
|
||||
|
||||
performWithAnimation { [self] in
|
||||
viewModel.addAttachment(attachment)
|
||||
layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
public func addFileAttachment(_ url: URL) {
|
||||
guard let fileData = try? Data(contentsOf: url) else { return }
|
||||
|
||||
let attachment = InputAttachment(
|
||||
type: .file,
|
||||
data: fileData,
|
||||
name: url.lastPathComponent,
|
||||
size: Int64(fileData.count)
|
||||
)
|
||||
|
||||
performWithAnimation { [self] in
|
||||
viewModel.addAttachment(attachment)
|
||||
layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
public var inputBoxData: InputBoxData {
|
||||
viewModel.prepareSendData()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InputBoxFunctionBarDelegate
|
||||
|
||||
extension InputBox: InputBoxFunctionBarDelegate {
|
||||
func functionBarDidTapTakePhoto(_: InputBoxFunctionBar) {
|
||||
delegate?.inputBoxDidSelectTakePhoto(self)
|
||||
}
|
||||
|
||||
func functionBarDidTapPhotoLibrary(_: InputBoxFunctionBar) {
|
||||
delegate?.inputBoxDidSelectPhotoLibrary(self)
|
||||
}
|
||||
|
||||
func functionBarDidTapAttachFiles(_: InputBoxFunctionBar) {
|
||||
delegate?.inputBoxDidSelectAttachFiles(self)
|
||||
}
|
||||
|
||||
func functionBarDidTapEmbedDocs(_: InputBoxFunctionBar) {
|
||||
delegate?.inputBoxDidSelectEmbedDocs(self)
|
||||
}
|
||||
|
||||
func functionBarDidTapTool(_: InputBoxFunctionBar) {
|
||||
viewModel.toggleTool()
|
||||
}
|
||||
|
||||
func functionBarDidTapNetwork(_: InputBoxFunctionBar) {
|
||||
viewModel.toggleNetwork()
|
||||
}
|
||||
|
||||
func functionBarDidTapDeepThinking(_: InputBoxFunctionBar) {
|
||||
viewModel.toggleDeepThinking()
|
||||
}
|
||||
|
||||
func functionBarDidTapSend(_: InputBoxFunctionBar) {
|
||||
delegate?.inputBoxDidSend(self)
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "255",
|
||||
"red" : "255"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "32",
|
||||
"green" : "32",
|
||||
"red" : "32"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// InputBoxDelegate.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/18/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol InputBoxDelegate: AnyObject {
|
||||
func inputBoxDidSelectTakePhoto(_ inputBox: InputBox)
|
||||
func inputBoxDidSelectPhotoLibrary(_ inputBox: InputBox)
|
||||
func inputBoxDidSelectAttachFiles(_ inputBox: InputBox)
|
||||
func inputBoxDidSelectEmbedDocs(_ inputBox: InputBox)
|
||||
func inputBoxDidSend(_ inputBox: InputBox)
|
||||
func inputBoxTextDidChange(_ text: String)
|
||||
}
|
||||
|
||||
extension InputBox: InputBoxImageBarDelegate {
|
||||
func inputBoxImageBar(_: InputBoxImageBar, didRemoveImageWithId id: InputAttachment.ID) {
|
||||
performWithAnimation { [self] in
|
||||
viewModel.removeAttachment(withId: id)
|
||||
layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension InputBox: UITextViewDelegate {
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
viewModel.updateText(textView.text ?? "")
|
||||
delegate?.inputBoxTextDidChange(textView.text ?? "")
|
||||
updatePlaceholderVisibility()
|
||||
updateTextViewHeight()
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
import SnapKit
|
||||
import Then
|
||||
import UIKit
|
||||
|
||||
protocol InputBoxFunctionBarDelegate: AnyObject {
|
||||
func functionBarDidTapTakePhoto(_ functionBar: InputBoxFunctionBar)
|
||||
func functionBarDidTapPhotoLibrary(_ functionBar: InputBoxFunctionBar)
|
||||
func functionBarDidTapAttachFiles(_ functionBar: InputBoxFunctionBar)
|
||||
func functionBarDidTapEmbedDocs(_ functionBar: InputBoxFunctionBar)
|
||||
func functionBarDidTapTool(_ functionBar: InputBoxFunctionBar)
|
||||
func functionBarDidTapNetwork(_ functionBar: InputBoxFunctionBar)
|
||||
func functionBarDidTapDeepThinking(_ functionBar: InputBoxFunctionBar)
|
||||
func functionBarDidTapSend(_ functionBar: InputBoxFunctionBar)
|
||||
}
|
||||
|
||||
private let unselectedColor: UIColor = .secondaryLabel
|
||||
private let selectedColor: UIColor = .accent
|
||||
|
||||
class InputBoxFunctionBar: UIView {
|
||||
weak var delegate: InputBoxFunctionBarDelegate?
|
||||
|
||||
lazy var attachmentButton = UIButton(type: .system).then {
|
||||
$0.setImage(UIImage(named: "inputbox.add.attachment", in: .module, with: nil), for: .normal)
|
||||
$0.tintColor = unselectedColor
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.showsMenuAsPrimaryAction = true
|
||||
$0.menu = createAttachmentMenu()
|
||||
}
|
||||
|
||||
lazy var toolButton = UIButton(type: .system).then {
|
||||
$0.setImage(UIImage(named: "inputbox.tool", in: .module, with: nil), for: .normal)
|
||||
$0.tintColor = unselectedColor
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.addTarget(self, action: #selector(toolButtonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
lazy var networkButton = UIButton(type: .system).then {
|
||||
$0.setImage(UIImage(named: "inputbox.network", in: .module, with: nil), for: .normal)
|
||||
$0.tintColor = unselectedColor
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.addTarget(self, action: #selector(networkButtonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
lazy var deepThinkingButton = UIButton(type: .system).then {
|
||||
$0.setImage(UIImage(named: "inputbox.deep.thinking", in: .module, with: nil), for: .normal)
|
||||
$0.tintColor = unselectedColor
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.addTarget(self, action: #selector(deepThinkingButtonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
lazy var sendButton = UIButton(type: .system).then {
|
||||
$0.setImage(UIImage(named: "inputbox.send", in: .module, with: nil), for: .normal)
|
||||
$0.tintColor = .white
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.addTarget(self, action: #selector(sendButtonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
lazy var leftButtonsStackView = UIStackView().then {
|
||||
$0.axis = .horizontal
|
||||
$0.spacing = 16
|
||||
$0.alignment = .center
|
||||
$0.addArrangedSubview(attachmentButton)
|
||||
}
|
||||
|
||||
lazy var rightButtonsStackView = UIStackView().then {
|
||||
$0.axis = .horizontal
|
||||
$0.spacing = 16
|
||||
$0.alignment = .center
|
||||
$0.addArrangedSubview(toolButton)
|
||||
$0.addArrangedSubview(networkButton)
|
||||
$0.addArrangedSubview(deepThinkingButton)
|
||||
$0.addArrangedSubview(sendButton)
|
||||
}
|
||||
|
||||
lazy var stackView = UIStackView().then {
|
||||
$0.axis = .horizontal
|
||||
$0.spacing = 12
|
||||
$0.alignment = .center
|
||||
$0.addArrangedSubview(leftButtonsStackView)
|
||||
$0.addArrangedSubview(UIView()) // spacer
|
||||
$0.addArrangedSubview(rightButtonsStackView)
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupViews()
|
||||
setupConstraints()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func setupViews() {
|
||||
addSubview(stackView)
|
||||
}
|
||||
|
||||
private func setupConstraints() {
|
||||
stackView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
for button in [attachmentButton, toolButton, networkButton, deepThinkingButton, sendButton] {
|
||||
button.snp.makeConstraints { make in
|
||||
make.width.height.equalTo(32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
sendButton.layer.cornerRadius = sendButton.bounds.height / 2
|
||||
for button in [toolButton, networkButton, deepThinkingButton] {
|
||||
button.layer.cornerRadius = button.bounds.height / 2
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public Methods
|
||||
|
||||
func updateToolState(isEnabled: Bool) {
|
||||
toolButton.tintColor = isEnabled ? selectedColor : unselectedColor
|
||||
}
|
||||
|
||||
func updateNetworkState(isEnabled: Bool) {
|
||||
networkButton.tintColor = isEnabled ? selectedColor : unselectedColor
|
||||
}
|
||||
|
||||
func updateDeepThinkingState(isEnabled: Bool) {
|
||||
deepThinkingButton.tintColor = isEnabled ? selectedColor : unselectedColor
|
||||
}
|
||||
|
||||
func updateSendState(canSend: Bool) {
|
||||
sendButton.isEnabled = canSend
|
||||
sendButton.alpha = canSend ? 1.0 : 0.5
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
private func createAttachmentMenu() -> UIMenu {
|
||||
let takePhotoAction = UIAction(
|
||||
title: "Take Photo or Video",
|
||||
image: UIImage(systemName: "camera")
|
||||
) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
delegate?.functionBarDidTapTakePhoto(self)
|
||||
}
|
||||
|
||||
let photoLibraryAction = UIAction(
|
||||
title: "Photo Library",
|
||||
image: UIImage(systemName: "photo.on.rectangle")
|
||||
) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
delegate?.functionBarDidTapPhotoLibrary(self)
|
||||
}
|
||||
|
||||
let attachFilesAction = UIAction(
|
||||
title: "Attach Files (pdf, txt, csv)",
|
||||
image: UIImage(systemName: "arrow.up.doc")
|
||||
) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
delegate?.functionBarDidTapAttachFiles(self)
|
||||
}
|
||||
|
||||
let embedDocsAction = UIAction(
|
||||
title: "Embed AFFINE Docs",
|
||||
image: UIImage(systemName: "doc.text")
|
||||
) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
delegate?.functionBarDidTapEmbedDocs(self)
|
||||
}
|
||||
|
||||
return UIMenu(
|
||||
options: [.displayInline],
|
||||
children: [takePhotoAction, photoLibraryAction, attachFilesAction, embedDocsAction].reversed()
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func toolButtonTapped() {
|
||||
delegate?.functionBarDidTapTool(self)
|
||||
}
|
||||
|
||||
@objc private func networkButtonTapped() {
|
||||
delegate?.functionBarDidTapNetwork(self)
|
||||
}
|
||||
|
||||
@objc private func deepThinkingButtonTapped() {
|
||||
delegate?.functionBarDidTapDeepThinking(self)
|
||||
}
|
||||
|
||||
@objc private func sendButtonTapped() {
|
||||
delegate?.functionBarDidTapSend(self)
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// InputBoxImageBar.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/18/25.
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import Then
|
||||
import UIKit
|
||||
|
||||
protocol InputBoxImageBarDelegate: AnyObject {
|
||||
func inputBoxImageBar(_ imageBar: InputBoxImageBar, didRemoveImageWithId id: UUID)
|
||||
}
|
||||
|
||||
private class AttachmentViewModel {
|
||||
let attachment: InputAttachment
|
||||
let imageCell: InputBoxImageBar.ImageCell
|
||||
|
||||
init(attachment: InputAttachment, imageCell: InputBoxImageBar.ImageCell) {
|
||||
self.attachment = attachment
|
||||
self.imageCell = imageCell
|
||||
}
|
||||
}
|
||||
|
||||
class InputBoxImageBar: UIScrollView {
|
||||
weak var imageBarDelegate: InputBoxImageBarDelegate?
|
||||
|
||||
private var attachmentViewModels: [AttachmentViewModel] = []
|
||||
private let cellSpacing: CGFloat = 8
|
||||
private let constantHeight: CGFloat = 80
|
||||
|
||||
override init(frame: CGRect = .zero) {
|
||||
super.init(frame: frame)
|
||||
showsHorizontalScrollIndicator = false
|
||||
showsVerticalScrollIndicator = false
|
||||
|
||||
snp.makeConstraints { make in
|
||||
make.height.equalTo(constantHeight)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func updateImageBarContent(_ attachments: [InputAttachment]) {
|
||||
let currentIds = Set(attachmentViewModels.map(\.attachment.id))
|
||||
let imageAttachments = attachments.filter { $0.type == .image }
|
||||
let newIds = Set(imageAttachments.map(\.id))
|
||||
|
||||
// 移除不再存在的附件
|
||||
let idsToRemove = currentIds.subtracting(newIds)
|
||||
for id in idsToRemove {
|
||||
if let index = attachmentViewModels.firstIndex(where: { $0.attachment.id == id }) {
|
||||
let viewModel = attachmentViewModels.remove(at: index)
|
||||
viewModel.imageCell.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
// 添加新的附件
|
||||
let idsToAdd = newIds.subtracting(currentIds)
|
||||
for attachment in imageAttachments {
|
||||
if idsToAdd.contains(attachment.id),
|
||||
let data = attachment.data,
|
||||
let image = UIImage(data: data)
|
||||
{
|
||||
let imageCell = ImageCell(
|
||||
// for animation to work
|
||||
frame: .init(x: 0, y: 0, width: constantHeight, height: constantHeight),
|
||||
image: image,
|
||||
attachmentId: attachment.id
|
||||
)
|
||||
imageCell.onRemove = { [weak self] cell in
|
||||
self?.removeImageCell(cell)
|
||||
}
|
||||
|
||||
let viewModel = AttachmentViewModel(attachment: attachment, imageCell: imageCell)
|
||||
attachmentViewModels.append(viewModel)
|
||||
addSubview(imageCell)
|
||||
}
|
||||
}
|
||||
|
||||
layoutImageCells()
|
||||
}
|
||||
|
||||
func removeImageCell(_ cell: ImageCell) {
|
||||
if let index = attachmentViewModels.firstIndex(where: { $0.imageCell === cell }) {
|
||||
let viewModel = attachmentViewModels.remove(at: index)
|
||||
viewModel.imageCell.removeFromSuperviewWithExplodeEffect()
|
||||
imageBarDelegate?.inputBoxImageBar(self, didRemoveImageWithId: cell.attachmentId)
|
||||
layoutImageCells()
|
||||
}
|
||||
}
|
||||
|
||||
func clear() {
|
||||
for viewModel in attachmentViewModels {
|
||||
viewModel.imageCell.removeFromSuperview()
|
||||
}
|
||||
attachmentViewModels.removeAll()
|
||||
contentSize = .zero
|
||||
}
|
||||
|
||||
private func layoutImageCells() {
|
||||
var xOffset: CGFloat = 0
|
||||
|
||||
for viewModel in attachmentViewModels {
|
||||
viewModel.imageCell.frame = CGRect(x: xOffset, y: 0, width: constantHeight, height: constantHeight)
|
||||
xOffset += constantHeight + cellSpacing
|
||||
}
|
||||
|
||||
// Update content size
|
||||
let totalWidth = max(0, xOffset - cellSpacing)
|
||||
contentSize = CGSize(width: totalWidth, height: constantHeight)
|
||||
}
|
||||
}
|
||||
|
||||
extension InputBoxImageBar {
|
||||
class ImageCell: UIView {
|
||||
let attachmentId: UUID
|
||||
var onRemove: ((ImageCell) -> Void)?
|
||||
|
||||
private lazy var imageView = UIImageView(frame: bounds).then {
|
||||
$0.contentMode = .scaleAspectFill
|
||||
$0.clipsToBounds = true
|
||||
$0.layer.cornerRadius = 12
|
||||
$0.layer.cornerCurve = .continuous
|
||||
$0.backgroundColor = .systemGray6
|
||||
}
|
||||
|
||||
private lazy var removeButton = DeleteButtonView(frame: removeButtonFrame).then {
|
||||
$0.onTapped = { [weak self] in
|
||||
self?.removeButtonTapped()
|
||||
}
|
||||
}
|
||||
|
||||
init(frame: CGRect, image: UIImage, attachmentId: UUID) {
|
||||
self.attachmentId = attachmentId
|
||||
super.init(frame: frame)
|
||||
addSubview(imageView)
|
||||
addSubview(removeButton)
|
||||
imageView.image = image
|
||||
}
|
||||
|
||||
var removeButtonFrame: CGRect {
|
||||
let buttonSize: CGFloat = 18
|
||||
let buttonInset: CGFloat = 6
|
||||
return CGRect(
|
||||
x: bounds.width - buttonSize - buttonInset,
|
||||
y: buttonInset,
|
||||
width: buttonSize,
|
||||
height: buttonSize
|
||||
)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
imageView.frame = bounds
|
||||
|
||||
removeButton.frame = removeButtonFrame
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
@objc private func removeButtonTapped() {
|
||||
onRemove?(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
//
|
||||
// InputBoxViewModel.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by AI Assistant on 6/17/25.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
// MARK: - Data Models
|
||||
|
||||
public struct InputAttachment: Identifiable, Equatable, Hashable, Codable {
|
||||
public var id: UUID = .init()
|
||||
public var type: AttachmentType
|
||||
public var data: Data?
|
||||
public var url: URL?
|
||||
public var name: String
|
||||
public var size: Int64
|
||||
|
||||
public enum AttachmentType: String, Equatable, Hashable, Codable {
|
||||
case image
|
||||
case document
|
||||
case file
|
||||
}
|
||||
|
||||
public init(
|
||||
type: AttachmentType,
|
||||
data: Data? = nil,
|
||||
url: URL? = nil,
|
||||
name: String,
|
||||
size: Int64 = 0
|
||||
) {
|
||||
self.type = type
|
||||
self.data = data
|
||||
self.url = url
|
||||
self.name = name
|
||||
self.size = size
|
||||
}
|
||||
}
|
||||
|
||||
public struct InputBoxData {
|
||||
public var text: String
|
||||
public var attachments: [InputAttachment]
|
||||
public var isToolEnabled: Bool
|
||||
public var isNetworkEnabled: Bool
|
||||
public var isDeepThinkingEnabled: Bool
|
||||
|
||||
public init(
|
||||
text: String,
|
||||
attachments: [InputAttachment],
|
||||
isToolEnabled: Bool,
|
||||
isNetworkEnabled: Bool,
|
||||
isDeepThinkingEnabled: Bool
|
||||
) {
|
||||
self.text = text
|
||||
self.attachments = attachments
|
||||
self.isToolEnabled = isToolEnabled
|
||||
self.isNetworkEnabled = isNetworkEnabled
|
||||
self.isDeepThinkingEnabled = isDeepThinkingEnabled
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - View Model
|
||||
|
||||
public class InputBoxViewModel: ObservableObject {
|
||||
// MARK: - Published Properties
|
||||
|
||||
@Published public var inputText: String = ""
|
||||
@Published public var isToolEnabled: Bool = false
|
||||
@Published public var isNetworkEnabled: Bool = false
|
||||
@Published public var isDeepThinkingEnabled: Bool = false
|
||||
@Published public var hasAttachments: Bool = false
|
||||
@Published public var attachments: [InputAttachment] = []
|
||||
@Published public var canSend: Bool = false
|
||||
|
||||
// MARK: - Private Properties
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
public init() {
|
||||
setupBindings()
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
private func setupBindings() {
|
||||
// 监听文本变化,自动更新发送按钮状态
|
||||
$inputText
|
||||
.map { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
|
||||
.assign(to: \.canSend, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
// 监听附件变化
|
||||
$attachments
|
||||
.map { !$0.isEmpty }
|
||||
.assign(to: \.hasAttachments, on: self)
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Text Management
|
||||
|
||||
public extension InputBoxViewModel {
|
||||
func updateText(_ text: String) {
|
||||
inputText = text
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Feature Toggles
|
||||
|
||||
public extension InputBoxViewModel {
|
||||
func toggleTool() {
|
||||
isToolEnabled.toggle()
|
||||
}
|
||||
|
||||
func toggleNetwork() {
|
||||
isNetworkEnabled.toggle()
|
||||
}
|
||||
|
||||
func toggleDeepThinking() {
|
||||
isDeepThinkingEnabled.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Attachment Management
|
||||
|
||||
public extension InputBoxViewModel {
|
||||
func addAttachment(_ attachment: InputAttachment) {
|
||||
attachments.append(attachment)
|
||||
}
|
||||
|
||||
func removeAttachment(withId id: UUID) {
|
||||
attachments.removeAll { $0.id == id }
|
||||
}
|
||||
|
||||
func clearAttachments() {
|
||||
attachments.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Send Management
|
||||
|
||||
public extension InputBoxViewModel {
|
||||
func prepareSendData() -> InputBoxData {
|
||||
InputBoxData(
|
||||
text: inputText.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
attachments: attachments,
|
||||
isToolEnabled: isToolEnabled,
|
||||
isNetworkEnabled: isNetworkEnabled,
|
||||
isDeepThinkingEnabled: isDeepThinkingEnabled
|
||||
)
|
||||
}
|
||||
|
||||
func resetInput() {
|
||||
inputText = ""
|
||||
attachments.removeAll()
|
||||
isToolEnabled = false
|
||||
isNetworkEnabled = false
|
||||
isDeepThinkingEnabled = false
|
||||
}
|
||||
}
|
||||
+11
-20
@@ -5,6 +5,7 @@
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
public extension UIViewController {
|
||||
@@ -16,15 +17,15 @@ public extension UIViewController {
|
||||
let button = IntelligentsButton()
|
||||
view.addSubview(button)
|
||||
view.bringSubviewToFront(button)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
[
|
||||
button.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
|
||||
button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20 - Constant.affineTabbarHeight),
|
||||
button.widthAnchor.constraint(equalToConstant: 50),
|
||||
button.heightAnchor.constraint(equalToConstant: 50),
|
||||
].forEach { $0.isActive = true }
|
||||
button.snp.makeConstraints { make in
|
||||
make.trailing.equalTo(view.safeAreaLayoutGuide).offset(-20)
|
||||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-20 - 44)
|
||||
make.width.height.equalTo(50)
|
||||
}
|
||||
button.transform = .init(scaleX: 0, y: 0)
|
||||
view.layoutIfNeeded()
|
||||
if view.frame != .zero {
|
||||
view.layoutIfNeeded()
|
||||
}
|
||||
return button
|
||||
}
|
||||
|
||||
@@ -47,12 +48,7 @@ public extension UIViewController {
|
||||
button.stopProgress()
|
||||
view.layoutIfNeeded()
|
||||
|
||||
UIView.animate(
|
||||
withDuration: 0.5,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1.0,
|
||||
initialSpringVelocity: 0.8
|
||||
) {
|
||||
performWithAnimation {
|
||||
button.alpha = 1
|
||||
button.transform = .identity
|
||||
button.setNeedsLayout()
|
||||
@@ -77,12 +73,7 @@ public extension UIViewController {
|
||||
button.stopProgress()
|
||||
button.setNeedsLayout()
|
||||
view.layoutIfNeeded()
|
||||
UIView.animate(
|
||||
withDuration: 0.5,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 1.0,
|
||||
initialSpringVelocity: 0.8
|
||||
) {
|
||||
performWithAnimation {
|
||||
button.alpha = 0
|
||||
button.transform = .init(scaleX: 0, y: 0)
|
||||
button.setNeedsLayout()
|
||||
+56
-42
@@ -5,13 +5,26 @@
|
||||
// Created by 秋星桥 on 2024/11/18.
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import SwifterSwift
|
||||
import Then
|
||||
import UIKit
|
||||
|
||||
// floating button to open intelligent panel
|
||||
public class IntelligentsButton: UIView {
|
||||
let image = UIImageView()
|
||||
let background = UIView()
|
||||
let activityIndicator = UIActivityIndicatorView()
|
||||
lazy var image = UIImageView().then {
|
||||
$0.image = .init(named: "spark", in: .module, with: .none)
|
||||
$0.contentMode = .scaleAspectFit
|
||||
}
|
||||
|
||||
lazy var background = UIView().then {
|
||||
$0.backgroundColor = .init(
|
||||
light: .systemBackground,
|
||||
dark: .darkGray.withAlphaComponent(0.25)
|
||||
)
|
||||
}
|
||||
|
||||
lazy var activityIndicator = UIActivityIndicatorView()
|
||||
|
||||
public weak var delegate: (any IntelligentsButtonDelegate)? = nil {
|
||||
didSet { assert(Thread.isMainThread) }
|
||||
@@ -19,45 +32,10 @@ public class IntelligentsButton: UIView {
|
||||
|
||||
public init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
background.backgroundColor = .white
|
||||
addSubview(background)
|
||||
background.translatesAutoresizingMaskIntoConstraints = false
|
||||
[
|
||||
background.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
background.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
background.topAnchor.constraint(equalTo: topAnchor),
|
||||
background.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
].forEach { $0.isActive = true }
|
||||
|
||||
image.image = .init(named: "spark", in: .module, with: .none)
|
||||
image.contentMode = .scaleAspectFit
|
||||
image.tintColor = .accent
|
||||
addSubview(image)
|
||||
let imageInsetValue: CGFloat = 12
|
||||
image.translatesAutoresizingMaskIntoConstraints = false
|
||||
[
|
||||
image.leadingAnchor.constraint(equalTo: leadingAnchor, constant: imageInsetValue),
|
||||
image.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -imageInsetValue),
|
||||
image.topAnchor.constraint(equalTo: topAnchor, constant: imageInsetValue),
|
||||
image.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -imageInsetValue),
|
||||
].forEach { $0.isActive = true }
|
||||
|
||||
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(activityIndicator)
|
||||
[
|
||||
activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
].forEach { $0.isActive = true }
|
||||
|
||||
clipsToBounds = true
|
||||
layer.borderWidth = 2
|
||||
layer.borderColor = UIColor.gray.withAlphaComponent(0.1).cgColor
|
||||
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
|
||||
addGestureRecognizer(tap)
|
||||
isUserInteractionEnabled = true
|
||||
|
||||
setupViews()
|
||||
setupConstraints()
|
||||
setupGesture()
|
||||
setupAppearance()
|
||||
stopProgress()
|
||||
}
|
||||
|
||||
@@ -97,3 +75,39 @@ public class IntelligentsButton: UIView {
|
||||
image.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Setup Methods
|
||||
|
||||
private extension IntelligentsButton {
|
||||
func setupViews() {
|
||||
addSubview(background)
|
||||
addSubview(image)
|
||||
addSubview(activityIndicator)
|
||||
}
|
||||
|
||||
func setupConstraints() {
|
||||
background.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
image.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(12)
|
||||
}
|
||||
|
||||
activityIndicator.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
func setupGesture() {
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
|
||||
addGestureRecognizer(tap)
|
||||
isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
func setupAppearance() {
|
||||
clipsToBounds = true
|
||||
layer.borderWidth = 2
|
||||
layer.borderColor = UIColor.gray.withAlphaComponent(0.1).cgColor
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import SnapKit
|
||||
import Then
|
||||
import UIKit
|
||||
|
||||
protocol MainHeaderViewDelegate: AnyObject {
|
||||
func mainHeaderViewDidTapClose()
|
||||
func mainHeaderViewDidTapDropdown()
|
||||
func mainHeaderViewDidTapMenu()
|
||||
}
|
||||
|
||||
class MainHeaderView: UIView {
|
||||
weak var delegate: MainHeaderViewDelegate?
|
||||
|
||||
private lazy var closeButton = UIButton(type: .system).then {
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.setImage(UIImage.affineClose, for: .normal)
|
||||
$0.tintColor = UIColor.affineIconPrimary
|
||||
$0.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
|
||||
$0.setContentHuggingPriority(.required, for: .horizontal)
|
||||
}
|
||||
|
||||
private lazy var titleLabel = UILabel().then {
|
||||
$0.text = "AFFiNE AI"
|
||||
$0.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
$0.textColor = UIColor.affineTextPrimary
|
||||
$0.textAlignment = .center
|
||||
}
|
||||
|
||||
private lazy var dropdownButton = UIButton(type: .system).then {
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.setImage(UIImage.affineArrowDown, for: .normal)
|
||||
$0.tintColor = UIColor.affineIconPrimary
|
||||
$0.addTarget(self, action: #selector(dropdownButtonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
private lazy var centerStackView = UIStackView().then {
|
||||
$0.axis = .horizontal
|
||||
$0.spacing = 8
|
||||
$0.alignment = .center
|
||||
$0.addArrangedSubview(titleLabel)
|
||||
$0.addArrangedSubview(dropdownButton)
|
||||
}
|
||||
|
||||
private lazy var menuButton = UIButton(type: .system).then {
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.setImage(UIImage.affineMore, for: .normal)
|
||||
$0.tintColor = UIColor.affineIconPrimary
|
||||
$0.addTarget(self, action: #selector(menuButtonTapped), for: .touchUpInside)
|
||||
$0.setContentHuggingPriority(.required, for: .horizontal)
|
||||
}
|
||||
|
||||
private lazy var leftSpacerView = UIView()
|
||||
|
||||
private lazy var rightSpacerView = UIView()
|
||||
|
||||
private lazy var mainStackView = UIStackView().then {
|
||||
$0.axis = .horizontal
|
||||
$0.alignment = .center
|
||||
$0.distribution = .fill
|
||||
$0.spacing = 16
|
||||
$0.addArrangedSubview(closeButton)
|
||||
$0.addArrangedSubview(leftSpacerView)
|
||||
$0.addArrangedSubview(centerStackView)
|
||||
$0.addArrangedSubview(rightSpacerView)
|
||||
$0.addArrangedSubview(menuButton)
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
|
||||
backgroundColor = UIColor.clear
|
||||
|
||||
addSubview(mainStackView)
|
||||
|
||||
mainStackView.snp.makeConstraints { make in
|
||||
make.left.right.equalToSuperview().inset(16)
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
|
||||
closeButton.snp.makeConstraints { make in
|
||||
make.size.equalTo(titleLabel.font.pointSize + 16)
|
||||
}
|
||||
|
||||
menuButton.snp.makeConstraints { make in
|
||||
make.size.equalTo(titleLabel.font.pointSize + 16)
|
||||
}
|
||||
|
||||
dropdownButton.snp.makeConstraints { make in
|
||||
make.size.equalTo(titleLabel.font.pointSize + 16)
|
||||
}
|
||||
|
||||
// ensure center stack to be center
|
||||
leftSpacerView.snp.makeConstraints { make in
|
||||
make.width.equalTo(rightSpacerView)
|
||||
}
|
||||
|
||||
snp.makeConstraints { make in
|
||||
make.height.equalTo(52)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
fatalError()
|
||||
}
|
||||
|
||||
@objc private func closeButtonTapped() {
|
||||
delegate?.mainHeaderViewDidTapClose()
|
||||
}
|
||||
|
||||
@objc private func dropdownButtonTapped() {
|
||||
delegate?.mainHeaderViewDidTapDropdown()
|
||||
}
|
||||
|
||||
@objc private func menuButtonTapped() {
|
||||
delegate?.mainHeaderViewDidTapMenu()
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import UIKit
|
||||
|
||||
class DeleteButtonView: UIView {
|
||||
let imageView = UIImageView(image: .init(systemName: "xmark")).then {
|
||||
$0.tintColor = .white
|
||||
$0.contentMode = .scaleAspectFit
|
||||
}
|
||||
|
||||
let blur = UIVisualEffectView(
|
||||
effect: UIBlurEffect(style: .systemUltraThinMaterialDark)
|
||||
).then {
|
||||
$0.clipsToBounds = true
|
||||
}
|
||||
|
||||
var onTapped: () -> Void = {}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
isUserInteractionEnabled = true
|
||||
|
||||
addSubview(blur)
|
||||
addSubview(imageView)
|
||||
|
||||
blur.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
imageView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(2)
|
||||
}
|
||||
|
||||
let gesture = UITapGestureRecognizer(target: self, action: #selector(tapped))
|
||||
addGestureRecognizer(gesture)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
if bounds.width < 50 || bounds.height < 50 {
|
||||
return bounds.insetBy(dx: -20, dy: -20).contains(point)
|
||||
}
|
||||
return super.point(inside: point, with: event)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
blur.layer.cornerRadius = min(bounds.width, bounds.height) / 2
|
||||
}
|
||||
|
||||
@objc func tapped() {
|
||||
onTapped()
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// IntelligentContext.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 6/17/25.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import WebKit
|
||||
|
||||
public class IntelligentContext {
|
||||
// shared across the app, we expect our app to have a single context and webview
|
||||
public static let shared = IntelligentContext()
|
||||
|
||||
public var webView: WKWebView!
|
||||
|
||||
public lazy var temporaryDirectory: URL = {
|
||||
let tempDir = FileManager.default.temporaryDirectory
|
||||
return tempDir.appendingPathComponent("IntelligentContext")
|
||||
}()
|
||||
|
||||
private init() {}
|
||||
|
||||
public func preparePresent(_ completion: @escaping () -> Void) {
|
||||
DispatchQueue.global(qos: .userInitiated).async { [self] in
|
||||
prepareTemporaryDirectory()
|
||||
// TODO: used to gathering information, populate content from webview, etc.
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func prepareTemporaryDirectory() {
|
||||
if FileManager.default.fileExists(atPath: temporaryDirectory.path) {
|
||||
try? FileManager.default.removeItem(at: temporaryDirectory)
|
||||
}
|
||||
try? FileManager.default.createDirectory(
|
||||
at: temporaryDirectory,
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
/// Primary icon color
|
||||
static var affineIconPrimary: UIColor {
|
||||
UIColor(named: "affine.icon.primary", in: .module, compatibleWith: nil) ?? .black
|
||||
}
|
||||
|
||||
/// Primary background layer color
|
||||
static var affineLayerBackgroundPrimary: UIColor {
|
||||
UIColor(named: "affine.layer.background.primary", in: .module, compatibleWith: nil) ?? .white
|
||||
}
|
||||
|
||||
/// Secondary background layer color
|
||||
static var affineLayerBackgroundSecondary: UIColor {
|
||||
UIColor(named: "affine.layer.background.secondary", in: .module, compatibleWith: nil) ?? .systemGray6
|
||||
}
|
||||
|
||||
/// Border layer color
|
||||
static var affineLayerBorder: UIColor {
|
||||
UIColor(named: "affine.layer.border", in: .module, compatibleWith: nil) ?? .gray
|
||||
}
|
||||
|
||||
/// Pure white layer color
|
||||
static var affineLayerPureWhite: UIColor {
|
||||
UIColor(named: "affine.layer.pureWhite", in: .module, compatibleWith: nil) ?? .white
|
||||
}
|
||||
|
||||
/// Primary button color
|
||||
static var affineButtonPrimary: UIColor {
|
||||
UIColor(named: "affine.button.primary", in: .module, compatibleWith: nil) ?? .blue
|
||||
}
|
||||
|
||||
/// Activated icon color
|
||||
static var affineIconActivated: UIColor {
|
||||
UIColor(named: "affine.icon.activated", in: .module, compatibleWith: nil) ?? .blue
|
||||
}
|
||||
|
||||
/// Text emphasis color
|
||||
static var affineTextEmphasis: UIColor {
|
||||
UIColor(named: "affine.text.emphasis", in: .module, compatibleWith: nil) ?? .blue
|
||||
}
|
||||
|
||||
/// Text link color
|
||||
static var affineTextLink: UIColor {
|
||||
UIColor(named: "affine.text.link", in: .module, compatibleWith: nil) ?? .blue
|
||||
}
|
||||
|
||||
/// List dot and number color
|
||||
static var affineTextListDotAndNumber: UIColor {
|
||||
UIColor(named: "affine.text.listDotAndNumber", in: .module, compatibleWith: nil) ?? .blue
|
||||
}
|
||||
|
||||
/// Placeholder text color
|
||||
static var affineTextPlaceholder: UIColor {
|
||||
UIColor(named: "affine.text.placeholder", in: .module, compatibleWith: nil) ?? .gray
|
||||
}
|
||||
|
||||
/// Primary text color
|
||||
static var affineTextPrimary: UIColor {
|
||||
UIColor(named: "affine.text.primary", in: .module, compatibleWith: nil) ?? .black
|
||||
}
|
||||
|
||||
/// Pure white text color
|
||||
static var affineTextPureWhite: UIColor {
|
||||
UIColor(named: "affine.text.pureWhite", in: .module, compatibleWith: nil) ?? .white
|
||||
}
|
||||
|
||||
/// Secondary text color
|
||||
static var affineTextSecondary: UIColor {
|
||||
UIColor(named: "affine.text.secondary", in: .module, compatibleWith: nil) ?? .gray
|
||||
}
|
||||
|
||||
/// Tertiary text color
|
||||
static var affineTextTertiary: UIColor {
|
||||
UIColor(named: "affine.text.tertiary", in: .module, compatibleWith: nil) ?? .gray
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
import UIKit
|
||||
|
||||
extension UIImage {
|
||||
/// Check circle icon
|
||||
static var affineCheckCircle: UIImage {
|
||||
UIImage(named: "CheckCircle", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// More options icon
|
||||
static var affineMore: UIImage {
|
||||
UIImage(named: "More", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Arrow down icon
|
||||
static var affineArrowDown: UIImage {
|
||||
UIImage(named: "ArrowDown", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Tools icon
|
||||
static var affineTools: UIImage {
|
||||
UIImage(named: "Tools", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Box icon
|
||||
static var affineBox: UIImage {
|
||||
UIImage(named: "Box", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Think icon
|
||||
static var affineThink: UIImage {
|
||||
UIImage(named: "Think", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Web icon
|
||||
static var affineWeb: UIImage {
|
||||
UIImage(named: "Web", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Calendar icon
|
||||
static var affineCalendar: UIImage {
|
||||
UIImage(named: "Calendar", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Camera icon
|
||||
static var affineCamera: UIImage {
|
||||
UIImage(named: "Camera", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Close icon
|
||||
static var affineClose: UIImage {
|
||||
UIImage(named: "Close", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Big arrow up icon
|
||||
static var affineArrowUpBig: UIImage {
|
||||
UIImage(named: "ArrowUpBig", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Broom icon
|
||||
static var affineBroom: UIImage {
|
||||
UIImage(named: "Broom", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Bubble icon
|
||||
static var affineBubble: UIImage {
|
||||
UIImage(named: "Bubble", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Image icon
|
||||
static var affineImage: UIImage {
|
||||
UIImage(named: "Image", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Page icon
|
||||
static var affinePage: UIImage {
|
||||
UIImage(named: "Page", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Plus icon
|
||||
static var affinePlus: UIImage {
|
||||
UIImage(named: "Plus", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Settings icon
|
||||
static var affineSettings: UIImage {
|
||||
UIImage(named: "Settings", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
|
||||
/// Upload icon
|
||||
static var affineUpload: UIImage {
|
||||
UIImage(named: "Upload", in: .module, compatibleWith: nil) ?? UIImage()
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xEB",
|
||||
"green" : "0x96",
|
||||
"red" : "0x1E"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xEB",
|
||||
"green" : "0x96",
|
||||
"red" : "0x1E"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xEB",
|
||||
"green" : "0x96",
|
||||
"red" : "0x1E"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xEB",
|
||||
"green" : "0x96",
|
||||
"red" : "0x1E"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x7A",
|
||||
"green" : "0x7A",
|
||||
"red" : "0x7A"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xF3",
|
||||
"green" : "0xF3",
|
||||
"red" : "0xF3"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x14",
|
||||
"green" : "0x14",
|
||||
"red" : "0x14"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xF5",
|
||||
"green" : "0xF5",
|
||||
"red" : "0xF5"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x25",
|
||||
"green" : "0x25",
|
||||
"red" : "0x25"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xE6",
|
||||
"green" : "0xE6",
|
||||
"red" : "0xE6"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x41",
|
||||
"green" : "0x41",
|
||||
"red" : "0x41"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "255",
|
||||
"red" : "255"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xEB",
|
||||
"green" : "0x96",
|
||||
"red" : "0x1E"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xEB",
|
||||
"green" : "0x96",
|
||||
"red" : "0x1E"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x9F",
|
||||
"green" : "0x5F",
|
||||
"red" : "0x03"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x9F",
|
||||
"green" : "0x5F",
|
||||
"red" : "0x03"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xCB",
|
||||
"green" : "0x77",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xCB",
|
||||
"green" : "0x77",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x92",
|
||||
"green" : "0x92",
|
||||
"red" : "0x92"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x7A",
|
||||
"green" : "0x7A",
|
||||
"red" : "0x7A"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x14",
|
||||
"green" : "0x14",
|
||||
"red" : "0x14"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xE6",
|
||||
"green" : "0xE6",
|
||||
"red" : "0xE6"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x7A",
|
||||
"green" : "0x7A",
|
||||
"red" : "0x7A"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x92",
|
||||
"green" : "0x92",
|
||||
"red" : "0x92"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x92",
|
||||
"green" : "0x92",
|
||||
"red" : "0x92"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x7A",
|
||||
"green" : "0x7A",
|
||||
"red" : "0x7A"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "More Options Icon.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ArrowUpBig.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Left icon-5.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Broom.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Bubble.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Left icon-1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Camera.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Left icon.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Close.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Image.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user