mirror of
https://github.com/lichess-org/mobile.git
synced 2026-05-26 13:50:52 +00:00
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:
+19
-12
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user