Compare commits

...

7 commits

Author SHA1 Message Date
Jerry Zhang 771aff9539
Merge 6ba75846c1 into 51fecb01f5 2024-05-06 04:42:57 -05:00
Thomas Ricouard 51fecb01f5 Bump version to 1.10.39 2024-05-06 09:25:36 +02:00
Thomas Ricouard c29de44d8c Widget: Mentions only allow large size 2024-05-06 09:20:01 +02:00
Thomas Ricouard 1d79832544 Bump version to 1.10.38 2024-05-06 08:41:33 +02:00
Thomas Ricouard a37316c56f Lint 2024-05-06 08:38:37 +02:00
Thomas Ricouard 189e10f2b4 Widget: Add mentions widget 2024-05-06 08:37:58 +02:00
Jerry 6ba75846c1 Update Simplified Chinese localization 2024-05-04 14:55:25 +08:00
15 changed files with 521 additions and 229 deletions

View file

@ -124,6 +124,8 @@
9FF2FB632BE7F5D9001560CE /* HashtagPostsWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB5E2BE7F56F001560CE /* HashtagPostsWidgetConfiguration.swift */; };
9FF2FB672BE7F816001560CE /* PostsWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB652BE7F805001560CE /* PostsWidgetView.swift */; };
9FF2FB6A2BE7F84E001560CE /* SharedUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB682BE7F842001560CE /* SharedUtils.swift */; };
9FF2FB702BE8AE9D001560CE /* MentionWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB6E2BE8AE9B001560CE /* MentionWidgetConfiguration.swift */; };
9FF2FB712BE8AEA0001560CE /* MentionWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF2FB6C2BE8AE90001560CE /* MentionWidget.swift */; };
9FFF677C299B7B2C00FE700A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677B299B7B2C00FE700A /* Notifications */; };
9FFF6780299B7D2B00FE700A /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677F299B7D2B00FE700A /* DesignSystem */; };
9FFF6782299B7D3A00FE700A /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF6781299B7D3A00FE700A /* Account */; };
@ -298,6 +300,8 @@
9FF2FB602BE7F5A7001560CE /* HashtagPostsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagPostsWidget.swift; sourceTree = "<group>"; };
9FF2FB652BE7F805001560CE /* PostsWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsWidgetView.swift; sourceTree = "<group>"; };
9FF2FB682BE7F842001560CE /* SharedUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedUtils.swift; sourceTree = "<group>"; };
9FF2FB6C2BE8AE90001560CE /* MentionWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionWidget.swift; sourceTree = "<group>"; };
9FF2FB6E2BE8AE9B001560CE /* MentionWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionWidgetConfiguration.swift; sourceTree = "<group>"; };
B0BAB49E29B3D7A9008F54D7 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
C4CBB90B298A0DA3007E1707 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
C4FBCF6F298FD88A0015DF22 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
@ -479,9 +483,10 @@
9F7788CA2BE652B1004E6BEF /* IceCubesAppWidgetsExtension */ = {
isa = PBXGroup;
children = (
9FF2FB6B2BE8AE78001560CE /* MentionWidget */,
9FF2FB642BE7F7FA001560CE /* Shared */,
9FF2FB5D2BE7F559001560CE /* HashtagPostsWidget */,
9FF2FB5C2BE7F549001560CE /* LatestPosts */,
9FF2FB5C2BE7F549001560CE /* LatestPostsWidget */,
9F7788D72BE652B2004E6BEF /* IceCubesAppWidgetsExtensionExtension.entitlements */,
9F7788CB2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift */,
9F7788D12BE652B2004E6BEF /* Assets.xcassets */,
@ -641,13 +646,13 @@
path = Settings;
sourceTree = "<group>";
};
9FF2FB5C2BE7F549001560CE /* LatestPosts */ = {
9FF2FB5C2BE7F549001560CE /* LatestPostsWidget */ = {
isa = PBXGroup;
children = (
9F7788CD2BE652B1004E6BEF /* LatestPostsWidget.swift */,
9F7788CF2BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift */,
);
path = LatestPosts;
path = LatestPostsWidget;
sourceTree = "<group>";
};
9FF2FB5D2BE7F559001560CE /* HashtagPostsWidget */ = {
@ -668,6 +673,15 @@
path = Shared;
sourceTree = "<group>";
};
9FF2FB6B2BE8AE78001560CE /* MentionWidget */ = {
isa = PBXGroup;
children = (
9FF2FB6C2BE8AE90001560CE /* MentionWidget.swift */,
9FF2FB6E2BE8AE9B001560CE /* MentionWidgetConfiguration.swift */,
);
path = MentionWidget;
sourceTree = "<group>";
};
E9B576C029743F2A00BCE646 /* Localization */ = {
isa = PBXGroup;
children = (
@ -982,11 +996,13 @@
buildActionMask = 2147483647;
files = (
9FF2FB622BE7F5D5001560CE /* HashtagPostsWidget.swift in Sources */,
9FF2FB712BE8AEA0001560CE /* MentionWidget.swift in Sources */,
9F7788EA2BE65585004E6BEF /* AppAccountEntity.swift in Sources */,
9FF2FB6A2BE7F84E001560CE /* SharedUtils.swift in Sources */,
9F7788CE2BE652B1004E6BEF /* LatestPostsWidget.swift in Sources */,
9F7788EE2BE78D7B004E6BEF /* TimelineFilterEntity.swift in Sources */,
9F7788CC2BE652B1004E6BEF /* IceCubesAppWidgetsExtensionBundle.swift in Sources */,
9FF2FB702BE8AE9D001560CE /* MentionWidgetConfiguration.swift in Sources */,
9F7788D02BE652B1004E6BEF /* LatestPostsWidgetConfiguration.swift in Sources */,
9FF2FB672BE7F816001560CE /* PostsWidgetView.swift in Sources */,
9FF2FB632BE7F5D9001560CE /* HashtagPostsWidgetConfiguration.swift in Sources */,
@ -1141,7 +1157,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.37;
MARKETING_VERSION = 1.10.39;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1176,7 +1192,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.37;
MARKETING_VERSION = 1.10.39;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesNotifications";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1284,7 +1300,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.37;
MARKETING_VERSION = 1.10.39;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1318,7 +1334,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.37;
MARKETING_VERSION = 1.10.39;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1499,7 +1515,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.10.37;
MARKETING_VERSION = 1.10.39;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
PRODUCT_NAME = "Ice Cubes";
SDKROOT = auto;
@ -1554,7 +1570,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.10.37;
MARKETING_VERSION = 1.10.39;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp";
PRODUCT_NAME = "Ice Cubes";
SDKROOT = auto;
@ -1589,7 +1605,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.37;
MARKETING_VERSION = 1.10.39;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1624,7 +1640,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.10.37;
MARKETING_VERSION = 1.10.39;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).IceCubesApp.IceCubesActionExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;

View file

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1530"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9F7788C42BE652B1004E6BEF"
BuildableName = "IceCubesAppWidgetsExtensionExtension.appex"
BlueprintName = "IceCubesAppWidgetsExtensionExtension"
ReferencedContainer = "container:IceCubesApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9FBFE638292A715500C250E9"
BuildableName = "Ice Cubes.app"
BlueprintName = "IceCubesApp"
ReferencedContainer = "container:IceCubesApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9FBFE638292A715500C250E9"
BuildableName = "Ice Cubes.app"
BlueprintName = "IceCubesApp"
ReferencedContainer = "container:IceCubesApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = ""
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetDefaultView"
value = "timeline"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetFamily"
value = "systemMedium"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9FBFE638292A715500C250E9"
BuildableName = "Ice Cubes.app"
BlueprintName = "IceCubesApp"
ReferencedContainer = "container:IceCubesApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -7545,7 +7545,7 @@
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "已静音"
"value" : "已免打扰"
}
},
"zh-Hant" : {
@ -11583,8 +11583,8 @@
},
"zh-Hans" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Blocked accounts"
"state" : "translated",
"value" : "已屏蔽的用户"
}
},
"zh-Hant" : {
@ -17427,8 +17427,8 @@
},
"zh-Hans" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Muted accounts"
"state" : "translated",
"value" : "已免打扰的账户"
}
},
"zh-Hant" : {
@ -18391,126 +18391,6 @@
}
}
},
"action.cancel" : {
"comment" : "MARK: Common strings",
"extractionState" : "stale",
"localizations" : {
"be" : {
"stringUnit" : {
"state" : "translated",
"value" : "Скасаваць"
}
},
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancel·la"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Abbrechen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancel"
}
},
"en-GB" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancel"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancelar"
}
},
"eu" : {
"stringUnit" : {
"state" : "translated",
"value" : "Utzi"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Annuler"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Annulla"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "キャンセル"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "취소"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Avbryt"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Annuleer"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Anuluj"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancelar"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "İptal Et"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "Відміна"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "取消"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "取消"
}
}
}
},
"action.delete" : {
"extractionState" : "manual",
"localizations" : {
@ -20536,6 +20416,12 @@
"state" : "translated",
"value" : "Lesezeichen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "书签"
}
}
}
},
@ -26464,6 +26350,12 @@
"state" : "translated",
"value" : "Erkunden & Trends"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "探索 & 当下流行"
}
}
}
},
@ -28498,6 +28390,12 @@
"state" : "translated",
"value" : "Favoriten"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "喜欢"
}
}
}
},
@ -28508,6 +28406,12 @@
"state" : "translated",
"value" : "Föderierte Timeline"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "跨站时间线"
}
}
}
},
@ -30780,6 +30684,12 @@
"state" : "translated",
"value" : "Gefolgte Tags"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "正在关注的标签"
}
}
}
},
@ -30793,6 +30703,12 @@
"state" : "translated",
"value" : "Eigene Timeline"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "主页时间线"
}
}
}
},
@ -30803,6 +30719,12 @@
"state" : "translated",
"value" : "Bild"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "图片"
}
}
}
},
@ -30813,6 +30735,12 @@
"state" : "translated",
"value" : "Bild zum Veröffentlichen auf Mastodon"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "向 Mastodon 发布的图片"
}
}
}
},
@ -33072,6 +33000,12 @@
"state" : "translated",
"value" : "Listen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "列表"
}
}
}
},
@ -34035,6 +33969,12 @@
"state" : "translated",
"value" : "Lokale Timeline"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "本地时间线"
}
}
}
},
@ -34045,6 +33985,12 @@
"state" : "translated",
"value" : "Erwähnungen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "提及"
}
}
}
},
@ -34764,6 +34710,12 @@
"state" : "translated",
"value" : "Neuer Beitrag"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "新嘟文"
}
}
}
},
@ -34774,6 +34726,12 @@
"state" : "translated",
"value" : "Benachrichtigungen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "通知"
}
}
}
},
@ -39865,6 +39823,12 @@
"state" : "translated",
"value" : "Ice Cubes öffnen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "打开 Ice Cubes"
}
}
}
},
@ -39875,6 +39839,12 @@
"state" : "translated",
"value" : "Mit einem Tab öffnen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "打开标签页"
}
}
}
},
@ -39885,6 +39855,12 @@
"state" : "translated",
"value" : "Die App in einem vorgegebenen Tab öffnen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "在特定标签页上打开应用"
}
}
}
},
@ -40132,6 +40108,12 @@
"state" : "translated",
"value" : "Einen Status veröffentlichen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "发布状态"
}
}
}
},
@ -40145,6 +40127,12 @@
"state" : "translated",
"value" : "Ein Bild auf Mastodon veröffentlichen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "向 Mastodon 发布图片"
}
}
}
},
@ -40155,6 +40143,12 @@
"state" : "translated",
"value" : "Inhalt veröffentlichen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "发布内容"
}
}
}
},
@ -40166,6 +40160,12 @@
"state" : "translated",
"value" : "Bilder veröffentlichen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "发布图片"
}
}
}
},
@ -40177,6 +40177,12 @@
"state" : "translated",
"value" : "Status auf Mastodon veröffentlichen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "向 Mastodon 发布状态"
}
}
}
},
@ -40193,6 +40199,12 @@
"state" : "translated",
"value" : "Private Nachrichten"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "私信"
}
}
}
},
@ -40203,6 +40215,12 @@
"state" : "translated",
"value" : "Profil"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "个人资料"
}
}
}
},
@ -40693,6 +40711,12 @@
"state" : "translated",
"value" : "Ausgewählter Tab"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "选择标签页"
}
}
}
},
@ -40712,6 +40736,12 @@
"state" : "translated",
"value" : "Einstellungen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "设置"
}
}
}
},
@ -74897,6 +74927,12 @@
"state" : "translated",
"value" : "Tab"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "标签页"
}
}
}
},
@ -80432,6 +80468,12 @@
"state" : "translated",
"value" : "Links im Trend"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "当下流行的链接"
}
}
}
},
@ -80442,6 +80484,12 @@
"state" : "translated",
"value" : "Timeline im Trend"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "当下流行时间线"
}
}
}
},
@ -80818,6 +80866,12 @@
"state" : "translated",
"value" : "Ice Cubes benutzen, um einen Status auf Mastodon zu veröffentlichen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "使用 Ice Cubes 向 Mastodon 发布状态"
}
}
}
},
@ -80829,6 +80883,12 @@
"state" : "translated",
"value" : "Ice Cubes benutzen, um einen Status mit einem Bild auf Mastodon zu veröffentlichen"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "使用 Ice Cubes 向 Mastodon 发布带有图片的状态"
}
}
}
},

View file

@ -1,13 +1,12 @@
import AppIntents
import AppAccount
import AppIntents
import Env
import Foundation
import Models
import Network
extension IntentDescription: @unchecked Sendable { }
extension TypeDisplayRepresentation: @unchecked Sendable { }
extension IntentDescription: @unchecked Sendable {}
extension TypeDisplayRepresentation: @unchecked Sendable {}
public struct AppAccountEntity: Identifiable, AppEntity {
public var id: String { account.id }
@ -24,8 +23,8 @@ public struct AppAccountEntity: Identifiable, AppEntity {
}
public struct DefaultAppAccountEntityQuery: EntityQuery {
public init() { }
public init() {}
public func entities(for identifiers: [AppAccountEntity.ID]) async throws -> [AppAccountEntity] {
return await AppAccountsManager.shared.availableAccounts.filter { account in
identifiers.contains { id in

View file

@ -1,5 +1,5 @@
import AppIntents
import AppAccount
import AppIntents
import Env
import Foundation
import Models
@ -21,14 +21,14 @@ public struct TimelineFilterEntity: Identifiable, AppEntity {
}
public struct DefaultTimelineEntityQuery: EntityQuery {
public init() { }
public func entities(for identifiers: [TimelineFilter.ID]) async throws -> [TimelineFilterEntity] {
[.home, .trending, .federated, .local].map{ .init(timeline: $0) }
public init() {}
public func entities(for _: [TimelineFilter.ID]) async throws -> [TimelineFilterEntity] {
[.home, .trending, .federated, .local].map { .init(timeline: $0) }
}
public func suggestedEntities() async throws -> [TimelineFilterEntity] {
[.home, .trending, .federated, .local].map{ .init(timeline: $0) }
[.home, .trending, .federated, .local].map { .init(timeline: $0) }
}
public func defaultResult() async -> TimelineFilterEntity? {

View file

@ -1,60 +1,61 @@
import WidgetKit
import SwiftUI
import Network
import DesignSystem
import Models
import Network
import SwiftUI
import Timeline
import WidgetKit
struct HashtagPostsWidgetProvider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> PostsWidgetEntry {
func placeholder(in _: Context) -> PostsWidgetEntry {
.init(date: Date(),
timeline: .hashtag(tag: "Mastodon", accountId: nil),
title: "#Mastodon",
statuses: [.placeholder()],
images: [:])
}
func snapshot(for configuration: HashtagPostsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry {
if let entry = await timeline(for: configuration, context: context).entries.first {
return entry
}
return .init(date: Date(),
timeline: .hashtag(tag: "Mastodon", accountId: nil),
title: "#Mastodon",
statuses: [],
images: [:])
}
func timeline(for configuration: HashtagPostsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
await timeline(for: configuration, context: context)
}
private func timeline(for configuration: HashtagPostsWidgetConfiguration, context: Context) async -> Timeline<PostsWidgetEntry> {
do {
let statuses = await loadStatuses(for: .hashtag(tag: configuration.hashgtag, accountId: nil),
let timeline: TimelineFilter = .hashtag(tag: configuration.hashgtag, accountId: nil)
let statuses = await loadStatuses(for: timeline,
account: configuration.account,
widgetFamily: context.family)
let images = try await loadImages(urls: statuses.map{ $0.account.avatar } )
let images = try await loadImages(urls: statuses.map { $0.account.avatar })
return Timeline(entries: [.init(date: Date(),
timeline: .hashtag(tag: configuration.hashgtag,
accountId: nil),
statuses: statuses,
images: images)], policy: .atEnd)
title: timeline.title,
statuses: statuses,
images: images)], policy: .atEnd)
} catch {
return Timeline(entries: [.init(date: Date(),
timeline: .hashtag(tag: "Mastodon", accountId: nil),
title: "#Mastodon",
statuses: [],
images: [:])],
policy: .atEnd)
policy: .atEnd)
}
}
}
struct HashtagPostsWidget: Widget {
let kind: String = "HashtagPostsWidget"
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind,
intent: HashtagPostsWidgetConfiguration.self,
provider: HashtagPostsWidgetProvider()) { entry in
provider: HashtagPostsWidgetProvider())
{ entry in
PostsWidgetView(entry: entry)
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
}
@ -64,12 +65,11 @@ struct HashtagPostsWidget: Widget {
}
}
#Preview(as: .systemMedium) {
HashtagPostsWidget()
} timeline: {
PostsWidgetEntry(date: .now,
timeline: .hashtag(tag: "Matodon", accountId: nil),
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
images: [:])
title: "#Mastodon",
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
images: [:])
}

View file

@ -1,13 +1,13 @@
import WidgetKit
import AppIntents
import WidgetKit
struct HashtagPostsWidgetConfiguration: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Configuration"
static let description = IntentDescription("Choose the account and hashtag for this widget")
@Parameter(title: "Account")
var account: AppAccountEntity
@Parameter(title: "Hashtag")
var hashgtag: String
}

View file

@ -1,10 +1,11 @@
import WidgetKit
import SwiftUI
import WidgetKit
@main
struct IceCubesAppWidgetsExtensionBundle: WidgetBundle {
var body: some Widget {
LatestPostsWidget()
HashtagPostsWidget()
MentionsWidget()
}
}

View file

@ -1,50 +1,51 @@
import WidgetKit
import SwiftUI
import Network
import DesignSystem
import Models
import Network
import SwiftUI
import Timeline
import WidgetKit
struct LatestPostsWidgetProvider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> PostsWidgetEntry {
func placeholder(in _: Context) -> PostsWidgetEntry {
.init(date: Date(),
timeline: .home,
title: "Home",
statuses: [.placeholder()],
images: [:])
}
func snapshot(for configuration: LatestPostsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry {
if let entry = await timeline(for: configuration, context: context).entries.first {
return entry
}
return .init(date: Date(),
timeline: .home, statuses: [],
title: configuration.timeline.timeline.title,
statuses: [],
images: [:])
}
func timeline(for configuration: LatestPostsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
await timeline(for: configuration, context: context)
}
private func timeline(for configuration: LatestPostsWidgetConfiguration, context: Context) async -> Timeline<PostsWidgetEntry> {
do {
let statuses = await loadStatuses(for: configuration.timeline.timeline,
account: configuration.account,
widgetFamily: context.family)
let images = try await loadImages(urls: statuses.map{ $0.account.avatar } )
let images = try await loadImages(urls: statuses.map { $0.account.avatar })
return Timeline(entries: [.init(date: Date(),
timeline: configuration.timeline.timeline,
statuses: statuses,
images: images)], policy: .atEnd)
title: configuration.timeline.timeline.title,
statuses: statuses,
images: images)], policy: .atEnd)
} catch {
return Timeline(entries: [.init(date: Date(),
timeline: .home,
title: configuration.timeline.timeline.title,
statuses: [],
images: [:])],
policy: .atEnd)
policy: .atEnd)
}
}
private func loadImages(urls: [URL]) async throws -> [URL: UIImage] {
try await withThrowingTaskGroup(of: (URL, UIImage?).self) { group in
for url in urls {
@ -53,13 +54,13 @@ struct LatestPostsWidgetProvider: AppIntentTimelineProvider {
return (url, UIImage(data: response.0))
}
}
var images: [URL: UIImage] = [:]
for try await (url, image) in group {
images[url] = image
}
return images
}
}
@ -67,11 +68,12 @@ struct LatestPostsWidgetProvider: AppIntentTimelineProvider {
struct LatestPostsWidget: Widget {
let kind: String = "LatestPostsWidget"
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind,
intent: LatestPostsWidgetConfiguration.self,
provider: LatestPostsWidgetProvider()) { entry in
provider: LatestPostsWidgetProvider())
{ entry in
PostsWidgetView(entry: entry)
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
}
@ -81,12 +83,11 @@ struct LatestPostsWidget: Widget {
}
}
#Preview(as: .systemMedium) {
LatestPostsWidget()
} timeline: {
PostsWidgetEntry(date: .now,
timeline: .home,
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
images: [:])
PostsWidgetEntry(date: .now,
title: "Mastodon",
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
images: [:])
}

View file

@ -1,13 +1,13 @@
import WidgetKit
import AppIntents
import WidgetKit
struct LatestPostsWidgetConfiguration: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Configuration"
static let description = IntentDescription("Choose the account and timeline for this widget")
@Parameter(title: "Account")
var account: AppAccountEntity
@Parameter(title: "Timeline")
var timeline: TimelineFilterEntity
}

View file

@ -0,0 +1,81 @@
import DesignSystem
import Models
import Network
import SwiftUI
import Timeline
import WidgetKit
struct MentionsWidgetProvider: AppIntentTimelineProvider {
func placeholder(in _: Context) -> PostsWidgetEntry {
.init(date: Date(),
title: "Mentions",
statuses: [.placeholder()],
images: [:])
}
func snapshot(for configuration: MentionsWidgetConfiguration, in context: Context) async -> PostsWidgetEntry {
if let entry = await timeline(for: configuration, context: context).entries.first {
return entry
}
return .init(date: Date(),
title: "Mentions",
statuses: [],
images: [:])
}
func timeline(for configuration: MentionsWidgetConfiguration, in context: Context) async -> Timeline<PostsWidgetEntry> {
await timeline(for: configuration, context: context)
}
private func timeline(for configuration: MentionsWidgetConfiguration, context _: Context) async -> Timeline<PostsWidgetEntry> {
do {
let client = Client(server: configuration.account.account.server,
oauthToken: configuration.account.account.oauthToken)
var excludedTypes = Models.Notification.NotificationType.allCases
excludedTypes.removeAll(where: { $0 == .mention })
var notifications: [Models.Notification] =
try await client.get(endpoint: Notifications.notifications(minId: nil,
maxId: nil,
types: excludedTypes.map(\.rawValue),
limit: 5))
let statuses = notifications.compactMap { $0.status }
let images = try await loadImages(urls: statuses.map { $0.account.avatar })
return Timeline(entries: [.init(date: Date(),
title: "Mentions",
statuses: statuses,
images: images)], policy: .atEnd)
} catch {
return Timeline(entries: [.init(date: Date(),
title: "Mentions",
statuses: [],
images: [:])],
policy: .atEnd)
}
}
}
struct MentionsWidget: Widget {
let kind: String = "MentionsWidget"
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind,
intent: MentionsWidgetConfiguration.self,
provider: MentionsWidgetProvider())
{ entry in
PostsWidgetView(entry: entry)
.containerBackground(Color("WidgetBackground").gradient, for: .widget)
}
.configurationDisplayName("Mentions")
.description("Show the latest mentions for the selected account.")
.supportedFamilies([.systemLarge, .systemExtraLarge])
}
}
#Preview(as: .systemMedium) {
MentionsWidget()
} timeline: {
PostsWidgetEntry(date: .now,
title: "Mentions",
statuses: [.placeholder(), .placeholder(), .placeholder(), .placeholder()],
images: [:])
}

View file

@ -0,0 +1,18 @@
import AppIntents
import WidgetKit
struct MentionsWidgetConfiguration: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Configuration"
static let description = IntentDescription("Choose the account for this widget")
@Parameter(title: "Account")
var account: AppAccountEntity
}
extension MentionsWidgetConfiguration {
static var previewAccount: MentionsWidgetConfiguration {
let intent = MentionsWidgetConfiguration()
intent.account = .init(account: .init(server: "Test", accountName: "Test account"))
return intent
}
}

View file

@ -1,23 +1,23 @@
import WidgetKit
import SwiftUI
import Network
import DesignSystem
import Models
import Network
import SwiftUI
import Timeline
import WidgetKit
struct PostsWidgetEntry: TimelineEntry {
let date: Date
let timeline: TimelineFilter
let title: String
let statuses: [Status]
let images: [URL: UIImage]
}
struct PostsWidgetView : View {
struct PostsWidgetView: View {
var entry: LatestPostsWidgetProvider.Entry
@Environment(\.widgetFamily) var family
@Environment(\.redactionReasons) var redacted
var contentLineLimit: Int {
switch family {
case .systemSmall, .systemMedium:
@ -26,6 +26,7 @@ struct PostsWidgetView : View {
return 2
}
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
headerView
@ -36,10 +37,10 @@ struct PostsWidgetView : View {
}
.frame(maxWidth: .infinity)
}
private var headerView: some View {
HStack {
Text(entry.timeline.title)
Text(entry.title)
Spacer()
Image(systemName: "cube")
}
@ -47,7 +48,7 @@ struct PostsWidgetView : View {
.fontWeight(.bold)
.foregroundStyle(Color("AccentColor"))
}
@ViewBuilder
private func makeStatusView(_ status: Status) -> some View {
if let url = URL(string: status.url ?? "") {
@ -63,7 +64,7 @@ struct PostsWidgetView : View {
})
}
}
private func makeStatusHeaderView(_ status: Status) -> some View {
HStack(alignment: .center, spacing: 4) {
if let image = entry.images[status.account.avatar] {

View file

@ -1,30 +1,31 @@
import StatusKit
import WidgetKit
import Timeline
import Foundation
import UIKit
import AppAccount
import Foundation
import Models
import Network
import StatusKit
import Timeline
import UIKit
import WidgetKit
func loadStatuses(for timeline: TimelineFilter,
account: AppAccountEntity,
widgetFamily: WidgetFamily) async -> [Status] {
widgetFamily: WidgetFamily) async -> [Status]
{
let client = Client(server: account.account.server, oauthToken: account.account.oauthToken)
do {
var statuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: nil,
maxId: nil,
minId: nil,
offset: nil))
statuses = statuses.filter{ $0.reblog == nil && !$0.content.asRawText.isEmpty }
statuses = statuses.filter { $0.reblog == nil && !$0.content.asRawText.isEmpty }
switch widgetFamily {
case .systemSmall, .systemMedium:
if statuses.count >= 1 {
statuses = statuses.prefix(upTo: 1).map{ $0 }
statuses = statuses.prefix(upTo: 1).map { $0 }
}
case .systemLarge, .systemExtraLarge:
if statuses.count >= 5 {
statuses = statuses.prefix(upTo: 5).map{ $0 }
statuses = statuses.prefix(upTo: 5).map { $0 }
}
default:
break
@ -34,7 +35,7 @@ func loadStatuses(for timeline: TimelineFilter,
return []
}
}
func loadImages(urls: [URL]) async throws -> [URL: UIImage] {
try await withThrowingTaskGroup(of: (URL, UIImage?).self) { group in
for url in urls {
@ -43,13 +44,13 @@ func loadImages(urls: [URL]) async throws -> [URL: UIImage] {
return (url, UIImage(data: response.0))
}
}
var images: [URL: UIImage] = [:]
for try await (url, image) in group {
images[url] = image
}
return images
}
}

View file

@ -765,7 +765,7 @@ public extension StatusEditor {
error: nil
))
}
url.stopAccessingSecurityScopedResource()
}