Compare commits
66 Commits
feature/ho
...
main
Author | SHA1 | Date | |
---|---|---|---|
51f01a661a | |||
|
0331605e1e | ||
|
11b040ae9e | ||
|
1f1bc63bc1 | ||
|
e5f1c55993 | ||
|
596abc850c | ||
|
b6699831d7 | ||
|
371421a049 | ||
|
1b508f3200 | ||
|
dadb6275fc | ||
|
c00dae0b97 | ||
|
01ba5f624f | ||
|
e41f2ba530 | ||
|
1dfa1574c8 | ||
|
095205d047 | ||
|
648eac7298 | ||
|
a1f2a778a2 | ||
|
7f892df3c8 | ||
|
9c64993cff | ||
|
097295f393 | ||
|
bb2564efdd | ||
|
85379a148a | ||
|
8d5daf3edd | ||
|
45a9a8348f | ||
|
3ee40b811a | ||
|
b7b9141682 | ||
|
9194c8a41a | ||
|
2faa7d7aab | ||
|
663ad161fd | ||
|
9d2409cc9d | ||
|
b28c6994f7 | ||
|
c018fa3dd1 | ||
|
be5e4fbdf3 | ||
|
a71de46804 | ||
|
4b34f81555 | ||
|
2f500a344e | ||
|
fa640e005d | ||
|
54cfee18c3 | ||
|
a40ce7c080 | ||
|
76eba1e3e9 | ||
|
4913c2fe92 | ||
|
5d8da79941 | ||
|
848ae5cbb6 | ||
|
5fea5fc017 | ||
|
59af5d9e3e | ||
|
1ffc18d001 | ||
|
80a5cbdcca | ||
|
537d2ed9e4 | ||
|
c450c71d05 | ||
|
c45e1fc77e | ||
|
ea47695847 | ||
|
cbbd343f71 | ||
|
90e642f4b2 | ||
|
539b79a20e | ||
|
2853af8f61 | ||
|
44300dbe23 | ||
|
c0fae98895 | ||
|
bbab178465 | ||
|
28734b7f1f | ||
|
00e3dcc234 | ||
|
3ce1d3a68d | ||
|
6e3d94eece | ||
|
3d9d15c5a0 | ||
|
824f4a1ff1 | ||
|
a6c99352e6 | ||
|
74b536b366 |
@ -9,6 +9,11 @@
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
errors:
|
||||
prefer_const_constructors: ignore
|
||||
unused_element: ignore # mostly because of super.key
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
@ -22,9 +27,8 @@ linter:
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
always_declare_return_types: true
|
||||
always_use_package_imports: true
|
||||
prefer_const_constructors: false
|
||||
prefer_single_quotes: true
|
||||
- always_declare_return_types
|
||||
- always_use_package_imports
|
||||
- prefer_single_quotes
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
@ -33,7 +33,7 @@ apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
compileSdkVersion 34
|
||||
|
||||
defaultConfig {
|
||||
configurations.all {
|
||||
@ -47,17 +47,9 @@ android {
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,4 +80,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
COCOAPODS: 1.15.0
|
||||
|
@ -9,6 +9,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
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 */; };
|
||||
297F6FC92AD06E0D00FF159E /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 297F6FC82AD06E0D00FF159E /* SwiftUI.framework */; };
|
||||
297F6FCC2AD06E0D00FF159E /* WonderousWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297F6FCB2AD06E0D00FF159E /* WonderousWidgetBundle.swift */; };
|
||||
@ -73,6 +74,7 @@
|
||||
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>"; };
|
||||
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; };
|
||||
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; };
|
||||
@ -141,6 +143,7 @@
|
||||
297FD5732AE18011008D8BFE /* WonderousWidgetView.swift */,
|
||||
297FD5752AE19BD9008D8BFE /* WonderWidgetViewComponents.swift */,
|
||||
296251242AE7410D00D574FF /* Colors.swift */,
|
||||
2978ECDC2B62D00C00E36CE8 /* FlutterAssets.swift */,
|
||||
);
|
||||
path = WonderousWidget;
|
||||
sourceTree = "<group>";
|
||||
@ -451,6 +454,7 @@
|
||||
297FD5762AE19BD9008D8BFE /* WonderWidgetViewComponents.swift in Sources */,
|
||||
296251252AE7410D00D574FF /* Colors.swift in Sources */,
|
||||
297F6FD32AD06E0F00FF159E /* WonderousWidget.intentdefinition in Sources */,
|
||||
2978ECDD2B62D00C00E36CE8 /* FlutterAssets.swift in Sources */,
|
||||
297FD5742AE18011008D8BFE /* WonderousWidgetView.swift in Sources */,
|
||||
297F6FCE2AD06E0D00FF159E /* WonderousWidget.swift in Sources */,
|
||||
297F6FCC2AD06E0D00FF159E /* WonderousWidgetBundle.swift in Sources */,
|
||||
@ -640,14 +644,14 @@
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = "WonderousWidgetExtension.entitlements";
|
||||
CODE_SIGN_ENTITLEMENTS = WonderousWidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = S3TL5AY6Y3;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "WonderousWidget/Info.plist";
|
||||
INFOPLIST_FILE = WonderousWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Wonderous Widget";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
/// Define some custom extensions on the Color class, so we can use the shorthand syntax `..myColor`
|
||||
extension Color {
|
||||
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)
|
||||
|
22
ios/WonderousWidget/FlutterAssets.swift
Normal file
22
ios/WonderousWidget/FlutterAssets.swift
Normal file
@ -0,0 +1,22 @@
|
||||
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
|
||||
}
|
@ -10,15 +10,14 @@ struct BgImage : View {
|
||||
var uiImage:UIImage?;
|
||||
// If there is no saved imageData, use the default bg image
|
||||
if(entry.imageData.isEmpty){
|
||||
let defaultImage = flutterAssetBundle.appending(path: "/assets/images/widget/background-empty.jpg").path();
|
||||
uiImage = UIImage(contentsOfFile: defaultImage);
|
||||
uiImage = UIImage(contentsOfFile: FlutterImages.bgEmpty);
|
||||
}
|
||||
// Load a base64 encoded image that has been written by the flutter app
|
||||
else {
|
||||
uiImage = UIImage(data: Data(base64Encoded: entry.imageData)!)
|
||||
}
|
||||
if(uiImage != nil){
|
||||
// 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)
|
||||
// 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)
|
||||
let image = GeometryReader { geometry in
|
||||
Image(uiImage: uiImage!)
|
||||
.resizable()
|
||||
@ -34,6 +33,7 @@ struct BgImage : View {
|
||||
|
||||
}
|
||||
|
||||
// Declares a restyled version of the native ProgressView
|
||||
struct GaugeProgressStyle: ProgressViewStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
let fractionCompleted = configuration.fractionCompleted ?? 0
|
||||
|
@ -2,19 +2,20 @@ import WidgetKit
|
||||
import SwiftUI
|
||||
import Intents
|
||||
|
||||
/// Entry, is passed into the view and defines the data it needs
|
||||
/// Every home-widget requires a TimelineEntry. This is passed into the view and propvides any data it needs
|
||||
struct WonderousTimelineEntry : TimelineEntry {
|
||||
// Date is a mandatory field for all TimelineEntries
|
||||
let date: Date
|
||||
// Custom field for the wonderous view
|
||||
let discoveredCount:Int;
|
||||
var title:String = "";
|
||||
var subTitle:String = "";
|
||||
var imageData:String = "";
|
||||
}
|
||||
|
||||
// Widget, defines the display name and description and also declared the main View
|
||||
/// Widget, defines some high level configuration options as well as the primary view that will display the widget.
|
||||
struct WonderousWidget: Widget {
|
||||
let kind: String = "WonderousWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: WonderousTimelineProvider()) { entry in
|
||||
WonderousWidgetView(entry: entry)
|
||||
@ -26,7 +27,12 @@ struct WonderousWidget: Widget {
|
||||
}
|
||||
}
|
||||
|
||||
// Provider,returns various WonderousEntry configs based on current context
|
||||
struct WonderousConfig {
|
||||
let iosKey = "group.com.gskinner.flutter.wonders.widget"
|
||||
let discoveredCountKey = "dicoveredCount"
|
||||
}
|
||||
|
||||
/// TimelineProvider, returns various WonderousTimelineEntry configurations for different contexts
|
||||
struct WonderousTimelineProvider: TimelineProvider {
|
||||
// Provide an entry for a placeholder version of the widget
|
||||
func placeholder(in context: Context) -> WonderousTimelineEntry {
|
||||
|
@ -1,13 +1,9 @@
|
||||
//
|
||||
// WonderousWidgetBundle.swift
|
||||
// Wonderous Widget
|
||||
//
|
||||
// Created by Shawn on 2023-10-06.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
// WonderousWidgetBundle
|
||||
// -> WonderousWidgetView
|
||||
// -> WonderousWidgetViewComponents
|
||||
@main
|
||||
struct WonderousWidgetBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
|
@ -2,7 +2,7 @@ import WidgetKit
|
||||
import SwiftUI
|
||||
import Intents
|
||||
|
||||
// Defines the view / layout of the widget
|
||||
/// Defines the view / layout of the widget
|
||||
struct WonderousWidgetView : View {
|
||||
@Environment(\.widgetFamily) var family: WidgetFamily
|
||||
var entry: WonderousTimelineProvider.Entry
|
||||
@ -10,14 +10,13 @@ struct WonderousWidgetView : View {
|
||||
let showTitle = family == .systemLarge
|
||||
let showIcon = family != .systemSmall
|
||||
let showTitleAndDesc = family != .systemSmall
|
||||
|
||||
let progress = Double(entry.discoveredCount) / 24.0
|
||||
let iconImage = flutterAssetBundle.appending(
|
||||
path: "/assets/images/widget/wonderous-icon.png"
|
||||
).path()
|
||||
let progressPct = Double(entry.discoveredCount) / 24.0
|
||||
let iconImage = FlutterImages.icon;
|
||||
let title = entry.title.isEmpty ? "Wonderous" : entry.title;
|
||||
let subTitle = entry.subTitle.isEmpty ? "Search for hidden artifacts" : entry.subTitle;
|
||||
|
||||
let content = VStack{
|
||||
// Top row with optional Title and Icon
|
||||
HStack {
|
||||
if(showTitle) {
|
||||
Text("Collection")
|
||||
@ -32,7 +31,10 @@ struct WonderousWidgetView : View {
|
||||
.frame(height: 24)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer();
|
||||
|
||||
// Bottom hz row with title, desc and progress gauge
|
||||
HStack {
|
||||
if(showTitleAndDesc) {
|
||||
VStack(alignment: .leading){
|
||||
@ -46,14 +48,16 @@ struct WonderousWidgetView : View {
|
||||
}
|
||||
Spacer();
|
||||
ZStack{
|
||||
ProgressView(value: progress)
|
||||
ProgressView(value: progressPct)
|
||||
.progressViewStyle(GaugeProgressStyle())
|
||||
.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{
|
||||
BgImage(entry: entry).opacity(0.8)
|
||||
LinearGradient(
|
||||
@ -62,21 +66,11 @@ struct WonderousWidgetView : View {
|
||||
endPoint: .bottom)
|
||||
content.padding(16)
|
||||
}
|
||||
// Ios requires that widgets have a background color
|
||||
.widgetBackground(Color.darkGrey)
|
||||
// Deeplink into collections view when tapped
|
||||
.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
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ final int maxYear = wondersLogic.timelineEndYear;
|
||||
const int maxRequests = 32;
|
||||
|
||||
class ArtifactSearchHelper extends StatefulWidget {
|
||||
const ArtifactSearchHelper({Key? key}) : super(key: key);
|
||||
const ArtifactSearchHelper({super.key});
|
||||
|
||||
@override
|
||||
State<ArtifactSearchHelper> createState() => _ArtifactSearchHelperState();
|
||||
@ -161,10 +161,14 @@ class _ArtifactSearchHelperState extends State<ArtifactSearchHelper> {
|
||||
//if (!json.containsKey('isPublicDomain') || !json['isPublicDomain']) return _logError(id, 'not public domain')
|
||||
|
||||
final int year = ((json['objectBeginDate'] as int) + (json['objectEndDate'] as int)) ~/ 2;
|
||||
if (year < minYear || year > maxYear) return _logError(id, 'year is out of range');
|
||||
if (year < minYear || year > maxYear) {
|
||||
return _logError(id, 'year is out of range');
|
||||
}
|
||||
|
||||
String? imageUrlSmall = json['primaryImageSmall'];
|
||||
if (imageUrlSmall == null || imageUrlSmall.isEmpty) return _logError(id, 'no small image url');
|
||||
if (imageUrlSmall == null || imageUrlSmall.isEmpty) {
|
||||
return _logError(id, 'no small image url');
|
||||
}
|
||||
// if (!imageUrlSmall.startsWith(SearchData.baseImagePath)) {
|
||||
// return _logError(id, 'unexpected image uri: "$imageUrlSmall"');
|
||||
// }
|
||||
|
@ -43,24 +43,16 @@ class SvgPaths {
|
||||
/// For wonder specific assets, add an extension to [WonderType] for easy lookup
|
||||
extension WonderAssetExtensions on WonderType {
|
||||
String get assetPath {
|
||||
switch (this) {
|
||||
case WonderType.pyramidsGiza:
|
||||
return '${ImagePaths.root}/pyramids';
|
||||
case WonderType.greatWall:
|
||||
return '${ImagePaths.root}/great_wall_of_china';
|
||||
case WonderType.petra:
|
||||
return '${ImagePaths.root}/petra';
|
||||
case WonderType.colosseum:
|
||||
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';
|
||||
}
|
||||
return switch (this) {
|
||||
WonderType.pyramidsGiza => '${ImagePaths.root}/pyramids',
|
||||
WonderType.greatWall => '${ImagePaths.root}/great_wall_of_china',
|
||||
WonderType.petra => '${ImagePaths.root}/petra',
|
||||
WonderType.colosseum => '${ImagePaths.root}/colosseum',
|
||||
WonderType.chichenItza => '${ImagePaths.root}/chichen_itza',
|
||||
WonderType.machuPicchu => '${ImagePaths.root}/machu_picchu',
|
||||
WonderType.tajMahal => '${ImagePaths.root}/taj_mahal',
|
||||
WonderType.christRedeemer => '${ImagePaths.root}/christ_the_redeemer'
|
||||
};
|
||||
}
|
||||
|
||||
String get homeBtn => '$assetPath/wonder-button.png';
|
||||
|
@ -1,4 +1,5 @@
|
||||
/// Consolidate imports that are common across the app.
|
||||
library;
|
||||
|
||||
export 'dart:math';
|
||||
|
||||
|
@ -427,5 +427,7 @@
|
||||
"timelineEvent1969ce": "Apollo 11 mission lands on the moon",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"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."
|
||||
}
|
@ -405,5 +405,7 @@
|
||||
"timelineEvent1957ce": "苏联发射斯普特尼克1号",
|
||||
"timelineEvent1969ce": "阿波罗11号在月球着陆",
|
||||
"privacyPolicy": "隐私政策",
|
||||
"privacyStatement": "gskinner 非常重视对用户隐私的保护,正如{privacyUrl}里所诉,gskinner 不会收集您的个人信息。"
|
||||
"privacyStatement": "gskinner 非常重视对用户隐私的保护,正如{privacyUrl}里所诉,gskinner 不会收集您的个人信息。",
|
||||
"pageNotFoundBackButton": "回到文明",
|
||||
"pageNotFoundMessage": "您正在寻找的页面不存在"
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:desktop_window/desktop_window.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
@ -34,10 +33,6 @@ class AppLogic {
|
||||
/// Loads settings, sets up services etc.
|
||||
Future<void> bootstrap() async {
|
||||
debugPrint('bootstrap start...');
|
||||
// Set min-sizes for desktop apps
|
||||
if (PlatformInfo.isDesktop) {
|
||||
await DesktopWindow.setMinWindowSize($styles.sizes.minAppSize);
|
||||
}
|
||||
|
||||
if (kIsWeb) {
|
||||
// SB: This is intentionally not a debugPrint, as it's a message for users who open the console on web.
|
||||
@ -81,7 +76,7 @@ class AppLogic {
|
||||
if (showIntro) {
|
||||
appRouter.go(ScreenPaths.intro);
|
||||
} else {
|
||||
appRouter.go(ScreenPaths.home);
|
||||
appRouter.go(initialDeeplink ?? ScreenPaths.home);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,14 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/common/save_load_mixin.dart';
|
||||
import 'package:wonders/logic/data/collectible_data.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:wonders/logic/native_widget_service.dart';
|
||||
|
||||
class CollectiblesLogic with ThrottledSaveLoadMixin {
|
||||
@override
|
||||
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
|
||||
final List<CollectibleData> all = collectiblesData;
|
||||
@ -26,9 +24,9 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
|
||||
|
||||
int get exploredCount => _exploredCount;
|
||||
|
||||
void init() {
|
||||
HomeWidget.setAppGroupId(_appGroupId);
|
||||
}
|
||||
late final _nativeWidget = GetIt.I<NativeWidgetService>();
|
||||
|
||||
void init() => _nativeWidget.init();
|
||||
|
||||
CollectibleData? fromId(String? id) => id == null ? null : all.firstWhereOrNull((o) => o.id == id);
|
||||
|
||||
@ -42,7 +40,7 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
|
||||
statesById.value = states;
|
||||
if (state == CollectibleState.discovered) {
|
||||
final data = fromId(id)!;
|
||||
_updateHomeWidgetTextData(
|
||||
_updateNativeHomeWidgetData(
|
||||
title: data.title,
|
||||
id: data.id,
|
||||
imageUrl: data.imageUrlSmall,
|
||||
@ -58,9 +56,10 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
|
||||
if (state == CollectibleState.explored) _exploredCount++;
|
||||
});
|
||||
final foundCount = discoveredCount + exploredCount;
|
||||
HomeWidget.saveWidgetData<int>('discoveredCount', foundCount).then((value) {
|
||||
HomeWidget.updateWidget(iOSName: _appName);
|
||||
_nativeWidget.save<int>('discoveredCount', foundCount).then((value) {
|
||||
_nativeWidget.markDirty();
|
||||
});
|
||||
|
||||
debugPrint('setting discoveredCount for home widget $foundCount');
|
||||
}
|
||||
|
||||
@ -92,32 +91,33 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
|
||||
for (int i = 0; i < all.length; i++) {
|
||||
states[all[i].id] = CollectibleState.lost;
|
||||
}
|
||||
_updateHomeWidgetTextData(); // clear home widget data
|
||||
_updateNativeHomeWidgetData(); // clear home widget data
|
||||
statesById.value = states;
|
||||
debugPrint('collection reset');
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
Future<void> _updateHomeWidgetTextData({String title = '', String id = '', String imageUrl = ''}) async {
|
||||
Future<void> _updateNativeHomeWidgetData({String title = '', String id = '', String imageUrl = ''}) async {
|
||||
if (!_nativeWidget.isSupported) return;
|
||||
// Save title
|
||||
await HomeWidget.saveWidgetData<String>('lastDiscoveredTitle', title);
|
||||
await _nativeWidget.save<String>('lastDiscoveredTitle', title);
|
||||
// Subtitle
|
||||
String subTitle = '';
|
||||
if (id.isNotEmpty) {
|
||||
final artifactData = await artifactLogic.getArtifactByID(id);
|
||||
subTitle = artifactData?.date ?? '';
|
||||
}
|
||||
await HomeWidget.saveWidgetData<String>('lastDiscoveredSubTitle', subTitle);
|
||||
await _nativeWidget.save<String>('lastDiscoveredSubTitle', subTitle);
|
||||
// Image,
|
||||
// Download, convert to base64 string and write to shared widget data
|
||||
String imageBase64 = '';
|
||||
if (imageUrl.isNotEmpty) {
|
||||
var bytes = await http.readBytes(Uri.parse(imageUrl));
|
||||
imageBase64 = base64Encode(bytes);
|
||||
debugPrint('Saving base64 bytes: $imageBase64');
|
||||
debugPrint('Saving base64 bytes for homeWidget');
|
||||
}
|
||||
await HomeWidget.saveWidgetData<String>('lastDiscoveredImageData', imageBase64);
|
||||
await HomeWidget.updateWidget(iOSName: _appName);
|
||||
await _nativeWidget.save<String>('lastDiscoveredImageData', imageBase64);
|
||||
await _nativeWidget.markDirty();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -90,7 +90,7 @@ class RetryImage extends ImageProvider<Object> {
|
||||
if (other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is RetryImage && other.imageProvider == other.imageProvider && other.scale == scale;
|
||||
return other is RetryImage && other.imageProvider == imageProvider && other.scale == scale;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -16,7 +16,7 @@ mixin ThrottledSaveLoadMixin {
|
||||
}
|
||||
|
||||
Future<void> save() async {
|
||||
debugPrint('Saving...');
|
||||
if (!kIsWeb) debugPrint('Saving...');
|
||||
try {
|
||||
await _file.save(toJson());
|
||||
} on Exception catch (e) {
|
||||
|
@ -14,18 +14,11 @@ class UnsplashPhotoData {
|
||||
String getUnsplashUrl(int size) => '$url?q=90&fm=jpg&w=$size&fit=max';
|
||||
|
||||
static String getSelfHostedUrl(String id, UnsplashPhotoSize targetSize) {
|
||||
late int size;
|
||||
switch (targetSize) {
|
||||
case UnsplashPhotoSize.med:
|
||||
size = 400;
|
||||
break;
|
||||
case UnsplashPhotoSize.large:
|
||||
size = 800;
|
||||
break;
|
||||
case UnsplashPhotoSize.xl:
|
||||
size = 1200;
|
||||
break;
|
||||
}
|
||||
int size = switch (targetSize) {
|
||||
UnsplashPhotoSize.med => 400,
|
||||
UnsplashPhotoSize.large => 800,
|
||||
UnsplashPhotoSize.xl => 1200
|
||||
};
|
||||
if (PlatformInfo.pixelRatio >= 1.5 || PlatformInfo.isDesktop) {
|
||||
size *= 2;
|
||||
}
|
||||
|
28
lib/logic/native_widget_service.dart
Normal file
28
lib/logic/native_widget_service.dart
Normal file
@ -0,0 +1,28 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -10,15 +10,25 @@ class SettingsLogic with ThrottledSaveLoadMixin {
|
||||
late final hasDismissedSearchMessage = ValueNotifier<bool>(false)..addListener(scheduleSave);
|
||||
late final isSearchPanelOpen = ValueNotifier<bool>(true)..addListener(scheduleSave);
|
||||
late final currentLocale = ValueNotifier<String?>(null)..addListener(scheduleSave);
|
||||
late final prevWonderIndex = ValueNotifier<int?>(null)..addListener(scheduleSave);
|
||||
|
||||
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
|
||||
void copyFromJson(Map<String, dynamic> value) {
|
||||
hasCompletedOnboarding.value = value['hasCompletedOnboarding'] ?? false;
|
||||
hasDismissedSearchMessage.value = value['hasDismissedSearchMessage'] ?? false;
|
||||
currentLocale.value = value['currentLocale'];
|
||||
isSearchPanelOpen.value = value['isSearchPanelOpen'] ?? false;
|
||||
prevWonderIndex.value = value['lastWonderIndex'];
|
||||
}
|
||||
|
||||
@override
|
||||
@ -28,14 +38,7 @@ class SettingsLogic with ThrottledSaveLoadMixin {
|
||||
'hasDismissedSearchMessage': hasDismissedSearchMessage.value,
|
||||
'currentLocale': currentLocale.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();
|
||||
}
|
||||
}
|
||||
|
@ -8,5 +8,4 @@ class UnsplashLogic {
|
||||
UnsplashService get service => GetIt.I.get<UnsplashService>();
|
||||
|
||||
List<String>? getCollectionPhotos(String collectionId) => _idsByCollection[collectionId];
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/artifact_api_logic.dart';
|
||||
import 'package:wonders/logic/artifact_api_service.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/timeline_logic.dart';
|
||||
import 'package:wonders/logic/unsplash_logic.dart';
|
||||
@ -15,9 +16,11 @@ void main() async {
|
||||
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||
// Keep native splash screen up until app is finished bootstrapping
|
||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||
GoRouter.optionURLReflectsImperativeAPIs = true;
|
||||
|
||||
// Start app
|
||||
registerSingletons();
|
||||
|
||||
runApp(WondersApp());
|
||||
await appLogic.bootstrap();
|
||||
|
||||
@ -27,7 +30,7 @@ void main() async {
|
||||
|
||||
/// Creates an app using the [MaterialApp.router] constructor and the global `appRouter`, an instance of [GoRouter].
|
||||
class WondersApp extends StatelessWidget with GetItMixin {
|
||||
WondersApp({Key? key}) : super(key: key);
|
||||
WondersApp({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final locale = watchX((SettingsLogic s) => s.currentLocale);
|
||||
@ -39,6 +42,7 @@ class WondersApp extends StatelessWidget with GetItMixin {
|
||||
routerDelegate: appRouter.routerDelegate,
|
||||
shortcuts: AppShortcuts.defaults,
|
||||
theme: ThemeData(fontFamily: $styles.text.body.fontFamily, useMaterial3: true),
|
||||
color: $styles.colors.black,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
@ -69,6 +73,8 @@ void registerSingletons() {
|
||||
GetIt.I.registerLazySingleton<CollectiblesLogic>(() => CollectiblesLogic());
|
||||
// Localizations
|
||||
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
|
||||
|
143
lib/router.dart
143
lib/router.dart
@ -1,13 +1,14 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:wonders/common_libs.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/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_search/artifact_search_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/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/wonder_details/wonders_details_screen.dart';
|
||||
|
||||
@ -17,20 +18,53 @@ class ScreenPaths {
|
||||
static String intro = '/welcome';
|
||||
static String home = '/home';
|
||||
static String settings = '/settings';
|
||||
static String wonderDetails(WonderType type, {int tabIndex = 0}) => '/wonder/${type.name}?t=$tabIndex';
|
||||
static String video(String id) => '/video/$id';
|
||||
static String highlights(WonderType type) => '/highlights/${type.name}';
|
||||
static String search(WonderType type) => '/search/${type.name}';
|
||||
static String artifact(String id) => '/artifact/$id';
|
||||
static String collection(String id) => '/collection?id=$id';
|
||||
static String maps(WonderType type) => '/maps/${type.name}';
|
||||
static String timeline(WonderType? type) => '/timeline?type=${type?.name ?? ''}';
|
||||
static String wallpaperPhoto(WonderType type) => '/wallpaperPhoto/${type.name}';
|
||||
|
||||
static String wonderDetails(WonderType type, {required int tabIndex}) => '$home/wonder/${type.name}?t=$tabIndex';
|
||||
|
||||
/// Dynamically nested pages, always added on to the existing path
|
||||
static String video(String id) => _appendToCurrentPath('/video/$id');
|
||||
static String search(WonderType type) => _appendToCurrentPath('/search/${type.name}');
|
||||
static String maps(WonderType type) => _appendToCurrentPath('/maps/${type.name}');
|
||||
static String timeline(WonderType? type) => _appendToCurrentPath('/timeline?type=${type?.name ?? ''}');
|
||||
static String artifact(String id, {bool append = true}) =>
|
||||
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
|
||||
final appRouter = GoRouter(
|
||||
redirect: _handleRedirect,
|
||||
errorPageBuilder: (context, state) => MaterialPage(child: PageNotFound(state.uri.toString())),
|
||||
routes: [
|
||||
ShellRoute(
|
||||
builder: (context, router, navigator) {
|
||||
@ -38,40 +72,50 @@ final appRouter = GoRouter(
|
||||
},
|
||||
routes: [
|
||||
AppRoute(ScreenPaths.splash, (_) => Container(color: $styles.colors.greyStrong)), // This will be hidden
|
||||
AppRoute(ScreenPaths.home, (_) => HomeScreen(), routes: [
|
||||
AppRoute('collection', (s) {
|
||||
return CollectionScreen(fromId: s.queryParams['id'] ?? '');
|
||||
}),
|
||||
]),
|
||||
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']));
|
||||
}),
|
||||
AppRoute(ScreenPaths.home, (_) => HomeScreen(), routes: [
|
||||
_timelineRoute,
|
||||
_collectionRoute,
|
||||
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']),
|
||||
)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
);
|
||||
@ -103,12 +147,21 @@ class AppRoute extends GoRoute {
|
||||
final bool useFade;
|
||||
}
|
||||
|
||||
String? get initialDeeplink => _initialDeeplink;
|
||||
String? _initialDeeplink;
|
||||
|
||||
String? _handleRedirect(BuildContext context, GoRouterState state) {
|
||||
// Prevent anyone from navigating away from `/` if app is starting up.
|
||||
if (!appLogic.isBootstrapComplete && state.location != ScreenPaths.splash) {
|
||||
if (!appLogic.isBootstrapComplete && state.uri.path != ScreenPaths.splash) {
|
||||
debugPrint('Redirecting from ${state.uri.path} to ${ScreenPaths.splash}.');
|
||||
_initialDeeplink ??= state.uri.toString();
|
||||
return ScreenPaths.splash;
|
||||
}
|
||||
debugPrint('Navigate to: ${state.location}');
|
||||
if (appLogic.isBootstrapComplete && state.uri.path == ScreenPaths.splash) {
|
||||
debugPrint('Redirecting from ${state.uri.path} to ${ScreenPaths.home}');
|
||||
return ScreenPaths.home;
|
||||
}
|
||||
if (!kIsWeb) debugPrint('Navigate to: ${state.uri}');
|
||||
return null; // do nothing
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,9 @@ class AppColors {
|
||||
final Color greyStrong = const Color(0xFF272625);
|
||||
final Color greyMedium = const Color(0xFF9D9995);
|
||||
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 bool isDark = false;
|
||||
|
@ -1,7 +1,5 @@
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
export 'colors.dart';
|
||||
@ -16,20 +14,13 @@ class AppStyle {
|
||||
final shortestSide = screenSize.shortestSide;
|
||||
const tabletXl = 1000;
|
||||
const tabletLg = 800;
|
||||
const tabletSm = 600;
|
||||
const phoneLg = 400;
|
||||
if (shortestSide > tabletXl) {
|
||||
scale = 1.25;
|
||||
scale = 1.2;
|
||||
} else if (shortestSide > tabletLg) {
|
||||
scale = 1.15;
|
||||
} else if (shortestSide > tabletSm) {
|
||||
scale = 1;
|
||||
} else if (shortestSide > phoneLg) {
|
||||
scale = .9; // phone
|
||||
scale = 1.1;
|
||||
} else {
|
||||
scale = .85; // small phone
|
||||
scale = 1;
|
||||
}
|
||||
//debugPrint('screenSize=$screenSize, scale=$scale');
|
||||
}
|
||||
|
||||
late final double scale;
|
||||
|
@ -2,45 +2,29 @@ import 'package:wonders/common_libs.dart';
|
||||
|
||||
extension WonderColorExtensions on WonderType {
|
||||
Color get bgColor {
|
||||
switch (this) {
|
||||
case WonderType.pyramidsGiza:
|
||||
return const Color(0xFF16184D);
|
||||
case WonderType.greatWall:
|
||||
return const Color(0xFF642828);
|
||||
case WonderType.petra:
|
||||
return const Color(0xFF444B9B);
|
||||
case WonderType.colosseum:
|
||||
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);
|
||||
}
|
||||
return switch (this) {
|
||||
WonderType.pyramidsGiza => const Color(0xFF16184D),
|
||||
WonderType.greatWall => const Color(0xFF642828),
|
||||
WonderType.petra => const Color(0xFF444B9B),
|
||||
WonderType.colosseum => const Color(0xFF1E736D),
|
||||
WonderType.chichenItza => const Color(0xFF164F2A),
|
||||
WonderType.machuPicchu => const Color(0xFF0E4064),
|
||||
WonderType.tajMahal => const Color(0xFFC96454),
|
||||
WonderType.christRedeemer => const Color(0xFF1C4D46)
|
||||
};
|
||||
}
|
||||
|
||||
Color get fgColor {
|
||||
switch (this) {
|
||||
case WonderType.pyramidsGiza:
|
||||
return const Color(0xFF444B9B);
|
||||
case WonderType.greatWall:
|
||||
return const Color(0xFF688750);
|
||||
case WonderType.petra:
|
||||
return const Color(0xFF1B1A65);
|
||||
case WonderType.colosseum:
|
||||
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);
|
||||
}
|
||||
return switch (this) {
|
||||
WonderType.pyramidsGiza => const Color(0xFF444B9B),
|
||||
WonderType.greatWall => const Color(0xFF688750),
|
||||
WonderType.petra => const Color(0xFF1B1A65),
|
||||
WonderType.colosseum => const Color(0xFF4AA39D),
|
||||
WonderType.chichenItza => const Color(0xFFE2CFBB),
|
||||
WonderType.machuPicchu => const Color(0xFFC1D9D1),
|
||||
WonderType.tajMahal => const Color(0xFF642828),
|
||||
WonderType.christRedeemer => const Color(0xFFED7967)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/ui/common/app_scroll_behavior.dart';
|
||||
|
||||
class WondersAppScaffold extends StatelessWidget {
|
||||
const WondersAppScaffold({Key? key, required this.child}) : super(key: key);
|
||||
const WondersAppScaffold({super.key, required this.child});
|
||||
final Widget child;
|
||||
static AppStyle get style => _style;
|
||||
static AppStyle _style = AppStyle();
|
||||
|
@ -1,14 +1,13 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class AppBackdrop extends StatelessWidget {
|
||||
const AppBackdrop({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.strength = 1,
|
||||
this.child,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final double strength;
|
||||
final Widget? child;
|
||||
|
@ -2,7 +2,7 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class AppIcon extends StatelessWidget {
|
||||
const AppIcon(this.icon, {Key? key, this.size = 22, this.color}) : super(key: key);
|
||||
const AppIcon(this.icon, {super.key, this.size = 22, this.color});
|
||||
final AppIcons icon;
|
||||
final double size;
|
||||
final Color? color;
|
||||
|
@ -34,14 +34,11 @@ class AppShortcuts {
|
||||
};
|
||||
|
||||
static Map<ShortcutActivator, Intent>? get defaults {
|
||||
switch (defaultTargetPlatform) {
|
||||
return switch (defaultTargetPlatform) {
|
||||
// fall back to default shortcuts for ios and android
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.android:
|
||||
return null;
|
||||
TargetPlatform.iOS || TargetPlatform.android => null,
|
||||
// unify shortcuts for desktop/web
|
||||
default:
|
||||
return _defaultWebAndDesktopShortcuts;
|
||||
}
|
||||
_ => _defaultWebAndDesktopShortcuts
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,7 @@ class BlendMask extends SingleChildRenderObjectWidget {
|
||||
final List<BlendMode> blendModes;
|
||||
final double opacity;
|
||||
|
||||
const BlendMask({required this.blendModes, this.opacity = 1.0, Key? key, required Widget child})
|
||||
: super(key: key, child: child);
|
||||
const BlendMask({required this.blendModes, this.opacity = 1.0, super.key, required Widget super.child});
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(context) => RenderBlendMask(blendModes, opacity);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class CenteredBox extends StatelessWidget {
|
||||
const CenteredBox({Key? key, required this.child, this.width, this.height, this.padding}) : super(key: key);
|
||||
const CenteredBox({super.key, required this.child, this.width, this.height, this.padding});
|
||||
final Widget child;
|
||||
final double? width;
|
||||
final double? height;
|
||||
|
@ -6,7 +6,7 @@ import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||
import 'package:wonders/ui/screens/collectible_found/collectible_found_screen.dart';
|
||||
|
||||
class CollectibleItem extends StatelessWidget with GetItMixin {
|
||||
CollectibleItem(this.collectible, {this.size = 64.0, Key? key}) : super(key: key) {
|
||||
CollectibleItem(this.collectible, {this.size = 64.0, super.key, this.focus}) {
|
||||
// pre-fetch the image, so it's ready if we show the collectible found screen.
|
||||
_imageProvider = NetworkImage(collectible.imageUrl);
|
||||
_imageProvider.resolve(ImageConfiguration()).addListener(ImageStreamListener((_, __) {}));
|
||||
@ -15,6 +15,7 @@ class CollectibleItem extends StatelessWidget with GetItMixin {
|
||||
final CollectibleData collectible;
|
||||
final double size;
|
||||
late final ImageProvider _imageProvider;
|
||||
final FocusNode? focus;
|
||||
|
||||
void _handleTap(BuildContext context) async {
|
||||
final screen = CollectibleFoundScreen(collectible: collectible, imageProvider: _imageProvider);
|
||||
@ -39,6 +40,7 @@ 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.
|
||||
closedBuilder: (_) => SizedBox(width: 1, height: 0),
|
||||
openBuilder: (_) => AppBtn.basic(
|
||||
focusNode: focus,
|
||||
semanticLabel: $strings.collectibleItemSemanticCollectible,
|
||||
onPressed: () => _handleTap(context),
|
||||
enableFeedback: false,
|
||||
@ -54,9 +56,9 @@ class CollectibleItem extends StatelessWidget with GetItMixin {
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.shimmer(delay: 4000.ms, duration: $styles.times.med * 3)
|
||||
.shake(curve: Curves.easeInOutCubic, hz: 4)
|
||||
.scale(begin: 1.0, end: 1.1, duration: $styles.times.med)
|
||||
.scale(begin: Offset(1.0, 1.0), end: Offset(1.1, 1.1), duration: $styles.times.med)
|
||||
.then(delay: $styles.times.med)
|
||||
.scale(begin: 1.0, end: 1 / 1.1),
|
||||
.scale(begin: Offset(1.0, 1.0), end: Offset(1 / 1.1, 1 / 1.1)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -2,8 +2,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class CompassDivider extends StatelessWidget {
|
||||
const CompassDivider({Key? key, required this.isExpanded, this.duration, this.linesColor, this.compassColor})
|
||||
: super(key: key);
|
||||
const CompassDivider({super.key, required this.isExpanded, this.duration, this.linesColor, this.compassColor});
|
||||
final bool isExpanded;
|
||||
final Duration? duration;
|
||||
final Color? linesColor;
|
||||
|
@ -3,7 +3,7 @@ import 'package:wonders/ui/common/app_icons.dart';
|
||||
|
||||
class AppHeader extends StatelessWidget {
|
||||
const AppHeader(
|
||||
{Key? key,
|
||||
{super.key,
|
||||
this.title,
|
||||
this.subtitle,
|
||||
this.showBackBtn = true,
|
||||
@ -11,8 +11,7 @@ class AppHeader extends StatelessWidget {
|
||||
this.onBack,
|
||||
this.trailing,
|
||||
this.backIcon = AppIcons.prev,
|
||||
this.backBtnSemantics})
|
||||
: super(key: key);
|
||||
this.backBtnSemantics});
|
||||
final String? title;
|
||||
final String? subtitle;
|
||||
final bool showBackBtn;
|
||||
|
@ -6,7 +6,7 @@ import 'package:wonders/ui/common/controls/app_loading_indicator.dart';
|
||||
|
||||
class AppImage extends StatefulWidget {
|
||||
const AppImage({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.image,
|
||||
this.fit = BoxFit.scaleDown,
|
||||
this.alignment = Alignment.center,
|
||||
@ -16,7 +16,7 @@ class AppImage extends StatefulWidget {
|
||||
this.progress = false,
|
||||
this.color,
|
||||
this.scale,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final ImageProvider? image;
|
||||
final BoxFit fit;
|
||||
@ -88,7 +88,9 @@ class _AppImageState extends State<AppImage> {
|
||||
|
||||
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
|
||||
if (kIsWeb) return image; // TODO: Remove this when the web engine is updated to support non-blocking image resizing
|
||||
if (kIsWeb) {
|
||||
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;
|
||||
final MediaQueryData mq = MediaQuery.of(context);
|
||||
final Size screenSize = mq.size * mq.devicePixelRatio * widget.scale!;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class AppLoadingIndicator extends StatelessWidget {
|
||||
const AppLoadingIndicator({Key? key, this.value, this.color}) : super(key: key);
|
||||
const AppLoadingIndicator({super.key, this.value, this.color});
|
||||
final Color? color;
|
||||
final double? value;
|
||||
|
||||
|
@ -3,15 +3,14 @@ import 'package:wonders/common_libs.dart';
|
||||
|
||||
class AppPageIndicator extends StatefulWidget {
|
||||
AppPageIndicator({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.count,
|
||||
required this.controller,
|
||||
this.onDotPressed,
|
||||
this.color,
|
||||
this.dotSize,
|
||||
String? semanticPageTitle,
|
||||
}) : semanticPageTitle = semanticPageTitle ?? $strings.appPageDefaultTitlePage,
|
||||
super(key: key);
|
||||
}) : semanticPageTitle = semanticPageTitle ?? $strings.appPageDefaultTitlePage;
|
||||
final int count;
|
||||
final PageController controller;
|
||||
final void Function(int index)? onDotPressed;
|
||||
|
@ -9,7 +9,7 @@ Widget _buildIcon(BuildContext context, AppIcons icon, {required bool isSecondar
|
||||
class AppBtn extends StatelessWidget {
|
||||
// ignore: prefer_const_constructors_in_immutables
|
||||
AppBtn({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.semanticLabel,
|
||||
this.enableFeedback = true,
|
||||
@ -24,11 +24,10 @@ class AppBtn extends StatelessWidget {
|
||||
this.border,
|
||||
this.focusNode,
|
||||
this.onFocusChanged,
|
||||
}) : _builder = null,
|
||||
super(key: key);
|
||||
}) : _builder = null;
|
||||
|
||||
AppBtn.from({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
this.enableFeedback = true,
|
||||
this.pressEffect = true,
|
||||
@ -45,9 +44,10 @@ class AppBtn extends StatelessWidget {
|
||||
AppIcons? icon,
|
||||
double? iconSize,
|
||||
}) : child = null,
|
||||
circular = false,
|
||||
super(key: key) {
|
||||
if (semanticLabel == null && text == null) throw ('AppBtn.from must include either text or semanticLabel');
|
||||
circular = false {
|
||||
if (semanticLabel == null && text == null) {
|
||||
throw ('AppBtn.from must include either text or semanticLabel');
|
||||
}
|
||||
this.semanticLabel = semanticLabel ?? text ?? '';
|
||||
_builder = (context) {
|
||||
if (text == null && icon == null) return SizedBox.shrink();
|
||||
@ -70,7 +70,7 @@ class AppBtn extends StatelessWidget {
|
||||
|
||||
// ignore: prefer_const_constructors_in_immutables
|
||||
AppBtn.basic({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.semanticLabel,
|
||||
this.enableFeedback = true,
|
||||
@ -85,8 +85,7 @@ class AppBtn extends StatelessWidget {
|
||||
}) : expand = false,
|
||||
bgColor = Colors.transparent,
|
||||
border = null,
|
||||
_builder = null,
|
||||
super(key: key);
|
||||
_builder = null;
|
||||
|
||||
// interaction:
|
||||
final VoidCallback? onPressed;
|
||||
@ -185,7 +184,7 @@ class AppBtn extends StatelessWidget {
|
||||
/// Add a transparency-based press effect to buttons.
|
||||
/// //////////////////////////////////////////////////
|
||||
class _ButtonPressEffect extends StatefulWidget {
|
||||
const _ButtonPressEffect(this.child, {Key? key}) : super(key: key);
|
||||
const _ButtonPressEffect(this.child);
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
@ -212,7 +211,7 @@ class _ButtonPressEffectState extends State<_ButtonPressEffect> {
|
||||
}
|
||||
|
||||
class _CustomFocusBuilder extends StatefulWidget {
|
||||
const _CustomFocusBuilder({Key? key, required this.builder, this.focusNode, this.onFocusChanged}) : super(key: key);
|
||||
const _CustomFocusBuilder({required this.builder, this.focusNode, this.onFocusChanged});
|
||||
final Widget Function(BuildContext context, FocusNode focus) builder;
|
||||
final void Function(bool hasFocus)? onFocusChanged;
|
||||
final FocusNode? focusNode;
|
||||
@ -232,7 +231,9 @@ class _CustomFocusBuilderState extends State<_CustomFocusBuilder> {
|
||||
|
||||
void _handleFocusChanged() {
|
||||
widget.onFocusChanged?.call(_focusNode.hasFocus);
|
||||
setState(() {});
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2,8 +2,7 @@ import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||
|
||||
class SimpleCheckbox extends StatelessWidget {
|
||||
const SimpleCheckbox({Key? key, required this.active, required this.onToggled, required this.label})
|
||||
: super(key: key);
|
||||
const SimpleCheckbox({super.key, required this.active, required this.onToggled, required this.label});
|
||||
final bool active;
|
||||
final String label;
|
||||
final Function(bool? onToggle) onToggled;
|
||||
|
@ -4,14 +4,14 @@ import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart';
|
||||
|
||||
class CircleBtn extends StatelessWidget {
|
||||
const CircleBtn({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.onPressed,
|
||||
this.border,
|
||||
this.bgColor,
|
||||
this.size,
|
||||
required this.semanticLabel,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
static double defaultSize = 48;
|
||||
|
||||
@ -40,7 +40,7 @@ class CircleBtn extends StatelessWidget {
|
||||
|
||||
class CircleIconBtn extends StatelessWidget {
|
||||
const CircleIconBtn({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.onPressed,
|
||||
this.border,
|
||||
@ -50,7 +50,7 @@ class CircleIconBtn extends StatelessWidget {
|
||||
this.iconSize,
|
||||
this.flipIcon = false,
|
||||
required this.semanticLabel,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
//TODO: Reduce size if design re-exports icon-images without padding
|
||||
static double defaultSize = 28;
|
||||
@ -87,13 +87,13 @@ class CircleIconBtn extends StatelessWidget {
|
||||
|
||||
class BackBtn extends StatelessWidget {
|
||||
const BackBtn({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.icon = AppIcons.prev,
|
||||
this.onPressed,
|
||||
this.semanticLabel,
|
||||
this.bgColor,
|
||||
this.iconColor,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final Color? bgColor;
|
||||
final Color? iconColor;
|
||||
@ -121,20 +121,23 @@ class BackBtn extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FullscreenKeyboardListener(
|
||||
onKeyDown: (event) => _handleKeyDown(context, event), child: CircleIconBtn(
|
||||
icon: icon,
|
||||
bgColor: bgColor,
|
||||
color: iconColor,
|
||||
onPressed: onPressed ?? () {
|
||||
final nav = Navigator.of(context);
|
||||
if(nav.canPop()){
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
context.go(ScreenPaths.home);
|
||||
}
|
||||
},
|
||||
semanticLabel: semanticLabel ?? $strings.circleButtonsSemanticBack,
|
||||
),);
|
||||
onKeyDown: (event) => _handleKeyDown(context, event),
|
||||
child: CircleIconBtn(
|
||||
icon: icon,
|
||||
bgColor: bgColor,
|
||||
color: iconColor,
|
||||
onPressed: onPressed ??
|
||||
() {
|
||||
final nav = Navigator.of(context);
|
||||
if (nav.canPop()) {
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
context.go(ScreenPaths.home);
|
||||
}
|
||||
},
|
||||
semanticLabel: semanticLabel ?? $strings.circleButtonsSemanticBack,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget safe() => _SafeAreaWithPadding(child: this);
|
||||
@ -143,13 +146,17 @@ class BackBtn extends StatelessWidget {
|
||||
if (onPressed != null) {
|
||||
onPressed?.call();
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
if (context.canPop()) {
|
||||
context.pop();
|
||||
} else {
|
||||
context.go(ScreenPaths.home);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SafeAreaWithPadding extends StatelessWidget {
|
||||
const _SafeAreaWithPadding({Key? key, required this.child}) : super(key: key);
|
||||
const _SafeAreaWithPadding({required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
|
@ -2,7 +2,7 @@ import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/ui/common/static_text_scale.dart';
|
||||
|
||||
class DiagonalTextPageIndicator extends StatelessWidget {
|
||||
const DiagonalTextPageIndicator({Key? key, required this.current, required this.total}) : super(key: key);
|
||||
const DiagonalTextPageIndicator({super.key, required this.current, required this.total});
|
||||
final int current;
|
||||
final int total;
|
||||
static final _fontSize = 26 * $styles.scale;
|
||||
|
@ -1,8 +1,7 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class EightWaySwipeDetector extends StatefulWidget {
|
||||
const EightWaySwipeDetector({Key? key, required this.child, this.threshold = 50, required this.onSwipe})
|
||||
: super(key: key);
|
||||
const EightWaySwipeDetector({super.key, required this.child, this.threshold = 50, required this.onSwipe});
|
||||
final Widget child;
|
||||
final double threshold;
|
||||
final void Function(Offset dir)? onSwipe;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class LocaleSwitcher extends StatelessWidget with GetItMixin {
|
||||
LocaleSwitcher({Key? key}) : super(key: key);
|
||||
LocaleSwitcher({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -11,6 +11,7 @@ class LocaleSwitcher extends StatelessWidget with GetItMixin {
|
||||
await settingsLogic.changeLocale(newLocale);
|
||||
}
|
||||
|
||||
return AppBtn.from(text: $strings.localeSwapButton, onPressed: handleSwapLocale, padding: EdgeInsets.all($styles.insets.sm));
|
||||
return AppBtn.from(
|
||||
text: $strings.localeSwapButton, onPressed: handleSwapLocale, padding: EdgeInsets.all($styles.insets.sm));
|
||||
}
|
||||
}
|
||||
|
@ -6,19 +6,19 @@ class ScrollDecorator extends StatefulWidget {
|
||||
/// its ScrollController.
|
||||
// ignore: prefer_const_constructors_in_immutables
|
||||
ScrollDecorator({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.builder,
|
||||
this.fgBuilder,
|
||||
this.bgBuilder,
|
||||
this.controller,
|
||||
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
|
||||
/// position. For example on a vertical list, it would fade in the `begin` widget when the list is not scrolled to the
|
||||
/// top.
|
||||
ScrollDecorator.fade({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.builder,
|
||||
this.controller,
|
||||
this.onInit,
|
||||
@ -27,7 +27,7 @@ class ScrollDecorator extends StatefulWidget {
|
||||
bool bg = false,
|
||||
Axis direction = Axis.vertical,
|
||||
Duration duration = const Duration(milliseconds: 150),
|
||||
}) : super(key: key) {
|
||||
}) {
|
||||
Flex flexBuilder(controller) {
|
||||
return Flex(
|
||||
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.
|
||||
ScrollDecorator.shadow({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.builder,
|
||||
this.controller,
|
||||
this.onInit,
|
||||
Color color = Colors.black54,
|
||||
}) : super(key: key) {
|
||||
}) {
|
||||
bgBuilder = null;
|
||||
fgBuilder = (controller) {
|
||||
final double ratio = controller.hasClients ? min(1, controller.position.extentBefore / 60) : 0;
|
||||
|
@ -40,33 +40,29 @@ class ArchPoint {
|
||||
|
||||
List<ArchPoint> _getArchPts(Size size, ArchType type) {
|
||||
double distanceFromTop = size.width / 3;
|
||||
switch (type) {
|
||||
case ArchType.pyramid:
|
||||
return [
|
||||
return switch (type) {
|
||||
ArchType.pyramid => [
|
||||
ArchPoint(Offset(0, size.height)),
|
||||
ArchPoint(Offset(0, distanceFromTop)),
|
||||
ArchPoint(Offset(size.width / 2, 0)),
|
||||
ArchPoint(Offset(size.width, distanceFromTop)),
|
||||
ArchPoint(Offset(size.width, size.height)),
|
||||
];
|
||||
case ArchType.spade:
|
||||
return [
|
||||
],
|
||||
ArchType.spade => [
|
||||
ArchPoint(Offset(0, size.height)),
|
||||
ArchPoint(Offset(0, distanceFromTop)),
|
||||
ArchPoint(Offset(size.width / 2, 0), Offset(0, distanceFromTop * .66)),
|
||||
ArchPoint(Offset(size.width, distanceFromTop), Offset(size.width, distanceFromTop * .66)),
|
||||
ArchPoint(Offset(size.width, size.height)),
|
||||
];
|
||||
case ArchType.arch:
|
||||
return [
|
||||
],
|
||||
ArchType.arch => [
|
||||
ArchPoint(Offset(0, size.height)),
|
||||
ArchPoint(Offset(0, size.width / 2)),
|
||||
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.height)),
|
||||
];
|
||||
case ArchType.wideArch:
|
||||
return [
|
||||
],
|
||||
ArchType.wideArch => [
|
||||
ArchPoint(Offset(0, size.height)),
|
||||
ArchPoint(Offset(0, size.width / 2)),
|
||||
ArchPoint(Offset(0, distanceFromTop)),
|
||||
@ -74,17 +70,16 @@ List<ArchPoint> _getArchPts(Size size, ArchType type) {
|
||||
ArchPoint(Offset(size.width, distanceFromTop), Offset(size.width, 0)),
|
||||
ArchPoint(Offset(size.width, size.width / 2)),
|
||||
ArchPoint(Offset(size.width, size.height)),
|
||||
];
|
||||
case ArchType.flatPyramid:
|
||||
return [
|
||||
],
|
||||
ArchType.flatPyramid => [
|
||||
ArchPoint(Offset(0, size.height)),
|
||||
ArchPoint(Offset(0, distanceFromTop)),
|
||||
ArchPoint(Offset(size.width * 0.8 / 2, 0)),
|
||||
ArchPoint(Offset(size.width * 1.2 / 2, 0)),
|
||||
ArchPoint(Offset(size.width, distanceFromTop)),
|
||||
ArchPoint(Offset(size.width, size.height)),
|
||||
];
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
class CurvedTopClipper extends CustomClipper<Path> {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class DashedLine extends StatelessWidget {
|
||||
const DashedLine({Key? key, this.vertical = false}) : super(key: key);
|
||||
const DashedLine({super.key, this.vertical = false});
|
||||
final bool vertical;
|
||||
|
||||
@override
|
||||
|
@ -3,7 +3,7 @@ import 'package:wonders/common_libs.dart';
|
||||
/// Colored box that can fade in and out, should yield better performance than
|
||||
/// fading with an additional Opacity layer.
|
||||
class FadeColorTransition extends StatelessWidget {
|
||||
const FadeColorTransition({Key? key, required this.animation, required this.color}) : super(key: key);
|
||||
const FadeColorTransition({super.key, required this.animation, required this.color});
|
||||
final Animation<double> animation;
|
||||
final Color color;
|
||||
|
||||
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class GradientContainer extends StatelessWidget {
|
||||
const GradientContainer(this.colors, this.stops,
|
||||
{Key? key,
|
||||
{super.key,
|
||||
this.child,
|
||||
this.width,
|
||||
this.height,
|
||||
@ -10,8 +10,7 @@ class GradientContainer extends StatelessWidget {
|
||||
this.begin,
|
||||
this.end,
|
||||
this.blendMode,
|
||||
this.borderRadius})
|
||||
: super(key: key);
|
||||
this.borderRadius});
|
||||
final List<Color> colors;
|
||||
final List<double> stops;
|
||||
final double? width;
|
||||
@ -45,41 +44,12 @@ class GradientContainer extends StatelessWidget {
|
||||
}
|
||||
|
||||
class HzGradient extends GradientContainer {
|
||||
const HzGradient(List<Color> colors, List<double> stops,
|
||||
{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);
|
||||
const HzGradient(super.colors, super.stops,
|
||||
{super.key, super.child, super.width, super.height, super.alignment, super.blendMode, super.borderRadius});
|
||||
}
|
||||
|
||||
class VtGradient extends GradientContainer {
|
||||
const VtGradient(List<Color> colors, List<double> stops,
|
||||
{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,
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
blendMode: blendMode,
|
||||
borderRadius: borderRadius);
|
||||
const VtGradient(super.colors, super.stops,
|
||||
{super.key, super.child, super.width, super.height, super.alignment, super.blendMode, super.borderRadius})
|
||||
: super(begin: Alignment.topCenter, end: Alignment.bottomCenter);
|
||||
}
|
||||
|
@ -5,13 +5,14 @@ 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.
|
||||
/// If `wonders` is empty, then the collectible is always shown.
|
||||
class HiddenCollectible extends StatelessWidget with GetItMixin {
|
||||
HiddenCollectible(this.currentWonder, {Key? key, required this.index, this.matches = const [], this.size = 64})
|
||||
: assert(index <= 2, 'index should not exceed 2'),
|
||||
super(key: key);
|
||||
HiddenCollectible(this.currentWonder,
|
||||
{super.key, required this.index, this.matches = const [], this.size = 64, this.focus})
|
||||
: assert(index <= 2, 'index should not exceed 2');
|
||||
final int index;
|
||||
final double size;
|
||||
final List<WonderType> matches;
|
||||
final WonderType currentWonder;
|
||||
final FocusNode? focus;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final data = collectiblesLogic.forWonder(currentWonder);
|
||||
@ -19,6 +20,6 @@ class HiddenCollectible extends StatelessWidget with GetItMixin {
|
||||
if (matches.isNotEmpty && matches.contains(currentWonder) == false) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
return CollectibleItem(data[index], size: size);
|
||||
return CollectibleItem(data[index], size: size, focus: focus);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class ArrowDir {
|
||||
}
|
||||
|
||||
class KeyboardArrowsListener extends StatefulWidget {
|
||||
const KeyboardArrowsListener({Key? key, required this.child, required this.onArrow}) : super(key: key);
|
||||
const KeyboardArrowsListener({super.key, required this.child, required this.onArrow});
|
||||
final Widget child;
|
||||
final void Function(ArrowDir dir) onArrow;
|
||||
@override
|
||||
|
@ -3,13 +3,13 @@ import 'package:flutter/material.dart';
|
||||
/// A lazy-loading [IndexedStack] that loads [children] accordingly.
|
||||
class LazyIndexedStack extends StatefulWidget {
|
||||
const LazyIndexedStack({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.alignment = AlignmentDirectional.topStart,
|
||||
this.textDirection,
|
||||
this.sizing = StackFit.loose,
|
||||
this.index = 0,
|
||||
this.children = const [],
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final AlignmentGeometry alignment;
|
||||
final TextDirection? textDirection;
|
||||
|
@ -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.
|
||||
/// Can be placed at the top or bottom of a list, using the `flip' option when on the bottom
|
||||
class ListOverscollGradient extends StatelessWidget {
|
||||
const ListOverscollGradient({Key? key, this.size = 100, this.color, this.bottomUp = false}) : super(key: key);
|
||||
const ListOverscollGradient({super.key, this.size = 100, this.color, this.bottomUp = false});
|
||||
final bool bottomUp;
|
||||
final double size;
|
||||
final Color? color;
|
||||
|
@ -4,7 +4,7 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class MeasurableWidget extends SingleChildRenderObjectWidget {
|
||||
const MeasurableWidget({Key? key, required this.onChange, required Widget child}) : super(key: key, child: child);
|
||||
const MeasurableWidget({super.key, required this.onChange, required Widget super.child});
|
||||
final void Function(Size size) onChange;
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) => MeasureSizeRenderObject(onChange);
|
||||
|
@ -11,7 +11,7 @@ Future<bool?> showModal(BuildContext context, {required Widget child}) async {
|
||||
}
|
||||
|
||||
class LoadingModal extends StatelessWidget {
|
||||
const LoadingModal({Key? key, this.title, this.msg, this.child}) : super(key: key);
|
||||
const LoadingModal({super.key, this.title, this.msg, this.child});
|
||||
final String? title;
|
||||
final String? msg;
|
||||
final Widget? child;
|
||||
@ -28,7 +28,7 @@ class LoadingModal extends StatelessWidget {
|
||||
}
|
||||
|
||||
class OkModal extends StatelessWidget {
|
||||
const OkModal({Key? key, this.title, this.msg, this.child}) : super(key: key);
|
||||
const OkModal({super.key, this.title, this.msg, this.child});
|
||||
final String? title;
|
||||
final String? msg;
|
||||
final Widget? child;
|
||||
@ -51,7 +51,7 @@ class OkModal extends StatelessWidget {
|
||||
}
|
||||
|
||||
class OkCancelModal extends StatelessWidget {
|
||||
const OkCancelModal({Key? key, this.title, this.msg, this.child}) : super(key: key);
|
||||
const OkCancelModal({super.key, this.title, this.msg, this.child});
|
||||
final String? title;
|
||||
final String? msg;
|
||||
final Widget? child;
|
||||
@ -83,7 +83,7 @@ class _BaseContentModal extends StatelessWidget {
|
||||
final Widget? child;
|
||||
final List<Widget> buttons;
|
||||
|
||||
const _BaseContentModal({Key? key, this.title, this.msg, required this.buttons, this.child}) : super(key: key);
|
||||
const _BaseContentModal({this.title, this.msg, required this.buttons, this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -5,7 +5,7 @@ import 'package:wonders/ui/common/controls/app_header.dart';
|
||||
import 'package:wonders/ui/common/google_maps_marker.dart';
|
||||
|
||||
class FullscreenMapsViewer extends StatelessWidget {
|
||||
FullscreenMapsViewer({Key? key, required this.type}) : super(key: key);
|
||||
FullscreenMapsViewer({super.key, required this.type});
|
||||
final WonderType type;
|
||||
|
||||
WonderData get data => wondersLogic.getData(type);
|
||||
|
@ -7,7 +7,7 @@ import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart';
|
||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||
|
||||
class FullscreenUrlImgViewer extends StatefulWidget {
|
||||
const FullscreenUrlImgViewer({Key? key, required this.urls, this.index = 0}) : super(key: key);
|
||||
const FullscreenUrlImgViewer({super.key, required this.urls, this.index = 0});
|
||||
final List<String> urls;
|
||||
final int index;
|
||||
|
||||
@ -126,7 +126,7 @@ class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
|
||||
}
|
||||
|
||||
class _Viewer extends StatefulWidget {
|
||||
const _Viewer(this.url, this.isZoomed, {Key? key}) : super(key: key);
|
||||
const _Viewer(this.url, this.isZoomed);
|
||||
|
||||
final String url;
|
||||
final ValueNotifier<bool> isZoomed;
|
||||
|
@ -5,7 +5,7 @@ import 'package:wonders/logic/common/platform_info.dart';
|
||||
import 'package:youtube_player_iframe/youtube_player_iframe.dart';
|
||||
|
||||
class FullscreenVideoViewer extends StatefulWidget {
|
||||
const FullscreenVideoViewer({Key? key, required this.id}) : super(key: key);
|
||||
const FullscreenVideoViewer({super.key, required this.id});
|
||||
final String id;
|
||||
|
||||
@override
|
||||
|
@ -2,7 +2,7 @@ import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class FullscreenWebView extends StatelessWidget {
|
||||
FullscreenWebView(this.url, {Key? key}) : super(key: key);
|
||||
FullscreenWebView(this.url, {super.key});
|
||||
final String url;
|
||||
|
||||
late final controller = WebViewController()
|
||||
|
@ -3,13 +3,12 @@ import 'package:wonders/ui/common/measurable_widget.dart';
|
||||
|
||||
class OpeningCard extends StatefulWidget {
|
||||
const OpeningCard(
|
||||
{Key? key,
|
||||
{super.key,
|
||||
required this.closedBuilder,
|
||||
required this.openBuilder,
|
||||
required this.isOpen,
|
||||
this.background,
|
||||
this.padding})
|
||||
: super(key: key);
|
||||
this.padding});
|
||||
|
||||
final Widget Function(BuildContext) closedBuilder;
|
||||
final Widget Function(BuildContext) openBuilder;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class PopNavigatorUnderlay extends StatelessWidget {
|
||||
const PopNavigatorUnderlay({Key? key}) : super(key: key);
|
||||
const PopNavigatorUnderlay({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class PopRouterOnOverScroll extends StatefulWidget {
|
||||
const PopRouterOnOverScroll({Key? key, required this.child, required this.controller}) : super(key: key);
|
||||
const PopRouterOnOverScroll({super.key, required this.child, required this.controller});
|
||||
final ScrollController controller;
|
||||
final Widget child;
|
||||
|
||||
|
@ -2,7 +2,7 @@ import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/ui/common/utils/context_utils.dart';
|
||||
|
||||
class AnimatedListItem extends StatelessWidget {
|
||||
const AnimatedListItem({Key? key, required this.scrollPos, required this.builder}) : super(key: key);
|
||||
const AnimatedListItem({super.key, required this.scrollPos, required this.builder});
|
||||
final ValueNotifier<double> scrollPos;
|
||||
final Widget Function(BuildContext context, double pctVisible) builder;
|
||||
|
||||
@ -35,7 +35,7 @@ class AnimatedListItem extends StatelessWidget {
|
||||
/// Takes a scroll position notifier and a child.
|
||||
/// Scales its child as it scrolls onto screen for a nice effect.
|
||||
class ScalingListItem extends StatelessWidget {
|
||||
const ScalingListItem({Key? key, required this.scrollPos, required this.child}) : super(key: key);
|
||||
const ScalingListItem({super.key, required this.scrollPos, required this.child});
|
||||
final ValueNotifier<double> scrollPos;
|
||||
final Widget child;
|
||||
|
||||
|
@ -11,12 +11,12 @@ import 'package:wonders/common_libs.dart';
|
||||
///
|
||||
class StackedPageViewBuilder extends StatefulWidget {
|
||||
const StackedPageViewBuilder({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.initialIndex = 0,
|
||||
required this.pageCount,
|
||||
required this.builder,
|
||||
this.onInit,
|
||||
}) : super(key: key);
|
||||
});
|
||||
final int initialIndex;
|
||||
final int pageCount;
|
||||
final Widget Function(BuildContext builder, PageController controller, PageController follower) builder;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class StaticTextScale extends StatelessWidget {
|
||||
const StaticTextScale({Key? key, required this.child, this.scale = 1}) : super(key: key);
|
||||
const StaticTextScale({super.key, required this.child, this.scale = 1});
|
||||
final Widget child;
|
||||
final double scale;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
class DefaultTextColor extends StatelessWidget {
|
||||
const DefaultTextColor({Key? key, required this.color, required this.child}) : super(key: key);
|
||||
const DefaultTextColor({super.key, required this.color, required this.child});
|
||||
final Color color;
|
||||
final Widget child;
|
||||
|
||||
@ -15,7 +15,7 @@ class DefaultTextColor extends StatelessWidget {
|
||||
}
|
||||
|
||||
class LightText extends StatelessWidget {
|
||||
const LightText({Key? key, required this.child}) : super(key: key);
|
||||
const LightText({super.key, required this.child});
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
@ -26,7 +26,7 @@ class LightText extends StatelessWidget {
|
||||
}
|
||||
|
||||
class DarkText extends StatelessWidget {
|
||||
const DarkText({Key? key, required this.child}) : super(key: key);
|
||||
const DarkText({super.key, required this.child});
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
|
@ -3,7 +3,7 @@ import 'package:wonders/logic/common/string_utils.dart';
|
||||
import 'package:wonders/ui/common/themed_text.dart';
|
||||
|
||||
class TimelineEventCard extends StatelessWidget {
|
||||
const TimelineEventCard({Key? key, required this.year, required this.text, this.darkMode = false}) : super(key: key);
|
||||
const TimelineEventCard({super.key, required this.year, required this.text, this.darkMode = false});
|
||||
final int year;
|
||||
final String text;
|
||||
final bool darkMode;
|
||||
|
@ -2,8 +2,7 @@ import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/data/unsplash_photo_data.dart';
|
||||
|
||||
class UnsplashPhoto extends StatelessWidget {
|
||||
const UnsplashPhoto(this.id, {Key? key, this.fit = BoxFit.cover, required this.size, this.showCredits = false})
|
||||
: super(key: key);
|
||||
const UnsplashPhoto(this.id, {super.key, this.fit = BoxFit.cover, required this.size, this.showCredits = false});
|
||||
final String id;
|
||||
final BoxFit fit;
|
||||
final UnsplashPhotoSize size;
|
||||
|
16
lib/ui/common/wonderous_logo.dart
Normal file
16
lib/ui/common/wonderous_logo.dart
Normal file
@ -0,0 +1,16 @@
|
||||
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,
|
||||
);
|
||||
}
|
@ -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
|
||||
class WondersTimelineBuilder extends StatelessWidget {
|
||||
const WondersTimelineBuilder({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.selectedWonders = const [],
|
||||
this.timelineBuilder,
|
||||
this.axis = Axis.horizontal,
|
||||
this.crossAxisGap,
|
||||
this.minSize = 10,
|
||||
}) : super(key: key);
|
||||
});
|
||||
final List<WonderType> selectedWonders;
|
||||
final Widget Function(BuildContext, WonderData type, bool isSelected)? timelineBuilder;
|
||||
final Axis axis;
|
||||
@ -96,7 +96,7 @@ class WondersTimelineBuilder extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _DefaultTrackEntry extends StatelessWidget {
|
||||
const _DefaultTrackEntry({Key? key, required this.isSelected}) : super(key: key);
|
||||
const _DefaultTrackEntry({required this.isSelected});
|
||||
final bool isSelected;
|
||||
|
||||
@override
|
||||
|
@ -12,7 +12,7 @@ part 'widgets/_bottom_text_content.dart';
|
||||
part 'widgets/_collapsing_carousel_item.dart';
|
||||
|
||||
class ArtifactCarouselScreen extends StatefulWidget {
|
||||
const ArtifactCarouselScreen({Key? key, required this.type, this.contentPadding = EdgeInsets.zero}) : super(key: key);
|
||||
const ArtifactCarouselScreen({super.key, required this.type, this.contentPadding = EdgeInsets.zero});
|
||||
final WonderType type;
|
||||
final EdgeInsets contentPadding;
|
||||
|
||||
@ -34,13 +34,13 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
||||
_currentArtifactIndex.value = _wrappedPageIndex;
|
||||
}
|
||||
|
||||
void _handleSearchTap() => context.push(ScreenPaths.search(widget.type));
|
||||
void _handleSearchTap() => context.go(ScreenPaths.search(widget.type));
|
||||
|
||||
void _handleArtifactTap(int index) {
|
||||
int delta = index - _currentPage.value.round();
|
||||
if (delta == 0) {
|
||||
HighlightData data = _artifacts[index % _artifacts.length];
|
||||
context.push(ScreenPaths.artifact(data.artifactId));
|
||||
context.go(ScreenPaths.artifact(data.artifactId));
|
||||
} else {
|
||||
_pageController?.animateToPage(
|
||||
_currentPage.value.round() + delta,
|
||||
|
@ -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.
|
||||
class _BlurredImageBg extends StatelessWidget {
|
||||
const _BlurredImageBg({Key? key, this.url}) : super(key: key);
|
||||
const _BlurredImageBg({super.key, this.url});
|
||||
final String? url;
|
||||
|
||||
@override
|
||||
|
@ -2,8 +2,7 @@ part of '../artifact_carousel_screen.dart';
|
||||
|
||||
class _BottomTextContent extends StatelessWidget {
|
||||
const _BottomTextContent(
|
||||
{Key? key, required this.artifact, required this.height, required this.state, required this.shortMode})
|
||||
: super(key: key);
|
||||
{super.key, required this.artifact, required this.height, required this.state, required this.shortMode});
|
||||
|
||||
final HighlightData artifact;
|
||||
final double height;
|
||||
|
@ -4,13 +4,12 @@ part of '../artifact_carousel_screen.dart';
|
||||
/// This lets the child simply render it's contents
|
||||
class _CollapsingCarouselItem extends StatelessWidget {
|
||||
const _CollapsingCarouselItem(
|
||||
{Key? key,
|
||||
{super.key,
|
||||
required this.child,
|
||||
required this.indexOffset,
|
||||
required this.width,
|
||||
required this.onPressed,
|
||||
required this.title})
|
||||
: super(key: key);
|
||||
required this.title});
|
||||
final Widget child;
|
||||
final int indexOffset;
|
||||
final double width;
|
||||
@ -50,11 +49,11 @@ class _CollapsingCarouselItem extends StatelessWidget {
|
||||
|
||||
class _AnimatedTranslate extends StatelessWidget {
|
||||
const _AnimatedTranslate({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.duration,
|
||||
required this.offset,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
});
|
||||
final Duration duration;
|
||||
final Offset offset;
|
||||
final Widget child;
|
||||
@ -71,7 +70,7 @@ class _AnimatedTranslate extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _DoubleBorderImage extends StatelessWidget {
|
||||
const _DoubleBorderImage(this.data, {Key? key}) : super(key: key);
|
||||
const _DoubleBorderImage(this.data, {super.key});
|
||||
final HighlightData data;
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
|
@ -10,7 +10,7 @@ part 'widgets/_info_column.dart';
|
||||
part 'widgets/_artifact_image_btn.dart';
|
||||
|
||||
class ArtifactDetailsScreen extends StatefulWidget {
|
||||
const ArtifactDetailsScreen({Key? key, required this.artifactId}) : super(key: key);
|
||||
const ArtifactDetailsScreen({super.key, required this.artifactId});
|
||||
final String artifactId;
|
||||
|
||||
@override
|
||||
|
@ -1,7 +1,7 @@
|
||||
part of '../artifact_details_screen.dart';
|
||||
|
||||
class _ArtifactImageBtn extends StatelessWidget {
|
||||
const _ArtifactImageBtn({Key? key, required this.data}) : super(key: key);
|
||||
const _ArtifactImageBtn({super.key, required this.data});
|
||||
final ArtifactData data;
|
||||
|
||||
@override
|
||||
|
@ -1,7 +1,7 @@
|
||||
part of '../artifact_details_screen.dart';
|
||||
|
||||
class _InfoColumn extends StatelessWidget {
|
||||
const _InfoColumn({Key? key, required this.data}) : super(key: key);
|
||||
const _InfoColumn({super.key, required this.data});
|
||||
final ArtifactData data;
|
||||
|
||||
@override
|
||||
@ -67,7 +67,7 @@ class _InfoColumn extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _InfoRow extends StatelessWidget {
|
||||
const _InfoRow(this.label, this.value, {Key? key}) : super(key: key);
|
||||
const _InfoRow(this.label, this.value, {super.key});
|
||||
|
||||
final String label;
|
||||
final String value;
|
||||
|
@ -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
|
||||
/// appear as images, which the user can click on to being up the details view for more information.
|
||||
class ArtifactSearchScreen extends StatefulWidget with GetItStatefulWidgetMixin {
|
||||
ArtifactSearchScreen({Key? key, required this.type}) : super(key: key);
|
||||
ArtifactSearchScreen({super.key, required this.type});
|
||||
final WonderType type;
|
||||
|
||||
@override
|
||||
@ -65,7 +65,7 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
|
||||
_updateFilter();
|
||||
}
|
||||
|
||||
void _handleResultPressed(SearchData o) => context.push(ScreenPaths.artifact(o.id.toString()));
|
||||
void _handleResultPressed(SearchData o) => context.go(ScreenPaths.artifact(o.id.toString()));
|
||||
|
||||
void _handlePanelControllerChanged() {
|
||||
settingsLogic.isSearchPanelOpen.value = panelController.value;
|
||||
@ -203,7 +203,7 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
|
||||
}
|
||||
|
||||
class PanelController extends ValueNotifier<bool> {
|
||||
PanelController(bool value) : super(value);
|
||||
PanelController(super.value);
|
||||
void toggle() => value = !value;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
class ExpandingTimeRangeSelector extends StatefulWidget {
|
||||
const ExpandingTimeRangeSelector({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.wonder,
|
||||
required this.startYear,
|
||||
required this.endYear,
|
||||
required this.onChanged,
|
||||
required this.panelController,
|
||||
required this.vizController,
|
||||
}) : super(key: key);
|
||||
});
|
||||
final WonderData wonder;
|
||||
final double startYear;
|
||||
final double endYear;
|
||||
@ -95,10 +95,9 @@ class _ExpandingTimeRangeSelectorState extends State<ExpandingTimeRangeSelector>
|
||||
|
||||
class _ClosedTimeRange extends StatelessWidget {
|
||||
const _ClosedTimeRange({
|
||||
Key? key,
|
||||
required this.startYear,
|
||||
required this.endYear,
|
||||
}) : super(key: key);
|
||||
});
|
||||
final double startYear, endYear;
|
||||
|
||||
@override
|
||||
@ -120,14 +119,13 @@ class _ClosedTimeRange extends StatelessWidget {
|
||||
|
||||
class _OpenedTimeRange extends StatelessWidget {
|
||||
const _OpenedTimeRange({
|
||||
Key? key,
|
||||
required this.onChange,
|
||||
required this.startYear,
|
||||
required this.endYear,
|
||||
required this.wonder,
|
||||
required this.painter,
|
||||
required this.onClose,
|
||||
}) : super(key: key);
|
||||
});
|
||||
final double startYear;
|
||||
final double endYear;
|
||||
final void Function(double start, double end) onChange;
|
||||
|
@ -6,7 +6,7 @@ class RangeSelector extends StatefulWidget {
|
||||
static const double handleWidth = 20;
|
||||
|
||||
const RangeSelector({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.start,
|
||||
required this.end,
|
||||
required this.min,
|
||||
@ -15,7 +15,7 @@ class RangeSelector extends StatefulWidget {
|
||||
this.isLocked = false,
|
||||
this.onUpdated,
|
||||
this.onChanged,
|
||||
}) : super(key: key);
|
||||
});
|
||||
final double start;
|
||||
final double end;
|
||||
final double min;
|
||||
|
@ -1,7 +1,7 @@
|
||||
part of '../artifact_search_screen.dart';
|
||||
|
||||
class _ResultTile extends StatelessWidget {
|
||||
const _ResultTile({Key? key, required this.onPressed, required this.data}) : super(key: key);
|
||||
const _ResultTile({super.key, required this.onPressed, required this.data});
|
||||
|
||||
final void Function(SearchData data) onPressed;
|
||||
final SearchData data;
|
||||
|
@ -2,7 +2,7 @@ part of '../artifact_search_screen.dart';
|
||||
|
||||
/// Staggered Masonry styled grid for displaying two columns of different aspect-ratio images.
|
||||
class _ResultsGrid extends StatefulWidget {
|
||||
const _ResultsGrid({Key? key, required this.searchResults, required this.onPressed}) : super(key: key);
|
||||
const _ResultsGrid({super.key, required this.searchResults, required this.onPressed});
|
||||
final void Function(SearchData) onPressed;
|
||||
final List<SearchData> searchResults;
|
||||
|
||||
|
@ -2,7 +2,7 @@ part of '../artifact_search_screen.dart';
|
||||
|
||||
/// Autopopulating textfield used for searching for Artifacts by name.
|
||||
class _SearchInput extends StatelessWidget {
|
||||
const _SearchInput({Key? key, required this.onSubmit, required this.wonder}) : super(key: key);
|
||||
const _SearchInput({super.key, required this.onSubmit, required this.wonder});
|
||||
final void Function(String) onSubmit;
|
||||
final WonderData wonder;
|
||||
|
||||
@ -23,7 +23,9 @@ class _SearchInput extends StatelessWidget {
|
||||
}
|
||||
|
||||
Iterable<String> _getSuggestions(TextEditingValue textEditingValue) {
|
||||
if (textEditingValue.text == '') return wonder.searchSuggestions.getRange(0, 10);
|
||||
if (textEditingValue.text == '') {
|
||||
return wonder.searchSuggestions.getRange(0, 10);
|
||||
}
|
||||
|
||||
return wonder.searchSuggestions.where((str) {
|
||||
return str.startsWith(textEditingValue.text.toLowerCase());
|
||||
|
@ -12,7 +12,7 @@ part 'widgets/_celebration_particles.dart';
|
||||
class CollectibleFoundScreen extends StatelessWidget {
|
||||
// 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.
|
||||
const CollectibleFoundScreen({required this.collectible, required this.imageProvider, Key? key}) : super(key: key);
|
||||
const CollectibleFoundScreen({required this.collectible, required this.imageProvider, super.key});
|
||||
|
||||
final CollectibleData collectible;
|
||||
final ImageProvider imageProvider;
|
||||
@ -44,7 +44,10 @@ class CollectibleFoundScreen extends StatelessWidget {
|
||||
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(),
|
||||
)
|
||||
]);
|
||||
}
|
||||
@ -140,14 +143,14 @@ class CollectibleFoundScreen extends StatelessWidget {
|
||||
child: child,
|
||||
),
|
||||
)
|
||||
.scale(begin: 0.3, duration: t * 2, curve: Curves.easeOutExpo, alignment: Alignment(0, 0.7));
|
||||
.scale(begin: Offset(0.3, 0.3), duration: t * 2, curve: Curves.easeOutExpo, alignment: Alignment(0, 0.7));
|
||||
}
|
||||
|
||||
Widget _buildRibbon(BuildContext context) {
|
||||
Duration t = $styles.times.fast;
|
||||
return _AnimatedRibbon($strings.collectibleFoundTitleArtifactDiscovered.toUpperCase())
|
||||
.animate()
|
||||
.scale(begin: 0.3, duration: t * 2, curve: Curves.easeOutExpo, alignment: Alignment(0, -1));
|
||||
.scale(begin: Offset(0.3, 0.3), duration: t * 2, curve: Curves.easeOutExpo, alignment: Alignment(0, -1));
|
||||
}
|
||||
|
||||
Widget _buildTitle(BuildContext context, String text, TextStyle style, Color color, Duration delay) {
|
||||
@ -184,6 +187,6 @@ class CollectibleFoundScreen extends StatelessWidget {
|
||||
|
||||
void _handleViewCollectionPressed(BuildContext context) {
|
||||
Navigator.pop(context);
|
||||
context.push(ScreenPaths.collection(collectible.id));
|
||||
context.go(ScreenPaths.collection(collectible.id));
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
part of '../collectible_found_screen.dart';
|
||||
|
||||
class _AnimatedRibbon extends StatelessWidget {
|
||||
const _AnimatedRibbon(this.text, {Key? key}) : super(key: key);
|
||||
const _AnimatedRibbon(this.text, {super.key});
|
||||
|
||||
final String text;
|
||||
static const double height = 48;
|
||||
|
@ -1,7 +1,7 @@
|
||||
part of '../collectible_found_screen.dart';
|
||||
|
||||
class _CelebrationParticles extends StatelessWidget {
|
||||
const _CelebrationParticles({Key? key, this.fadeMs = 1000}) : super(key: key);
|
||||
const _CelebrationParticles({super.key, this.fadeMs = 1000});
|
||||
|
||||
final int fadeMs;
|
||||
|
||||
|
@ -16,7 +16,7 @@ part 'widgets/_collection_list_card.dart';
|
||||
part 'widgets/_newly_discovered_items_btn.dart';
|
||||
|
||||
class CollectionScreen extends StatefulWidget with GetItStatefulWidgetMixin {
|
||||
CollectionScreen({required this.fromId, Key? key}) : super(key: key);
|
||||
CollectionScreen({required this.fromId, super.key});
|
||||
|
||||
final String fromId;
|
||||
|
||||
|
@ -2,12 +2,12 @@ part of '../collection_screen.dart';
|
||||
|
||||
class _CollectibleImage extends StatelessWidget {
|
||||
const _CollectibleImage({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.collectible,
|
||||
required this.state,
|
||||
required this.onPressed,
|
||||
this.heroTag,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final CollectibleData collectible;
|
||||
final ValueSetter<CollectibleData> onPressed;
|
||||
|
@ -2,7 +2,7 @@ part of '../collection_screen.dart';
|
||||
|
||||
@immutable
|
||||
class _CollectionFooter extends StatelessWidget {
|
||||
const _CollectionFooter({Key? key, required this.count, required this.total}) : super(key: key);
|
||||
const _CollectionFooter({super.key, required this.count, required this.total});
|
||||
|
||||
final int count;
|
||||
final int total;
|
||||
@ -42,9 +42,10 @@ class _CollectionFooter extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildProgressRow(BuildContext context) {
|
||||
int percent = (count / total * 100).round();
|
||||
return Row(children: [
|
||||
Text(
|
||||
$strings.collectionLabelDiscovered((count / total * 100).round()),
|
||||
$strings.collectionLabelDiscovered(percent),
|
||||
style: $styles.text.body.copyWith(color: $styles.colors.accent1),
|
||||
),
|
||||
Spacer(),
|
||||
|
@ -3,11 +3,11 @@ part of '../collection_screen.dart';
|
||||
@immutable
|
||||
class _CollectionList extends StatefulWidget with GetItStatefulWidgetMixin {
|
||||
_CollectionList({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.onReset,
|
||||
required this.fromId,
|
||||
this.scrollKey,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
static const double _vtCardExtent = 300;
|
||||
static const double _hzCardExtent = 600;
|
||||
@ -63,7 +63,7 @@ class _CollectionListState extends State<_CollectionList> with GetItStateMixin {
|
||||
fromId: widget.fromId,
|
||||
data: d,
|
||||
);
|
||||
}).toList()
|
||||
})
|
||||
];
|
||||
// Scroll view adapts to scroll vertically or horizontally
|
||||
return SingleChildScrollView(
|
||||
|
@ -1,7 +1,7 @@
|
||||
part of '../collection_screen.dart';
|
||||
|
||||
class _CollectionListCard extends StatelessWidget with GetItMixin {
|
||||
_CollectionListCard({Key? key, this.width, this.height, required this.data, required this.fromId}) : super(key: key);
|
||||
_CollectionListCard({super.key, this.width, this.height, required this.data, required this.fromId});
|
||||
|
||||
final double? width;
|
||||
final double? height;
|
||||
@ -9,7 +9,7 @@ class _CollectionListCard extends StatelessWidget with GetItMixin {
|
||||
final String fromId;
|
||||
|
||||
void _showDetails(BuildContext context, CollectibleData collectible) {
|
||||
context.push(ScreenPaths.artifact(collectible.artifactId));
|
||||
context.go(ScreenPaths.artifact(collectible.artifactId));
|
||||
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,
|
||||
),
|
||||
);
|
||||
}).toList()
|
||||
})
|
||||
]),
|
||||
)
|
||||
],
|
||||
|
@ -2,7 +2,7 @@ part of '../collection_screen.dart';
|
||||
|
||||
@immutable
|
||||
class _NewlyDiscoveredItemsBtn extends StatelessWidget {
|
||||
const _NewlyDiscoveredItemsBtn({Key? key, this.count = 0, required this.onPressed}) : super(key: key);
|
||||
const _NewlyDiscoveredItemsBtn({super.key, this.count = 0, required this.onPressed});
|
||||
|
||||
final int count;
|
||||
final VoidCallback onPressed;
|
||||
|
@ -38,7 +38,7 @@ part 'widgets/_title_text.dart';
|
||||
part 'widgets/_top_illustration.dart';
|
||||
|
||||
class WonderEditorialScreen extends StatefulWidget {
|
||||
const WonderEditorialScreen(this.data, {Key? key, required this.contentPadding}) : super(key: key);
|
||||
const WonderEditorialScreen(this.data, {super.key, required this.contentPadding});
|
||||
final WonderData data;
|
||||
//final void Function(double scrollPos) onScroll;
|
||||
final EdgeInsets contentPadding;
|
||||
@ -63,6 +63,8 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
|
||||
_scrollPos.value = _scroller.position.pixels;
|
||||
}
|
||||
|
||||
void _handleBackPressed() => context.go(ScreenPaths.home);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(builder: (_, constraints) {
|
||||
@ -181,7 +183,7 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
|
||||
alignment: backBtnAlign,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all($styles.insets.sm),
|
||||
child: BackBtn(icon: AppIcons.north),
|
||||
child: BackBtn(icon: AppIcons.north, onPressed: _handleBackPressed),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
part of '../editorial_screen.dart';
|
||||
|
||||
class _AppBar extends StatelessWidget {
|
||||
_AppBar(this.wonderType, {Key? key, required this.sectionIndex, required this.scrollPos}) : super(key: key);
|
||||
_AppBar(this.wonderType, {super.key, required this.sectionIndex, required this.scrollPos});
|
||||
final WonderType wonderType;
|
||||
final ValueNotifier<int> sectionIndex;
|
||||
final ValueNotifier<double> scrollPos;
|
||||
@ -18,24 +18,16 @@ class _AppBar extends StatelessWidget {
|
||||
];
|
||||
|
||||
ArchType _getArchType() {
|
||||
switch (wonderType) {
|
||||
case WonderType.chichenItza:
|
||||
return ArchType.flatPyramid;
|
||||
case WonderType.christRedeemer:
|
||||
return ArchType.wideArch;
|
||||
case WonderType.colosseum:
|
||||
return ArchType.arch;
|
||||
case WonderType.greatWall:
|
||||
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;
|
||||
}
|
||||
return switch (wonderType) {
|
||||
WonderType.chichenItza => ArchType.flatPyramid,
|
||||
WonderType.christRedeemer => ArchType.wideArch,
|
||||
WonderType.colosseum => ArchType.arch,
|
||||
WonderType.greatWall => ArchType.arch,
|
||||
WonderType.machuPicchu => ArchType.pyramid,
|
||||
WonderType.petra => ArchType.wideArch,
|
||||
WonderType.pyramidsGiza => ArchType.pyramid,
|
||||
WonderType.tajMahal => ArchType.spade
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -3,7 +3,7 @@ part of '../editorial_screen.dart';
|
||||
class _Callout extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
const _Callout({Key? key, required this.text}) : super(key: key);
|
||||
const _Callout({super.key, required this.text});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IntrinsicHeight(
|
||||
|
@ -1,9 +1,8 @@
|
||||
part of '../editorial_screen.dart';
|
||||
|
||||
class _CircularTitleBar extends StatelessWidget {
|
||||
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.'),
|
||||
super(key: key);
|
||||
const _CircularTitleBar({super.key, required this.titles, required this.icons, required this.index})
|
||||
: assert(titles.length == icons.length, 'The number of titles and icons do not match.');
|
||||
final List<String> titles;
|
||||
final List<String> icons;
|
||||
final int index;
|
||||
@ -35,10 +34,8 @@ class _CircularTitleBar extends StatelessWidget {
|
||||
BottomCenter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: 20),
|
||||
child: Image.asset('${ImagePaths.common}/${icons[index]}')
|
||||
.animate(key: ValueKey(index))
|
||||
.fade()
|
||||
.scale(begin: .5, end: 1, curve: Curves.easeOutBack, duration: $styles.times.med),
|
||||
child: Image.asset('${ImagePaths.common}/${icons[index]}').animate(key: ValueKey(index)).fade().scale(
|
||||
begin: Offset(.5, .5), end: Offset(1, 1), curve: Curves.easeOutBack, duration: $styles.times.med),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -50,10 +47,10 @@ class _CircularTitleBar extends StatelessWidget {
|
||||
|
||||
class _AnimatedCircleWithText extends StatefulWidget {
|
||||
const _AnimatedCircleWithText({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.titles,
|
||||
required this.index,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final List<String> titles;
|
||||
final int index;
|
||||
|
@ -1,7 +1,7 @@
|
||||
part of '../editorial_screen.dart';
|
||||
|
||||
class _CollapsingPullQuoteImage extends StatelessWidget {
|
||||
const _CollapsingPullQuoteImage({Key? key, required this.scrollPos, required this.data}) : super(key: key);
|
||||
const _CollapsingPullQuoteImage({super.key, required this.scrollPos, required this.data});
|
||||
final ValueNotifier<double> scrollPos;
|
||||
final WonderData data;
|
||||
|
||||
@ -25,7 +25,7 @@ class _CollapsingPullQuoteImage extends StatelessWidget {
|
||||
if (top) offsetY *= -1; // flip?
|
||||
return Transform.translate(
|
||||
offset: Offset(0, offsetY),
|
||||
child:Text(value, style: quoteStyle, textAlign: TextAlign.center),
|
||||
child: Text(value, style: quoteStyle, textAlign: TextAlign.center),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
part of '../editorial_screen.dart';
|
||||
|
||||
class _LargeSimpleQuote extends StatelessWidget {
|
||||
const _LargeSimpleQuote({Key? key, required this.text, required this.author}) : super(key: key);
|
||||
const _LargeSimpleQuote({super.key, required this.text, required this.author});
|
||||
final String text;
|
||||
final String author;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user