mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-03-28 04:25:28 +00:00
Merge branch 'main' into tip-system
This commit is contained in:
commit
7b9544d1e0
22 changed files with 281 additions and 131 deletions
|
@ -1514,7 +1514,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIconAlternate0 AppIconAlternate6 AppIconAlternate7 AppIconAlternate8 AppIconAlternate10 AppIconAlternate11 AppIconAlternate12 AppIconAlternate13 AppIconAlternate14 AppIconAlternate15 AppIconAlternate16 AppIconAlternate17 AppIconAlternate19 AppIconAlternate18 AppIconAlternate20 AppIconAlternate21 AppIconAlternate22 AppIconAlternate23 AppIconAlternate24 AppIconAlternate25 AppIconAlternate26 AppIconAlternate27 AppIconAlternate28 AppIconAlternate29 AppIconAlternate30 AppIconAlternate31 AppIconAlternate32 AppIconAlternate33 AppIconAlternate34 AppIconAlternate35 AppIconAlternate36 AppIconAlternate37 AppIconAlternate38 AppIconAlternate39 AppIconAlternate40 AppIconAlternate42 AppIconAlternate2 AppIconAlternate41 AppIconAlternate45 AppIconAlternate44 AppIconAlternate1 AppIconAlternate4 AppIconAlternate3 AppIconAlternate5 AppIconAlternate46 AppIconAlternate9 AppIconAlternate43";
|
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIconAlternate0 AppIconAlternate6 AppIconAlternate7 AppIconAlternate8 AppIconAlternate10 AppIconAlternate11 AppIconAlternate12 AppIconAlternate13 AppIconAlternate14 AppIconAlternate15 AppIconAlternate16 AppIconAlternate17 AppIconAlternate19 AppIconAlternate18 AppIconAlternate20 AppIconAlternate21 AppIconAlternate22 AppIconAlternate23 AppIconAlternate24 AppIconAlternate25 AppIconAlternate26 AppIconAlternate27 AppIconAlternate28 AppIconAlternate29 AppIconAlternate30 AppIconAlternate31 AppIconAlternate32 AppIconAlternate33 AppIconAlternate34 AppIconAlternate35 AppIconAlternate36 AppIconAlternate37 AppIconAlternate38 AppIconAlternate39 AppIconAlternate40 AppIconAlternate42 AppIconAlternate2 AppIconAlternate41 AppIconAlternate45 AppIconAlternate44 AppIconAlternate1 AppIconAlternate4 AppIconAlternate3 AppIconAlternate5 AppIconAlternate46 AppIconAlternate9 AppIconAlternate49 AppIconAlternate48 AppIconAlternate47 AppIconAlternate43";
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||||
|
@ -1570,7 +1570,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIconAlternate0 AppIconAlternate6 AppIconAlternate7 AppIconAlternate8 AppIconAlternate10 AppIconAlternate11 AppIconAlternate12 AppIconAlternate13 AppIconAlternate14 AppIconAlternate15 AppIconAlternate16 AppIconAlternate17 AppIconAlternate19 AppIconAlternate18 AppIconAlternate20 AppIconAlternate21 AppIconAlternate22 AppIconAlternate23 AppIconAlternate24 AppIconAlternate25 AppIconAlternate26 AppIconAlternate27 AppIconAlternate28 AppIconAlternate29 AppIconAlternate30 AppIconAlternate31 AppIconAlternate32 AppIconAlternate33 AppIconAlternate34 AppIconAlternate35 AppIconAlternate36 AppIconAlternate37 AppIconAlternate38 AppIconAlternate39 AppIconAlternate40 AppIconAlternate42 AppIconAlternate2 AppIconAlternate41 AppIconAlternate45 AppIconAlternate44 AppIconAlternate1 AppIconAlternate4 AppIconAlternate3 AppIconAlternate5 AppIconAlternate46 AppIconAlternate9 AppIconAlternate43";
|
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIconAlternate0 AppIconAlternate6 AppIconAlternate7 AppIconAlternate8 AppIconAlternate10 AppIconAlternate11 AppIconAlternate12 AppIconAlternate13 AppIconAlternate14 AppIconAlternate15 AppIconAlternate16 AppIconAlternate17 AppIconAlternate19 AppIconAlternate18 AppIconAlternate20 AppIconAlternate21 AppIconAlternate22 AppIconAlternate23 AppIconAlternate24 AppIconAlternate25 AppIconAlternate26 AppIconAlternate27 AppIconAlternate28 AppIconAlternate29 AppIconAlternate30 AppIconAlternate31 AppIconAlternate32 AppIconAlternate33 AppIconAlternate34 AppIconAlternate35 AppIconAlternate36 AppIconAlternate37 AppIconAlternate38 AppIconAlternate39 AppIconAlternate40 AppIconAlternate42 AppIconAlternate2 AppIconAlternate41 AppIconAlternate45 AppIconAlternate44 AppIconAlternate1 AppIconAlternate4 AppIconAlternate3 AppIconAlternate5 AppIconAlternate46 AppIconAlternate9 AppIconAlternate49 AppIconAlternate48 AppIconAlternate47 AppIconAlternate43";
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||||
|
|
|
@ -122,6 +122,7 @@ struct AppView: View {
|
||||||
.tag(tab)
|
.tag(tab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.id(availableTabs.count) /// Resets the TabView state when the number of tabs changes to avoid navigation bar issues and prevent crashes
|
||||||
.introspect(.tabView, on: .iOS(.v17, .v18)) { (tabview: UITabBarController) in
|
.introspect(.tabView, on: .iOS(.v17, .v18)) { (tabview: UITabBarController) in
|
||||||
tabview.tabBar.isHidden = horizontalSizeClass == .regular
|
tabview.tabBar.isHidden = horizontalSizeClass == .regular
|
||||||
tabview.customizableViewControllers = []
|
tabview.customizableViewControllers = []
|
||||||
|
|
|
@ -136,11 +136,12 @@ struct AddAccountView: View {
|
||||||
instance = nil
|
instance = nil
|
||||||
instanceFetchError = nil
|
instanceFetchError = nil
|
||||||
}
|
}
|
||||||
} catch _ as DecodingError {
|
} catch _ as ServerError {
|
||||||
instance = nil
|
instance = nil
|
||||||
instanceFetchError = "account.add.error.instance-not-supported"
|
instanceFetchError = "account.add.error.instance-not-supported"
|
||||||
} catch {
|
} catch {
|
||||||
instance = nil
|
instance = nil
|
||||||
|
instanceFetchError = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ struct IconSelectorView: View {
|
||||||
case alt39, alt40, alt41, alt42, alt43
|
case alt39, alt40, alt41, alt42, alt43
|
||||||
case alt44, alt45
|
case alt44, alt45
|
||||||
case alt46
|
case alt46
|
||||||
|
case alt47, alt48
|
||||||
|
case alt49
|
||||||
|
|
||||||
var appIconName: String {
|
var appIconName: String {
|
||||||
return "AppIconAlternate\(rawValue)"
|
return "AppIconAlternate\(rawValue)"
|
||||||
|
@ -51,6 +53,8 @@ struct IconSelectorView: View {
|
||||||
IconSelector(title: "\("settings.app.icon.designed-by".localized) Duncan Horne", icons: [.alt38]),
|
IconSelector(title: "\("settings.app.icon.designed-by".localized) Duncan Horne", icons: [.alt38]),
|
||||||
IconSelector(title: "\("settings.app.icon.designed-by".localized) BeAware@social.beaware.live", icons: [.alt39, .alt40, .alt41, .alt42, .alt43]),
|
IconSelector(title: "\("settings.app.icon.designed-by".localized) BeAware@social.beaware.live", icons: [.alt39, .alt40, .alt41, .alt42, .alt43]),
|
||||||
IconSelector(title: "\("settings.app.icon.designed-by".localized) Simone Margio", icons: [.alt44, .alt45]),
|
IconSelector(title: "\("settings.app.icon.designed-by".localized) Simone Margio", icons: [.alt44, .alt45]),
|
||||||
|
IconSelector(title: "\("settings.app.icon.designed-by".localized) Peter Broqvist (@PKB)", icons: [.alt47, .alt48]),
|
||||||
|
IconSelector(title: "\("settings.app.icon.designed-by".localized) Oz Tsori (@oztsori)", icons: [.alt49]),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,17 +15,19 @@ struct ToolbarTab: ToolbarContent {
|
||||||
|
|
||||||
var body: some ToolbarContent {
|
var body: some ToolbarContent {
|
||||||
if !isSecondaryColumn {
|
if !isSecondaryColumn {
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
if horizontalSizeClass == .regular {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
Button {
|
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
withAnimation {
|
Button {
|
||||||
userPreferences.isSidebarExpanded.toggle()
|
withAnimation {
|
||||||
}
|
userPreferences.isSidebarExpanded.toggle()
|
||||||
} label: {
|
}
|
||||||
if userPreferences.isSidebarExpanded {
|
} label: {
|
||||||
Image(systemName: "sidebar.squares.left")
|
if userPreferences.isSidebarExpanded {
|
||||||
} else {
|
Image(systemName: "sidebar.squares.left")
|
||||||
Image(systemName: "sidebar.left")
|
} else {
|
||||||
|
Image(systemName: "sidebar.left")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 143 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "AppIconAlternate47-fs8.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 234 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "AppIconAlternate48-fs8.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "AppIconAlternate49-fs8.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -710,6 +710,12 @@
|
||||||
"%@ ist keine valide Instanz" : {
|
"%@ ist keine valide Instanz" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "%@ no es una instancia válida"
|
||||||
|
}
|
||||||
|
},
|
||||||
"uk" : {
|
"uk" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
@ -13521,6 +13527,12 @@
|
||||||
"value" : "Delete avatar"
|
"value" : "Delete avatar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Eliminar imagen de perfil"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eu" : {
|
"eu" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
@ -13539,6 +13551,12 @@
|
||||||
"value" : "Verwijder profielfoto"
|
"value" : "Verwijder profielfoto"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Avatarı Sil"
|
||||||
|
}
|
||||||
|
},
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
@ -14032,6 +14050,12 @@
|
||||||
"value" : "Delete header"
|
"value" : "Delete header"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Eliminar cabecera"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eu" : {
|
"eu" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
@ -14050,6 +14074,12 @@
|
||||||
"value" : "Verwijder omslagfoto"
|
"value" : "Verwijder omslagfoto"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Başlığı Sil"
|
||||||
|
}
|
||||||
|
},
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
@ -28582,8 +28612,8 @@
|
||||||
},
|
},
|
||||||
"es" : {
|
"es" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Enlaces que son tendencia"
|
"value" : "Noticias"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eu" : {
|
"eu" : {
|
||||||
|
@ -28642,8 +28672,8 @@
|
||||||
},
|
},
|
||||||
"tr" : {
|
"tr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Yükselişteki Bağlantılar"
|
"value" : "Trend Bağlantılar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uk" : {
|
"uk" : {
|
||||||
|
@ -28762,7 +28792,7 @@
|
||||||
"tr" : {
|
"tr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Yükselişteki Gönderiler"
|
"value" : "Trend Gönderiler"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uk" : {
|
"uk" : {
|
||||||
|
@ -28881,7 +28911,7 @@
|
||||||
"tr" : {
|
"tr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Yükselişteki Etiketler"
|
"value" : "Trend Etiketler"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uk" : {
|
"uk" : {
|
||||||
|
@ -35814,7 +35844,7 @@
|
||||||
},
|
},
|
||||||
"es" : {
|
"es" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Ajustes ..."
|
"value" : "Ajustes ..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -35874,7 +35904,7 @@
|
||||||
},
|
},
|
||||||
"tr" : {
|
"tr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Ayarlar ..."
|
"value" : "Ayarlar ..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -41454,6 +41484,12 @@
|
||||||
"value" : "Einen Status veröffentlichen"
|
"value" : "Einen Status veröffentlichen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Hacer una publicación"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eu" : {
|
"eu" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
@ -41633,6 +41669,12 @@
|
||||||
"value" : "Bilder veröffentlichen"
|
"value" : "Bilder veröffentlichen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Publicar imágenes"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eu" : {
|
"eu" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
@ -41674,6 +41716,12 @@
|
||||||
"value" : "Status auf Mastodon veröffentlichen"
|
"value" : "Status auf Mastodon veröffentlichen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Hacer una publicación en Mastodon"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eu" : {
|
"eu" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
@ -62385,7 +62433,26 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Show Content Gradient" : {
|
"Show Content Gradient" : {
|
||||||
|
"localizations" : {
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Mostrar contenido con gradiente"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "İçerik Gradyanını Göster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "启用渐变嘟文背景"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status.action.bookmark" : {
|
"status.action.bookmark" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
|
@ -78056,7 +78123,7 @@
|
||||||
"tr" : {
|
"tr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Yükselişte"
|
"value" : "Trend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uk" : {
|
"uk" : {
|
||||||
|
@ -82904,14 +82971,14 @@
|
||||||
"plural" : {
|
"plural" : {
|
||||||
"one" : {
|
"one" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "%lld persona hablando"
|
"value" : "%lld publicación"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"other" : {
|
"other" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "%lld personas hablando"
|
"value" : "%lld publicaciones"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83096,14 +83163,14 @@
|
||||||
"plural" : {
|
"plural" : {
|
||||||
"one" : {
|
"one" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "%lld kişi konuşuyor"
|
"value" : "%lld gönderi"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"other" : {
|
"other" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "%lld kişi konuşuyor"
|
"value" : "%lld gönderi"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83132,13 +83199,13 @@
|
||||||
"plural" : {
|
"plural" : {
|
||||||
"one" : {
|
"one" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "%lld 人正在参与讨论"
|
"value" : "%lld 人正在参与讨论"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"other" : {
|
"other" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "%lld 人正在参与讨论"
|
"value" : "%lld 人正在参与讨论"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83254,6 +83321,12 @@
|
||||||
"value" : "Ice Cubes benutzen, um einen Status auf Mastodon zu veröffentlichen"
|
"value" : "Ice Cubes benutzen, um einen Status auf Mastodon zu veröffentlichen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Usar Ice Cubes para hacer una publicación en Mastodon"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eu" : {
|
"eu" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
@ -83295,6 +83368,12 @@
|
||||||
"value" : "Ice Cubes benutzen, um einen Status mit einem Bild auf Mastodon zu veröffentlichen"
|
"value" : "Ice Cubes benutzen, um einen Status mit einem Bild auf Mastodon zu veröffentlichen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Usar Ice Cubes para hacer una publicación con una imagen en Mastodon"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eu" : {
|
"eu" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
|
@ -83421,4 +83500,4 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version" : "1.0"
|
"version" : "1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ public let availableColorsSets: [ColorSetCouple] =
|
||||||
.init(light: ConstellationLight(), dark: ConstellationDark()),
|
.init(light: ConstellationLight(), dark: ConstellationDark()),
|
||||||
.init(light: ThreadsLight(), dark: ThreadsDark())]
|
.init(light: ThreadsLight(), dark: ThreadsDark())]
|
||||||
|
|
||||||
public protocol ColorSet {
|
public protocol ColorSet: Sendable {
|
||||||
var name: ColorSetName { get }
|
var name: ColorSetName { get }
|
||||||
var scheme: ColorScheme { get }
|
var scheme: ColorScheme { get }
|
||||||
var tintColor: Color { get set }
|
var tintColor: Color { get set }
|
||||||
|
@ -18,11 +18,11 @@ public protocol ColorSet {
|
||||||
var labelColor: Color { get set }
|
var labelColor: Color { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ColorScheme: String {
|
public enum ColorScheme: String, Sendable {
|
||||||
case dark, light
|
case dark, light
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ColorSetName: String {
|
public enum ColorSetName: String, Sendable {
|
||||||
case iceCubeDark = "Ice Cube - Dark"
|
case iceCubeDark = "Ice Cube - Dark"
|
||||||
case iceCubeLight = "Ice Cube - Light"
|
case iceCubeLight = "Ice Cube - Light"
|
||||||
case iceCubeNeonDark = "Ice Cube Neon - Dark"
|
case iceCubeNeonDark = "Ice Cube Neon - Dark"
|
||||||
|
@ -39,7 +39,7 @@ public enum ColorSetName: String {
|
||||||
case threadsDark = "Threads - Dark"
|
case threadsDark = "Threads - Dark"
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ColorSetCouple: Identifiable {
|
public struct ColorSetCouple: Identifiable, Sendable {
|
||||||
public var id: String {
|
public var id: String {
|
||||||
dark.name.rawValue + light.name.rawValue
|
dark.name.rawValue + light.name.rawValue
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,23 @@ struct MediaUIAttachmentImageView: View {
|
||||||
.progressViewStyle(.circular)
|
.progressViewStyle(.circular)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.draggable(MediaUIImageTransferable(url: url))
|
||||||
|
.contextMenu {
|
||||||
|
MediaUIShareLink(url: url, type: .image)
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
let transferable = MediaUIImageTransferable(url: url)
|
||||||
|
UIPasteboard.general.image = UIImage(data: await transferable.fetchData())
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("status.media.contextmenu.copy", systemImage: "doc.on.doc")
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
UIPasteboard.general.url = url
|
||||||
|
} label: {
|
||||||
|
Label("status.action.copy-link", systemImage: "link")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
Packages/MediaUI/Sources/MediaUI/MediaUIShareLink.swift
Normal file
16
Packages/MediaUI/Sources/MediaUI/MediaUIShareLink.swift
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MediaUIShareLink: View, @unchecked Sendable {
|
||||||
|
let url: URL
|
||||||
|
let type: DisplayType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if type == .image {
|
||||||
|
let transferable = MediaUIImageTransferable(url: url)
|
||||||
|
ShareLink(item: transferable, preview: .init("status.media.contextmenu.share",
|
||||||
|
image: transferable))
|
||||||
|
} else {
|
||||||
|
ShareLink(item: url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,13 +6,7 @@ struct ShareToolbarItem: ToolbarContent, @unchecked Sendable {
|
||||||
|
|
||||||
var body: some ToolbarContent {
|
var body: some ToolbarContent {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
if type == .image {
|
MediaUIShareLink(url: url, type: type)
|
||||||
let transferable = MediaUIImageTransferable(url: url)
|
|
||||||
ShareLink(item: transferable, preview: .init("status.media.contextmenu.share",
|
|
||||||
image: transferable))
|
|
||||||
} else {
|
|
||||||
ShareLink(item: url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ public extension StatusEditor {
|
||||||
didSet {
|
didSet {
|
||||||
if let itemsProvider {
|
if let itemsProvider {
|
||||||
mediaContainers = []
|
mediaContainers = []
|
||||||
processItemsProvider(items: itemsProvider)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,7 @@ public struct StatusRowView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.if(viewModel.url != nil) { $0.draggable(viewModel.url!) }
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
contextMenu
|
contextMenu
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|
|
@ -105,7 +105,11 @@ import SwiftUI
|
||||||
status.reblog?.inReplyToId != nil || status.reblog?.inReplyToAccountId != nil ||
|
status.reblog?.inReplyToId != nil || status.reblog?.inReplyToAccountId != nil ||
|
||||||
status.inReplyToId != nil || status.inReplyToAccountId != nil
|
status.inReplyToId != nil || status.inReplyToAccountId != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var url: URL? {
|
||||||
|
(status.reblog?.url ?? status.url).flatMap(URL.init(string:))
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeBackgroundColor(isHomeTimeline: Bool) -> some View {
|
func makeBackgroundColor(isHomeTimeline: Bool) -> some View {
|
||||||
if isHomeTimeline, theme.showContentGradient {
|
if isHomeTimeline, theme.showContentGradient {
|
||||||
|
|
|
@ -81,6 +81,7 @@ public struct StatusRowCardView: View {
|
||||||
.stroke(.gray.opacity(0.35), lineWidth: 1)
|
.stroke(.gray.opacity(0.35), lineWidth: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.draggable(url)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
ShareLink(item: url) {
|
ShareLink(item: url) {
|
||||||
Label("status.card.share", systemImage: "square.and.arrow.up")
|
Label("status.card.share", systemImage: "square.and.arrow.up")
|
||||||
|
|
|
@ -88,9 +88,7 @@ struct StatusRowContextMenu: View {
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
Menu("status.action.share-title") {
|
Menu("status.action.share-title") {
|
||||||
if let urlString = viewModel.status.reblog?.url ?? viewModel.status.url,
|
if let url = viewModel.url {
|
||||||
let url = URL(string: urlString)
|
|
||||||
{
|
|
||||||
ShareLink(item: url,
|
ShareLink(item: url,
|
||||||
subject: Text(viewModel.status.reblog?.account.safeDisplayName ?? viewModel.status.account.safeDisplayName),
|
subject: Text(viewModel.status.reblog?.account.safeDisplayName ?? viewModel.status.account.safeDisplayName),
|
||||||
message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText))
|
message: Text(viewModel.status.reblog?.content.asRawText ?? viewModel.status.content.asRawText))
|
||||||
|
@ -133,7 +131,7 @@ struct StatusRowContextMenu: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let url = URL(string: viewModel.status.reblog?.url ?? viewModel.status.url ?? "") {
|
if let url = viewModel.url {
|
||||||
Button { UIApplication.shared.open(url) } label: {
|
Button { UIApplication.shared.open(url) } label: {
|
||||||
Label("status.action.view-in-browser", systemImage: "safari")
|
Label("status.action.view-in-browser", systemImage: "safari")
|
||||||
}
|
}
|
||||||
|
@ -152,7 +150,7 @@ struct StatusRowContextMenu: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
UIPasteboard.general.string = viewModel.status.reblog?.url ?? viewModel.status.url
|
UIPasteboard.general.url = viewModel.url
|
||||||
} label: {
|
} label: {
|
||||||
Label("status.action.copy-link", systemImage: "link")
|
Label("status.action.copy-link", systemImage: "link")
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,109 +307,100 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
private func fetchNewPagesFrom(latestStatus: String, client: Client) async throws {
|
private func fetchNewPagesFrom(latestStatus: String, client: Client) async throws {
|
||||||
canStreamEvents = false
|
canStreamEvents = false
|
||||||
let initialTimeline = timeline
|
let initialTimeline = timeline
|
||||||
var newStatuses: [Status] = await fetchNewPages(minId: latestStatus, maxPages: 5)
|
|
||||||
|
let newStatuses = await fetchAndDedupNewStatuses(latestStatus: latestStatus, client: client)
|
||||||
// Dedup statuses, a status with the same id could have been streamed in.
|
|
||||||
|
guard !newStatuses.isEmpty,
|
||||||
|
isTimelineVisible,
|
||||||
|
!Task.isCancelled,
|
||||||
|
initialTimeline == timeline else {
|
||||||
|
canStreamEvents = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateTimelineWithNewStatuses(newStatuses)
|
||||||
|
|
||||||
|
if !Task.isCancelled, let latest = await datasource.get().first {
|
||||||
|
pendingStatusesObserver.isLoadingNewStatuses = true
|
||||||
|
try await fetchNewPagesFrom(latestStatus: latest.id, client: client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fetchAndDedupNewStatuses(latestStatus: String, client: Client) async -> [Status] {
|
||||||
|
var newStatuses = await fetchNewPages(minId: latestStatus, maxPages: 5)
|
||||||
let ids = await datasource.get().map(\.id)
|
let ids = await datasource.get().map(\.id)
|
||||||
newStatuses = newStatuses.filter { status in
|
newStatuses = newStatuses.filter { status in
|
||||||
!ids.contains(where: { $0 == status.id })
|
!ids.contains(where: { $0 == status.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
||||||
|
return newStatuses
|
||||||
// If no new statuses, resume streaming and exit.
|
}
|
||||||
guard !newStatuses.isEmpty else {
|
|
||||||
canStreamEvents = true
|
private func updateTimelineWithNewStatuses(_ newStatuses: [Status]) async {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the timeline is not visible, we don't update it as it would mess up the user position.
|
|
||||||
guard isTimelineVisible else {
|
|
||||||
canStreamEvents = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return if task has been cancelled.
|
|
||||||
guard !Task.isCancelled else {
|
|
||||||
canStreamEvents = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// As this is a long runnign task we need to ensure that the user didn't changed the timeline filter.
|
|
||||||
guard initialTimeline == timeline else {
|
|
||||||
canStreamEvents = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of the top most status, so we can scroll back to it after view update.
|
|
||||||
let topStatus = await datasource.getFiltered().first
|
let topStatus = await datasource.getFiltered().first
|
||||||
|
|
||||||
// Insert new statuses in internal datasource.
|
|
||||||
await datasource.insert(contentOf: newStatuses, at: 0)
|
await datasource.insert(contentOf: newStatuses, at: 0)
|
||||||
|
|
||||||
// Cache statuses for timeline.
|
|
||||||
await cache()
|
await cache()
|
||||||
|
|
||||||
// Append new statuses in the timeline indicator.
|
|
||||||
pendingStatusesObserver.pendingStatuses.insert(contentsOf: newStatuses.map(\.id), at: 0)
|
pendingStatusesObserver.pendingStatuses.insert(contentsOf: newStatuses.map(\.id), at: 0)
|
||||||
|
|
||||||
// High chance the user is scrolled to the top.
|
let statuses = await datasource.getFiltered()
|
||||||
// We need to update the statuses state, and then scroll to the previous top most status.
|
let nextPageState: StatusesState.PagingState = statuses.count < 20 ? .none : .hasNextPage
|
||||||
if let topStatus, visibileStatuses.contains(where: { $0.id == topStatus.id }), scrollToTopVisible {
|
|
||||||
pendingStatusesObserver.disableUpdate = true
|
if let topStatus = topStatus,
|
||||||
let statuses = await datasource.getFiltered()
|
visibileStatuses.contains(where: { $0.id == topStatus.id }),
|
||||||
statusesState = .display(statuses: statuses,
|
scrollToTopVisible {
|
||||||
nextPageState: statuses.count < 20 ? .none : .hasNextPage)
|
updateTimelineWithScrollToTop(newStatuses: newStatuses, statuses: statuses, nextPageState: nextPageState)
|
||||||
scrollToIndexAnimated = false
|
|
||||||
scrollToIndex = newStatuses.count + 1
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.pendingStatusesObserver.disableUpdate = false
|
|
||||||
self.canStreamEvents = true
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// This will keep the scroll position (if the list is scrolled) and prepend statuses on the top.
|
updateTimelineWithAnimation(statuses: statuses, nextPageState: nextPageState)
|
||||||
let statuses = await datasource.getFiltered()
|
|
||||||
withAnimation {
|
|
||||||
statusesState = .display(statuses: statuses,
|
|
||||||
nextPageState: statuses.count < 20 ? .none : .hasNextPage)
|
|
||||||
canStreamEvents = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if !Task.isCancelled,
|
|
||||||
let latest = await datasource.get().first
|
// Refresh the timeline while keeping the scroll position to the top status.
|
||||||
{
|
private func updateTimelineWithScrollToTop(newStatuses: [Status], statuses: [Status], nextPageState: StatusesState.PagingState) {
|
||||||
pendingStatusesObserver.isLoadingNewStatuses = true
|
pendingStatusesObserver.disableUpdate = true
|
||||||
try await fetchNewPagesFrom(latestStatus: latest.id, client: client)
|
statusesState = .display(statuses: statuses, nextPageState: nextPageState)
|
||||||
|
scrollToIndexAnimated = false
|
||||||
|
scrollToIndex = newStatuses.count + 1
|
||||||
|
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.pendingStatusesObserver.disableUpdate = false
|
||||||
|
self?.canStreamEvents = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the timeline while keeping the user current position.
|
||||||
|
// It works because a side effect of withAnimation is that it keep scroll position IF the List is not scrolled to the top.
|
||||||
|
private func updateTimelineWithAnimation(statuses: [Status], nextPageState: StatusesState.PagingState) {
|
||||||
|
withAnimation {
|
||||||
|
statusesState = .display(statuses: statuses, nextPageState: nextPageState)
|
||||||
|
canStreamEvents = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchNewPages(minId: String, maxPages: Int) async -> [Status] {
|
private func fetchNewPages(minId: String, maxPages: Int) async -> [Status] {
|
||||||
guard let client else { return [] }
|
guard let client else { return [] }
|
||||||
var pagesLoaded = 0
|
|
||||||
var allStatuses: [Status] = []
|
var allStatuses: [Status] = []
|
||||||
var latestMinId = minId
|
var latestMinId = minId
|
||||||
do {
|
do {
|
||||||
while
|
for _ in 1...maxPages {
|
||||||
!Task.isCancelled,
|
if Task.isCancelled { break }
|
||||||
let newStatuses: [Status] =
|
|
||||||
try await client.get(endpoint: timeline.endpoint(sinceId: nil,
|
let newStatuses: [Status] = try await client.get(endpoint: timeline.endpoint(
|
||||||
maxId: nil,
|
sinceId: nil,
|
||||||
minId: latestMinId,
|
maxId: nil,
|
||||||
offset: datasource.get().count)),
|
minId: latestMinId,
|
||||||
!newStatuses.isEmpty,
|
offset: nil
|
||||||
pagesLoaded < maxPages
|
))
|
||||||
{
|
|
||||||
pagesLoaded += 1
|
if newStatuses.isEmpty { break }
|
||||||
|
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
||||||
|
|
||||||
allStatuses.insert(contentsOf: newStatuses, at: 0)
|
allStatuses.insert(contentsOf: newStatuses, at: 0)
|
||||||
latestMinId = newStatuses.first?.id ?? ""
|
latestMinId = newStatuses.first?.id ?? latestMinId
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
return allStatuses
|
return allStatuses
|
||||||
}
|
}
|
||||||
|
|
||||||
return allStatuses
|
return allStatuses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue