Compare commits

..

No commits in common. "main" and "feature/home-widget" have entirely different histories.

154 changed files with 773 additions and 858 deletions

View File

@ -9,11 +9,6 @@
# packages, and plugins designed to encourage good coding practices. # packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
analyzer:
errors:
prefer_const_constructors: ignore
unused_element: ignore # mostly because of super.key
linter: linter:
# The lint rules applied to this project can be customized in the # The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml` # section below to disable rules from the `package:flutter_lints/flutter.yaml`
@ -27,8 +22,9 @@ linter:
# `// ignore_for_file: name_of_lint` syntax on the line or in the file # `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint. # producing the lint.
rules: rules:
- always_declare_return_types always_declare_return_types: true
- always_use_package_imports always_use_package_imports: true
- prefer_single_quotes prefer_const_constructors: false
prefer_single_quotes: true
# Additional information about this file can be found at # Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options # https://dart.dev/guides/language/analysis-options

View File

@ -33,7 +33,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 34 compileSdkVersion flutter.compileSdkVersion
defaultConfig { defaultConfig {
configurations.all { configurations.all {
@ -47,9 +47,17 @@ android {
versionName flutterVersionName versionName flutterVersionName
} }
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes { buildTypes {
release { release {
signingConfig signingConfigs.debug signingConfig signingConfigs.release
} }
} }

View File

@ -80,4 +80,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.15.0 COCOAPODS: 1.11.3

View File

@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
296251252AE7410D00D574FF /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296251242AE7410D00D574FF /* Colors.swift */; }; 296251252AE7410D00D574FF /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296251242AE7410D00D574FF /* Colors.swift */; };
2978ECDD2B62D00C00E36CE8 /* FlutterAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2978ECDC2B62D00C00E36CE8 /* FlutterAssets.swift */; };
297F6FC72AD06E0D00FF159E /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 297F6FC62AD06E0D00FF159E /* WidgetKit.framework */; }; 297F6FC72AD06E0D00FF159E /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 297F6FC62AD06E0D00FF159E /* WidgetKit.framework */; };
297F6FC92AD06E0D00FF159E /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 297F6FC82AD06E0D00FF159E /* SwiftUI.framework */; }; 297F6FC92AD06E0D00FF159E /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 297F6FC82AD06E0D00FF159E /* SwiftUI.framework */; };
297F6FCC2AD06E0D00FF159E /* WonderousWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297F6FCB2AD06E0D00FF159E /* WonderousWidgetBundle.swift */; }; 297F6FCC2AD06E0D00FF159E /* WonderousWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297F6FCB2AD06E0D00FF159E /* WonderousWidgetBundle.swift */; };
@ -74,7 +73,6 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
296251242AE7410D00D574FF /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; }; 296251242AE7410D00D574FF /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
2978ECDC2B62D00C00E36CE8 /* FlutterAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlutterAssets.swift; sourceTree = "<group>"; };
297F6FC52AD06E0D00FF159E /* Wonderous WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; name = "Wonderous WidgetExtension.appex"; path = WonderousWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 297F6FC52AD06E0D00FF159E /* Wonderous WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; name = "Wonderous WidgetExtension.appex"; path = WonderousWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
297F6FC62AD06E0D00FF159E /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 297F6FC62AD06E0D00FF159E /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
297F6FC82AD06E0D00FF159E /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 297F6FC82AD06E0D00FF159E /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
@ -143,7 +141,6 @@
297FD5732AE18011008D8BFE /* WonderousWidgetView.swift */, 297FD5732AE18011008D8BFE /* WonderousWidgetView.swift */,
297FD5752AE19BD9008D8BFE /* WonderWidgetViewComponents.swift */, 297FD5752AE19BD9008D8BFE /* WonderWidgetViewComponents.swift */,
296251242AE7410D00D574FF /* Colors.swift */, 296251242AE7410D00D574FF /* Colors.swift */,
2978ECDC2B62D00C00E36CE8 /* FlutterAssets.swift */,
); );
path = WonderousWidget; path = WonderousWidget;
sourceTree = "<group>"; sourceTree = "<group>";
@ -454,7 +451,6 @@
297FD5762AE19BD9008D8BFE /* WonderWidgetViewComponents.swift in Sources */, 297FD5762AE19BD9008D8BFE /* WonderWidgetViewComponents.swift in Sources */,
296251252AE7410D00D574FF /* Colors.swift in Sources */, 296251252AE7410D00D574FF /* Colors.swift in Sources */,
297F6FD32AD06E0F00FF159E /* WonderousWidget.intentdefinition in Sources */, 297F6FD32AD06E0F00FF159E /* WonderousWidget.intentdefinition in Sources */,
2978ECDD2B62D00C00E36CE8 /* FlutterAssets.swift in Sources */,
297FD5742AE18011008D8BFE /* WonderousWidgetView.swift in Sources */, 297FD5742AE18011008D8BFE /* WonderousWidgetView.swift in Sources */,
297F6FCE2AD06E0D00FF159E /* WonderousWidget.swift in Sources */, 297F6FCE2AD06E0D00FF159E /* WonderousWidget.swift in Sources */,
297F6FCC2AD06E0D00FF159E /* WonderousWidgetBundle.swift in Sources */, 297F6FCC2AD06E0D00FF159E /* WonderousWidgetBundle.swift in Sources */,
@ -644,14 +640,14 @@
CLANG_ENABLE_OBJC_WEAK = YES; CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = WonderousWidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = "WonderousWidgetExtension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = S3TL5AY6Y3; DEVELOPMENT_TEAM = S3TL5AY6Y3;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = WonderousWidget/Info.plist; INFOPLIST_FILE = "WonderousWidget/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Wonderous Widget"; INFOPLIST_KEY_CFBundleDisplayName = "Wonderous Widget";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.4; IPHONEOS_DEPLOYMENT_TARGET = 16.4;

View File

@ -1,7 +1,6 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
/// Define some custom extensions on the Color class, so we can use the shorthand syntax `..myColor`
extension Color { extension Color {
public static let accent = Color(red: 0.89, green: 0.58, blue: 0.36) public static let accent = Color(red: 0.89, green: 0.58, blue: 0.36)
public static let offWhite = Color(red: 0.97, green: 0.92, blue: 0.9) public static let offWhite = Color(red: 0.97, green: 0.92, blue: 0.9)

View File

@ -1,22 +0,0 @@
import Foundation
struct FlutterImages {
static let bgEmpty = getAssetPath("/assets/images/widget/background-empty.jpg")
static let icon = getAssetPath("/assets/images/widget/wonderous-icon.png")
}
func getAssetPath(_ path : String) -> String {
return assetBundleUrl.appending(path: path).path()
}
// Returns a file path to the location of the flutter assetBundle
var assetBundleUrl: URL {
let bundle = Bundle.main
if bundle.bundleURL.pathExtension == "appex" {
// Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
var url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent()
url.append(component: "Frameworks/App.framework/flutter_assets")
return url
}
return bundle.bundleURL
}

View File

@ -10,14 +10,15 @@ struct BgImage : View {
var uiImage:UIImage?; var uiImage:UIImage?;
// If there is no saved imageData, use the default bg image // If there is no saved imageData, use the default bg image
if(entry.imageData.isEmpty){ if(entry.imageData.isEmpty){
uiImage = UIImage(contentsOfFile: FlutterImages.bgEmpty); let defaultImage = flutterAssetBundle.appending(path: "/assets/images/widget/background-empty.jpg").path();
uiImage = UIImage(contentsOfFile: defaultImage);
} }
// Load a base64 encoded image that has been written by the flutter app // Load a base64 encoded image that has been written by the flutter app
else { else {
uiImage = UIImage(data: Data(base64Encoded: entry.imageData)!) uiImage = UIImage(data: Data(base64Encoded: entry.imageData)!)
} }
if(uiImage != nil){ if(uiImage != nil){
// Use geometry reader to prevent an oversized bg image from pushing the other content out of the widgets bounds (https://stackoverflow.com/questions/57593552/swiftui-prevent-image-from-expanding-view-rect-outside-of-screen-bounds) // Use geometry reader to prevent the image from pushing the other content out of the widgets bounds (https://stackoverflow.com/questions/57593552/swiftui-prevent-image-from-expanding-view-rect-outside-of-screen-bounds)
let image = GeometryReader { geometry in let image = GeometryReader { geometry in
Image(uiImage: uiImage!) Image(uiImage: uiImage!)
.resizable() .resizable()
@ -33,7 +34,6 @@ struct BgImage : View {
} }
// Declares a restyled version of the native ProgressView
struct GaugeProgressStyle: ProgressViewStyle { struct GaugeProgressStyle: ProgressViewStyle {
func makeBody(configuration: Configuration) -> some View { func makeBody(configuration: Configuration) -> some View {
let fractionCompleted = configuration.fractionCompleted ?? 0 let fractionCompleted = configuration.fractionCompleted ?? 0

View File

@ -2,20 +2,19 @@ import WidgetKit
import SwiftUI import SwiftUI
import Intents import Intents
/// Every home-widget requires a TimelineEntry. This is passed into the view and propvides any data it needs /// Entry, is passed into the view and defines the data it needs
struct WonderousTimelineEntry : TimelineEntry { struct WonderousTimelineEntry : TimelineEntry {
// Date is a mandatory field for all TimelineEntries
let date: Date let date: Date
// Custom field for the wonderous view
let discoveredCount:Int; let discoveredCount:Int;
var title:String = ""; var title:String = "";
var subTitle:String = ""; var subTitle:String = "";
var imageData:String = ""; var imageData:String = "";
} }
/// Widget, defines some high level configuration options as well as the primary view that will display the widget. // Widget, defines the display name and description and also declared the main View
struct WonderousWidget: Widget { struct WonderousWidget: Widget {
let kind: String = "WonderousWidget" let kind: String = "WonderousWidget"
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: WonderousTimelineProvider()) { entry in StaticConfiguration(kind: kind, provider: WonderousTimelineProvider()) { entry in
WonderousWidgetView(entry: entry) WonderousWidgetView(entry: entry)
@ -27,12 +26,7 @@ struct WonderousWidget: Widget {
} }
} }
struct WonderousConfig { // Provider,returns various WonderousEntry configs based on current context
let iosKey = "group.com.gskinner.flutter.wonders.widget"
let discoveredCountKey = "dicoveredCount"
}
/// TimelineProvider, returns various WonderousTimelineEntry configurations for different contexts
struct WonderousTimelineProvider: TimelineProvider { struct WonderousTimelineProvider: TimelineProvider {
// Provide an entry for a placeholder version of the widget // Provide an entry for a placeholder version of the widget
func placeholder(in context: Context) -> WonderousTimelineEntry { func placeholder(in context: Context) -> WonderousTimelineEntry {

View File

@ -1,9 +1,13 @@
//
// WonderousWidgetBundle.swift
// Wonderous Widget
//
// Created by Shawn on 2023-10-06.
//
import WidgetKit import WidgetKit
import SwiftUI import SwiftUI
// WonderousWidgetBundle
// -> WonderousWidgetView
// -> WonderousWidgetViewComponents
@main @main
struct WonderousWidgetBundle: WidgetBundle { struct WonderousWidgetBundle: WidgetBundle {
var body: some Widget { var body: some Widget {

View File

@ -2,7 +2,7 @@ import WidgetKit
import SwiftUI import SwiftUI
import Intents import Intents
/// Defines the view / layout of the widget // Defines the view / layout of the widget
struct WonderousWidgetView : View { struct WonderousWidgetView : View {
@Environment(\.widgetFamily) var family: WidgetFamily @Environment(\.widgetFamily) var family: WidgetFamily
var entry: WonderousTimelineProvider.Entry var entry: WonderousTimelineProvider.Entry
@ -10,13 +10,14 @@ struct WonderousWidgetView : View {
let showTitle = family == .systemLarge let showTitle = family == .systemLarge
let showIcon = family != .systemSmall let showIcon = family != .systemSmall
let showTitleAndDesc = family != .systemSmall let showTitleAndDesc = family != .systemSmall
let progressPct = Double(entry.discoveredCount) / 24.0
let iconImage = FlutterImages.icon; let progress = Double(entry.discoveredCount) / 24.0
let iconImage = flutterAssetBundle.appending(
path: "/assets/images/widget/wonderous-icon.png"
).path()
let title = entry.title.isEmpty ? "Wonderous" : entry.title; let title = entry.title.isEmpty ? "Wonderous" : entry.title;
let subTitle = entry.subTitle.isEmpty ? "Search for hidden artifacts" : entry.subTitle; let subTitle = entry.subTitle.isEmpty ? "Search for hidden artifacts" : entry.subTitle;
let content = VStack{ let content = VStack{
// Top row with optional Title and Icon
HStack { HStack {
if(showTitle) { if(showTitle) {
Text("Collection") Text("Collection")
@ -31,10 +32,7 @@ struct WonderousWidgetView : View {
.frame(height: 24) .frame(height: 24)
} }
} }
Spacer(); Spacer();
// Bottom hz row with title, desc and progress gauge
HStack { HStack {
if(showTitleAndDesc) { if(showTitleAndDesc) {
VStack(alignment: .leading){ VStack(alignment: .leading){
@ -48,16 +46,14 @@ struct WonderousWidgetView : View {
} }
Spacer(); Spacer();
ZStack{ ZStack{
ProgressView(value: progressPct) ProgressView(value: progress)
.progressViewStyle(GaugeProgressStyle()) .progressViewStyle(GaugeProgressStyle())
.frame(width: 48, height: 48) .frame(width: 48, height: 48)
Text("\(Int(progress * 100))%").font(.system(size: 13)).foregroundColor(.white)
Text("\(Int((progressPct * 100).rounded()))%").font(.system(size: 13)).foregroundColor(.white)
} }
} }
} }
// Stack content on top of the background image and a gradient
return ZStack{ return ZStack{
BgImage(entry: entry).opacity(0.8) BgImage(entry: entry).opacity(0.8)
LinearGradient( LinearGradient(
@ -66,11 +62,21 @@ struct WonderousWidgetView : View {
endPoint: .bottom) endPoint: .bottom)
content.padding(16) content.padding(16)
} }
// Ios requires that widgets have a background color
.widgetBackground(Color.darkGrey) .widgetBackground(Color.darkGrey)
// Deeplink into collections view when tapped
.widgetURL(URL(string: "wonderous:///home/collection")) .widgetURL(URL(string: "wonderous:///home/collection"))
} }
} }
// Todo: Refactor to getFlutterAsset(String path), include /assets, or maybe just getFlutterImage(String path), include assets/images
// Returns a file path to the location of the flutter assetBundle
var flutterAssetBundle: URL {
let bundle = Bundle.main
if bundle.bundleURL.pathExtension == "appex" {
// Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
var url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent()
url.append(component: "Frameworks/App.framework/flutter_assets")
return url
}
return bundle.bundleURL
}

View File

@ -16,7 +16,7 @@ final int maxYear = wondersLogic.timelineEndYear;
const int maxRequests = 32; const int maxRequests = 32;
class ArtifactSearchHelper extends StatefulWidget { class ArtifactSearchHelper extends StatefulWidget {
const ArtifactSearchHelper({super.key}); const ArtifactSearchHelper({Key? key}) : super(key: key);
@override @override
State<ArtifactSearchHelper> createState() => _ArtifactSearchHelperState(); State<ArtifactSearchHelper> createState() => _ArtifactSearchHelperState();
@ -161,14 +161,10 @@ class _ArtifactSearchHelperState extends State<ArtifactSearchHelper> {
//if (!json.containsKey('isPublicDomain') || !json['isPublicDomain']) return _logError(id, 'not public domain') //if (!json.containsKey('isPublicDomain') || !json['isPublicDomain']) return _logError(id, 'not public domain')
final int year = ((json['objectBeginDate'] as int) + (json['objectEndDate'] as int)) ~/ 2; final int year = ((json['objectBeginDate'] as int) + (json['objectEndDate'] as int)) ~/ 2;
if (year < minYear || year > maxYear) { if (year < minYear || year > maxYear) return _logError(id, 'year is out of range');
return _logError(id, 'year is out of range');
}
String? imageUrlSmall = json['primaryImageSmall']; String? imageUrlSmall = json['primaryImageSmall'];
if (imageUrlSmall == null || imageUrlSmall.isEmpty) { if (imageUrlSmall == null || imageUrlSmall.isEmpty) return _logError(id, 'no small image url');
return _logError(id, 'no small image url');
}
// if (!imageUrlSmall.startsWith(SearchData.baseImagePath)) { // if (!imageUrlSmall.startsWith(SearchData.baseImagePath)) {
// return _logError(id, 'unexpected image uri: "$imageUrlSmall"'); // return _logError(id, 'unexpected image uri: "$imageUrlSmall"');
// } // }

View File

@ -43,16 +43,24 @@ class SvgPaths {
/// For wonder specific assets, add an extension to [WonderType] for easy lookup /// For wonder specific assets, add an extension to [WonderType] for easy lookup
extension WonderAssetExtensions on WonderType { extension WonderAssetExtensions on WonderType {
String get assetPath { String get assetPath {
return switch (this) { switch (this) {
WonderType.pyramidsGiza => '${ImagePaths.root}/pyramids', case WonderType.pyramidsGiza:
WonderType.greatWall => '${ImagePaths.root}/great_wall_of_china', return '${ImagePaths.root}/pyramids';
WonderType.petra => '${ImagePaths.root}/petra', case WonderType.greatWall:
WonderType.colosseum => '${ImagePaths.root}/colosseum', return '${ImagePaths.root}/great_wall_of_china';
WonderType.chichenItza => '${ImagePaths.root}/chichen_itza', case WonderType.petra:
WonderType.machuPicchu => '${ImagePaths.root}/machu_picchu', return '${ImagePaths.root}/petra';
WonderType.tajMahal => '${ImagePaths.root}/taj_mahal', case WonderType.colosseum:
WonderType.christRedeemer => '${ImagePaths.root}/christ_the_redeemer' return '${ImagePaths.root}/colosseum';
}; case WonderType.chichenItza:
return '${ImagePaths.root}/chichen_itza';
case WonderType.machuPicchu:
return '${ImagePaths.root}/machu_picchu';
case WonderType.tajMahal:
return '${ImagePaths.root}/taj_mahal';
case WonderType.christRedeemer:
return '${ImagePaths.root}/christ_the_redeemer';
}
} }
String get homeBtn => '$assetPath/wonder-button.png'; String get homeBtn => '$assetPath/wonder-button.png';

View File

@ -1,5 +1,4 @@
/// Consolidate imports that are common across the app. /// Consolidate imports that are common across the app.
library;
export 'dart:math'; export 'dart:math';

View File

@ -427,7 +427,5 @@
"timelineEvent1969ce": "Apollo 11 mission lands on the moon", "timelineEvent1969ce": "Apollo 11 mission lands on the moon",
"privacyPolicy": "Privacy Policy", "privacyPolicy": "Privacy Policy",
"privacyStatement": "As explained in our {privacyUrl} we do not collect any personal information.", "privacyStatement": "As explained in our {privacyUrl} we do not collect any personal information.",
"@privacyStatement": {"placeholders": {"privacyUrl": {}}}, "@privacyStatement": {"placeholders": {"privacyUrl": {}}}
"pageNotFoundBackButton": "Back to civilization",
"pageNotFoundMessage": "The page you are looking for does not exist."
} }

View File

@ -405,7 +405,5 @@
"timelineEvent1957ce": "苏联发射斯普特尼克1号", "timelineEvent1957ce": "苏联发射斯普特尼克1号",
"timelineEvent1969ce": "阿波罗11号在月球着陆", "timelineEvent1969ce": "阿波罗11号在月球着陆",
"privacyPolicy": "隐私政策", "privacyPolicy": "隐私政策",
"privacyStatement": "gskinner 非常重视对用户隐私的保护,正如{privacyUrl}里所诉gskinner 不会收集您的个人信息。", "privacyStatement": "gskinner 非常重视对用户隐私的保护,正如{privacyUrl}里所诉gskinner 不会收集您的个人信息。"
"pageNotFoundBackButton": "回到文明",
"pageNotFoundMessage": "您正在寻找的页面不存在"
} }

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui'; import 'dart:ui';
import 'package:desktop_window/desktop_window.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
@ -33,6 +34,10 @@ class AppLogic {
/// Loads settings, sets up services etc. /// Loads settings, sets up services etc.
Future<void> bootstrap() async { Future<void> bootstrap() async {
debugPrint('bootstrap start...'); debugPrint('bootstrap start...');
// Set min-sizes for desktop apps
if (PlatformInfo.isDesktop) {
await DesktopWindow.setMinWindowSize($styles.sizes.minAppSize);
}
if (kIsWeb) { if (kIsWeb) {
// SB: This is intentionally not a debugPrint, as it's a message for users who open the console on web. // SB: This is intentionally not a debugPrint, as it's a message for users who open the console on web.
@ -76,7 +81,7 @@ class AppLogic {
if (showIntro) { if (showIntro) {
appRouter.go(ScreenPaths.intro); appRouter.go(ScreenPaths.intro);
} else { } else {
appRouter.go(initialDeeplink ?? ScreenPaths.home); appRouter.go(ScreenPaths.home);
} }
} }

View File

@ -1,14 +1,16 @@
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:home_widget/home_widget.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/common/save_load_mixin.dart'; import 'package:wonders/logic/common/save_load_mixin.dart';
import 'package:wonders/logic/data/collectible_data.dart'; import 'package:wonders/logic/data/collectible_data.dart';
import 'package:wonders/logic/native_widget_service.dart'; import 'package:http/http.dart' as http;
class CollectiblesLogic with ThrottledSaveLoadMixin { class CollectiblesLogic with ThrottledSaveLoadMixin {
@override @override
String get fileName => 'collectibles.dat'; String get fileName => 'collectibles.dat';
static const _appGroupId = 'group.com.gskinner.flutter.wonders.widget';
static const _appName = 'WonderousWidget';
/// Holds all collectibles that the views should care about /// Holds all collectibles that the views should care about
final List<CollectibleData> all = collectiblesData; final List<CollectibleData> all = collectiblesData;
@ -24,9 +26,9 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
int get exploredCount => _exploredCount; int get exploredCount => _exploredCount;
late final _nativeWidget = GetIt.I<NativeWidgetService>(); void init() {
HomeWidget.setAppGroupId(_appGroupId);
void init() => _nativeWidget.init(); }
CollectibleData? fromId(String? id) => id == null ? null : all.firstWhereOrNull((o) => o.id == id); CollectibleData? fromId(String? id) => id == null ? null : all.firstWhereOrNull((o) => o.id == id);
@ -40,7 +42,7 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
statesById.value = states; statesById.value = states;
if (state == CollectibleState.discovered) { if (state == CollectibleState.discovered) {
final data = fromId(id)!; final data = fromId(id)!;
_updateNativeHomeWidgetData( _updateHomeWidgetTextData(
title: data.title, title: data.title,
id: data.id, id: data.id,
imageUrl: data.imageUrlSmall, imageUrl: data.imageUrlSmall,
@ -56,10 +58,9 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
if (state == CollectibleState.explored) _exploredCount++; if (state == CollectibleState.explored) _exploredCount++;
}); });
final foundCount = discoveredCount + exploredCount; final foundCount = discoveredCount + exploredCount;
_nativeWidget.save<int>('discoveredCount', foundCount).then((value) { HomeWidget.saveWidgetData<int>('discoveredCount', foundCount).then((value) {
_nativeWidget.markDirty(); HomeWidget.updateWidget(iOSName: _appName);
}); });
debugPrint('setting discoveredCount for home widget $foundCount'); debugPrint('setting discoveredCount for home widget $foundCount');
} }
@ -91,33 +92,32 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
for (int i = 0; i < all.length; i++) { for (int i = 0; i < all.length; i++) {
states[all[i].id] = CollectibleState.lost; states[all[i].id] = CollectibleState.lost;
} }
_updateNativeHomeWidgetData(); // clear home widget data _updateHomeWidgetTextData(); // clear home widget data
statesById.value = states; statesById.value = states;
debugPrint('collection reset'); debugPrint('collection reset');
scheduleSave(); scheduleSave();
} }
Future<void> _updateNativeHomeWidgetData({String title = '', String id = '', String imageUrl = ''}) async { Future<void> _updateHomeWidgetTextData({String title = '', String id = '', String imageUrl = ''}) async {
if (!_nativeWidget.isSupported) return;
// Save title // Save title
await _nativeWidget.save<String>('lastDiscoveredTitle', title); await HomeWidget.saveWidgetData<String>('lastDiscoveredTitle', title);
// Subtitle // Subtitle
String subTitle = ''; String subTitle = '';
if (id.isNotEmpty) { if (id.isNotEmpty) {
final artifactData = await artifactLogic.getArtifactByID(id); final artifactData = await artifactLogic.getArtifactByID(id);
subTitle = artifactData?.date ?? ''; subTitle = artifactData?.date ?? '';
} }
await _nativeWidget.save<String>('lastDiscoveredSubTitle', subTitle); await HomeWidget.saveWidgetData<String>('lastDiscoveredSubTitle', subTitle);
// Image, // Image,
// Download, convert to base64 string and write to shared widget data // Download, convert to base64 string and write to shared widget data
String imageBase64 = ''; String imageBase64 = '';
if (imageUrl.isNotEmpty) { if (imageUrl.isNotEmpty) {
var bytes = await http.readBytes(Uri.parse(imageUrl)); var bytes = await http.readBytes(Uri.parse(imageUrl));
imageBase64 = base64Encode(bytes); imageBase64 = base64Encode(bytes);
debugPrint('Saving base64 bytes for homeWidget'); debugPrint('Saving base64 bytes: $imageBase64');
} }
await _nativeWidget.save<String>('lastDiscoveredImageData', imageBase64); await HomeWidget.saveWidgetData<String>('lastDiscoveredImageData', imageBase64);
await _nativeWidget.markDirty(); await HomeWidget.updateWidget(iOSName: _appName);
} }
@override @override

View File

@ -90,7 +90,7 @@ class RetryImage extends ImageProvider<Object> {
if (other.runtimeType != runtimeType) { if (other.runtimeType != runtimeType) {
return false; return false;
} }
return other is RetryImage && other.imageProvider == imageProvider && other.scale == scale; return other is RetryImage && other.imageProvider == other.imageProvider && other.scale == scale;
} }
@override @override

View File

@ -16,7 +16,7 @@ mixin ThrottledSaveLoadMixin {
} }
Future<void> save() async { Future<void> save() async {
if (!kIsWeb) debugPrint('Saving...'); debugPrint('Saving...');
try { try {
await _file.save(toJson()); await _file.save(toJson());
} on Exception catch (e) { } on Exception catch (e) {

View File

@ -14,11 +14,18 @@ class UnsplashPhotoData {
String getUnsplashUrl(int size) => '$url?q=90&fm=jpg&w=$size&fit=max'; String getUnsplashUrl(int size) => '$url?q=90&fm=jpg&w=$size&fit=max';
static String getSelfHostedUrl(String id, UnsplashPhotoSize targetSize) { static String getSelfHostedUrl(String id, UnsplashPhotoSize targetSize) {
int size = switch (targetSize) { late int size;
UnsplashPhotoSize.med => 400, switch (targetSize) {
UnsplashPhotoSize.large => 800, case UnsplashPhotoSize.med:
UnsplashPhotoSize.xl => 1200 size = 400;
}; break;
case UnsplashPhotoSize.large:
size = 800;
break;
case UnsplashPhotoSize.xl:
size = 1200;
break;
}
if (PlatformInfo.pixelRatio >= 1.5 || PlatformInfo.isDesktop) { if (PlatformInfo.pixelRatio >= 1.5 || PlatformInfo.isDesktop) {
size *= 2; size *= 2;
} }

View File

@ -1,28 +0,0 @@
import 'package:home_widget/home_widget.dart';
import 'package:wonders/logic/common/platform_info.dart';
/// Small facade for the HomeWidget package
class NativeWidgetService {
static const _iosAppGroupId = 'group.com.gskinner.flutter.wonders.widget';
static const _iosAppName = 'WonderousWidget';
final bool isSupported = PlatformInfo.isIOS;
Future<void> init() async {
if (!isSupported) return;
await HomeWidget.setAppGroupId(_iosAppGroupId);
}
Future<bool?> save<T>(String s, T value, {void Function(bool?)? onSaveComplete}) async {
if (!isSupported) return false;
return await HomeWidget.saveWidgetData<T>(s, value).then((value) {
onSaveComplete?.call(value);
return null;
});
}
Future<bool?> markDirty() async {
if (!isSupported) return false;
return await HomeWidget.updateWidget(iOSName: _iosAppName);
}
}

View File

@ -10,25 +10,15 @@ class SettingsLogic with ThrottledSaveLoadMixin {
late final hasDismissedSearchMessage = ValueNotifier<bool>(false)..addListener(scheduleSave); late final hasDismissedSearchMessage = ValueNotifier<bool>(false)..addListener(scheduleSave);
late final isSearchPanelOpen = ValueNotifier<bool>(true)..addListener(scheduleSave); late final isSearchPanelOpen = ValueNotifier<bool>(true)..addListener(scheduleSave);
late final currentLocale = ValueNotifier<String?>(null)..addListener(scheduleSave); late final currentLocale = ValueNotifier<String?>(null)..addListener(scheduleSave);
late final prevWonderIndex = ValueNotifier<int?>(null)..addListener(scheduleSave);
final bool useBlurs = !PlatformInfo.isAndroid; final bool useBlurs = !PlatformInfo.isAndroid;
Future<void> changeLocale(Locale value) async {
currentLocale.value = value.languageCode;
await localeLogic.loadIfChanged(value);
// Re-init controllers that have some cached data that is localized
wondersLogic.init();
timelineLogic.init();
}
@override @override
void copyFromJson(Map<String, dynamic> value) { void copyFromJson(Map<String, dynamic> value) {
hasCompletedOnboarding.value = value['hasCompletedOnboarding'] ?? false; hasCompletedOnboarding.value = value['hasCompletedOnboarding'] ?? false;
hasDismissedSearchMessage.value = value['hasDismissedSearchMessage'] ?? false; hasDismissedSearchMessage.value = value['hasDismissedSearchMessage'] ?? false;
currentLocale.value = value['currentLocale']; currentLocale.value = value['currentLocale'];
isSearchPanelOpen.value = value['isSearchPanelOpen'] ?? false; isSearchPanelOpen.value = value['isSearchPanelOpen'] ?? false;
prevWonderIndex.value = value['lastWonderIndex'];
} }
@override @override
@ -38,7 +28,14 @@ class SettingsLogic with ThrottledSaveLoadMixin {
'hasDismissedSearchMessage': hasDismissedSearchMessage.value, 'hasDismissedSearchMessage': hasDismissedSearchMessage.value,
'currentLocale': currentLocale.value, 'currentLocale': currentLocale.value,
'isSearchPanelOpen': isSearchPanelOpen.value, 'isSearchPanelOpen': isSearchPanelOpen.value,
'lastWonderIndex': prevWonderIndex.value,
}; };
} }
Future<void> changeLocale(Locale value) async {
currentLocale.value = value.languageCode;
await localeLogic.loadIfChanged(value);
// Re-init controllers that have some cached data that is localized
wondersLogic.init();
timelineLogic.init();
}
} }

View File

@ -8,4 +8,5 @@ class UnsplashLogic {
UnsplashService get service => GetIt.I.get<UnsplashService>(); UnsplashService get service => GetIt.I.get<UnsplashService>();
List<String>? getCollectionPhotos(String collectionId) => _idsByCollection[collectionId]; List<String>? getCollectionPhotos(String collectionId) => _idsByCollection[collectionId];
} }

View File

@ -5,7 +5,6 @@ import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/artifact_api_logic.dart'; import 'package:wonders/logic/artifact_api_logic.dart';
import 'package:wonders/logic/artifact_api_service.dart'; import 'package:wonders/logic/artifact_api_service.dart';
import 'package:wonders/logic/collectibles_logic.dart'; import 'package:wonders/logic/collectibles_logic.dart';
import 'package:wonders/logic/native_widget_service.dart';
import 'package:wonders/logic/locale_logic.dart'; import 'package:wonders/logic/locale_logic.dart';
import 'package:wonders/logic/timeline_logic.dart'; import 'package:wonders/logic/timeline_logic.dart';
import 'package:wonders/logic/unsplash_logic.dart'; import 'package:wonders/logic/unsplash_logic.dart';
@ -16,11 +15,9 @@ void main() async {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
// Keep native splash screen up until app is finished bootstrapping // Keep native splash screen up until app is finished bootstrapping
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
GoRouter.optionURLReflectsImperativeAPIs = true;
// Start app // Start app
registerSingletons(); registerSingletons();
runApp(WondersApp()); runApp(WondersApp());
await appLogic.bootstrap(); await appLogic.bootstrap();
@ -30,7 +27,7 @@ void main() async {
/// Creates an app using the [MaterialApp.router] constructor and the global `appRouter`, an instance of [GoRouter]. /// Creates an app using the [MaterialApp.router] constructor and the global `appRouter`, an instance of [GoRouter].
class WondersApp extends StatelessWidget with GetItMixin { class WondersApp extends StatelessWidget with GetItMixin {
WondersApp({super.key}); WondersApp({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final locale = watchX((SettingsLogic s) => s.currentLocale); final locale = watchX((SettingsLogic s) => s.currentLocale);
@ -42,7 +39,6 @@ class WondersApp extends StatelessWidget with GetItMixin {
routerDelegate: appRouter.routerDelegate, routerDelegate: appRouter.routerDelegate,
shortcuts: AppShortcuts.defaults, shortcuts: AppShortcuts.defaults,
theme: ThemeData(fontFamily: $styles.text.body.fontFamily, useMaterial3: true), theme: ThemeData(fontFamily: $styles.text.body.fontFamily, useMaterial3: true),
color: $styles.colors.black,
localizationsDelegates: const [ localizationsDelegates: const [
AppLocalizations.delegate, AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
@ -73,8 +69,6 @@ void registerSingletons() {
GetIt.I.registerLazySingleton<CollectiblesLogic>(() => CollectiblesLogic()); GetIt.I.registerLazySingleton<CollectiblesLogic>(() => CollectiblesLogic());
// Localizations // Localizations
GetIt.I.registerLazySingleton<LocaleLogic>(() => LocaleLogic()); GetIt.I.registerLazySingleton<LocaleLogic>(() => LocaleLogic());
// Home Widget Service
GetIt.I.registerLazySingleton<NativeWidgetService>(() => NativeWidgetService());
} }
/// Add syntax sugar for quickly accessing the main "logic" controllers in the app /// Add syntax sugar for quickly accessing the main "logic" controllers in the app

View File

@ -1,14 +1,13 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/modals//fullscreen_video_viewer.dart'; import 'package:wonders/ui/common/modals//fullscreen_video_viewer.dart';
import 'package:wonders/ui/common/modals/fullscreen_maps_viewer.dart'; import 'package:wonders/ui/common/modals/fullscreen_maps_viewer.dart';
import 'package:wonders/ui/screens/artifact/artifact_carousel/artifact_carousel_screen.dart';
import 'package:wonders/ui/screens/artifact/artifact_details/artifact_details_screen.dart'; import 'package:wonders/ui/screens/artifact/artifact_details/artifact_details_screen.dart';
import 'package:wonders/ui/screens/artifact/artifact_search/artifact_search_screen.dart'; import 'package:wonders/ui/screens/artifact/artifact_search/artifact_search_screen.dart';
import 'package:wonders/ui/screens/collection/collection_screen.dart'; import 'package:wonders/ui/screens/collection/collection_screen.dart';
import 'package:wonders/ui/screens/home/wonders_home_screen.dart'; import 'package:wonders/ui/screens/home/wonders_home_screen.dart';
import 'package:wonders/ui/screens/intro/intro_screen.dart'; import 'package:wonders/ui/screens/intro/intro_screen.dart';
import 'package:wonders/ui/screens/page_not_found/page_not_found.dart';
import 'package:wonders/ui/screens/timeline/timeline_screen.dart'; import 'package:wonders/ui/screens/timeline/timeline_screen.dart';
import 'package:wonders/ui/screens/wonder_details/wonders_details_screen.dart'; import 'package:wonders/ui/screens/wonder_details/wonders_details_screen.dart';
@ -18,53 +17,20 @@ class ScreenPaths {
static String intro = '/welcome'; static String intro = '/welcome';
static String home = '/home'; static String home = '/home';
static String settings = '/settings'; static String settings = '/settings';
static String wonderDetails(WonderType type, {int tabIndex = 0}) => '/wonder/${type.name}?t=$tabIndex';
static String wonderDetails(WonderType type, {required int tabIndex}) => '$home/wonder/${type.name}?t=$tabIndex'; static String video(String id) => '/video/$id';
static String highlights(WonderType type) => '/highlights/${type.name}';
/// Dynamically nested pages, always added on to the existing path static String search(WonderType type) => '/search/${type.name}';
static String video(String id) => _appendToCurrentPath('/video/$id'); static String artifact(String id) => '/artifact/$id';
static String search(WonderType type) => _appendToCurrentPath('/search/${type.name}'); static String collection(String id) => '/collection?id=$id';
static String maps(WonderType type) => _appendToCurrentPath('/maps/${type.name}'); static String maps(WonderType type) => '/maps/${type.name}';
static String timeline(WonderType? type) => _appendToCurrentPath('/timeline?type=${type?.name ?? ''}'); static String timeline(WonderType? type) => '/timeline?type=${type?.name ?? ''}';
static String artifact(String id, {bool append = true}) => static String wallpaperPhoto(WonderType type) => '/wallpaperPhoto/${type.name}';
append ? _appendToCurrentPath('/artifact/$id') : '/artifact/$id';
static String collection(String id) => _appendToCurrentPath('/collection${id.isEmpty ? '' : '?id=$id'}');
static String _appendToCurrentPath(String newPath) {
final newPathUri = Uri.parse(newPath);
final currentUri = appRouter.routeInformationProvider.value.uri;
Map<String, dynamic> params = Map.of(currentUri.queryParameters);
params.addAll(newPathUri.queryParameters);
Uri? loc = Uri(path: '${currentUri.path}/${newPathUri.path}'.replaceAll('//', '/'), queryParameters: params);
return loc.toString();
}
}
// Routes that are used multiple times
AppRoute get _artifactRoute => AppRoute(
'artifact/:artifactId',
(s) => ArtifactDetailsScreen(artifactId: s.pathParameters['artifactId']!),
);
AppRoute get _timelineRoute {
return AppRoute(
'timeline',
(s) => TimelineScreen(type: _tryParseWonderType(s.uri.queryParameters['type']!)),
);
}
AppRoute get _collectionRoute {
return AppRoute(
'collection',
(s) => CollectionScreen(fromId: s.uri.queryParameters['id'] ?? ''),
routes: [_artifactRoute],
);
} }
/// Routing table, matches string paths to UI Screens, optionally parses params from the paths /// Routing table, matches string paths to UI Screens, optionally parses params from the paths
final appRouter = GoRouter( final appRouter = GoRouter(
redirect: _handleRedirect, redirect: _handleRedirect,
errorPageBuilder: (context, state) => MaterialPage(child: PageNotFound(state.uri.toString())),
routes: [ routes: [
ShellRoute( ShellRoute(
builder: (context, router, navigator) { builder: (context, router, navigator) {
@ -72,50 +38,40 @@ final appRouter = GoRouter(
}, },
routes: [ routes: [
AppRoute(ScreenPaths.splash, (_) => Container(color: $styles.colors.greyStrong)), // This will be hidden AppRoute(ScreenPaths.splash, (_) => Container(color: $styles.colors.greyStrong)), // This will be hidden
AppRoute(ScreenPaths.intro, (_) => IntroScreen()),
AppRoute(ScreenPaths.home, (_) => HomeScreen(), routes: [ AppRoute(ScreenPaths.home, (_) => HomeScreen(), routes: [
_timelineRoute, AppRoute('collection', (s) {
_collectionRoute, return CollectionScreen(fromId: s.queryParams['id'] ?? '');
AppRoute( }),
'wonder/:detailsType',
(s) {
int tab = int.tryParse(s.uri.queryParameters['t'] ?? '') ?? 0;
return WonderDetailsScreen(
type: _parseWonderType(s.pathParameters['detailsType']),
tabIndex: tab,
);
},
useFade: true,
// Wonder sub-routes
routes: [
_timelineRoute,
_collectionRoute,
_artifactRoute,
// Youtube Video
AppRoute('video/:videoId', (s) {
return FullscreenVideoViewer(id: s.pathParameters['videoId']!);
}),
// Search
AppRoute(
'search/:searchType',
(s) {
return ArtifactSearchScreen(type: _parseWonderType(s.pathParameters['searchType']));
},
routes: [
_artifactRoute,
],
),
// Maps
AppRoute(
'maps/:mapsType',
(s) => FullscreenMapsViewer(
type: _parseWonderType(s.pathParameters['mapsType']),
)),
],
),
]), ]),
AppRoute(ScreenPaths.intro, (_) => IntroScreen()),
AppRoute('/wonder/:type', (s) {
int tab = int.tryParse(s.queryParams['t'] ?? '') ?? 0;
return WonderDetailsScreen(
type: _parseWonderType(s.params['type']),
initialTabIndex: tab,
);
}, useFade: true),
AppRoute('/timeline', (s) {
return TimelineScreen(type: _tryParseWonderType(s.queryParams['type']!));
}),
AppRoute('/video/:id', (s) {
return FullscreenVideoViewer(id: s.params['id']!);
}),
AppRoute('/highlights/:type', (s) {
return ArtifactCarouselScreen(type: _parseWonderType(s.params['type']));
}),
AppRoute('/search/:type', (s) {
return ArtifactSearchScreen(type: _parseWonderType(s.params['type']));
}),
AppRoute('/artifact/:id', (s) {
return ArtifactDetailsScreen(artifactId: s.params['id']!);
}),
AppRoute('/collection', (s) {
return CollectionScreen(fromId: s.queryParams['id'] ?? '');
}),
AppRoute('/maps/:type', (s) {
return FullscreenMapsViewer(type: _parseWonderType(s.params['type']));
}),
]), ]),
], ],
); );
@ -147,21 +103,12 @@ class AppRoute extends GoRoute {
final bool useFade; final bool useFade;
} }
String? get initialDeeplink => _initialDeeplink;
String? _initialDeeplink;
String? _handleRedirect(BuildContext context, GoRouterState state) { String? _handleRedirect(BuildContext context, GoRouterState state) {
// Prevent anyone from navigating away from `/` if app is starting up. // Prevent anyone from navigating away from `/` if app is starting up.
if (!appLogic.isBootstrapComplete && state.uri.path != ScreenPaths.splash) { if (!appLogic.isBootstrapComplete && state.location != ScreenPaths.splash) {
debugPrint('Redirecting from ${state.uri.path} to ${ScreenPaths.splash}.');
_initialDeeplink ??= state.uri.toString();
return ScreenPaths.splash; return ScreenPaths.splash;
} }
if (appLogic.isBootstrapComplete && state.uri.path == ScreenPaths.splash) { debugPrint('Navigate to: ${state.location}');
debugPrint('Redirecting from ${state.uri.path} to ${ScreenPaths.home}');
return ScreenPaths.home;
}
if (!kIsWeb) debugPrint('Navigate to: ${state.uri}');
return null; // do nothing return null; // do nothing
} }

View File

@ -13,9 +13,6 @@ class AppColors {
final Color greyStrong = const Color(0xFF272625); final Color greyStrong = const Color(0xFF272625);
final Color greyMedium = const Color(0xFF9D9995); final Color greyMedium = const Color(0xFF9D9995);
final Color white = Colors.white; final Color white = Colors.white;
// NOTE: If this color is changed, also change it in
// - web/manifest.json
// - web/index.html -
final Color black = const Color(0xFF1E1B18); final Color black = const Color(0xFF1E1B18);
final bool isDark = false; final bool isDark = false;

View File

@ -1,5 +1,7 @@
// ignore_for_file: library_private_types_in_public_api // ignore_for_file: library_private_types_in_public_api
import 'dart:ui';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
export 'colors.dart'; export 'colors.dart';
@ -14,13 +16,20 @@ class AppStyle {
final shortestSide = screenSize.shortestSide; final shortestSide = screenSize.shortestSide;
const tabletXl = 1000; const tabletXl = 1000;
const tabletLg = 800; const tabletLg = 800;
const tabletSm = 600;
const phoneLg = 400;
if (shortestSide > tabletXl) { if (shortestSide > tabletXl) {
scale = 1.2; scale = 1.25;
} else if (shortestSide > tabletLg) { } else if (shortestSide > tabletLg) {
scale = 1.1; scale = 1.15;
} else { } else if (shortestSide > tabletSm) {
scale = 1; scale = 1;
} else if (shortestSide > phoneLg) {
scale = .9; // phone
} else {
scale = .85; // small phone
} }
//debugPrint('screenSize=$screenSize, scale=$scale');
} }
late final double scale; late final double scale;

View File

@ -2,29 +2,45 @@ import 'package:wonders/common_libs.dart';
extension WonderColorExtensions on WonderType { extension WonderColorExtensions on WonderType {
Color get bgColor { Color get bgColor {
return switch (this) { switch (this) {
WonderType.pyramidsGiza => const Color(0xFF16184D), case WonderType.pyramidsGiza:
WonderType.greatWall => const Color(0xFF642828), return const Color(0xFF16184D);
WonderType.petra => const Color(0xFF444B9B), case WonderType.greatWall:
WonderType.colosseum => const Color(0xFF1E736D), return const Color(0xFF642828);
WonderType.chichenItza => const Color(0xFF164F2A), case WonderType.petra:
WonderType.machuPicchu => const Color(0xFF0E4064), return const Color(0xFF444B9B);
WonderType.tajMahal => const Color(0xFFC96454), case WonderType.colosseum:
WonderType.christRedeemer => const Color(0xFF1C4D46) return const Color(0xFF1E736D);
}; case WonderType.chichenItza:
return const Color(0xFF164F2A);
case WonderType.machuPicchu:
return const Color(0xFF0E4064);
case WonderType.tajMahal:
return const Color(0xFFC96454);
case WonderType.christRedeemer:
return const Color(0xFF1C4D46);
}
} }
Color get fgColor { Color get fgColor {
return switch (this) { switch (this) {
WonderType.pyramidsGiza => const Color(0xFF444B9B), case WonderType.pyramidsGiza:
WonderType.greatWall => const Color(0xFF688750), return const Color(0xFF444B9B);
WonderType.petra => const Color(0xFF1B1A65), case WonderType.greatWall:
WonderType.colosseum => const Color(0xFF4AA39D), return const Color(0xFF688750);
WonderType.chichenItza => const Color(0xFFE2CFBB), case WonderType.petra:
WonderType.machuPicchu => const Color(0xFFC1D9D1), return const Color(0xFF1B1A65);
WonderType.tajMahal => const Color(0xFF642828), case WonderType.colosseum:
WonderType.christRedeemer => const Color(0xFFED7967) return const Color(0xFF4AA39D);
}; case WonderType.chichenItza:
return const Color(0xFFE2CFBB);
case WonderType.machuPicchu:
return const Color(0xFFC1D9D1);
case WonderType.tajMahal:
return const Color(0xFF642828);
case WonderType.christRedeemer:
return const Color(0xFFED7967);
}
} }
} }

View File

@ -2,7 +2,7 @@ import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/app_scroll_behavior.dart'; import 'package:wonders/ui/common/app_scroll_behavior.dart';
class WondersAppScaffold extends StatelessWidget { class WondersAppScaffold extends StatelessWidget {
const WondersAppScaffold({super.key, required this.child}); const WondersAppScaffold({Key? key, required this.child}) : super(key: key);
final Widget child; final Widget child;
static AppStyle get style => _style; static AppStyle get style => _style;
static AppStyle _style = AppStyle(); static AppStyle _style = AppStyle();

View File

@ -1,13 +1,14 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class AppBackdrop extends StatelessWidget { class AppBackdrop extends StatelessWidget {
const AppBackdrop({ const AppBackdrop({
super.key, Key? key,
this.strength = 1, this.strength = 1,
this.child, this.child,
}); }) : super(key: key);
final double strength; final double strength;
final Widget? child; final Widget? child;

View File

@ -2,7 +2,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class AppIcon extends StatelessWidget { class AppIcon extends StatelessWidget {
const AppIcon(this.icon, {super.key, this.size = 22, this.color}); const AppIcon(this.icon, {Key? key, this.size = 22, this.color}) : super(key: key);
final AppIcons icon; final AppIcons icon;
final double size; final double size;
final Color? color; final Color? color;

View File

@ -34,11 +34,14 @@ class AppShortcuts {
}; };
static Map<ShortcutActivator, Intent>? get defaults { static Map<ShortcutActivator, Intent>? get defaults {
return switch (defaultTargetPlatform) { switch (defaultTargetPlatform) {
// fall back to default shortcuts for ios and android // fall back to default shortcuts for ios and android
TargetPlatform.iOS || TargetPlatform.android => null, case TargetPlatform.iOS:
case TargetPlatform.android:
return null;
// unify shortcuts for desktop/web // unify shortcuts for desktop/web
_ => _defaultWebAndDesktopShortcuts default:
}; return _defaultWebAndDesktopShortcuts;
}
} }
} }

View File

@ -5,7 +5,8 @@ class BlendMask extends SingleChildRenderObjectWidget {
final List<BlendMode> blendModes; final List<BlendMode> blendModes;
final double opacity; final double opacity;
const BlendMask({required this.blendModes, this.opacity = 1.0, super.key, required Widget super.child}); const BlendMask({required this.blendModes, this.opacity = 1.0, Key? key, required Widget child})
: super(key: key, child: child);
@override @override
RenderObject createRenderObject(context) => RenderBlendMask(blendModes, opacity); RenderObject createRenderObject(context) => RenderBlendMask(blendModes, opacity);

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class CenteredBox extends StatelessWidget { class CenteredBox extends StatelessWidget {
const CenteredBox({super.key, required this.child, this.width, this.height, this.padding}); const CenteredBox({Key? key, required this.child, this.width, this.height, this.padding}) : super(key: key);
final Widget child; final Widget child;
final double? width; final double? width;
final double? height; final double? height;

View File

@ -6,7 +6,7 @@ import 'package:wonders/ui/common/utils/app_haptics.dart';
import 'package:wonders/ui/screens/collectible_found/collectible_found_screen.dart'; import 'package:wonders/ui/screens/collectible_found/collectible_found_screen.dart';
class CollectibleItem extends StatelessWidget with GetItMixin { class CollectibleItem extends StatelessWidget with GetItMixin {
CollectibleItem(this.collectible, {this.size = 64.0, super.key, this.focus}) { CollectibleItem(this.collectible, {this.size = 64.0, Key? key}) : super(key: key) {
// pre-fetch the image, so it's ready if we show the collectible found screen. // pre-fetch the image, so it's ready if we show the collectible found screen.
_imageProvider = NetworkImage(collectible.imageUrl); _imageProvider = NetworkImage(collectible.imageUrl);
_imageProvider.resolve(ImageConfiguration()).addListener(ImageStreamListener((_, __) {})); _imageProvider.resolve(ImageConfiguration()).addListener(ImageStreamListener((_, __) {}));
@ -15,7 +15,6 @@ class CollectibleItem extends StatelessWidget with GetItMixin {
final CollectibleData collectible; final CollectibleData collectible;
final double size; final double size;
late final ImageProvider _imageProvider; late final ImageProvider _imageProvider;
final FocusNode? focus;
void _handleTap(BuildContext context) async { void _handleTap(BuildContext context) async {
final screen = CollectibleFoundScreen(collectible: collectible, imageProvider: _imageProvider); final screen = CollectibleFoundScreen(collectible: collectible, imageProvider: _imageProvider);
@ -40,7 +39,6 @@ class CollectibleItem extends StatelessWidget with GetItMixin {
// Note: In order for the collapse animation to run properly, we must return a non-zero height or width. // Note: In order for the collapse animation to run properly, we must return a non-zero height or width.
closedBuilder: (_) => SizedBox(width: 1, height: 0), closedBuilder: (_) => SizedBox(width: 1, height: 0),
openBuilder: (_) => AppBtn.basic( openBuilder: (_) => AppBtn.basic(
focusNode: focus,
semanticLabel: $strings.collectibleItemSemanticCollectible, semanticLabel: $strings.collectibleItemSemanticCollectible,
onPressed: () => _handleTap(context), onPressed: () => _handleTap(context),
enableFeedback: false, enableFeedback: false,
@ -56,9 +54,9 @@ class CollectibleItem extends StatelessWidget with GetItMixin {
.animate(onPlay: (controller) => controller.repeat()) .animate(onPlay: (controller) => controller.repeat())
.shimmer(delay: 4000.ms, duration: $styles.times.med * 3) .shimmer(delay: 4000.ms, duration: $styles.times.med * 3)
.shake(curve: Curves.easeInOutCubic, hz: 4) .shake(curve: Curves.easeInOutCubic, hz: 4)
.scale(begin: Offset(1.0, 1.0), end: Offset(1.1, 1.1), duration: $styles.times.med) .scale(begin: 1.0, end: 1.1, duration: $styles.times.med)
.then(delay: $styles.times.med) .then(delay: $styles.times.med)
.scale(begin: Offset(1.0, 1.0), end: Offset(1 / 1.1, 1 / 1.1)), .scale(begin: 1.0, end: 1 / 1.1),
), ),
), ),
), ),

View File

@ -2,7 +2,8 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class CompassDivider extends StatelessWidget { class CompassDivider extends StatelessWidget {
const CompassDivider({super.key, required this.isExpanded, this.duration, this.linesColor, this.compassColor}); const CompassDivider({Key? key, required this.isExpanded, this.duration, this.linesColor, this.compassColor})
: super(key: key);
final bool isExpanded; final bool isExpanded;
final Duration? duration; final Duration? duration;
final Color? linesColor; final Color? linesColor;

View File

@ -3,7 +3,7 @@ import 'package:wonders/ui/common/app_icons.dart';
class AppHeader extends StatelessWidget { class AppHeader extends StatelessWidget {
const AppHeader( const AppHeader(
{super.key, {Key? key,
this.title, this.title,
this.subtitle, this.subtitle,
this.showBackBtn = true, this.showBackBtn = true,
@ -11,7 +11,8 @@ class AppHeader extends StatelessWidget {
this.onBack, this.onBack,
this.trailing, this.trailing,
this.backIcon = AppIcons.prev, this.backIcon = AppIcons.prev,
this.backBtnSemantics}); this.backBtnSemantics})
: super(key: key);
final String? title; final String? title;
final String? subtitle; final String? subtitle;
final bool showBackBtn; final bool showBackBtn;

View File

@ -6,7 +6,7 @@ import 'package:wonders/ui/common/controls/app_loading_indicator.dart';
class AppImage extends StatefulWidget { class AppImage extends StatefulWidget {
const AppImage({ const AppImage({
super.key, Key? key,
required this.image, required this.image,
this.fit = BoxFit.scaleDown, this.fit = BoxFit.scaleDown,
this.alignment = Alignment.center, this.alignment = Alignment.center,
@ -16,7 +16,7 @@ class AppImage extends StatefulWidget {
this.progress = false, this.progress = false,
this.color, this.color,
this.scale, this.scale,
}); }) : super(key: key);
final ImageProvider? image; final ImageProvider? image;
final BoxFit fit; final BoxFit fit;
@ -88,9 +88,7 @@ class _AppImageState extends State<AppImage> {
ImageProvider? _capImageSize(ImageProvider? image) { ImageProvider? _capImageSize(ImageProvider? image) {
// Disable resizing for web as it is currently single-threaded and causes the UI to lock up when resizing large images // Disable resizing for web as it is currently single-threaded and causes the UI to lock up when resizing large images
if (kIsWeb) { if (kIsWeb) return image; // TODO: Remove this when the web engine is updated to support non-blocking image resizing
return image; // TODO: Remove this when the web engine is updated to support non-blocking image resizing
}
if (image == null || widget.scale == null) return image; if (image == null || widget.scale == null) return image;
final MediaQueryData mq = MediaQuery.of(context); final MediaQueryData mq = MediaQuery.of(context);
final Size screenSize = mq.size * mq.devicePixelRatio * widget.scale!; final Size screenSize = mq.size * mq.devicePixelRatio * widget.scale!;

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class AppLoadingIndicator extends StatelessWidget { class AppLoadingIndicator extends StatelessWidget {
const AppLoadingIndicator({super.key, this.value, this.color}); const AppLoadingIndicator({Key? key, this.value, this.color}) : super(key: key);
final Color? color; final Color? color;
final double? value; final double? value;

View File

@ -3,14 +3,15 @@ import 'package:wonders/common_libs.dart';
class AppPageIndicator extends StatefulWidget { class AppPageIndicator extends StatefulWidget {
AppPageIndicator({ AppPageIndicator({
super.key, Key? key,
required this.count, required this.count,
required this.controller, required this.controller,
this.onDotPressed, this.onDotPressed,
this.color, this.color,
this.dotSize, this.dotSize,
String? semanticPageTitle, String? semanticPageTitle,
}) : semanticPageTitle = semanticPageTitle ?? $strings.appPageDefaultTitlePage; }) : semanticPageTitle = semanticPageTitle ?? $strings.appPageDefaultTitlePage,
super(key: key);
final int count; final int count;
final PageController controller; final PageController controller;
final void Function(int index)? onDotPressed; final void Function(int index)? onDotPressed;

View File

@ -9,7 +9,7 @@ Widget _buildIcon(BuildContext context, AppIcons icon, {required bool isSecondar
class AppBtn extends StatelessWidget { class AppBtn extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables // ignore: prefer_const_constructors_in_immutables
AppBtn({ AppBtn({
super.key, Key? key,
required this.onPressed, required this.onPressed,
required this.semanticLabel, required this.semanticLabel,
this.enableFeedback = true, this.enableFeedback = true,
@ -24,10 +24,11 @@ class AppBtn extends StatelessWidget {
this.border, this.border,
this.focusNode, this.focusNode,
this.onFocusChanged, this.onFocusChanged,
}) : _builder = null; }) : _builder = null,
super(key: key);
AppBtn.from({ AppBtn.from({
super.key, Key? key,
required this.onPressed, required this.onPressed,
this.enableFeedback = true, this.enableFeedback = true,
this.pressEffect = true, this.pressEffect = true,
@ -44,10 +45,9 @@ class AppBtn extends StatelessWidget {
AppIcons? icon, AppIcons? icon,
double? iconSize, double? iconSize,
}) : child = null, }) : child = null,
circular = false { circular = false,
if (semanticLabel == null && text == null) { super(key: key) {
throw ('AppBtn.from must include either text or semanticLabel'); if (semanticLabel == null && text == null) throw ('AppBtn.from must include either text or semanticLabel');
}
this.semanticLabel = semanticLabel ?? text ?? ''; this.semanticLabel = semanticLabel ?? text ?? '';
_builder = (context) { _builder = (context) {
if (text == null && icon == null) return SizedBox.shrink(); if (text == null && icon == null) return SizedBox.shrink();
@ -70,7 +70,7 @@ class AppBtn extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables // ignore: prefer_const_constructors_in_immutables
AppBtn.basic({ AppBtn.basic({
super.key, Key? key,
required this.onPressed, required this.onPressed,
required this.semanticLabel, required this.semanticLabel,
this.enableFeedback = true, this.enableFeedback = true,
@ -85,7 +85,8 @@ class AppBtn extends StatelessWidget {
}) : expand = false, }) : expand = false,
bgColor = Colors.transparent, bgColor = Colors.transparent,
border = null, border = null,
_builder = null; _builder = null,
super(key: key);
// interaction: // interaction:
final VoidCallback? onPressed; final VoidCallback? onPressed;
@ -184,7 +185,7 @@ class AppBtn extends StatelessWidget {
/// Add a transparency-based press effect to buttons. /// Add a transparency-based press effect to buttons.
/// ////////////////////////////////////////////////// /// //////////////////////////////////////////////////
class _ButtonPressEffect extends StatefulWidget { class _ButtonPressEffect extends StatefulWidget {
const _ButtonPressEffect(this.child); const _ButtonPressEffect(this.child, {Key? key}) : super(key: key);
final Widget child; final Widget child;
@override @override
@ -211,7 +212,7 @@ class _ButtonPressEffectState extends State<_ButtonPressEffect> {
} }
class _CustomFocusBuilder extends StatefulWidget { class _CustomFocusBuilder extends StatefulWidget {
const _CustomFocusBuilder({required this.builder, this.focusNode, this.onFocusChanged}); const _CustomFocusBuilder({Key? key, required this.builder, this.focusNode, this.onFocusChanged}) : super(key: key);
final Widget Function(BuildContext context, FocusNode focus) builder; final Widget Function(BuildContext context, FocusNode focus) builder;
final void Function(bool hasFocus)? onFocusChanged; final void Function(bool hasFocus)? onFocusChanged;
final FocusNode? focusNode; final FocusNode? focusNode;
@ -231,9 +232,7 @@ class _CustomFocusBuilderState extends State<_CustomFocusBuilder> {
void _handleFocusChanged() { void _handleFocusChanged() {
widget.onFocusChanged?.call(_focusNode.hasFocus); widget.onFocusChanged?.call(_focusNode.hasFocus);
if (mounted) { setState(() {});
setState(() {});
}
} }
@override @override

View File

@ -2,7 +2,8 @@ import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/utils/app_haptics.dart'; import 'package:wonders/ui/common/utils/app_haptics.dart';
class SimpleCheckbox extends StatelessWidget { class SimpleCheckbox extends StatelessWidget {
const SimpleCheckbox({super.key, required this.active, required this.onToggled, required this.label}); const SimpleCheckbox({Key? key, required this.active, required this.onToggled, required this.label})
: super(key: key);
final bool active; final bool active;
final String label; final String label;
final Function(bool? onToggle) onToggled; final Function(bool? onToggle) onToggled;

View File

@ -4,14 +4,14 @@ import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart';
class CircleBtn extends StatelessWidget { class CircleBtn extends StatelessWidget {
const CircleBtn({ const CircleBtn({
super.key, Key? key,
required this.child, required this.child,
required this.onPressed, required this.onPressed,
this.border, this.border,
this.bgColor, this.bgColor,
this.size, this.size,
required this.semanticLabel, required this.semanticLabel,
}); }) : super(key: key);
static double defaultSize = 48; static double defaultSize = 48;
@ -40,7 +40,7 @@ class CircleBtn extends StatelessWidget {
class CircleIconBtn extends StatelessWidget { class CircleIconBtn extends StatelessWidget {
const CircleIconBtn({ const CircleIconBtn({
super.key, Key? key,
required this.icon, required this.icon,
required this.onPressed, required this.onPressed,
this.border, this.border,
@ -50,7 +50,7 @@ class CircleIconBtn extends StatelessWidget {
this.iconSize, this.iconSize,
this.flipIcon = false, this.flipIcon = false,
required this.semanticLabel, required this.semanticLabel,
}); }) : super(key: key);
//TODO: Reduce size if design re-exports icon-images without padding //TODO: Reduce size if design re-exports icon-images without padding
static double defaultSize = 28; static double defaultSize = 28;
@ -87,13 +87,13 @@ class CircleIconBtn extends StatelessWidget {
class BackBtn extends StatelessWidget { class BackBtn extends StatelessWidget {
const BackBtn({ const BackBtn({
super.key, Key? key,
this.icon = AppIcons.prev, this.icon = AppIcons.prev,
this.onPressed, this.onPressed,
this.semanticLabel, this.semanticLabel,
this.bgColor, this.bgColor,
this.iconColor, this.iconColor,
}); }) : super(key: key);
final Color? bgColor; final Color? bgColor;
final Color? iconColor; final Color? iconColor;
@ -121,23 +121,20 @@ class BackBtn extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FullscreenKeyboardListener( return FullscreenKeyboardListener(
onKeyDown: (event) => _handleKeyDown(context, event), onKeyDown: (event) => _handleKeyDown(context, event), child: CircleIconBtn(
child: CircleIconBtn( icon: icon,
icon: icon, bgColor: bgColor,
bgColor: bgColor, color: iconColor,
color: iconColor, onPressed: onPressed ?? () {
onPressed: onPressed ?? final nav = Navigator.of(context);
() { if(nav.canPop()){
final nav = Navigator.of(context); Navigator.pop(context);
if (nav.canPop()) { } else {
Navigator.pop(context); context.go(ScreenPaths.home);
} else { }
context.go(ScreenPaths.home); },
} semanticLabel: semanticLabel ?? $strings.circleButtonsSemanticBack,
}, ),);
semanticLabel: semanticLabel ?? $strings.circleButtonsSemanticBack,
),
);
} }
Widget safe() => _SafeAreaWithPadding(child: this); Widget safe() => _SafeAreaWithPadding(child: this);
@ -146,17 +143,13 @@ class BackBtn extends StatelessWidget {
if (onPressed != null) { if (onPressed != null) {
onPressed?.call(); onPressed?.call();
} else { } else {
if (context.canPop()) { Navigator.of(context).pop();
context.pop();
} else {
context.go(ScreenPaths.home);
}
} }
} }
} }
class _SafeAreaWithPadding extends StatelessWidget { class _SafeAreaWithPadding extends StatelessWidget {
const _SafeAreaWithPadding({required this.child}); const _SafeAreaWithPadding({Key? key, required this.child}) : super(key: key);
final Widget child; final Widget child;

View File

@ -2,7 +2,7 @@ import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/static_text_scale.dart'; import 'package:wonders/ui/common/static_text_scale.dart';
class DiagonalTextPageIndicator extends StatelessWidget { class DiagonalTextPageIndicator extends StatelessWidget {
const DiagonalTextPageIndicator({super.key, required this.current, required this.total}); const DiagonalTextPageIndicator({Key? key, required this.current, required this.total}) : super(key: key);
final int current; final int current;
final int total; final int total;
static final _fontSize = 26 * $styles.scale; static final _fontSize = 26 * $styles.scale;

View File

@ -1,7 +1,8 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class EightWaySwipeDetector extends StatefulWidget { class EightWaySwipeDetector extends StatefulWidget {
const EightWaySwipeDetector({super.key, required this.child, this.threshold = 50, required this.onSwipe}); const EightWaySwipeDetector({Key? key, required this.child, this.threshold = 50, required this.onSwipe})
: super(key: key);
final Widget child; final Widget child;
final double threshold; final double threshold;
final void Function(Offset dir)? onSwipe; final void Function(Offset dir)? onSwipe;

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class LocaleSwitcher extends StatelessWidget with GetItMixin { class LocaleSwitcher extends StatelessWidget with GetItMixin {
LocaleSwitcher({super.key}); LocaleSwitcher({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -11,7 +11,6 @@ class LocaleSwitcher extends StatelessWidget with GetItMixin {
await settingsLogic.changeLocale(newLocale); await settingsLogic.changeLocale(newLocale);
} }
return AppBtn.from( return AppBtn.from(text: $strings.localeSwapButton, onPressed: handleSwapLocale, padding: EdgeInsets.all($styles.insets.sm));
text: $strings.localeSwapButton, onPressed: handleSwapLocale, padding: EdgeInsets.all($styles.insets.sm));
} }
} }

View File

@ -6,19 +6,19 @@ class ScrollDecorator extends StatefulWidget {
/// its ScrollController. /// its ScrollController.
// ignore: prefer_const_constructors_in_immutables // ignore: prefer_const_constructors_in_immutables
ScrollDecorator({ ScrollDecorator({
super.key, Key? key,
required this.builder, required this.builder,
this.fgBuilder, this.fgBuilder,
this.bgBuilder, this.bgBuilder,
this.controller, this.controller,
this.onInit, this.onInit,
}); }) : super(key: key);
/// Creates a ScrollDecorator that fades a widget in at the begin or end of the scrolling widget based on the scroll /// Creates a ScrollDecorator that fades a widget in at the begin or end of the scrolling widget based on the scroll
/// position. For example on a vertical list, it would fade in the `begin` widget when the list is not scrolled to the /// position. For example on a vertical list, it would fade in the `begin` widget when the list is not scrolled to the
/// top. /// top.
ScrollDecorator.fade({ ScrollDecorator.fade({
super.key, Key? key,
required this.builder, required this.builder,
this.controller, this.controller,
this.onInit, this.onInit,
@ -27,7 +27,7 @@ class ScrollDecorator extends StatefulWidget {
bool bg = false, bool bg = false,
Axis direction = Axis.vertical, Axis direction = Axis.vertical,
Duration duration = const Duration(milliseconds: 150), Duration duration = const Duration(milliseconds: 150),
}) { }) : super(key: key) {
Flex flexBuilder(controller) { Flex flexBuilder(controller) {
return Flex( return Flex(
direction: direction, direction: direction,
@ -55,12 +55,12 @@ class ScrollDecorator extends StatefulWidget {
/// Creates an ScrollDecorator that adds a shadow to the top of a vertical list when it is scrolled down. /// Creates an ScrollDecorator that adds a shadow to the top of a vertical list when it is scrolled down.
ScrollDecorator.shadow({ ScrollDecorator.shadow({
super.key, Key? key,
required this.builder, required this.builder,
this.controller, this.controller,
this.onInit, this.onInit,
Color color = Colors.black54, Color color = Colors.black54,
}) { }) : super(key: key) {
bgBuilder = null; bgBuilder = null;
fgBuilder = (controller) { fgBuilder = (controller) {
final double ratio = controller.hasClients ? min(1, controller.position.extentBefore / 60) : 0; final double ratio = controller.hasClients ? min(1, controller.position.extentBefore / 60) : 0;

View File

@ -40,29 +40,33 @@ class ArchPoint {
List<ArchPoint> _getArchPts(Size size, ArchType type) { List<ArchPoint> _getArchPts(Size size, ArchType type) {
double distanceFromTop = size.width / 3; double distanceFromTop = size.width / 3;
return switch (type) { switch (type) {
ArchType.pyramid => [ case ArchType.pyramid:
return [
ArchPoint(Offset(0, size.height)), ArchPoint(Offset(0, size.height)),
ArchPoint(Offset(0, distanceFromTop)), ArchPoint(Offset(0, distanceFromTop)),
ArchPoint(Offset(size.width / 2, 0)), ArchPoint(Offset(size.width / 2, 0)),
ArchPoint(Offset(size.width, distanceFromTop)), ArchPoint(Offset(size.width, distanceFromTop)),
ArchPoint(Offset(size.width, size.height)), ArchPoint(Offset(size.width, size.height)),
], ];
ArchType.spade => [ case ArchType.spade:
return [
ArchPoint(Offset(0, size.height)), ArchPoint(Offset(0, size.height)),
ArchPoint(Offset(0, distanceFromTop)), ArchPoint(Offset(0, distanceFromTop)),
ArchPoint(Offset(size.width / 2, 0), Offset(0, distanceFromTop * .66)), ArchPoint(Offset(size.width / 2, 0), Offset(0, distanceFromTop * .66)),
ArchPoint(Offset(size.width, distanceFromTop), Offset(size.width, distanceFromTop * .66)), ArchPoint(Offset(size.width, distanceFromTop), Offset(size.width, distanceFromTop * .66)),
ArchPoint(Offset(size.width, size.height)), ArchPoint(Offset(size.width, size.height)),
], ];
ArchType.arch => [ case ArchType.arch:
return [
ArchPoint(Offset(0, size.height)), ArchPoint(Offset(0, size.height)),
ArchPoint(Offset(0, size.width / 2)), ArchPoint(Offset(0, size.width / 2)),
ArchPoint(Offset(size.width / 2, 0), Offset(0, 0)), ArchPoint(Offset(size.width / 2, 0), Offset(0, 0)),
ArchPoint(Offset(size.width, size.width / 2), Offset(size.width, 0)), ArchPoint(Offset(size.width, size.width / 2), Offset(size.width, 0)),
ArchPoint(Offset(size.width, size.height)), ArchPoint(Offset(size.width, size.height)),
], ];
ArchType.wideArch => [ case ArchType.wideArch:
return [
ArchPoint(Offset(0, size.height)), ArchPoint(Offset(0, size.height)),
ArchPoint(Offset(0, size.width / 2)), ArchPoint(Offset(0, size.width / 2)),
ArchPoint(Offset(0, distanceFromTop)), ArchPoint(Offset(0, distanceFromTop)),
@ -70,16 +74,17 @@ List<ArchPoint> _getArchPts(Size size, ArchType type) {
ArchPoint(Offset(size.width, distanceFromTop), Offset(size.width, 0)), ArchPoint(Offset(size.width, distanceFromTop), Offset(size.width, 0)),
ArchPoint(Offset(size.width, size.width / 2)), ArchPoint(Offset(size.width, size.width / 2)),
ArchPoint(Offset(size.width, size.height)), ArchPoint(Offset(size.width, size.height)),
], ];
ArchType.flatPyramid => [ case ArchType.flatPyramid:
return [
ArchPoint(Offset(0, size.height)), ArchPoint(Offset(0, size.height)),
ArchPoint(Offset(0, distanceFromTop)), ArchPoint(Offset(0, distanceFromTop)),
ArchPoint(Offset(size.width * 0.8 / 2, 0)), ArchPoint(Offset(size.width * 0.8 / 2, 0)),
ArchPoint(Offset(size.width * 1.2 / 2, 0)), ArchPoint(Offset(size.width * 1.2 / 2, 0)),
ArchPoint(Offset(size.width, distanceFromTop)), ArchPoint(Offset(size.width, distanceFromTop)),
ArchPoint(Offset(size.width, size.height)), ArchPoint(Offset(size.width, size.height)),
] ];
}; }
} }
class CurvedTopClipper extends CustomClipper<Path> { class CurvedTopClipper extends CustomClipper<Path> {

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class DashedLine extends StatelessWidget { class DashedLine extends StatelessWidget {
const DashedLine({super.key, this.vertical = false}); const DashedLine({Key? key, this.vertical = false}) : super(key: key);
final bool vertical; final bool vertical;
@override @override

View File

@ -3,7 +3,7 @@ import 'package:wonders/common_libs.dart';
/// Colored box that can fade in and out, should yield better performance than /// Colored box that can fade in and out, should yield better performance than
/// fading with an additional Opacity layer. /// fading with an additional Opacity layer.
class FadeColorTransition extends StatelessWidget { class FadeColorTransition extends StatelessWidget {
const FadeColorTransition({super.key, required this.animation, required this.color}); const FadeColorTransition({Key? key, required this.animation, required this.color}) : super(key: key);
final Animation<double> animation; final Animation<double> animation;
final Color color; final Color color;

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
class GradientContainer extends StatelessWidget { class GradientContainer extends StatelessWidget {
const GradientContainer(this.colors, this.stops, const GradientContainer(this.colors, this.stops,
{super.key, {Key? key,
this.child, this.child,
this.width, this.width,
this.height, this.height,
@ -10,7 +10,8 @@ class GradientContainer extends StatelessWidget {
this.begin, this.begin,
this.end, this.end,
this.blendMode, this.blendMode,
this.borderRadius}); this.borderRadius})
: super(key: key);
final List<Color> colors; final List<Color> colors;
final List<double> stops; final List<double> stops;
final double? width; final double? width;
@ -44,12 +45,41 @@ class GradientContainer extends StatelessWidget {
} }
class HzGradient extends GradientContainer { class HzGradient extends GradientContainer {
const HzGradient(super.colors, super.stops, const HzGradient(List<Color> colors, List<double> stops,
{super.key, super.child, super.width, super.height, super.alignment, super.blendMode, super.borderRadius}); {Key? key,
Widget? child,
double? width,
double? height,
Alignment? alignment,
BlendMode? blendMode,
BorderRadius? borderRadius})
: super(colors, stops,
key: key,
child: child,
width: width,
height: height,
alignment: alignment,
blendMode: blendMode,
borderRadius: borderRadius);
} }
class VtGradient extends GradientContainer { class VtGradient extends GradientContainer {
const VtGradient(super.colors, super.stops, const VtGradient(List<Color> colors, List<double> stops,
{super.key, super.child, super.width, super.height, super.alignment, super.blendMode, super.borderRadius}) {Key? key,
: super(begin: Alignment.topCenter, end: Alignment.bottomCenter); Widget? child,
double? width,
double? height,
Alignment? alignment,
BlendMode? blendMode,
BorderRadius? borderRadius})
: super(colors, stops,
key: key,
child: child,
width: width,
height: height,
alignment: alignment,
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
blendMode: blendMode,
borderRadius: borderRadius);
} }

View File

@ -5,14 +5,13 @@ import 'package:wonders/ui/common/collectible_item.dart';
/// The item is looked up via index, and expects that 3 items always exist for each wonder. /// The item is looked up via index, and expects that 3 items always exist for each wonder.
/// If `wonders` is empty, then the collectible is always shown. /// If `wonders` is empty, then the collectible is always shown.
class HiddenCollectible extends StatelessWidget with GetItMixin { class HiddenCollectible extends StatelessWidget with GetItMixin {
HiddenCollectible(this.currentWonder, HiddenCollectible(this.currentWonder, {Key? key, required this.index, this.matches = const [], this.size = 64})
{super.key, required this.index, this.matches = const [], this.size = 64, this.focus}) : assert(index <= 2, 'index should not exceed 2'),
: assert(index <= 2, 'index should not exceed 2'); super(key: key);
final int index; final int index;
final double size; final double size;
final List<WonderType> matches; final List<WonderType> matches;
final WonderType currentWonder; final WonderType currentWonder;
final FocusNode? focus;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final data = collectiblesLogic.forWonder(currentWonder); final data = collectiblesLogic.forWonder(currentWonder);
@ -20,6 +19,6 @@ class HiddenCollectible extends StatelessWidget with GetItMixin {
if (matches.isNotEmpty && matches.contains(currentWonder) == false) { if (matches.isNotEmpty && matches.contains(currentWonder) == false) {
return SizedBox.shrink(); return SizedBox.shrink();
} }
return CollectibleItem(data[index], size: size, focus: focus); return CollectibleItem(data[index], size: size);
} }
} }

View File

@ -9,7 +9,7 @@ class ArrowDir {
} }
class KeyboardArrowsListener extends StatefulWidget { class KeyboardArrowsListener extends StatefulWidget {
const KeyboardArrowsListener({super.key, required this.child, required this.onArrow}); const KeyboardArrowsListener({Key? key, required this.child, required this.onArrow}) : super(key: key);
final Widget child; final Widget child;
final void Function(ArrowDir dir) onArrow; final void Function(ArrowDir dir) onArrow;
@override @override

View File

@ -3,13 +3,13 @@ import 'package:flutter/material.dart';
/// A lazy-loading [IndexedStack] that loads [children] accordingly. /// A lazy-loading [IndexedStack] that loads [children] accordingly.
class LazyIndexedStack extends StatefulWidget { class LazyIndexedStack extends StatefulWidget {
const LazyIndexedStack({ const LazyIndexedStack({
super.key, Key? key,
this.alignment = AlignmentDirectional.topStart, this.alignment = AlignmentDirectional.topStart,
this.textDirection, this.textDirection,
this.sizing = StackFit.loose, this.sizing = StackFit.loose,
this.index = 0, this.index = 0,
this.children = const [], this.children = const [],
}); }) : super(key: key);
final AlignmentGeometry alignment; final AlignmentGeometry alignment;
final TextDirection? textDirection; final TextDirection? textDirection;

View File

@ -4,7 +4,7 @@ import 'package:wonders/ui/common/gradient_container.dart';
/// Used for situations where the list content should blend into a background color. /// Used for situations where the list content should blend into a background color.
/// Can be placed at the top or bottom of a list, using the `flip' option when on the bottom /// Can be placed at the top or bottom of a list, using the `flip' option when on the bottom
class ListOverscollGradient extends StatelessWidget { class ListOverscollGradient extends StatelessWidget {
const ListOverscollGradient({super.key, this.size = 100, this.color, this.bottomUp = false}); const ListOverscollGradient({Key? key, this.size = 100, this.color, this.bottomUp = false}) : super(key: key);
final bool bottomUp; final bool bottomUp;
final double size; final double size;
final Color? color; final Color? color;

View File

@ -4,7 +4,7 @@ import 'package:flutter/rendering.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class MeasurableWidget extends SingleChildRenderObjectWidget { class MeasurableWidget extends SingleChildRenderObjectWidget {
const MeasurableWidget({super.key, required this.onChange, required Widget super.child}); const MeasurableWidget({Key? key, required this.onChange, required Widget child}) : super(key: key, child: child);
final void Function(Size size) onChange; final void Function(Size size) onChange;
@override @override
RenderObject createRenderObject(BuildContext context) => MeasureSizeRenderObject(onChange); RenderObject createRenderObject(BuildContext context) => MeasureSizeRenderObject(onChange);

View File

@ -11,7 +11,7 @@ Future<bool?> showModal(BuildContext context, {required Widget child}) async {
} }
class LoadingModal extends StatelessWidget { class LoadingModal extends StatelessWidget {
const LoadingModal({super.key, this.title, this.msg, this.child}); const LoadingModal({Key? key, this.title, this.msg, this.child}) : super(key: key);
final String? title; final String? title;
final String? msg; final String? msg;
final Widget? child; final Widget? child;
@ -28,7 +28,7 @@ class LoadingModal extends StatelessWidget {
} }
class OkModal extends StatelessWidget { class OkModal extends StatelessWidget {
const OkModal({super.key, this.title, this.msg, this.child}); const OkModal({Key? key, this.title, this.msg, this.child}) : super(key: key);
final String? title; final String? title;
final String? msg; final String? msg;
final Widget? child; final Widget? child;
@ -51,7 +51,7 @@ class OkModal extends StatelessWidget {
} }
class OkCancelModal extends StatelessWidget { class OkCancelModal extends StatelessWidget {
const OkCancelModal({super.key, this.title, this.msg, this.child}); const OkCancelModal({Key? key, this.title, this.msg, this.child}) : super(key: key);
final String? title; final String? title;
final String? msg; final String? msg;
final Widget? child; final Widget? child;
@ -83,7 +83,7 @@ class _BaseContentModal extends StatelessWidget {
final Widget? child; final Widget? child;
final List<Widget> buttons; final List<Widget> buttons;
const _BaseContentModal({this.title, this.msg, required this.buttons, this.child}); const _BaseContentModal({Key? key, this.title, this.msg, required this.buttons, this.child}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -5,7 +5,7 @@ import 'package:wonders/ui/common/controls/app_header.dart';
import 'package:wonders/ui/common/google_maps_marker.dart'; import 'package:wonders/ui/common/google_maps_marker.dart';
class FullscreenMapsViewer extends StatelessWidget { class FullscreenMapsViewer extends StatelessWidget {
FullscreenMapsViewer({super.key, required this.type}); FullscreenMapsViewer({Key? key, required this.type}) : super(key: key);
final WonderType type; final WonderType type;
WonderData get data => wondersLogic.getData(type); WonderData get data => wondersLogic.getData(type);

View File

@ -7,7 +7,7 @@ import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart';
import 'package:wonders/ui/common/utils/app_haptics.dart'; import 'package:wonders/ui/common/utils/app_haptics.dart';
class FullscreenUrlImgViewer extends StatefulWidget { class FullscreenUrlImgViewer extends StatefulWidget {
const FullscreenUrlImgViewer({super.key, required this.urls, this.index = 0}); const FullscreenUrlImgViewer({Key? key, required this.urls, this.index = 0}) : super(key: key);
final List<String> urls; final List<String> urls;
final int index; final int index;
@ -126,7 +126,7 @@ class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
} }
class _Viewer extends StatefulWidget { class _Viewer extends StatefulWidget {
const _Viewer(this.url, this.isZoomed); const _Viewer(this.url, this.isZoomed, {Key? key}) : super(key: key);
final String url; final String url;
final ValueNotifier<bool> isZoomed; final ValueNotifier<bool> isZoomed;

View File

@ -5,7 +5,7 @@ import 'package:wonders/logic/common/platform_info.dart';
import 'package:youtube_player_iframe/youtube_player_iframe.dart'; import 'package:youtube_player_iframe/youtube_player_iframe.dart';
class FullscreenVideoViewer extends StatefulWidget { class FullscreenVideoViewer extends StatefulWidget {
const FullscreenVideoViewer({super.key, required this.id}); const FullscreenVideoViewer({Key? key, required this.id}) : super(key: key);
final String id; final String id;
@override @override

View File

@ -2,7 +2,7 @@ import 'package:webview_flutter/webview_flutter.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class FullscreenWebView extends StatelessWidget { class FullscreenWebView extends StatelessWidget {
FullscreenWebView(this.url, {super.key}); FullscreenWebView(this.url, {Key? key}) : super(key: key);
final String url; final String url;
late final controller = WebViewController() late final controller = WebViewController()

View File

@ -3,12 +3,13 @@ import 'package:wonders/ui/common/measurable_widget.dart';
class OpeningCard extends StatefulWidget { class OpeningCard extends StatefulWidget {
const OpeningCard( const OpeningCard(
{super.key, {Key? key,
required this.closedBuilder, required this.closedBuilder,
required this.openBuilder, required this.openBuilder,
required this.isOpen, required this.isOpen,
this.background, this.background,
this.padding}); this.padding})
: super(key: key);
final Widget Function(BuildContext) closedBuilder; final Widget Function(BuildContext) closedBuilder;
final Widget Function(BuildContext) openBuilder; final Widget Function(BuildContext) openBuilder;

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class PopNavigatorUnderlay extends StatelessWidget { class PopNavigatorUnderlay extends StatelessWidget {
const PopNavigatorUnderlay({super.key}); const PopNavigatorUnderlay({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class PopRouterOnOverScroll extends StatefulWidget { class PopRouterOnOverScroll extends StatefulWidget {
const PopRouterOnOverScroll({super.key, required this.child, required this.controller}); const PopRouterOnOverScroll({Key? key, required this.child, required this.controller}) : super(key: key);
final ScrollController controller; final ScrollController controller;
final Widget child; final Widget child;

View File

@ -2,7 +2,7 @@ import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/utils/context_utils.dart'; import 'package:wonders/ui/common/utils/context_utils.dart';
class AnimatedListItem extends StatelessWidget { class AnimatedListItem extends StatelessWidget {
const AnimatedListItem({super.key, required this.scrollPos, required this.builder}); const AnimatedListItem({Key? key, required this.scrollPos, required this.builder}) : super(key: key);
final ValueNotifier<double> scrollPos; final ValueNotifier<double> scrollPos;
final Widget Function(BuildContext context, double pctVisible) builder; final Widget Function(BuildContext context, double pctVisible) builder;
@ -35,7 +35,7 @@ class AnimatedListItem extends StatelessWidget {
/// Takes a scroll position notifier and a child. /// Takes a scroll position notifier and a child.
/// Scales its child as it scrolls onto screen for a nice effect. /// Scales its child as it scrolls onto screen for a nice effect.
class ScalingListItem extends StatelessWidget { class ScalingListItem extends StatelessWidget {
const ScalingListItem({super.key, required this.scrollPos, required this.child}); const ScalingListItem({Key? key, required this.scrollPos, required this.child}) : super(key: key);
final ValueNotifier<double> scrollPos; final ValueNotifier<double> scrollPos;
final Widget child; final Widget child;

View File

@ -11,12 +11,12 @@ import 'package:wonders/common_libs.dart';
/// ///
class StackedPageViewBuilder extends StatefulWidget { class StackedPageViewBuilder extends StatefulWidget {
const StackedPageViewBuilder({ const StackedPageViewBuilder({
super.key, Key? key,
this.initialIndex = 0, this.initialIndex = 0,
required this.pageCount, required this.pageCount,
required this.builder, required this.builder,
this.onInit, this.onInit,
}); }) : super(key: key);
final int initialIndex; final int initialIndex;
final int pageCount; final int pageCount;
final Widget Function(BuildContext builder, PageController controller, PageController follower) builder; final Widget Function(BuildContext builder, PageController controller, PageController follower) builder;

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class StaticTextScale extends StatelessWidget { class StaticTextScale extends StatelessWidget {
const StaticTextScale({super.key, required this.child, this.scale = 1}); const StaticTextScale({Key? key, required this.child, this.scale = 1}) : super(key: key);
final Widget child; final Widget child;
final double scale; final double scale;

View File

@ -1,7 +1,7 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
class DefaultTextColor extends StatelessWidget { class DefaultTextColor extends StatelessWidget {
const DefaultTextColor({super.key, required this.color, required this.child}); const DefaultTextColor({Key? key, required this.color, required this.child}) : super(key: key);
final Color color; final Color color;
final Widget child; final Widget child;
@ -15,7 +15,7 @@ class DefaultTextColor extends StatelessWidget {
} }
class LightText extends StatelessWidget { class LightText extends StatelessWidget {
const LightText({super.key, required this.child}); const LightText({Key? key, required this.child}) : super(key: key);
final Widget child; final Widget child;
@override @override
@ -26,7 +26,7 @@ class LightText extends StatelessWidget {
} }
class DarkText extends StatelessWidget { class DarkText extends StatelessWidget {
const DarkText({super.key, required this.child}); const DarkText({Key? key, required this.child}) : super(key: key);
final Widget child; final Widget child;
@override @override

View File

@ -3,7 +3,7 @@ import 'package:wonders/logic/common/string_utils.dart';
import 'package:wonders/ui/common/themed_text.dart'; import 'package:wonders/ui/common/themed_text.dart';
class TimelineEventCard extends StatelessWidget { class TimelineEventCard extends StatelessWidget {
const TimelineEventCard({super.key, required this.year, required this.text, this.darkMode = false}); const TimelineEventCard({Key? key, required this.year, required this.text, this.darkMode = false}) : super(key: key);
final int year; final int year;
final String text; final String text;
final bool darkMode; final bool darkMode;

View File

@ -2,7 +2,8 @@ import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/data/unsplash_photo_data.dart'; import 'package:wonders/logic/data/unsplash_photo_data.dart';
class UnsplashPhoto extends StatelessWidget { class UnsplashPhoto extends StatelessWidget {
const UnsplashPhoto(this.id, {super.key, this.fit = BoxFit.cover, required this.size, this.showCredits = false}); const UnsplashPhoto(this.id, {Key? key, this.fit = BoxFit.cover, required this.size, this.showCredits = false})
: super(key: key);
final String id; final String id;
final BoxFit fit; final BoxFit fit;
final UnsplashPhotoSize size; final UnsplashPhotoSize size;

View File

@ -1,16 +0,0 @@
import 'package:flutter/material.dart';
import 'package:wonders/assets.dart';
class WonderousLogo extends StatelessWidget {
const WonderousLogo({super.key, this.width = 100});
final double width;
@override
Widget build(BuildContext context) => Image.asset(
ImagePaths.appLogoPlain,
fit: BoxFit.cover,
width: width,
filterQuality: FilterQuality.high,
);
}

View File

@ -6,13 +6,13 @@ import 'package:wonders/logic/data/wonder_data.dart';
/// Provides a builder, so the visual representation of each track entry can be customized /// Provides a builder, so the visual representation of each track entry can be customized
class WondersTimelineBuilder extends StatelessWidget { class WondersTimelineBuilder extends StatelessWidget {
const WondersTimelineBuilder({ const WondersTimelineBuilder({
super.key, Key? key,
this.selectedWonders = const [], this.selectedWonders = const [],
this.timelineBuilder, this.timelineBuilder,
this.axis = Axis.horizontal, this.axis = Axis.horizontal,
this.crossAxisGap, this.crossAxisGap,
this.minSize = 10, this.minSize = 10,
}); }) : super(key: key);
final List<WonderType> selectedWonders; final List<WonderType> selectedWonders;
final Widget Function(BuildContext, WonderData type, bool isSelected)? timelineBuilder; final Widget Function(BuildContext, WonderData type, bool isSelected)? timelineBuilder;
final Axis axis; final Axis axis;
@ -96,7 +96,7 @@ class WondersTimelineBuilder extends StatelessWidget {
} }
class _DefaultTrackEntry extends StatelessWidget { class _DefaultTrackEntry extends StatelessWidget {
const _DefaultTrackEntry({required this.isSelected}); const _DefaultTrackEntry({Key? key, required this.isSelected}) : super(key: key);
final bool isSelected; final bool isSelected;
@override @override

View File

@ -12,7 +12,7 @@ part 'widgets/_bottom_text_content.dart';
part 'widgets/_collapsing_carousel_item.dart'; part 'widgets/_collapsing_carousel_item.dart';
class ArtifactCarouselScreen extends StatefulWidget { class ArtifactCarouselScreen extends StatefulWidget {
const ArtifactCarouselScreen({super.key, required this.type, this.contentPadding = EdgeInsets.zero}); const ArtifactCarouselScreen({Key? key, required this.type, this.contentPadding = EdgeInsets.zero}) : super(key: key);
final WonderType type; final WonderType type;
final EdgeInsets contentPadding; final EdgeInsets contentPadding;
@ -34,13 +34,13 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
_currentArtifactIndex.value = _wrappedPageIndex; _currentArtifactIndex.value = _wrappedPageIndex;
} }
void _handleSearchTap() => context.go(ScreenPaths.search(widget.type)); void _handleSearchTap() => context.push(ScreenPaths.search(widget.type));
void _handleArtifactTap(int index) { void _handleArtifactTap(int index) {
int delta = index - _currentPage.value.round(); int delta = index - _currentPage.value.round();
if (delta == 0) { if (delta == 0) {
HighlightData data = _artifacts[index % _artifacts.length]; HighlightData data = _artifacts[index % _artifacts.length];
context.go(ScreenPaths.artifact(data.artifactId)); context.push(ScreenPaths.artifact(data.artifactId));
} else { } else {
_pageController?.animateToPage( _pageController?.animateToPage(
_currentPage.value.round() + delta, _currentPage.value.round() + delta,

View File

@ -2,7 +2,7 @@ part of '../artifact_carousel_screen.dart';
/// Blurry image background for the Artifact Highlights view. Contains horizontal and vertical gradients that stack overtop the blended image. /// Blurry image background for the Artifact Highlights view. Contains horizontal and vertical gradients that stack overtop the blended image.
class _BlurredImageBg extends StatelessWidget { class _BlurredImageBg extends StatelessWidget {
const _BlurredImageBg({super.key, this.url}); const _BlurredImageBg({Key? key, this.url}) : super(key: key);
final String? url; final String? url;
@override @override

View File

@ -2,7 +2,8 @@ part of '../artifact_carousel_screen.dart';
class _BottomTextContent extends StatelessWidget { class _BottomTextContent extends StatelessWidget {
const _BottomTextContent( const _BottomTextContent(
{super.key, required this.artifact, required this.height, required this.state, required this.shortMode}); {Key? key, required this.artifact, required this.height, required this.state, required this.shortMode})
: super(key: key);
final HighlightData artifact; final HighlightData artifact;
final double height; final double height;

View File

@ -4,12 +4,13 @@ part of '../artifact_carousel_screen.dart';
/// This lets the child simply render it's contents /// This lets the child simply render it's contents
class _CollapsingCarouselItem extends StatelessWidget { class _CollapsingCarouselItem extends StatelessWidget {
const _CollapsingCarouselItem( const _CollapsingCarouselItem(
{super.key, {Key? key,
required this.child, required this.child,
required this.indexOffset, required this.indexOffset,
required this.width, required this.width,
required this.onPressed, required this.onPressed,
required this.title}); required this.title})
: super(key: key);
final Widget child; final Widget child;
final int indexOffset; final int indexOffset;
final double width; final double width;
@ -49,11 +50,11 @@ class _CollapsingCarouselItem extends StatelessWidget {
class _AnimatedTranslate extends StatelessWidget { class _AnimatedTranslate extends StatelessWidget {
const _AnimatedTranslate({ const _AnimatedTranslate({
super.key, Key? key,
required this.duration, required this.duration,
required this.offset, required this.offset,
required this.child, required this.child,
}); }) : super(key: key);
final Duration duration; final Duration duration;
final Offset offset; final Offset offset;
final Widget child; final Widget child;
@ -70,7 +71,7 @@ class _AnimatedTranslate extends StatelessWidget {
} }
class _DoubleBorderImage extends StatelessWidget { class _DoubleBorderImage extends StatelessWidget {
const _DoubleBorderImage(this.data, {super.key}); const _DoubleBorderImage(this.data, {Key? key}) : super(key: key);
final HighlightData data; final HighlightData data;
@override @override
Widget build(BuildContext context) => Container( Widget build(BuildContext context) => Container(

View File

@ -10,7 +10,7 @@ part 'widgets/_info_column.dart';
part 'widgets/_artifact_image_btn.dart'; part 'widgets/_artifact_image_btn.dart';
class ArtifactDetailsScreen extends StatefulWidget { class ArtifactDetailsScreen extends StatefulWidget {
const ArtifactDetailsScreen({super.key, required this.artifactId}); const ArtifactDetailsScreen({Key? key, required this.artifactId}) : super(key: key);
final String artifactId; final String artifactId;
@override @override

View File

@ -1,7 +1,7 @@
part of '../artifact_details_screen.dart'; part of '../artifact_details_screen.dart';
class _ArtifactImageBtn extends StatelessWidget { class _ArtifactImageBtn extends StatelessWidget {
const _ArtifactImageBtn({super.key, required this.data}); const _ArtifactImageBtn({Key? key, required this.data}) : super(key: key);
final ArtifactData data; final ArtifactData data;
@override @override

View File

@ -1,7 +1,7 @@
part of '../artifact_details_screen.dart'; part of '../artifact_details_screen.dart';
class _InfoColumn extends StatelessWidget { class _InfoColumn extends StatelessWidget {
const _InfoColumn({super.key, required this.data}); const _InfoColumn({Key? key, required this.data}) : super(key: key);
final ArtifactData data; final ArtifactData data;
@override @override
@ -67,7 +67,7 @@ class _InfoColumn extends StatelessWidget {
} }
class _InfoRow extends StatelessWidget { class _InfoRow extends StatelessWidget {
const _InfoRow(this.label, this.value, {super.key}); const _InfoRow(this.label, this.value, {Key? key}) : super(key: key);
final String label; final String label;
final String value; final String value;

View File

@ -15,7 +15,7 @@ part 'widgets/_search_input.dart';
/// User can use this screen to search the MET server for an artifact by name or timeline. Artifacts results will /// User can use this screen to search the MET server for an artifact by name or timeline. Artifacts results will
/// appear as images, which the user can click on to being up the details view for more information. /// appear as images, which the user can click on to being up the details view for more information.
class ArtifactSearchScreen extends StatefulWidget with GetItStatefulWidgetMixin { class ArtifactSearchScreen extends StatefulWidget with GetItStatefulWidgetMixin {
ArtifactSearchScreen({super.key, required this.type}); ArtifactSearchScreen({Key? key, required this.type}) : super(key: key);
final WonderType type; final WonderType type;
@override @override
@ -65,7 +65,7 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
_updateFilter(); _updateFilter();
} }
void _handleResultPressed(SearchData o) => context.go(ScreenPaths.artifact(o.id.toString())); void _handleResultPressed(SearchData o) => context.push(ScreenPaths.artifact(o.id.toString()));
void _handlePanelControllerChanged() { void _handlePanelControllerChanged() {
settingsLogic.isSearchPanelOpen.value = panelController.value; settingsLogic.isSearchPanelOpen.value = panelController.value;
@ -203,7 +203,7 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
} }
class PanelController extends ValueNotifier<bool> { class PanelController extends ValueNotifier<bool> {
PanelController(super.value); PanelController(bool value) : super(value);
void toggle() => value = !value; void toggle() => value = !value;
} }

View File

@ -11,14 +11,14 @@ import 'package:wonders/ui/screens/artifact/artifact_search/time_range_selector/
// Expandable timerange selector component that further refines Artifact Search based on date range. // Expandable timerange selector component that further refines Artifact Search based on date range.
class ExpandingTimeRangeSelector extends StatefulWidget { class ExpandingTimeRangeSelector extends StatefulWidget {
const ExpandingTimeRangeSelector({ const ExpandingTimeRangeSelector({
super.key, Key? key,
required this.wonder, required this.wonder,
required this.startYear, required this.startYear,
required this.endYear, required this.endYear,
required this.onChanged, required this.onChanged,
required this.panelController, required this.panelController,
required this.vizController, required this.vizController,
}); }) : super(key: key);
final WonderData wonder; final WonderData wonder;
final double startYear; final double startYear;
final double endYear; final double endYear;
@ -95,9 +95,10 @@ class _ExpandingTimeRangeSelectorState extends State<ExpandingTimeRangeSelector>
class _ClosedTimeRange extends StatelessWidget { class _ClosedTimeRange extends StatelessWidget {
const _ClosedTimeRange({ const _ClosedTimeRange({
Key? key,
required this.startYear, required this.startYear,
required this.endYear, required this.endYear,
}); }) : super(key: key);
final double startYear, endYear; final double startYear, endYear;
@override @override
@ -119,13 +120,14 @@ class _ClosedTimeRange extends StatelessWidget {
class _OpenedTimeRange extends StatelessWidget { class _OpenedTimeRange extends StatelessWidget {
const _OpenedTimeRange({ const _OpenedTimeRange({
Key? key,
required this.onChange, required this.onChange,
required this.startYear, required this.startYear,
required this.endYear, required this.endYear,
required this.wonder, required this.wonder,
required this.painter, required this.painter,
required this.onClose, required this.onClose,
}); }) : super(key: key);
final double startYear; final double startYear;
final double endYear; final double endYear;
final void Function(double start, double end) onChange; final void Function(double start, double end) onChange;

View File

@ -6,7 +6,7 @@ class RangeSelector extends StatefulWidget {
static const double handleWidth = 20; static const double handleWidth = 20;
const RangeSelector({ const RangeSelector({
super.key, Key? key,
required this.start, required this.start,
required this.end, required this.end,
required this.min, required this.min,
@ -15,7 +15,7 @@ class RangeSelector extends StatefulWidget {
this.isLocked = false, this.isLocked = false,
this.onUpdated, this.onUpdated,
this.onChanged, this.onChanged,
}); }) : super(key: key);
final double start; final double start;
final double end; final double end;
final double min; final double min;

View File

@ -1,7 +1,7 @@
part of '../artifact_search_screen.dart'; part of '../artifact_search_screen.dart';
class _ResultTile extends StatelessWidget { class _ResultTile extends StatelessWidget {
const _ResultTile({super.key, required this.onPressed, required this.data}); const _ResultTile({Key? key, required this.onPressed, required this.data}) : super(key: key);
final void Function(SearchData data) onPressed; final void Function(SearchData data) onPressed;
final SearchData data; final SearchData data;

View File

@ -2,7 +2,7 @@ part of '../artifact_search_screen.dart';
/// Staggered Masonry styled grid for displaying two columns of different aspect-ratio images. /// Staggered Masonry styled grid for displaying two columns of different aspect-ratio images.
class _ResultsGrid extends StatefulWidget { class _ResultsGrid extends StatefulWidget {
const _ResultsGrid({super.key, required this.searchResults, required this.onPressed}); const _ResultsGrid({Key? key, required this.searchResults, required this.onPressed}) : super(key: key);
final void Function(SearchData) onPressed; final void Function(SearchData) onPressed;
final List<SearchData> searchResults; final List<SearchData> searchResults;

View File

@ -2,7 +2,7 @@ part of '../artifact_search_screen.dart';
/// Autopopulating textfield used for searching for Artifacts by name. /// Autopopulating textfield used for searching for Artifacts by name.
class _SearchInput extends StatelessWidget { class _SearchInput extends StatelessWidget {
const _SearchInput({super.key, required this.onSubmit, required this.wonder}); const _SearchInput({Key? key, required this.onSubmit, required this.wonder}) : super(key: key);
final void Function(String) onSubmit; final void Function(String) onSubmit;
final WonderData wonder; final WonderData wonder;
@ -23,9 +23,7 @@ class _SearchInput extends StatelessWidget {
} }
Iterable<String> _getSuggestions(TextEditingValue textEditingValue) { Iterable<String> _getSuggestions(TextEditingValue textEditingValue) {
if (textEditingValue.text == '') { if (textEditingValue.text == '') return wonder.searchSuggestions.getRange(0, 10);
return wonder.searchSuggestions.getRange(0, 10);
}
return wonder.searchSuggestions.where((str) { return wonder.searchSuggestions.where((str) {
return str.startsWith(textEditingValue.text.toLowerCase()); return str.startsWith(textEditingValue.text.toLowerCase());

View File

@ -12,7 +12,7 @@ part 'widgets/_celebration_particles.dart';
class CollectibleFoundScreen extends StatelessWidget { class CollectibleFoundScreen extends StatelessWidget {
// CollectibleItem passes in a (theoretically) pre-loaded imageProvider. // CollectibleItem passes in a (theoretically) pre-loaded imageProvider.
// we could check for load completion, and hold after introT, but that shouldn't be necessary in a real-world scenario. // we could check for load completion, and hold after introT, but that shouldn't be necessary in a real-world scenario.
const CollectibleFoundScreen({required this.collectible, required this.imageProvider, super.key}); const CollectibleFoundScreen({required this.collectible, required this.imageProvider, Key? key}) : super(key: key);
final CollectibleData collectible; final CollectibleData collectible;
final ImageProvider imageProvider; final ImageProvider imageProvider;
@ -44,10 +44,7 @@ class CollectibleFoundScreen extends StatelessWidget {
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
) ).animate().scale(begin: 1.5, end: 3, curve: Curves.easeInExpo, delay: t, duration: t * 3).fadeOut(),
.animate()
.scale(begin: Offset(1.5, 1.5), end: Offset(3, 3), curve: Curves.easeInExpo, delay: t, duration: t * 3)
.fadeOut(),
) )
]); ]);
} }
@ -143,14 +140,14 @@ class CollectibleFoundScreen extends StatelessWidget {
child: child, child: child,
), ),
) )
.scale(begin: Offset(0.3, 0.3), duration: t * 2, curve: Curves.easeOutExpo, alignment: Alignment(0, 0.7)); .scale(begin: 0.3, duration: t * 2, curve: Curves.easeOutExpo, alignment: Alignment(0, 0.7));
} }
Widget _buildRibbon(BuildContext context) { Widget _buildRibbon(BuildContext context) {
Duration t = $styles.times.fast; Duration t = $styles.times.fast;
return _AnimatedRibbon($strings.collectibleFoundTitleArtifactDiscovered.toUpperCase()) return _AnimatedRibbon($strings.collectibleFoundTitleArtifactDiscovered.toUpperCase())
.animate() .animate()
.scale(begin: Offset(0.3, 0.3), duration: t * 2, curve: Curves.easeOutExpo, alignment: Alignment(0, -1)); .scale(begin: 0.3, duration: t * 2, curve: Curves.easeOutExpo, alignment: Alignment(0, -1));
} }
Widget _buildTitle(BuildContext context, String text, TextStyle style, Color color, Duration delay) { Widget _buildTitle(BuildContext context, String text, TextStyle style, Color color, Duration delay) {
@ -187,6 +184,6 @@ class CollectibleFoundScreen extends StatelessWidget {
void _handleViewCollectionPressed(BuildContext context) { void _handleViewCollectionPressed(BuildContext context) {
Navigator.pop(context); Navigator.pop(context);
context.go(ScreenPaths.collection(collectible.id)); context.push(ScreenPaths.collection(collectible.id));
} }
} }

View File

@ -1,7 +1,7 @@
part of '../collectible_found_screen.dart'; part of '../collectible_found_screen.dart';
class _AnimatedRibbon extends StatelessWidget { class _AnimatedRibbon extends StatelessWidget {
const _AnimatedRibbon(this.text, {super.key}); const _AnimatedRibbon(this.text, {Key? key}) : super(key: key);
final String text; final String text;
static const double height = 48; static const double height = 48;

View File

@ -1,7 +1,7 @@
part of '../collectible_found_screen.dart'; part of '../collectible_found_screen.dart';
class _CelebrationParticles extends StatelessWidget { class _CelebrationParticles extends StatelessWidget {
const _CelebrationParticles({super.key, this.fadeMs = 1000}); const _CelebrationParticles({Key? key, this.fadeMs = 1000}) : super(key: key);
final int fadeMs; final int fadeMs;

View File

@ -16,7 +16,7 @@ part 'widgets/_collection_list_card.dart';
part 'widgets/_newly_discovered_items_btn.dart'; part 'widgets/_newly_discovered_items_btn.dart';
class CollectionScreen extends StatefulWidget with GetItStatefulWidgetMixin { class CollectionScreen extends StatefulWidget with GetItStatefulWidgetMixin {
CollectionScreen({required this.fromId, super.key}); CollectionScreen({required this.fromId, Key? key}) : super(key: key);
final String fromId; final String fromId;

View File

@ -2,12 +2,12 @@ part of '../collection_screen.dart';
class _CollectibleImage extends StatelessWidget { class _CollectibleImage extends StatelessWidget {
const _CollectibleImage({ const _CollectibleImage({
super.key, Key? key,
required this.collectible, required this.collectible,
required this.state, required this.state,
required this.onPressed, required this.onPressed,
this.heroTag, this.heroTag,
}); }) : super(key: key);
final CollectibleData collectible; final CollectibleData collectible;
final ValueSetter<CollectibleData> onPressed; final ValueSetter<CollectibleData> onPressed;

View File

@ -2,7 +2,7 @@ part of '../collection_screen.dart';
@immutable @immutable
class _CollectionFooter extends StatelessWidget { class _CollectionFooter extends StatelessWidget {
const _CollectionFooter({super.key, required this.count, required this.total}); const _CollectionFooter({Key? key, required this.count, required this.total}) : super(key: key);
final int count; final int count;
final int total; final int total;
@ -42,10 +42,9 @@ class _CollectionFooter extends StatelessWidget {
} }
Widget _buildProgressRow(BuildContext context) { Widget _buildProgressRow(BuildContext context) {
int percent = (count / total * 100).round();
return Row(children: [ return Row(children: [
Text( Text(
$strings.collectionLabelDiscovered(percent), $strings.collectionLabelDiscovered((count / total * 100).round()),
style: $styles.text.body.copyWith(color: $styles.colors.accent1), style: $styles.text.body.copyWith(color: $styles.colors.accent1),
), ),
Spacer(), Spacer(),

View File

@ -3,11 +3,11 @@ part of '../collection_screen.dart';
@immutable @immutable
class _CollectionList extends StatefulWidget with GetItStatefulWidgetMixin { class _CollectionList extends StatefulWidget with GetItStatefulWidgetMixin {
_CollectionList({ _CollectionList({
super.key, Key? key,
this.onReset, this.onReset,
required this.fromId, required this.fromId,
this.scrollKey, this.scrollKey,
}); }) : super(key: key);
static const double _vtCardExtent = 300; static const double _vtCardExtent = 300;
static const double _hzCardExtent = 600; static const double _hzCardExtent = 600;
@ -63,7 +63,7 @@ class _CollectionListState extends State<_CollectionList> with GetItStateMixin {
fromId: widget.fromId, fromId: widget.fromId,
data: d, data: d,
); );
}) }).toList()
]; ];
// Scroll view adapts to scroll vertically or horizontally // Scroll view adapts to scroll vertically or horizontally
return SingleChildScrollView( return SingleChildScrollView(

View File

@ -1,7 +1,7 @@
part of '../collection_screen.dart'; part of '../collection_screen.dart';
class _CollectionListCard extends StatelessWidget with GetItMixin { class _CollectionListCard extends StatelessWidget with GetItMixin {
_CollectionListCard({super.key, this.width, this.height, required this.data, required this.fromId}); _CollectionListCard({Key? key, this.width, this.height, required this.data, required this.fromId}) : super(key: key);
final double? width; final double? width;
final double? height; final double? height;
@ -9,7 +9,7 @@ class _CollectionListCard extends StatelessWidget with GetItMixin {
final String fromId; final String fromId;
void _showDetails(BuildContext context, CollectibleData collectible) { void _showDetails(BuildContext context, CollectibleData collectible) {
context.go(ScreenPaths.artifact(collectible.artifactId)); context.push(ScreenPaths.artifact(collectible.artifactId));
Future.delayed(300.ms).then((_) => collectiblesLogic.setState(collectible.id, CollectibleState.explored)); Future.delayed(300.ms).then((_) => collectiblesLogic.setState(collectible.id, CollectibleState.explored));
} }
@ -48,7 +48,7 @@ class _CollectionListCard extends StatelessWidget with GetItMixin {
heroTag: e.id == fromId ? 'collectible_image_$fromId' : null, heroTag: e.id == fromId ? 'collectible_image_$fromId' : null,
), ),
); );
}) }).toList()
]), ]),
) )
], ],

View File

@ -2,7 +2,7 @@ part of '../collection_screen.dart';
@immutable @immutable
class _NewlyDiscoveredItemsBtn extends StatelessWidget { class _NewlyDiscoveredItemsBtn extends StatelessWidget {
const _NewlyDiscoveredItemsBtn({super.key, this.count = 0, required this.onPressed}); const _NewlyDiscoveredItemsBtn({Key? key, this.count = 0, required this.onPressed}) : super(key: key);
final int count; final int count;
final VoidCallback onPressed; final VoidCallback onPressed;

View File

@ -38,7 +38,7 @@ part 'widgets/_title_text.dart';
part 'widgets/_top_illustration.dart'; part 'widgets/_top_illustration.dart';
class WonderEditorialScreen extends StatefulWidget { class WonderEditorialScreen extends StatefulWidget {
const WonderEditorialScreen(this.data, {super.key, required this.contentPadding}); const WonderEditorialScreen(this.data, {Key? key, required this.contentPadding}) : super(key: key);
final WonderData data; final WonderData data;
//final void Function(double scrollPos) onScroll; //final void Function(double scrollPos) onScroll;
final EdgeInsets contentPadding; final EdgeInsets contentPadding;
@ -63,8 +63,6 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
_scrollPos.value = _scroller.position.pixels; _scrollPos.value = _scroller.position.pixels;
} }
void _handleBackPressed() => context.go(ScreenPaths.home);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, constraints) { return LayoutBuilder(builder: (_, constraints) {
@ -183,7 +181,7 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
alignment: backBtnAlign, alignment: backBtnAlign,
child: Padding( child: Padding(
padding: EdgeInsets.all($styles.insets.sm), padding: EdgeInsets.all($styles.insets.sm),
child: BackBtn(icon: AppIcons.north, onPressed: _handleBackPressed), child: BackBtn(icon: AppIcons.north),
), ),
), ),
) )

View File

@ -1,7 +1,7 @@
part of '../editorial_screen.dart'; part of '../editorial_screen.dart';
class _AppBar extends StatelessWidget { class _AppBar extends StatelessWidget {
_AppBar(this.wonderType, {super.key, required this.sectionIndex, required this.scrollPos}); _AppBar(this.wonderType, {Key? key, required this.sectionIndex, required this.scrollPos}) : super(key: key);
final WonderType wonderType; final WonderType wonderType;
final ValueNotifier<int> sectionIndex; final ValueNotifier<int> sectionIndex;
final ValueNotifier<double> scrollPos; final ValueNotifier<double> scrollPos;
@ -18,16 +18,24 @@ class _AppBar extends StatelessWidget {
]; ];
ArchType _getArchType() { ArchType _getArchType() {
return switch (wonderType) { switch (wonderType) {
WonderType.chichenItza => ArchType.flatPyramid, case WonderType.chichenItza:
WonderType.christRedeemer => ArchType.wideArch, return ArchType.flatPyramid;
WonderType.colosseum => ArchType.arch, case WonderType.christRedeemer:
WonderType.greatWall => ArchType.arch, return ArchType.wideArch;
WonderType.machuPicchu => ArchType.pyramid, case WonderType.colosseum:
WonderType.petra => ArchType.wideArch, return ArchType.arch;
WonderType.pyramidsGiza => ArchType.pyramid, case WonderType.greatWall:
WonderType.tajMahal => ArchType.spade return ArchType.arch;
}; case WonderType.machuPicchu:
return ArchType.pyramid;
case WonderType.petra:
return ArchType.wideArch;
case WonderType.pyramidsGiza:
return ArchType.pyramid;
case WonderType.tajMahal:
return ArchType.spade;
}
} }
@override @override

View File

@ -3,7 +3,7 @@ part of '../editorial_screen.dart';
class _Callout extends StatelessWidget { class _Callout extends StatelessWidget {
final String text; final String text;
const _Callout({super.key, required this.text}); const _Callout({Key? key, required this.text}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IntrinsicHeight( return IntrinsicHeight(

View File

@ -1,8 +1,9 @@
part of '../editorial_screen.dart'; part of '../editorial_screen.dart';
class _CircularTitleBar extends StatelessWidget { class _CircularTitleBar extends StatelessWidget {
const _CircularTitleBar({super.key, required this.titles, required this.icons, required this.index}) const _CircularTitleBar({Key? key, required this.titles, required this.icons, required this.index})
: assert(titles.length == icons.length, 'The number of titles and icons do not match.'); : assert(titles.length == icons.length, 'The number of titles and icons do not match.'),
super(key: key);
final List<String> titles; final List<String> titles;
final List<String> icons; final List<String> icons;
final int index; final int index;
@ -34,8 +35,10 @@ class _CircularTitleBar extends StatelessWidget {
BottomCenter( BottomCenter(
child: Padding( child: Padding(
padding: EdgeInsets.only(bottom: 20), padding: EdgeInsets.only(bottom: 20),
child: Image.asset('${ImagePaths.common}/${icons[index]}').animate(key: ValueKey(index)).fade().scale( child: Image.asset('${ImagePaths.common}/${icons[index]}')
begin: Offset(.5, .5), end: Offset(1, 1), curve: Curves.easeOutBack, duration: $styles.times.med), .animate(key: ValueKey(index))
.fade()
.scale(begin: .5, end: 1, curve: Curves.easeOutBack, duration: $styles.times.med),
), ),
), ),
], ],
@ -47,10 +50,10 @@ class _CircularTitleBar extends StatelessWidget {
class _AnimatedCircleWithText extends StatefulWidget { class _AnimatedCircleWithText extends StatefulWidget {
const _AnimatedCircleWithText({ const _AnimatedCircleWithText({
super.key, Key? key,
required this.titles, required this.titles,
required this.index, required this.index,
}); }) : super(key: key);
final List<String> titles; final List<String> titles;
final int index; final int index;

View File

@ -1,7 +1,7 @@
part of '../editorial_screen.dart'; part of '../editorial_screen.dart';
class _CollapsingPullQuoteImage extends StatelessWidget { class _CollapsingPullQuoteImage extends StatelessWidget {
const _CollapsingPullQuoteImage({super.key, required this.scrollPos, required this.data}); const _CollapsingPullQuoteImage({Key? key, required this.scrollPos, required this.data}) : super(key: key);
final ValueNotifier<double> scrollPos; final ValueNotifier<double> scrollPos;
final WonderData data; final WonderData data;
@ -25,7 +25,7 @@ class _CollapsingPullQuoteImage extends StatelessWidget {
if (top) offsetY *= -1; // flip? if (top) offsetY *= -1; // flip?
return Transform.translate( return Transform.translate(
offset: Offset(0, offsetY), offset: Offset(0, offsetY),
child: Text(value, style: quoteStyle, textAlign: TextAlign.center), child:Text(value, style: quoteStyle, textAlign: TextAlign.center),
); );
} }

View File

@ -1,7 +1,7 @@
part of '../editorial_screen.dart'; part of '../editorial_screen.dart';
class _LargeSimpleQuote extends StatelessWidget { class _LargeSimpleQuote extends StatelessWidget {
const _LargeSimpleQuote({super.key, required this.text, required this.author}); const _LargeSimpleQuote({Key? key, required this.text, required this.author}) : super(key: key);
final String text; final String text;
final String author; final String author;

Some files were not shown because too many files have changed in this diff Show More