Merge branch 'main' into ios-lichess-broadcasts-widget

# Conflicts:
#	ios/EXTENSIONS.md
#	ios/LichessWidgets/Daily Puzzle Widget/BoardStyle.swift
#	ios/LichessWidgets/Daily Puzzle Widget/DailyPuzzleEntry.swift
#	ios/LichessWidgets/Daily Puzzle Widget/DailyPuzzleFetcher.swift
#	ios/LichessWidgets/Daily Puzzle Widget/DailyPuzzleProvider.swift
#	ios/LichessWidgets/Daily Puzzle Widget/DailyPuzzleWidget.swift
#	ios/LichessWidgets/Daily Puzzle Widget/Views/ChessBoardView.swift
#	ios/LichessWidgets/Daily Puzzle Widget/Views/ChessPieceView.swift
#	ios/LichessWidgets/Daily Puzzle Widget/Views/DailyPuzzleWidgetLayout.swift
#	ios/LichessWidgets/Daily Puzzle Widget/Views/DailyPuzzleWidgetView.swift
#	ios/LichessWidgets/LichessWidgetsBundle.swift
#	ios/Runner.xcodeproj/xcshareddata/xcschemes/LichessWidgetsExtension.xcscheme
#	lib/src/app.dart
This commit is contained in:
Rafal Sroka
2026-04-14 14:35:10 +02:00
323 changed files with 1805 additions and 1087 deletions
+19 -12
View File
@@ -53,19 +53,26 @@ Also add the new bundle ID to both `app_identifier` arrays in `fastlane/Matchfil
## Chessboard assets
Board textures and piece images used by the widgets are sourced from the
[flutter-chessground](https://github.com/lichess-org/flutter-chessground) package
and stored in the widget extension's asset catalog under the `Chessboard` group.
Board textures, piece images, and theme colour data used by the widgets are
provided by the `ChessgroundAssets` Swift package, which lives inside the
[flutter-chessground](https://github.com/lichess-org/flutter-chessground)
repository. This means the widget always uses the same assets as the Flutter
app — one source of truth distributed via both pub and SPM.
### Adding the package (one-time Xcode setup)
1. In Xcode, select **File → Add Package Dependencies**.
2. Enter `https://github.com/lichess-org/flutter-chessground` as the URL.
3. Choose the version rule and add the `ChessgroundAssets` library to the
**LichessWidgets** extension target only (not the Runner target).
### Keeping assets in sync
After bumping the `chessground` version in `pubspec.yaml` and running
`flutter pub get`, regenerate the asset catalog group by running:
Assets are versioned alongside the Dart package. When `chessground` is bumped in
`pubspec.yaml`, update the SPM dependency to the matching version tag in Xcode
(**File → Packages → Update to Latest Package Versions**) or pin it to the
specific tag. No script needs to be run in this repository.
```sh
./scripts/sync-chessground-assets.sh
```
The script reads the locked version from `pubspec.lock`, finds the package in
the local pub cache, and replaces all `board_*` and `piece_*` imagesets in the
`Chessboard` group. No arguments are needed.
If you need to regenerate the xcassets on the flutter-chessground side (e.g.
after a new piece set is added), run `./scripts/gen-swift-xcassets.sh` there and
commit the result before cutting a new release tag.
@@ -1,136 +1,12 @@
import SwiftUI
/// Board colours, background image, and piece-set matching what the main Lichess app is using.
struct BoardStyle {
let lightSquare: Color
let darkSquare: Color
let lastMoveHighlight: Color
/// Asset name for image-backed board themes; `nil` means the theme uses solid colours.
let boardImageName: String?
let pieceSet: String
static let defaultPieceSet = "staunty"
static let defaultChessboardTheme = "brown"
import ChessgroundAssets
import Foundation
extension ChessboardTheme {
/// Reads the saved board theme and piece set from the shared App Group.
static func fromAppGroup() -> BoardStyle {
static func fromAppGroup() -> ChessboardTheme {
let defaults = UserDefaults(suiteName: LichessAppGroup.id)
let themeName = defaults?.string(forKey: LichessAppGroup.boardThemeKey) ?? defaultChessboardTheme
let pieceSetName = defaults?.string(forKey: LichessAppGroup.pieceSetKey) ?? defaultPieceSet
return BoardStyle.from(themeName: themeName, pieceSet: pieceSetName)
}
// MARK: - Theme style mapping
//
// Colors are taken from the Dart `ChessboardColorScheme` constants in the
// chessground package (board_color_scheme.dart).
//
// Solid-colour themes (brown, blue, green, ic, system) no board image.
// All other themes are image-backed; the asset name matches the Dart enum
// `.name` prefixed with "board_" (e.g. "board_wood2", "board_blueMarble").
// swiftlint:disable:next function_body_length
static func from(themeName: String, pieceSet: String = "staunty") -> BoardStyle {
switch themeName {
// Solid-colour themes
case "system":
return .init(light: 0xF0D9B6, dark: 0xB58863, pieceSet: pieceSet)
case "blue":
return .init(light: 0xDEE3E6, dark: 0x8CA2AD, pieceSet: pieceSet)
case "green":
return .init(light: 0xFFFFDD, dark: 0x86A666, lastMove: tealLastMove, pieceSet: pieceSet)
case "ic":
return .init(light: 0xECECEC, dark: 0xC1C18E, pieceSet: pieceSet)
// Image-backed themes
case "blue2":
return .init(light: 0x97B2C7, dark: 0x546F82,
boardImage: "board_blue2", pieceSet: pieceSet)
case "blue3":
return .init(light: 0xD9E0E6, dark: 0x315991,
boardImage: "board_blue3", pieceSet: pieceSet)
case "blueMarble":
return .init(light: 0xEAE6DD, dark: 0x7C7F87,
boardImage: "board_blueMarble", pieceSet: pieceSet)
case "canvas":
return .init(light: 0xD7DAEB, dark: 0x547388,
boardImage: "board_canvas", pieceSet: pieceSet)
case "greenPlastic":
return .init(light: 0xF2F9BB, dark: 0x59935D, lastMove: tealLastMove,
boardImage: "board_greenPlastic", pieceSet: pieceSet)
case "grey":
return .init(light: 0xB8B8B8, dark: 0x7D7D7D,
boardImage: "board_grey", pieceSet: pieceSet)
case "horsey":
return .init(light: 0xF0D9B5, dark: 0x946F51,
boardImage: "board_horsey", pieceSet: pieceSet)
case "leather":
return .init(light: 0xD1D1C9, dark: 0xC28E16,
boardImage: "board_leather", pieceSet: pieceSet)
case "maple":
return .init(light: 0xE8CEAB, dark: 0xBC7944,
boardImage: "board_maple", pieceSet: pieceSet)
case "maple2":
return .init(light: 0xE2C89F, dark: 0x996633,
boardImage: "board_maple2", pieceSet: pieceSet)
case "marble":
return .init(light: 0x93AB91, dark: 0x4F644E, lastMove: tealLastMove,
boardImage: "board_marble", pieceSet: pieceSet)
case "metal":
return .init(light: 0xC9C9C9, dark: 0x727272,
boardImage: "board_metal", pieceSet: pieceSet)
case "newspaper":
return .init(light: 0xFFFFFF, dark: 0x8D8D8D,
boardImage: "board_newspaper", pieceSet: pieceSet)
case "olive":
return .init(light: 0xB8B19F, dark: 0x6D6655,
boardImage: "board_olive", pieceSet: pieceSet)
case "pinkPyramid":
return .init(light: 0xE8E9B7, dark: 0xED7272,
boardImage: "board_pinkPyramid", pieceSet: pieceSet)
case "purple":
return .init(light: 0x9F90B0, dark: 0x7D4A8D,
boardImage: "board_purple", pieceSet: pieceSet)
case "purpleDiag":
return .init(light: 0xE5DAF0, dark: 0x957AB0,
boardImage: "board_purpleDiag", pieceSet: pieceSet)
case "wood":
return .init(light: 0xD8A45B, dark: 0x9B4D0F,
boardImage: "board_wood", pieceSet: pieceSet)
case "wood2":
return .init(light: 0xA38B5D, dark: 0x6C5017,
boardImage: "board_wood2", pieceSet: pieceSet)
case "wood3":
return .init(light: 0xD0CECA, dark: 0x755839,
boardImage: "board_wood3", pieceSet: pieceSet)
case "wood4":
return .init(light: 0xCAAF7D, dark: 0x7B5330,
boardImage: "board_wood4", pieceSet: pieceSet)
default: // "brown" and any unknown value
return .init(light: 0xF0D9B6, dark: 0xB58863, pieceSet: pieceSet)
}
}
// MARK: - Private
/// Teal last-move highlight used by the green, greenPlastic, and marble themes.
private static let tealLastMove =
Color(red: 0, green: 155 / 255, blue: 199 / 255).opacity(0.41)
private static let defaultLastMove =
Color(red: 156 / 255, green: 199 / 255, blue: 0).opacity(0.502)
private init(light: UInt,
dark: UInt,
lastMove: Color? = nil,
boardImage: String? = nil,
pieceSet: String) {
lightSquare = Color(rgb: light)
darkSquare = Color(rgb: dark)
lastMoveHighlight = lastMove ?? BoardStyle.defaultLastMove
boardImageName = boardImage
self.pieceSet = pieceSet
let themeName = defaults?.string(forKey: LichessAppGroup.boardThemeKey) ?? defaultThemeName
let pieceSet = defaults?.string(forKey: LichessAppGroup.pieceSetKey) ?? defaultPieceSet
return .from(themeName: themeName, pieceSet: pieceSet)
}
}
@@ -1,3 +1,4 @@
import ChessgroundAssets
import WidgetKit
struct DailyPuzzleEntry: TimelineEntry {
@@ -7,7 +8,7 @@ struct DailyPuzzleEntry: TimelineEntry {
let lastMove: String?
let rating: Int?
let showRating: Bool
let boardStyle: BoardStyle
let boardStyle: ChessboardTheme
let error: String?
var isWhiteToMove: Bool {
@@ -31,11 +32,11 @@ struct DailyPuzzleEntry: TimelineEntry {
DailyPuzzleEntry(
date: .now,
puzzleId: nil,
fen: "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
lastMove: "b8c6",
rating: 1500,
showRating: false,
boardStyle: BoardStyle.fromAppGroup(),
fen: "1n3rk1/4ppbp/rq1p2p1/3P4/2p1P3/2N2P1n/PPN3PP/R1BQ1R1K b - - 1 1",
lastMove: "g1h1",
rating: 1430,
showRating: true,
boardStyle: ChessboardTheme.fromAppGroup(),
error: nil
)
}
@@ -1,32 +1,20 @@
import ChessgroundAssets
import Foundation
struct DailyPuzzleFetcher {
/// The next update is scheduled for 00:05 UTC the following day.
/// Returns the date of the next update.
///
/// The daily puzzle is published at midnight UTC, so we use a UTC calendar to
/// compute the next trigger time rather than the device's local calendar a
/// device in UTC+12 would otherwise schedule the reload 12 hours too late.
static var nextUpdateDate: Date {
var utc = Calendar(identifier: .gregorian)
utc.timeZone = TimeZone(identifier: "UTC")!
var components = utc.dateComponents([.year, .month, .day], from: .now)
components.day = (components.day ?? 0) + 1
components.hour = 0
components.minute = 5
components.second = 0
return utc.date(from: components)
?? Calendar.current.date(byAdding: .hour, value: 24, to: .now)!
}
/// Returns the date of the next update: an hour from now on failure, next day 00:05 UTC on success.
/// On success: fetchTime + 6 h. Devices naturally stagger across the day
/// based on when the widget was first added, so no explicit jitter is needed.
/// On failure: 1 hour from the fetch time so a transient error is retried promptly.
static func nextUpdate(for entry: DailyPuzzleEntry) -> Date {
entry.error == nil
? nextUpdateDate
: Calendar.current.date(byAdding: .hour, value: 1, to: .now)!
? entry.date.addingTimeInterval(6 * 3600)
: entry.date.addingTimeInterval(3600)
}
func fetchEntry(showRating: Bool) async -> DailyPuzzleEntry {
let boardStyle = BoardStyle.fromAppGroup()
static func fetchEntry(showRating: Bool) async -> DailyPuzzleEntry {
let boardStyle = ChessboardTheme.fromAppGroup()
guard let url = LichessAppGroup.lichessURL(path: "/api/puzzle/daily") else {
return errorEntry(showRating: showRating, boardStyle: boardStyle)
}
@@ -39,9 +27,9 @@ struct DailyPuzzleFetcher {
return errorEntry(showRating: showRating, boardStyle: boardStyle)
}
}
// MARK: - Private
private struct APIResponse: Decodable {
struct Puzzle: Decodable {
let id: String
@@ -51,10 +39,10 @@ struct DailyPuzzleFetcher {
}
let puzzle: Puzzle
}
private func parse(_ data: Data,
showRating: Bool,
boardStyle: BoardStyle) throws -> DailyPuzzleEntry {
private static func parse(_ data: Data,
showRating: Bool,
boardStyle: ChessboardTheme) throws -> DailyPuzzleEntry {
let response = try JSONDecoder().decode(APIResponse.self, from: data)
return DailyPuzzleEntry(date: .now,
puzzleId: response.puzzle.id,
@@ -65,8 +53,8 @@ struct DailyPuzzleFetcher {
boardStyle: boardStyle,
error: nil)
}
private func errorEntry(showRating: Bool, boardStyle: BoardStyle) -> DailyPuzzleEntry {
private static func errorEntry(showRating: Bool, boardStyle: ChessboardTheme) -> DailyPuzzleEntry {
DailyPuzzleEntry(date: .now,
puzzleId: nil,
fen: nil,
@@ -1,29 +1,6 @@
import AppIntents
import WidgetKit
// MARK: - Small widget provider (no configuration)
struct DailyPuzzleStaticProvider: TimelineProvider {
private let fetcher = DailyPuzzleFetcher()
func placeholder(in context: Context) -> DailyPuzzleEntry {
.placeholder
}
func getSnapshot(in context: Context, completion: @escaping (DailyPuzzleEntry) -> Void) {
completion(.placeholder)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<DailyPuzzleEntry>) -> Void) {
Task {
let entry = await fetcher.fetchEntry(showRating: false)
completion(Timeline(entries: [entry], policy: .after(DailyPuzzleFetcher.nextUpdate(for: entry))))
}
}
}
// MARK: - Large widget intent + provider (with configuration)
struct DailyPuzzleIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Daily Puzzle"
static var description = IntentDescription("Configure the Daily Puzzle widget.")
@@ -37,8 +14,6 @@ struct DailyPuzzleIntent: WidgetConfigurationIntent {
}
struct DailyPuzzleProvider: AppIntentTimelineProvider {
private let fetcher = DailyPuzzleFetcher()
func placeholder(in context: Context) -> DailyPuzzleEntry {
.placeholder
}
@@ -51,7 +26,7 @@ struct DailyPuzzleProvider: AppIntentTimelineProvider {
}
func timeline(for configuration: DailyPuzzleIntent, in context: Context) async -> Timeline<DailyPuzzleEntry> {
let entry = await fetcher.fetchEntry(showRating: configuration.showRating)
let entry = await DailyPuzzleFetcher.fetchEntry(showRating: configuration.showRating)
return Timeline(entries: [entry], policy: .after(DailyPuzzleFetcher.nextUpdate(for: entry)))
}
}
@@ -5,22 +5,7 @@ import WidgetKit
private let displayName = "Daily Puzzle"
private let widgetDescription = "Today's chess puzzle from lichess.org."
struct DailyPuzzleSmallWidget: Widget {
let kind = "DailyPuzzleSmallWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: DailyPuzzleStaticProvider()) { entry in
DailyPuzzleWidgetView(entry: entry)
.containerBackground(.background, for: .widget)
}
.contentMarginsDisabled()
.configurationDisplayName(displayName)
.description(widgetDescription)
.supportedFamilies([.systemSmall])
}
}
struct DailyPuzzleLargeWidget: Widget {
struct DailyPuzzleWidget: Widget {
let kind = "DailyPuzzleLargeWidget"
var body: some WidgetConfiguration {
@@ -1,3 +1,4 @@
import ChessgroundAssets
import SwiftUI
/// Renders a chess position from a FEN string as an 8×8 grid.
@@ -13,7 +14,7 @@ struct ChessBoardView: View {
let fen: String
let lastMove: String?
let flipped: Bool
let boardStyle: BoardStyle
let boardStyle: ChessboardTheme
// MARK: - FEN parsing
@@ -61,7 +62,7 @@ struct ChessBoardView: View {
// Image-backed themes: one full-board texture scaled to fill.
// Solid-colour themes: drawn square-by-square in the grid below.
if let imageName = boardStyle.boardImageName {
Image(imageName)
Image(imageName, bundle: ChessgroundAssets.bundle)
.resizable()
.frame(width: side, height: side)
}
@@ -1,3 +1,4 @@
import ChessgroundAssets
import SwiftUI
struct ChessPieceView: View {
@@ -12,15 +13,15 @@ struct ChessPieceView: View {
}
/// Returns the asset name to use, falling back to the default piece set if the
/// configured one is missing from the widget's asset catalog.
/// configured one is missing from the Chessground asset bundle.
private var resolvedAssetName: String {
let name = assetName(for: pieceSet)
if UIImage(named: name) != nil { return name }
return assetName(for: BoardStyle.defaultPieceSet)
if UIImage(named: name, in: ChessgroundAssets.bundle, compatibleWith: nil) != nil { return name }
return assetName(for: ChessboardTheme.defaultPieceSet)
}
var body: some View {
Image(resolvedAssetName)
Image(resolvedAssetName, bundle: ChessgroundAssets.bundle)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: squareSize * DailyPuzzleWidgetLayout.pieceSizeFactor,
@@ -16,16 +16,10 @@ enum DailyPuzzleWidgetLayout {
static let sideIndicatorBorderOpacity: CGFloat = 0.4
static let sideIndicatorBorderWidth: CGFloat = 0.5
// Board small
static let smallBoardPadding: CGFloat = 8
static let smallBoardCornerRadius: CGFloat = 12
static let smallBoardBorderWidth: CGFloat = 1
// Board large
static let largeBoardCornerRadius: CGFloat = 6
static let largeBoardBorderWidth: CGFloat = 1
static let largeHorizontalPadding: CGFloat = 16
static let largeTopPadding: CGFloat = 11
// Board
static let boardBorderWidth: CGFloat = 1
static let horizontalPadding: CGFloat = 16
static let topPadding: CGFloat = 11
// Piece
static let pieceSizeFactor: CGFloat = 0.9
@@ -1,40 +1,23 @@
import ChessgroundAssets
import SwiftUI
import WidgetKit
struct DailyPuzzleWidgetView: View {
let entry: DailyPuzzleEntry
@Environment(\.widgetFamily) private var family
var body: some View {
Group {
if let error = entry.error {
errorView(error)
} else if family == .systemSmall {
smallView
} else {
largeView
contentView
}
}
.widgetURL(entry.puzzleURL)
}
// MARK: - Small (.systemSmall)
@ViewBuilder
private var smallView: some View {
boardView
.roundedCornerWithBorder(
lineWidth: DailyPuzzleWidgetLayout.smallBoardBorderWidth,
style: .tertiary,
radius: DailyPuzzleWidgetLayout.smallBoardCornerRadius
)
.padding(DailyPuzzleWidgetLayout.smallBoardPadding)
}
// MARK: - Large (.systemLarge)
@ViewBuilder
private var largeView: some View {
private var contentView: some View {
GeometryReader { geo in
VStack(spacing: 0) {
HStack(spacing: DailyPuzzleWidgetLayout.headerSpacing) {
@@ -80,20 +63,18 @@ struct DailyPuzzleWidgetView: View {
.padding(.bottom, DailyPuzzleWidgetLayout.headerBottomPadding)
boardView
.roundedCornerWithBorder(
lineWidth: DailyPuzzleWidgetLayout.largeBoardBorderWidth,
style: .tertiary,
radius: DailyPuzzleWidgetLayout.largeBoardCornerRadius
.clipShape(ContainerRelativeShape())
.overlay(
ContainerRelativeShape()
.stroke(.tertiary, lineWidth: DailyPuzzleWidgetLayout.boardBorderWidth)
)
.frame(width: geo.size.width, height: geo.size.width)
}
}
.padding(.horizontal, DailyPuzzleWidgetLayout.largeHorizontalPadding)
.padding(.top, DailyPuzzleWidgetLayout.largeTopPadding)
.padding(.horizontal, DailyPuzzleWidgetLayout.horizontalPadding)
.padding(.top, DailyPuzzleWidgetLayout.topPadding)
}
// MARK: - Reusable sub-views
@ViewBuilder
private var boardView: some View {
if let fen = entry.fen {
@@ -4,7 +4,7 @@
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.lichess.mobileV2</string>
<string>group.org.lichess.mobileV2.LichessWidgets</string>
</array>
</dict>
</plist>
@@ -4,8 +4,7 @@ import SwiftUI
@main
struct LichessWidgetsBundle: WidgetBundle {
var body: some Widget {
DailyPuzzleLargeWidget()
DailyPuzzleSmallWidget()
DailyPuzzleWidget()
BroadcastWidget()
CommunityBlogWidget()
UserBlogFeedWidget()
+18 -2
View File
@@ -13,6 +13,7 @@
2F2A90EB2F6DF5F4008DA3C7 /* LichessWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2F2A90DC2F6DF5F3008DA3C7 /* LichessWidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
2F2A90F42F6DFF0F008DA3C7 /* FeedKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2F2A90F32F6DFF0F008DA3C7 /* FeedKit */; };
2F2A90F62F6DFF0F008DA3C7 /* XMLKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2F2A90F52F6DFF0F008DA3C7 /* XMLKit */; };
2FCBD8E32F8D91DF00E9C376 /* ChessgroundAssets in Frameworks */ = {isa = PBXBuildFile; productRef = 2FCBD8E22F8D91DF00E9C376 /* ChessgroundAssets */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
4C1B4CBC5F6D1FB9AB4B2E2D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 150C426DBDFD9997221AABF1 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
@@ -112,6 +113,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2FCBD8E32F8D91DF00E9C376 /* ChessgroundAssets in Frameworks */,
2F2A90F42F6DFF0F008DA3C7 /* FeedKit in Frameworks */,
2F2A90E02F6DF5F3008DA3C7 /* SwiftUI.framework in Frameworks */,
2F2A90DE2F6DF5F3008DA3C7 /* WidgetKit.framework in Frameworks */,
@@ -222,6 +224,7 @@
packageProductDependencies = (
2F2A90F32F6DFF0F008DA3C7 /* FeedKit */,
2F2A90F52F6DFF0F008DA3C7 /* XMLKit */,
2FCBD8E22F8D91DF00E9C376 /* ChessgroundAssets */,
);
productName = LichessWidgetsExtension;
productReference = 2F2A90DC2F6DF5F3008DA3C7 /* LichessWidgetsExtension.appex */;
@@ -284,6 +287,7 @@
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
2F2A90F22F6DFF0F008DA3C7 /* XCRemoteSwiftPackageReference "FeedKit" */,
2FCBD8E12F8D91DF00E9C376 /* XCRemoteSwiftPackageReference "flutter-chessground" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
@@ -620,8 +624,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = HP5YLVDU97;
DEVELOPMENT_TEAM = HP5YLVDU97;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
@@ -918,6 +921,14 @@
version = 10.4.0;
};
};
2FCBD8E12F8D91DF00E9C376 /* XCRemoteSwiftPackageReference "flutter-chessground" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/lichess-org/flutter-chessground";
requirement = {
branch = main;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@@ -931,6 +942,11 @@
package = 2F2A90F22F6DFF0F008DA3C7 /* XCRemoteSwiftPackageReference "FeedKit" */;
productName = XMLKit;
};
2FCBD8E22F8D91DF00E9C376 /* ChessgroundAssets */ = {
isa = XCSwiftPackageProductDependency;
package = 2FCBD8E12F8D91DF00E9C376 /* XCRemoteSwiftPackageReference "flutter-chessground" */;
productName = ChessgroundAssets;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
@@ -1,5 +1,5 @@
{
"originHash" : "85372406515a86e7963c3630577b1ae0ef399277b93d1faf1ed1575b88a0468b",
"originHash" : "a5b9b5d2caf4f0d5fca77f5bae5cd533e4df22d2375aecab75a0f4506a9e6f35",
"pins" : [
{
"identity" : "feedkit",
@@ -9,6 +9,15 @@
"revision" : "e55b2cafaf67370092c91193cfb8a8b008e05888",
"version" : "10.4.0"
}
},
{
"identity" : "flutter-chessground",
"kind" : "remoteSourceControl",
"location" : "https://github.com/lichess-org/flutter-chessground",
"state" : {
"branch" : "main",
"revision" : "8f986f7107ceb9900ad6af1b981313fc6741be5b"
}
}
],
"version" : 3
@@ -1,5 +1,5 @@
{
"originHash" : "85372406515a86e7963c3630577b1ae0ef399277b93d1faf1ed1575b88a0468b",
"originHash" : "a5b9b5d2caf4f0d5fca77f5bae5cd533e4df22d2375aecab75a0f4506a9e6f35",
"pins" : [
{
"identity" : "feedkit",
@@ -9,6 +9,15 @@
"revision" : "e55b2cafaf67370092c91193cfb8a8b008e05888",
"version" : "10.4.0"
}
},
{
"identity" : "flutter-chessground",
"kind" : "remoteSourceControl",
"location" : "https://github.com/lichess-org/flutter-chessground",
"state" : {
"branch" : "main",
"revision" : "79be6bb9e3641fa55027237132c641b973e9da34"
}
}
],
"version" : 3
+1 -1
View File
@@ -10,7 +10,7 @@
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.lichess.mobileV2</string>
<string>group.org.lichess.mobileV2.LichessWidgets</string>
</array>
</dict>
</plist>