Compare commits

..

1 Commits

Author SHA1 Message Date
Mert eaf1c3bb5f fix(mobile): separate group ids for separate app installs (#28448) 2026-05-22 10:22:20 +05:30
19 changed files with 179 additions and 106 deletions
-1
View File
@@ -267,7 +267,6 @@ jobs:
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
ENVIRONMENT: ${{ inputs.environment || 'development' }}
BUNDLE_ID_SUFFIX: ${{ inputs.environment == 'production' && '' || 'development' }}
GITHUB_REF: ${{ github.ref }}
FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120
FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6
@@ -185,6 +185,7 @@ interface NetworkApi {
fun hasCertificate(): Boolean
fun getClientPointer(): Long
fun setRequestHeaders(headers: Map<String, String>, serverUrls: List<String>, token: String?)
fun getAppGroupId(): String
companion object {
/** The codec used by NetworkApi. */
@@ -300,6 +301,21 @@ interface NetworkApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.getAppGroupId())
} catch (exception: Throwable) {
NetworkPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -13,7 +13,7 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware {
private var networkApi: NetworkApiImpl? = null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
networkApi = NetworkApiImpl()
networkApi = NetworkApiImpl(binding.applicationContext)
NetworkApi.setUp(binding.binaryMessenger, networkApi)
}
@@ -39,9 +39,11 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware {
}
}
private class NetworkApiImpl : NetworkApi {
private class NetworkApiImpl(private val context: Context) : NetworkApi {
var activity: Activity? = null
override fun getAppGroupId(): String = context.packageName
override fun addCertificate(clientData: ClientCertData, callback: (Result<Unit>) -> Unit) {
try {
HttpClientManager.setKeyEntry(clientData.data, clientData.password.toCharArray())
+30 -34
View File
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -88,10 +88,11 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = "<group>"; };
65F32F30299BD2F800CE9261 /* BackgroundServicePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundServicePlugin.swift; sourceTree = "<group>"; };
65F32F32299D349D00CE9261 /* BackgroundSyncWorker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundSyncWorker.swift; sourceTree = "<group>"; };
65F32F33299D349D00CE9261 /* BackgroundSyncWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F32F32299D349D00CE9261 /* BackgroundSyncWorker.swift */; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@@ -151,11 +152,15 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */
B231F52D2E93A44A00BC45D1 /* Core */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Core;
sourceTree = "<group>";
};
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Sync;
sourceTree = "<group>";
};
@@ -177,6 +182,8 @@
};
FEE084F22EC172080045228E /* Schemas */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Schemas;
sourceTree = "<group>";
};
@@ -571,14 +578,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@@ -607,14 +610,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@@ -736,6 +735,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CUSTOM_GROUP_ID = group.app.immich.share.profile;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -768,8 +768,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79;
DEVELOPMENT_TEAM = 2W7AC6T8T5;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -778,7 +777,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.121.0;
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile;
PRODUCT_NAME = "Immich-Profile";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -819,6 +818,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CUSTOM_GROUP_ID = group.app.immich.share.debug;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -878,6 +878,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CUSTOM_GROUP_ID = group.app.immich.share;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -912,8 +913,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79;
DEVELOPMENT_TEAM = 2W7AC6T8T5;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -922,7 +922,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.121.0;
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug;
PRODUCT_NAME = "Immich-Debug";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -942,8 +942,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79;
DEVELOPMENT_TEAM = 2W7AC6T8T5;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -976,7 +975,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240;
DEVELOPMENT_TEAM = 2F67MQ8R79;
DEVELOPMENT_TEAM = 2W7AC6T8T5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
@@ -993,7 +992,7 @@
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.Widget;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug.Widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
@@ -1019,7 +1018,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240;
DEVELOPMENT_TEAM = 2F67MQ8R79;
DEVELOPMENT_TEAM = 2W7AC6T8T5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
@@ -1059,7 +1058,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240;
DEVELOPMENT_TEAM = 2F67MQ8R79;
DEVELOPMENT_TEAM = 2W7AC6T8T5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
@@ -1075,7 +1074,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.Widget;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile.Widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -1098,8 +1097,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79;
DEVELOPMENT_TEAM = 2W7AC6T8T5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
@@ -1116,7 +1114,7 @@
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.ShareExtension;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
@@ -1142,8 +1140,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79;
DEVELOPMENT_TEAM = 2W7AC6T8T5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
@@ -1183,8 +1180,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 240;
CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79;
DEVELOPMENT_TEAM = 2W7AC6T8T5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
@@ -1200,7 +1196,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.ShareExtension;
PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
@@ -1261,7 +1257,7 @@
repositoryURL = "https://github.com/pointfreeco/sqlite-data";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.3.0;
minimumVersion = 1.6.1;
};
};
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */ = {
@@ -1269,7 +1265,7 @@
repositoryURL = "https://github.com/apple/swift-http-structured-headers.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.5.0;
minimumVersion = 1.6.0;
};
};
/* End XCRemoteSwiftPackageReference section */
+14
View File
@@ -226,6 +226,7 @@ protocol NetworkApi {
func hasCertificate() throws -> Bool
func getClientPointer() throws -> Int64
func setRequestHeaders(headers: [String: String], serverUrls: [String], token: String?) throws
func getAppGroupId() throws -> String
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@@ -326,5 +327,18 @@ class NetworkApiSetup {
} else {
setRequestHeadersChannel.setMessageHandler(nil)
}
let getAppGroupIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getAppGroupIdChannel.setMessageHandler { _, reply in
do {
let result = try api.getAppGroupId()
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getAppGroupIdChannel.setMessageHandler(nil)
}
}
}
@@ -58,6 +58,10 @@ class NetworkApiImpl: NetworkApi {
return Int64(Int(bitPattern: pointer))
}
func getAppGroupId() throws -> String {
return Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String
}
func setRequestHeaders(headers: [String : String], serverUrls: [String], token: String?) throws {
URLSessionManager.setServerUrls(serverUrls)
@@ -4,7 +4,7 @@ import native_video_player
let CLIENT_CERT_LABEL = "app.alextran.immich.client_identity"
let HEADERS_KEY = "immich.request_headers"
let SERVER_URLS_KEY = "immich.server_urls"
let APP_GROUP = "group.app.immich.share"
let APP_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String
let COOKIE_EXPIRY_DAYS: TimeInterval = 400
enum AuthCookie: CaseIterable {
+1 -1
View File
@@ -10,7 +10,7 @@
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.app.immich.share</string>
<string>$(CUSTOM_GROUP_ID)</string>
</array>
</dict>
</plist>
+1 -1
View File
@@ -12,7 +12,7 @@
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.app.immich.share</string>
<string>$(CUSTOM_GROUP_ID)</string>
</array>
</dict>
</plist>
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.app.immich.share</string>
</array>
</dict>
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>$(CUSTOM_GROUP_ID)</string>
</array>
</dict>
</plist>
+1 -1
View File
@@ -2,7 +2,7 @@ import Foundation
import SwiftUI
import WidgetKit
let IMMICH_SHARE_GROUP = "group.app.immich.share"
let IMMICH_SHARE_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String
enum WidgetError: Error, Codable {
case noLogin
+2
View File
@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.app.immich.share</string>
</array>
</dict>
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>$(CUSTOM_GROUP_ID)</string>
</array>
</dict>
</plist>
+58 -48
View File
@@ -17,10 +17,12 @@ default_platform(:ios)
platform :ios do
# Constants
TEAM_ID = "2F67MQ8R79"
CODE_SIGN_IDENTITY = "Apple Distribution: Hau Tran (#{TEAM_ID})"
TEAM_ID = "2W7AC6T8T5"
CODE_SIGN_IDENTITY = "Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID})"
BASE_BUNDLE_ID = "app.alextran.immich"
DEV_BUNDLE_ID = "tech.futo.immich.testflight"
DEV_GROUP_ID = "group.app.immich.share.testflight"
# Helper method to get App Store Connect API key
def get_api_key
app_store_connect_api_key(
@@ -32,6 +34,13 @@ platform :ios do
)
end
# Helper method to assemble xcargs with optional CUSTOM_GROUP_ID override
def build_xcargs(group_id: nil)
args = "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual"
args += " CUSTOM_GROUP_ID='#{group_id}'" if group_id
args
end
# Helper method to get version from pubspec.yaml
def get_version_from_pubspec
require 'yaml'
@@ -44,62 +53,60 @@ def get_version_from_pubspec
end
# Helper method to configure code signing for all targets
def configure_code_signing(bundle_id_suffix: "", profile_name_main:, profile_name_share:, profile_name_widget:)
bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}"
def configure_code_signing(base_bundle_id:, profile_name_main:, profile_name_share:, profile_name_widget:)
# Runner (main app)
update_code_signing_settings(
use_automatic_signing: false,
path: "./Runner.xcodeproj",
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
code_sign_identity: CODE_SIGN_IDENTITY,
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}",
bundle_identifier: base_bundle_id,
profile_name: profile_name_main,
targets: ["Runner"]
)
# ShareExtension
update_code_signing_settings(
use_automatic_signing: false,
path: "./Runner.xcodeproj",
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
code_sign_identity: CODE_SIGN_IDENTITY,
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension",
bundle_identifier: "#{base_bundle_id}.ShareExtension",
profile_name: profile_name_share,
targets: ["ShareExtension"]
)
# WidgetExtension
update_code_signing_settings(
use_automatic_signing: false,
path: "./Runner.xcodeproj",
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
code_sign_identity: CODE_SIGN_IDENTITY,
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget",
bundle_identifier: "#{base_bundle_id}.Widget",
profile_name: profile_name_widget,
targets: ["WidgetExtension"]
)
end
# Helper method to build and upload to TestFlight
def build_and_upload(
api_key:,
bundle_id_suffix: "",
base_bundle_id:,
configuration: "Release",
distribute_external: true,
version_number: nil,
profile_name_main:,
profile_name_share:,
profile_name_widget:
profile_name_widget:,
group_id: nil
)
bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}"
app_identifier = "#{BASE_BUNDLE_ID}#{bundle_suffix}"
app_identifier = base_bundle_id
# Set version number if provided
if version_number
increment_version_number(version_number: version_number)
end
# Increment build number
increment_build_number(
build_number: latest_testflight_build_number(
@@ -108,14 +115,14 @@ end
) + 1,
xcodeproj: "./Runner.xcodeproj"
)
# Build the app
build_app(
scheme: "Runner",
workspace: "Runner.xcworkspace",
configuration: configuration,
export_method: "app-store",
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
xcargs: build_xcargs(group_id: group_id),
export_options: {
provisioningProfiles: {
"#{app_identifier}" => profile_name_main,
@@ -138,36 +145,37 @@ end
desc "iOS Development Build to TestFlight (requires separate bundle ID)"
lane :gha_testflight_dev do
api_key = get_api_key
# Download and install provisioning profiles from App Store Connect
# Certificate is imported by GHA workflow into build.keychain
# Capture profile names after each sigh call
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true)
sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true)
main_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true)
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true)
share_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true)
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true)
widget_profile_name = lane_context[SharedValues::SIGH_NAME]
# Configure code signing for dev bundle IDs using the downloaded profile names
configure_code_signing(
bundle_id_suffix: "development",
base_bundle_id: DEV_BUNDLE_ID,
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
)
# Build and upload
build_and_upload(
api_key: api_key,
bundle_id_suffix: "development",
base_bundle_id: DEV_BUNDLE_ID,
configuration: "Profile",
distribute_external: false,
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
profile_name_widget: widget_profile_name,
group_id: DEV_GROUP_ID
)
end
@@ -189,6 +197,7 @@ end
# Configure code signing for production bundle IDs
configure_code_signing(
base_bundle_id: BASE_BUNDLE_ID,
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
@@ -197,6 +206,7 @@ end
# Build and upload with version number
build_and_upload(
api_key: api_key,
base_bundle_id: BASE_BUNDLE_ID,
version_number: get_version_from_pubspec,
distribute_external: false,
profile_name_main: main_profile_name,
@@ -243,30 +253,30 @@ end
desc "iOS Build Only (no TestFlight upload)"
lane :gha_build_only do
# Use the same build process as production, just skip the upload
# This ensures PR builds validate the same way as production builds
# Use the same build process as the dev TestFlight lane, just skip the upload
# This ensures PR builds validate the same way as dev TestFlight builds
api_key = get_api_key
# Download and install provisioning profiles from App Store Connect
# Certificate is imported by GHA workflow into build.keychain
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true)
sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true)
main_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true)
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true)
share_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true)
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true)
widget_profile_name = lane_context[SharedValues::SIGH_NAME]
# Configure code signing for dev bundle IDs
configure_code_signing(
bundle_id_suffix: "development",
base_bundle_id: DEV_BUNDLE_ID,
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
)
# Build the app (same as gha_testflight_dev but without upload)
build_app(
scheme: "Runner",
@@ -274,12 +284,12 @@ end
configuration: "Release",
export_method: "app-store",
skip_package_ipa: true,
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
xcargs: build_xcargs(group_id: DEV_GROUP_ID),
export_options: {
provisioningProfiles: {
"#{BASE_BUNDLE_ID}.development" => main_profile_name,
"#{BASE_BUNDLE_ID}.development.ShareExtension" => share_profile_name,
"#{BASE_BUNDLE_ID}.development.Widget" => widget_profile_name
DEV_BUNDLE_ID => main_profile_name,
"#{DEV_BUNDLE_ID}.ShareExtension" => share_profile_name,
"#{DEV_BUNDLE_ID}.Widget" => widget_profile_name
},
signingStyle: "manual",
signingCertificate: CODE_SIGN_IDENTITY
@@ -287,4 +297,4 @@ end
)
end
end
end
-1
View File
@@ -34,7 +34,6 @@ const int kTimelineAssetLoadBatchSize = 1024;
const int kTimelineAssetLoadOppositeSize = 64;
// Widget keys
const String appShareGroupId = "group.app.immich.share";
const String kWidgetAuthToken = "widget_auth_token";
const String kWidgetServerEndpoint = "widget_server_url";
const String kWidgetCustomHeaders = "widget_custom_headers";
+28
View File
@@ -303,4 +303,32 @@ class NetworkApi {
return;
}
}
Future<String> getAppGroupId() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as String?)!;
}
}
}
@@ -1,5 +1,6 @@
import 'package:home_widget/home_widget.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
final widgetRepositoryProvider = Provider((_) => const WidgetRepository());
@@ -14,7 +15,7 @@ class WidgetRepository {
await HomeWidget.updateWidget(iOSName: iosName, qualifiedAndroidName: androidName);
}
Future<void> setAppGroupId(String appGroupId) async {
await HomeWidget.setAppGroupId(appGroupId);
Future<void> setAppGroupId() async {
await HomeWidget.setAppGroupId(await networkApi.getAppGroupId());
}
}
+2 -2
View File
@@ -12,7 +12,7 @@ class WidgetService {
const WidgetService(this._repository);
Future<void> writeCredentials(String serverURL, String sessionKey, String? customHeaders) async {
await _repository.setAppGroupId(appShareGroupId);
await _repository.setAppGroupId();
await _repository.saveData(kWidgetServerEndpoint, serverURL);
await _repository.saveData(kWidgetAuthToken, sessionKey);
@@ -25,7 +25,7 @@ class WidgetService {
}
Future<void> clearCredentials() async {
await _repository.setAppGroupId(appShareGroupId);
await _repository.setAppGroupId();
await _repository.saveData(kWidgetServerEndpoint, "");
await _repository.saveData(kWidgetAuthToken, "");
await _repository.saveData(kWidgetCustomHeaders, "");
+2
View File
@@ -44,4 +44,6 @@ abstract class NetworkApi {
int getClientPointer();
void setRequestHeaders(Map<String, String> headers, List<String> serverUrls, String? token);
String getAppGroupId();
}