初始化项目

main
落雨楓 2 years ago
commit 738a6364c2

45
.gitignore vendored

@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
.vscode/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
- platform: web
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

@ -0,0 +1,16 @@
# isekai_wiki
异世界百科APP
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
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`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

13
android/.gitignore vendored

@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

@ -0,0 +1,71 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 33
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "cn.isekai.wiki.isekai_wiki"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 19
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.isekai.wiki.isekai_wiki">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

@ -0,0 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.isekai.wiki.isekai_wiki">
<application
android:label="@string/app_name"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

@ -0,0 +1,6 @@
package cn.isekai.wiki.isekai_wiki
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">异世界百科</string>
</resources>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.isekai.wiki.isekai_wiki">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

@ -0,0 +1,2 @@
#!/bin/sh
flutter pub run build_runner build

34
ios/.gitignore vendored

@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
</dict>
</plist>

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

@ -0,0 +1,41 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

@ -0,0 +1,46 @@
PODS:
- Flutter (1.0.0)
- flutter_web_browser (0.17.1):
- Flutter
- package_info_plus (0.4.5):
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- wakelock (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_web_browser (from `.symlinks/plugins/flutter_web_browser/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_web_browser:
:path: ".symlinks/plugins/flutter_web_browser/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_web_browser: 7bccaafbb0c5b8862afe7bcd158f15557109f61f
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.11.3

@ -0,0 +1,549 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
629CF133F5059B08A0BD2C8B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 501274051A1E3548251A1B08 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
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>"; };
3AFB459EADF80E56F496475E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
501274051A1E3548251A1B08 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
67BED01427C75D31D69F353D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
F855FC5056C4167AE656E8FE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
629CF133F5059B08A0BD2C8B /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
6AC34FF921120CE1D2389405 /* Frameworks */ = {
isa = PBXGroup;
children = (
501274051A1E3548251A1B08 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
E938473C9A6FA23B4894DA7D /* Pods */,
6AC34FF921120CE1D2389405 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
E938473C9A6FA23B4894DA7D /* Pods */ = {
isa = PBXGroup;
children = (
67BED01427C75D31D69F353D /* Pods-Runner.debug.xcconfig */,
3AFB459EADF80E56F496475E /* Pods-Runner.release.xcconfig */,
F855FC5056C4167AE656E8FE /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
63EA0BA8292C989967BCB1F8 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
11A7373D03D48E9831D34A47 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
11A7373D03D48E9831D34A47 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
63EA0BA8292C989967BCB1F8 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = cn.isekai.wiki.isekaiWiki;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = cn.isekai.wiki.isekaiWiki;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = cn.isekai.wiki.isekaiWiki;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Isekai Wiki</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>isekai_wiki</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

@ -0,0 +1,205 @@
import 'package:isekai_wiki/api/mw/mw_api.dart';
import 'package:isekai_wiki/api/response/page_info.dart';
import 'package:isekai_wiki/api/response/recent_changes.dart';
class MWApiList {
///
static Future<MWResponse<List<RecentChangesItem>>> getRecentChanges(
String type,
{int? limit,
Map<String, String>? continueInfo}) async {
var query = {
"list": "recentchanges",
"rctype": type,
"rcnamespace": 0,
"rclimit": limit,
};
if (continueInfo != null && continueInfo.containsKey("rccontinue")) {
query["rccontinue"] = continueInfo["rccontinue"];
}
var mwRes = await MWApi.get("query", query: query);
if (!mwRes.ok) {
return MWResponse(errorList: mwRes.errorList);
}
if (mwRes.data != null) {
var rcRes = RecentChangesResponse.fromJson(mwRes.data!);
return MWResponse(
ok: true,
data: rcRes.recentchanges,
continueInfo: mwRes.continueInfo);
} else {
return MWResponse(
errorList: [MWError(code: 'response_data_empty', info: '加载的数据为空')]);
}
}
///
static Future<MWResponse<List<RecentChangesItem>>> getMixedRecentChanges(
{int? limit, Map<String, String>? continueInfo}) async {
Map<String, String> continueInfoNew = {};
Map<String, String> continueInfoEdit = {};
bool ignoreRcNew = false;
bool ignoreRcEdit = false;
if (continueInfo != null) {
ignoreRcNew = true;
ignoreRcEdit = true;
if (continueInfo.containsKey("rcnewcontinue")) {
continueInfoNew["rccontinue"] = continueInfo["rcnewcontinue"]!;
ignoreRcNew = false;
}
if (continueInfo.containsKey("rceditcontinue")) {
continueInfoEdit["rccontinue"] = continueInfo["rceditcontinue"]!;
ignoreRcEdit = false;
}
}
var rcResList = await Future.wait([
ignoreRcNew
? Future.value(
MWResponse<List<RecentChangesItem>>(ok: true, data: []))
: getRecentChanges("new",
limit: limit, continueInfo: continueInfoNew),
ignoreRcEdit
? Future.value(
MWResponse<List<RecentChangesItem>>(ok: true, data: []))
: getRecentChanges("edit",
limit: limit, continueInfo: continueInfoEdit),
]);
var rcNewRes = rcResList[0];
var rcEditRes = rcResList[1];
if (!rcNewRes.ok) {
return MWResponse(errorList: rcNewRes.errorList);
} else if (!rcEditRes.ok) {
return MWResponse(errorList: rcNewRes.errorList);
}
List<RecentChangesItem> mergedList = [
...rcNewRes.data!,
...rcEditRes.data!
];
mergedList.sort((a, b) {
// 7
var timeA = a.type == "new"
? a.timestamp.add(const Duration(days: 7))
: a.timestamp;
var timeB = b.type == "new"
? b.timestamp.add(const Duration(days: 7))
: b.timestamp;
return timeB.compareTo(timeA);
});
List<RecentChangesItem> uniqueMergedList = [];
for (var page in mergedList) {
if (uniqueMergedList
.indexWhere((element) => element.pageid == page.pageid) ==
-1) {
uniqueMergedList.add(page);
}
}
Map<String, String> mergedContinueInfo = {};
if (rcNewRes.continueInfo != null &&
rcNewRes.continueInfo!.containsKey("rccontinue")) {
mergedContinueInfo["rcnewcontinue"] =
rcNewRes.continueInfo!["rccontinue"]!;
}
if (rcEditRes.continueInfo != null &&
rcEditRes.continueInfo!.containsKey("rccontinue")) {
mergedContinueInfo["rceditcontinue"] =
rcEditRes.continueInfo!["rccontinue"]!;
}
return MWResponse(
ok: true,
data: uniqueMergedList,
continueInfo: mergedContinueInfo.isNotEmpty ? mergedContinueInfo : null,
);
}
static Future<MWResponse<List<PageInfo>>> getPageInfoList(
{List<int>? pageids,
List<String>? titles,
int extractChars = 200}) async {
var query = {
"prop": "extracts|info|pageimages",
"redirects": 1,
"converttitles": 1,
"exintro": 1,
"explaintext": 1,
"exchars": extractChars,
"inprop": "url",
"piprop": "thumbnail",
"pithumbsize": 640,
"pilicense": "any"
};
if (pageids != null) {
query["pageids"] = pageids.join("|");
}
if (titles != null) {
query["titles"] = titles.join("|");
}
var mwRes = await MWApi.get("query", query: query);
if (!mwRes.ok) {
return MWResponse(errorList: mwRes.errorList);
}
if (mwRes.data != null) {
var pagesRes = PagesResponse.fromJson(mwRes.data!);
var pageList = pagesRes.pages.map((pageInfo) {
if (pageInfo.description != null) {
pageInfo.description =
pageInfo.description!.replaceAll(RegExp(r"\n\n"), "\n");
}
if (pageInfo.title.contains("/")) {
var splitPos = pageInfo.title.lastIndexOf("/");
pageInfo.displayTitle = pageInfo.title.substring(splitPos + 1);
pageInfo.subtitle = pageInfo.title.substring(0, splitPos);
} else {
pageInfo.displayTitle = pageInfo.title;
}
return pageInfo;
}).toList();
//
List<PageInfo> sortedPages = [];
if (pageids != null) {
for (var pageid in pageids) {
var index =
pageList.indexWhere((element) => element.pageid == pageid);
if (index != -1) {
sortedPages.add(pageList[index]);
}
}
}
if (titles != null) {
for (var title in titles) {
var index =
pagesRes.pages.indexWhere((element) => element.title == title);
if (index != -1) {
sortedPages.add(pagesRes.pages[index]);
}
}
}
return MWResponse(
ok: true, data: sortedPages, continueInfo: mwRes.continueInfo);
} else {
return MWResponse(
errorList: [MWError(code: 'response_data_empty', info: '加载的数据为空')]);
}
}
}

@ -0,0 +1,169 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:isekai_wiki/global.dart';
import '../../utils/api_utils.dart';
const apiBaseUrl = 'https://www.isekai.cn/api.php';
class HttpResponseCodeError extends Error {
int? statusCode;
HttpResponseCodeError(this.statusCode);
@override
String toString() {
return "Http error: $statusCode";
}
}
class MWError {
String? code;
String? info;
String? detail;
MWError({this.code, this.info, this.detail});
static MWError fromMap(Map errorMap) {
var mwError = MWError();
if (errorMap.containsKey("code")) {
mwError.code = errorMap["code"].toString();
}
if (errorMap.containsKey("info")) {
mwError.info = errorMap["info"].toString();
}
if (errorMap.containsKey("*")) {
mwError.detail = errorMap["*"].toString();
}
return mwError;
}
}
class MWMultiError extends Error {
List<MWError> errorList;
MWMultiError(this.errorList);
@override
String toString() {
return errorList.map((e) => e.info).join("\n");
}
}
class MWResponse<T> {
bool ok = false;
List<MWError>? errorList;
T? data;
Map<String, String>? continueInfo;
MWResponse({this.ok = false, this.errorList, this.data, this.continueInfo});
}
class MWApiClient extends http.BaseClient {
final http.Client _inner;
MWApiClient(this._inner);
Future<http.StreamedResponse> send(http.BaseRequest request) async {
request.headers['user-agent'] = await ApiUtils.getUserAgent();
return await _inner.send(request);
}
}
class MWApi {
static Uri apiBaseUri = Uri.parse(apiBaseUrl);
static HttpClient getHttpClient() {
return HttpClient();
}
static Future<Map<String, String>> _getHeaders() async {
var headers = {
"X-IsekaiWikiApp-Version": Global.packageInfo?.version ?? "unknow",
};
if (!kIsWeb) {
headers["User-Agent"] = await ApiUtils.getUserAgent();
}
return headers;
}
static Future<MWResponse<Map<String, dynamic>>> get(String action,
{Map<String, dynamic>? query}) async {
Map<String, String> queryStr =
query?.map((key, value) => MapEntry(key, value.toString())) ?? {};
queryStr.addAll({
"action": action,
"format": "json",
"formatversion": "2",
"uselang": Global.wikiLang,
});
if (Global.webOrigin != null) {
queryStr["origin"] = Global.webOrigin!;
}
Uri requestUri = apiBaseUri.replace(queryParameters: queryStr);
var res = await http.get(requestUri, headers: await _getHeaders());
if (res.statusCode != 200) {
throw HttpResponseCodeError(res.statusCode);
}
var responseBody = res.body;
return parseMWResponse(action, responseBody);
}
static MWResponse<Map<String, dynamic>> parseMWResponse(String action, String resJson) {
var mwRes = MWResponse<Map<String, dynamic>>();
List<MWError> errorList = [];
var resData = jsonDecode(resJson);
if (resData is Map) {
//
var resError = resData["error"];
if (resError is Map) {
errorList.add(MWError.fromMap(resError));
} else if (resError is List) {
for (var errItem in resError) {
if (errItem is Map) {
errorList.add(MWError.fromMap(errItem));
}
}
}
if (errorList.isNotEmpty) {
mwRes.errorList = errorList;
return mwRes;
}
//
if (resData.containsKey(action) && resData[action] is Map) {
mwRes.data = resData[action] as Map<String, dynamic>;
mwRes.ok = true;
}
//
var batchcomplete = resData["batchcomplete"];
if (batchcomplete is bool && batchcomplete) {
var continueInfo = resData["continue"];
if (continueInfo is Map) {
mwRes.continueInfo = {};
continueInfo.forEach((key, value) {
var keyStr = key.toString();
mwRes.continueInfo![keyStr] = value.toString();
});
}
}
}
return mwRes;
}
}

@ -0,0 +1,89 @@
import 'package:json_annotation/json_annotation.dart';
part 'page_info.g.dart';
@JsonSerializable()
class PageInfo {
int pageid;
int ns;
String title;
String? displayTitle;
String? subtitle;
int? lastrevid;
String? contentmodel;
String? pagelanguage;
String? pagelanguagehtmlcode;
String? pagelanguagedir;
bool? inwatchlist;
int? length;
String? fullurl;
String? editurl;
String? canonicalurl;
PageImageInfo? thumbnail;
@JsonKey(name: "extract")
String? description;
@JsonKey(name: "touched")
DateTime? updatedTime;
PageInfo({
required this.pageid,
required this.ns,
required this.title,
this.subtitle,
this.displayTitle,
this.description,
this.contentmodel,
this.pagelanguage,
this.pagelanguagehtmlcode,
this.pagelanguagedir,
this.updatedTime,
this.lastrevid,
this.length,
this.fullurl,
this.editurl,
this.canonicalurl,
});
String get mainTitle {
return displayTitle ?? title;
}
String? get mainCategory {
return null;
}
factory PageInfo.fromJson(Map<String, dynamic> json) =>
_$PageInfoFromJson(json);
Map<String, dynamic> toJson() => _$PageInfoToJson(this);
}
@JsonSerializable()
class PagesResponse {
List<PageInfo> pages;
PagesResponse({required this.pages});
factory PagesResponse.fromJson(Map<String, dynamic> json) =>
_$PagesResponseFromJson(json);
Map<String, dynamic> toJson() => _$PagesResponseToJson(this);
}
@JsonSerializable()
class PageImageInfo {
String source;
int? width;
int? height;
PageImageInfo({required this.source, this.width, this.height});
factory PageImageInfo.fromJson(Map<String, dynamic> json) =>
_$PageImageInfoFromJson(json);
Map<String, dynamic> toJson() => _$PageImageInfoToJson(this);
}

@ -0,0 +1,79 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'page_info.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PageInfo _$PageInfoFromJson(Map<String, dynamic> json) => PageInfo(
pageid: json['pageid'] as int,
ns: json['ns'] as int,
title: json['title'] as String,
subtitle: json['subtitle'] as String?,
description: json['extract'] as String?,
contentmodel: json['contentmodel'] as String?,
pagelanguage: json['pagelanguage'] as String?,
pagelanguagehtmlcode: json['pagelanguagehtmlcode'] as String?,
pagelanguagedir: json['pagelanguagedir'] as String?,
updatedTime: json['touched'] == null
? null
: DateTime.parse(json['touched'] as String),
lastrevid: json['lastrevid'] as int?,
length: json['length'] as int?,
fullurl: json['fullurl'] as String?,
editurl: json['editurl'] as String?,
canonicalurl: json['canonicalurl'] as String?,
)
..displayTitle = json['displayTitle'] as String?
..inwatchlist = json['inwatchlist'] as bool?
..thumbnail = json['thumbnail'] == null
? null
: PageImageInfo.fromJson(json['thumbnail'] as Map<String, dynamic>);
Map<String, dynamic> _$PageInfoToJson(PageInfo instance) => <String, dynamic>{
'pageid': instance.pageid,
'ns': instance.ns,
'title': instance.title,
'displayTitle': instance.displayTitle,
'subtitle': instance.subtitle,
'lastrevid': instance.lastrevid,
'contentmodel': instance.contentmodel,
'pagelanguage': instance.pagelanguage,
'pagelanguagehtmlcode': instance.pagelanguagehtmlcode,
'pagelanguagedir': instance.pagelanguagedir,
'inwatchlist': instance.inwatchlist,
'length': instance.length,
'fullurl': instance.fullurl,
'editurl': instance.editurl,
'canonicalurl': instance.canonicalurl,
'thumbnail': instance.thumbnail,
'extract': instance.description,
'touched': instance.updatedTime?.toIso8601String(),
};
PagesResponse _$PagesResponseFromJson(Map<String, dynamic> json) =>
PagesResponse(
pages: (json['pages'] as List<dynamic>)
.map((e) => PageInfo.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$PagesResponseToJson(PagesResponse instance) =>
<String, dynamic>{
'pages': instance.pages,
};
PageImageInfo _$PageImageInfoFromJson(Map<String, dynamic> json) =>
PageImageInfo(
source: json['source'] as String,
width: json['width'] as int?,
height: json['height'] as int?,
);
Map<String, dynamic> _$PageImageInfoToJson(PageImageInfo instance) =>
<String, dynamic>{
'source': instance.source,
'width': instance.width,
'height': instance.height,
};

@ -0,0 +1,51 @@
import 'package:json_annotation/json_annotation.dart';
part 'recent_changes.g.dart';
@JsonSerializable()
class RecentChangesItem {
String? type;
int ns;
String title;
int pageid;
int revid;
@JsonKey(name: 'old_revid')
int? oldRevid;
int? rcid;
DateTime timestamp;
RecentChangesItem({
this.type,
required this.ns,
required this.title,
required this.pageid,
required this.revid,
this.oldRevid,
this.rcid,
required this.timestamp,
});
factory RecentChangesItem.fromJson(Map<String, dynamic> json) =>
_$RecentChangesItemFromJson(json);
Map<String, dynamic> toJson() => _$RecentChangesItemToJson(this);
}
@JsonSerializable()
class RecentChangesResponse {
List<RecentChangesItem> recentchanges;
RecentChangesResponse({required this.recentchanges});
factory RecentChangesResponse.fromJson(Map<String, dynamic> json) =>
_$RecentChangesResponseFromJson(json);
Map<String, dynamic> toJson() => _$RecentChangesResponseToJson(this);
}

@ -0,0 +1,45 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'recent_changes.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
RecentChangesItem _$RecentChangesItemFromJson(Map<String, dynamic> json) =>
RecentChangesItem(
type: json['type'] as String?,
ns: json['ns'] as int,
title: json['title'] as String,
pageid: json['pageid'] as int,
revid: json['revid'] as int,
oldRevid: json['old_revid'] as int?,
rcid: json['rcid'] as int?,
timestamp: DateTime.parse(json['timestamp'] as String),
);
Map<String, dynamic> _$RecentChangesItemToJson(RecentChangesItem instance) =>
<String, dynamic>{
'type': instance.type,
'ns': instance.ns,
'title': instance.title,
'pageid': instance.pageid,
'revid': instance.revid,
'old_revid': instance.oldRevid,
'rcid': instance.rcid,
'timestamp': instance.timestamp.toIso8601String(),
};
RecentChangesResponse _$RecentChangesResponseFromJson(
Map<String, dynamic> json) =>
RecentChangesResponse(
recentchanges: (json['recentchanges'] as List<dynamic>)
.map((e) => RecentChangesItem.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$RecentChangesResponseToJson(
RecentChangesResponse instance) =>
<String, dynamic>{
'recentchanges': instance.recentchanges,
};

@ -0,0 +1,12 @@
import 'package:isekai_wiki/api/restbase/restbase_api.dart';
class RestfulPageApi {
static Future<String?> getPageHtml(String title, {int? revId}) async {
title = Uri.encodeComponent(title);
var url = "/page/html/$title";
if (revId != null) {
url += "/$revId";
}
return await RestbaseApi.get(url);
}
}

@ -0,0 +1,58 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:isekai_wiki/global.dart';
import 'package:isekai_wiki/utils/api_utils.dart';
import '../mw/mw_api.dart';
const restBaseUrl = "https://www.isekai.cn/api/rest_v1";
class RestbaseApi {
static String restApiEndpoint = restBaseUrl;
static Uri getUri(String endpoint, {Map<String, dynamic>? search}) {
String url = restApiEndpoint;
if (url.endsWith("/")) {
url = url.substring(0, url.length - 1);
}
return Uri.parse(url + endpoint).replace(queryParameters: search);
}
static Future<Map<String, String>> _getHeaders() async {
Map<String, String> headers = {};
if (!kIsWeb) {
headers["X-IsekaiWikiApp-Version"] = Global.packageInfo?.version ?? "unknow";
headers["User-Agent"] = await ApiUtils.getUserAgent();
}
return headers;
}
static Future<String> get(String path, {Map<String, dynamic>? search}) async {
var uri = getUri(path, search: search);
var res = await http.get(uri, headers: await _getHeaders());
if (res.statusCode != 200) {
throw HttpResponseCodeError(res.statusCode);
}
return res.body;
}
static Future<Map> getJson(String path) async {
var resText = await get(path);
var resData = jsonDecode(resText);
if (resData is Map) {
return resData;
} else {
return {};
}
}
}

@ -0,0 +1,40 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'models/model.dart';
import 'pages/tab_page.dart';
import 'styles.dart';
class IsekaiWikiApp extends StatelessWidget {
const IsekaiWikiApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return Material(
child: GetCupertinoApp(
title: '异世界百科',
theme: const CupertinoThemeData(
textTheme: Styles.defaultTextTheme,
scaffoldBackgroundColor: Styles.themePageBackgroundColor),
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
DefaultCupertinoLocalizations.delegate,
],
initialBinding: InitialBinding(),
home: const IsekaiWikiTabsPage(),
builder: (context, child) {
if (child == null) {
return Container();
} else {
Styles.textScaleFactor = MediaQuery.of(context).textScaleFactor;
Styles.isXs = MediaQuery.of(context).size.width <= 340;
return child;
}
},
debugShowCheckedModeBanner: false,
),
);
}
}

@ -0,0 +1,21 @@
import 'package:flutter/cupertino.dart';
class BackButtonNav extends StatelessWidget {
final BuildContext context;
final Widget child;
const BackButtonNav({super.key, required this.context, required this.child});
Future<bool> _handleWillPop() async {
if (Navigator.canPop(context)) {
Navigator.pop(context);
return false;
}
return false;
}
@override
Widget build(BuildContext context) {
return WillPopScope(onWillPop: _handleWillPop, child: child);
}
}

@ -0,0 +1,14 @@
import 'package:flutter/cupertino.dart';
class CollapsedTabText extends StatelessWidget {
final String text;
const CollapsedTabText(this.text, {super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Center(child: Text(text)),
);
}
}

@ -0,0 +1,37 @@
import 'package:flutter/cupertino.dart';
class DummyIcon extends StatelessWidget {
const DummyIcon(
{Key? key,
required this.color,
required this.icon,
this.size = 30.0,
this.rounded = false})
: super(key: key);
final double size;
final Color color;
final IconData icon;
final bool rounded;
@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
color: color,
borderRadius: rounded
? BorderRadius.circular(size / 2)
: BorderRadius.circular(size / 6),
),
child: Center(
child: Icon(
icon,
color: CupertinoColors.white,
size: size * 0.6,
),
),
);
}
}

@ -0,0 +1,248 @@
library flutter_scale_tap;
import 'dart:math';
import 'package:flutter/physics.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
const double _DEFAULT_SCALE_MIN_VALUE = 0.96;
const double _DEFAULT_OPACITY_MIN_VALUE = 0.90;
final Curve _DEFAULT_SCALE_CURVE =
CurveSpring(); // ignore: non_constant_identifier_names
const Curve _DEFAULT_OPACITY_CURVE = Curves.ease;
const Duration _DEFAULT_DURATION = Duration(milliseconds: 250);
class ScaleTapConfig {
static double? scaleMinValue;
static Curve? scaleCurve;
static double? opacityMinValue;
static Curve? opacityCurve;
static Duration? duration;
}
class ScaleTapController extends GetxController {
var ignoredAreaPressing = false;
}
class ScaleTap extends StatefulWidget {
final Future<void> Function()? onPressed;
final Future<void> Function()? onLongPress;
final Widget? child;
final Duration? duration;
final double? scaleMinValue;
final Curve? scaleCurve;
final Curve? opacityCurve;
final double? opacityMinValue;
final bool enableFeedback;
ScaleTap({
this.enableFeedback = true,
this.onPressed,
this.onLongPress,
required this.child,
this.duration,
this.scaleMinValue,
this.opacityMinValue,
this.scaleCurve,
this.opacityCurve,
});
@override
_ScaleTapState createState() => _ScaleTapState();
}
class _ScaleTapState extends State<ScaleTap>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scale;
late Animation<double> _opacity;
var c = ScaleTapController();
bool _scaleAnimating = false;
bool _isPressing = false;
bool _isEventBlocking = false;
@override
void initState() {
super.initState();
c = Get.put(c);
_animationController = AnimationController(vsync: this);
_scale = Tween<double>(begin: 1.0, end: 1.0).animate(_animationController);
_opacity =
Tween<double>(begin: 1.0, end: 1.0).animate(_animationController);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
Future<void> anim({double? scale, double? opacity, Duration? duration}) {
_animationController.stop();
_animationController.duration = duration ?? Duration.zero;
_scale = Tween<double>(
begin: _scale.value,
end: scale,
).animate(CurvedAnimation(
curve: widget.scaleCurve ??
ScaleTapConfig.scaleCurve ??
_DEFAULT_SCALE_CURVE,
parent: _animationController,
));
_opacity = Tween<double>(
begin: _opacity.value,
end: opacity,
).animate(CurvedAnimation(
curve: widget.opacityCurve ??
ScaleTapConfig.opacityCurve ??
_DEFAULT_OPACITY_CURVE,
parent: _animationController,
));
_animationController.reset();
return _animationController.forward();
}
Future<void> recoverySize() async {
if (!_scaleAnimating && !_isPressing && !_isEventBlocking) {
return await anim(
scale: 1.0,
opacity: 1.0,
duration:
widget.duration ?? ScaleTapConfig.duration ?? _DEFAULT_DURATION,
);
}
}
Future<void> _onTapDown(_) async {
if (c.ignoredAreaPressing) return;
_isPressing = true;
_scaleAnimating = true;
await anim(
scale: widget.scaleMinValue ??
ScaleTapConfig.scaleMinValue ??
_DEFAULT_SCALE_MIN_VALUE,
opacity: widget.opacityMinValue ??
ScaleTapConfig.opacityMinValue ??
_DEFAULT_OPACITY_MIN_VALUE,
duration: widget.duration ?? ScaleTapConfig.duration ?? _DEFAULT_DURATION,
);
_scaleAnimating = false;
await recoverySize();
}
Future<void> _onTapUp(_) async {
_isPressing = false;
c.ignoredAreaPressing = false;
return await recoverySize();
}
Future<void> _onTapCancel(_) {
return _onTapUp(_);
}
@override
Widget build(BuildContext context) {
final bool isTapEnabled =
widget.onPressed != null || widget.onLongPress != null;
return AnimatedBuilder(
animation: _animationController,
builder: (_, Widget? child) {
return Opacity(
opacity: _opacity.value,
child: Transform.scale(
alignment: Alignment.center,
scale: _scale.value,
child: child,
),
);
},
child: Listener(
onPointerDown: isTapEnabled ? _onTapDown : null,
onPointerCancel: _onTapCancel,
onPointerUp: _onTapUp,
child: GestureDetector(
onTap: isTapEnabled
? () async {
if (c.ignoredAreaPressing) return;
if (widget.enableFeedback) {
SystemSound.play(SystemSoundType.click);
}
_isEventBlocking = true;
await Future.delayed(const Duration(milliseconds: 50));
await widget.onPressed?.call();
_isEventBlocking = false;
recoverySize();
}
: null,
onLongPress: isTapEnabled
? () async {
if (c.ignoredAreaPressing) return;
_isEventBlocking = true;
widget.onLongPress?.call();
_isEventBlocking = false;
recoverySize();
}
: null,
child: widget.child,
),
),
);
}
}
class ScaleTapIgnore extends StatelessWidget {
final Widget child;
const ScaleTapIgnore({super.key, required this.child});
@override
Widget build(BuildContext context) {
var c = Get.find<ScaleTapController>();
return Listener(
onPointerDown: (_) {
c.ignoredAreaPressing = true;
},
child: GestureDetector(
child: child,
onTap: () {},
onLongPress: () {},
),
);
}
}
class CurveSpring extends Curve {
final SpringSimulation sim;
CurveSpring() : this.sim = _sim(70, 20);
@override
double transform(double t) => sim.x(t) + t * (1 - sim.x(1.0));
}
_sim(double stiffness, double damping) => SpringSimulation(
SpringDescription.withDampingRatio(
mass: 1,
stiffness: stiffness,
ratio: 0.7,
),
0.0,
1.0,
0.0,
);

@ -0,0 +1,11 @@
import 'package:flutter/cupertino.dart';
import 'package:isekai_wiki/styles.dart';
class FollowTextScale extends StatelessWidget {
final Widget child;
const FollowTextScale({super.key, required this.child});
@override
Widget build(BuildContext context) {
return MediaQuery(data: MediaQueryData(textScaleFactor: Styles.textScaleFactor), child: child);
}
}

@ -0,0 +1,32 @@
import 'package:flutter/widgets.dart';
import '../styles.dart';
class OpacityGestureDetector extends StatefulWidget {
final String text;
const OpacityGestureDetector(this.text, {super.key});
@override
State<StatefulWidget> createState() => _OpacityGestureDetectorState();
}
class _OpacityGestureDetectorState extends State<OpacityGestureDetector> {
bool checked = false;
bool animChecked = false;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Text(widget.text,
style: const TextStyle(
color: Styles.themeNavTitleColor,
fontWeight: FontWeight.normal
),
);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,194 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
/// Implements a single iOS application page's layout.
///
/// The scaffold lays out the navigation bar on top and the content between or
/// behind the navigation bar.
///
/// When tapping a status bar at the top of the IsekaiPageScaffold, an
/// animation will complete for the current primary [ScrollView], scrolling to
/// the beginning. This is done using the [PrimaryScrollController] that
/// encloses the [ScrollView]. The [ScrollView.primary] flag is used to connect
/// a [ScrollView] to the enclosing [PrimaryScrollController].
///
/// {@tool dartpad}
/// This example shows a [IsekaiPageScaffold] with a [ListView] as a [child].
/// The [CupertinoButton] is connected to a callback that increments a counter.
/// The [backgroundColor] can be changed.
///
/// ** See code in examples/api/lib/cupertino/page_scaffold/cupertino_page_scaffold.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [CupertinoTabScaffold], a similar widget for tabbed applications.
/// * [CupertinoPageRoute], a modal page route that typically hosts a
/// [IsekaiPageScaffold] with support for iOS-style page transitions.
class IsekaiPageScaffold extends StatefulWidget {
/// Creates a layout for pages with a navigation bar at the top.
const IsekaiPageScaffold({
super.key,
this.navigationBar,
this.backgroundColor,
this.resizeToAvoidBottomInset = true,
required this.child,
}) : assert(child != null),
assert(resizeToAvoidBottomInset != null);
/// The [navigationBar], typically a [CupertinoNavigationBar], is drawn at the
/// top of the screen.
///
/// If translucent, the main content may slide behind it.
/// Otherwise, the main content's top margin will be offset by its height.
///
/// The scaffold assumes the navigation bar will account for the [MediaQuery]
/// top padding, also consume it if the navigation bar is opaque.
///
/// By default `navigationBar` has its text scale factor set to 1.0 and does
/// not respond to text scale factor changes from the operating system, to match
/// the native iOS behavior. To override such behavior, wrap each of the `navigationBar`'s
/// components inside a [MediaQuery] with the desired [MediaQueryData.textScaleFactor]
/// value. The text scale factor value from the operating system can be retrieved
/// in many ways, such as querying [MediaQuery.textScaleFactorOf] against
/// [CupertinoApp]'s [BuildContext].
// TODO(xster): document its page transition animation when ready
final ObstructingPreferredSizeWidget? navigationBar;
/// Widget to show in the main content area.
///
/// Content can slide under the [navigationBar] when they're translucent.
/// In that case, the child's [BuildContext]'s [MediaQuery] will have a
/// top padding indicating the area of obstructing overlap from the
/// [navigationBar].
final Widget child;
/// The color of the widget that underlies the entire scaffold.
///
/// By default uses [CupertinoTheme]'s `scaffoldBackgroundColor` when null.
final Color? backgroundColor;
/// Whether the [child] should size itself to avoid the window's bottom inset.
///
/// For example, if there is an onscreen keyboard displayed above the
/// scaffold, the body can be resized to avoid overlapping the keyboard, which
/// prevents widgets inside the body from being obscured by the keyboard.
///
/// Defaults to true and cannot be null.
final bool resizeToAvoidBottomInset;
@override
State<IsekaiPageScaffold> createState() => _IsekaiPageScaffoldState();
}
class _IsekaiPageScaffoldState extends State<IsekaiPageScaffold> {
void _handleStatusBarTap() {
final ScrollController? primaryScrollController = PrimaryScrollController.of(context);
// Only act on the scroll controller if it has any attached scroll positions.
if (primaryScrollController != null && primaryScrollController.hasClients) {
primaryScrollController.animateTo(
0.0,
// Eyeballed from iOS.
duration: const Duration(milliseconds: 500),
curve: Curves.linearToEaseOut,
);
}
}
@override
Widget build(BuildContext context) {
Widget paddedContent = widget.child;
final MediaQueryData existingMediaQuery = MediaQuery.of(context);
if (widget.navigationBar != null) {
// TODO(xster): Use real size after partial layout instead of preferred size.
// https://github.com/flutter/flutter/issues/12912
final double topPadding = widget.navigationBar!.preferredSize.height + existingMediaQuery.padding.top;
// Propagate bottom padding and include viewInsets if appropriate
final double bottomPadding = widget.resizeToAvoidBottomInset ? existingMediaQuery.viewInsets.bottom : 0.0;
final EdgeInsets newViewInsets = widget.resizeToAvoidBottomInset
// The insets are consumed by the scaffolds and no longer exposed to
// the descendant subtree.
? existingMediaQuery.viewInsets.copyWith(bottom: 0.0)
: existingMediaQuery.viewInsets;
final bool fullObstruction = widget.navigationBar!.shouldFullyObstruct(context);
// If navigation bar is opaquely obstructing, directly shift the main content
// down. If translucent, let main content draw behind navigation bar but hint the
// obstructed area.
if (fullObstruction) {
paddedContent = MediaQuery(
data: existingMediaQuery
// If the navigation bar is opaque, the top media query padding is fully consumed by the navigation bar.
.removePadding(removeTop: true)
.copyWith(
viewInsets: newViewInsets,
),
child: Padding(
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
child: paddedContent,
),
);
} else {
paddedContent = MediaQuery(
data: existingMediaQuery.copyWith(
padding: existingMediaQuery.padding.copyWith(
top: topPadding,
),
viewInsets: newViewInsets,
),
child: Padding(
padding: EdgeInsets.only(bottom: bottomPadding),
child: paddedContent,
),
);
}
} else {
// If there is no navigation bar, still may need to add padding in order
// to support resizeToAvoidBottomInset.
final double bottomPadding = widget.resizeToAvoidBottomInset ? existingMediaQuery.viewInsets.bottom : 0.0;
paddedContent = Padding(
padding: EdgeInsets.only(bottom: bottomPadding),
child: paddedContent,
);
}
return DecoratedBox(
decoration: BoxDecoration(
color: CupertinoDynamicColor.maybeResolve(widget.backgroundColor, context) ??
CupertinoTheme.of(context).scaffoldBackgroundColor,
),
child: Stack(
children: <Widget>[
// The main content being at the bottom is added to the stack first.
paddedContent,
if (widget.navigationBar != null)
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: widget.navigationBar!,
),
// Add a touch handler the size of the status bar on top of all contents
// to handle scroll to top by status bar taps.
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
height: existingMediaQuery.padding.top,
child: GestureDetector(
excludeFromSemantics: true,
onTap: _handleStatusBarTap,
),
),
],
),
);
}
}

@ -0,0 +1,375 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:isekai_wiki/api/response/page_info.dart';
import 'package:isekai_wiki/components/flutter_scale_tap/flutter_scale_tap.dart';
import 'package:isekai_wiki/components/utils.dart';
import 'package:isekai_wiki/pages/article.dart';
import 'package:isekai_wiki/reactive/reactive.dart';
import 'package:like_button/like_button.dart';
import 'package:pull_down_button/pull_down_button.dart';
import 'package:skeletons/skeletons.dart';
import '../styles.dart';
typedef AddFavoriteCallback = Future<bool> Function(
PageInfo pageInfo, bool localIsFavorite, bool showToast);
typedef PageInfoCallback = Future<void> Function(PageInfo pageInfo);
class PageCardStyles {
static const double cardInnerHeight = 140;
static const cardInnerPadding =
EdgeInsets.only(top: 16, left: 20, right: 20, bottom: 12);
static const double footerButtonSize = 30;
static const double footerButtonInnerSize = 26;
}
class PageCardController extends GetxController {
var isLoading = false.obs;
var pageInfo = Rx<PageInfo?>(null);
var isFavorite = false.obs;
AddFavoriteCallback? onSetFavorite;
PageInfoCallback? onShare;
Future<bool> handleFavoriteClick(bool localIsFavorite) async {
if (pageInfo.value != null && onSetFavorite != null) {
return await onSetFavorite!.call(pageInfo.value!, localIsFavorite, false);
} else {
return false;
}
}
Future<void> handleAddFavoriteMenuItemClick() async {
if (pageInfo.value != null && onSetFavorite != null) {
await onSetFavorite!.call(pageInfo.value!, true, true);
}
}
Future<void> handleRemoveFavoriteMenuItemClick() async {
if (pageInfo.value != null && onSetFavorite != null) {
await onSetFavorite!.call(pageInfo.value!, false, true);
}
}
handleShareClick() async {
if (pageInfo.value != null && onShare != null) {
await onShare!.call(pageInfo.value!);
}
}
void handlePageInfoClick() {
if (pageInfo.value != null) {}
}
Future<void> handleCardClick() async {
if (isLoading.value) {
return;
}
if (pageInfo.value != null) {
var cPageInfo = pageInfo.value!;
await Navigator.of(Get.context!).push(
CupertinoPageRoute(
builder: (_) => ArticlePage(
targetPage: cPageInfo.title,
initialArticleData: MinimumArticleData(
title: cPageInfo.mainTitle,
description: cPageInfo.description,
mainCategory: cPageInfo.mainCategory,
updateTime: cPageInfo.updatedTime,
),
),
),
);
}
}
}
class PageCard extends StatefulWidget {
final bool isLoading;
final PageInfo? pageInfo;
final bool isFavorite;
final AddFavoriteCallback? onSetFavorite;
final PageInfoCallback? onShare;
const PageCard({
super.key,
this.isLoading = false,
this.pageInfo,
this.isFavorite = false,
this.onSetFavorite,
this.onShare,
});
@override
State<StatefulWidget> createState() => _PageCardState();
}
class _PageCardState extends ReactiveState<PageCard> {
var c = PageCardController();
double textScale = 1;
@override
void initState() {
super.initState();
Get.put(c);
}
@override
void receiveProps() {
c.isLoading.value = widget.isLoading;
c.pageInfo.value = widget.pageInfo;
c.isFavorite.value = widget.isFavorite;
c.onSetFavorite = widget.onSetFavorite;
c.onShare = widget.onShare;
}
Widget _actionButtonSkeleton(bool isLoading, Widget child) {
return Skeleton(
isLoading: isLoading,
skeleton: const SkeletonLine(
style: SkeletonLineStyle(
width: PageCardStyles.footerButtonSize,
height: PageCardStyles.footerButtonSize),
),
child: child,
);
}
Widget _buildCardHeader(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Skeleton(
isLoading: c.isLoading.value,
skeleton: SkeletonLine(
style: SkeletonLineStyle(
height: (Styles.pageCardTitle.fontSize! + 4) * textScale,
randomLength: true),
),
child: Text(c.pageInfo.value?.mainTitle ?? "页面信息丢失",
style: Styles.pageCardTitle),
),
);
}
Widget _buildCardBody(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
flex: 1,
child: c.isLoading.value
? SkeletonParagraph(
style: SkeletonParagraphStyle(
lines: 3,
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 0),
lineStyle: SkeletonLineStyle(
randomLength: true,
height: Styles.pageCardDescription.fontSize! *
textScale),
),
)
: Text(c.pageInfo.value?.description ?? "没有简介",
overflow: TextOverflow.fade,
style: Styles.pageCardDescription),
),
const SizedBox(width: 10),
Skeleton(
isLoading: c.isLoading.value,
skeleton: const SkeletonAvatar(
style: SkeletonAvatarStyle(width: 114, height: 114),
),
child: Container(),
),
],
),
),
),
);
}
Widget _buildCardFooter(BuildContext context) {
return ScaleTapIgnore(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
//
c.pageInfo.value?.mainCategory != null
? Chip(
backgroundColor: const Color.fromARGB(1, 238, 238, 238),
label: Text(c.pageInfo.value!.mainCategory!,
style: const TextStyle(color: Colors.black54)),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap)
: const SizedBox(),
c.pageInfo.value?.mainCategory != null
? const SizedBox(width: 10)
: const SizedBox(),
//
Skeleton(
isLoading: c.isLoading.value,
skeleton: const SkeletonLine(
style: SkeletonLineStyle(width: 100),
),
child: Text(
c.pageInfo.value?.updatedTime != null
? Utils.getFriendDate(c.pageInfo.value!.updatedTime!)
: "",
style: Styles.pageCardDescription),
),
const Spacer(),
//
Obx(
() => _actionButtonSkeleton(
c.isLoading.value,
LikeButton(
size: PageCardStyles.footerButtonInnerSize,
isLiked: c.isFavorite.value,
onTap: c.handleFavoriteClick,
likeBuilder: (bool isLiked) {
return Icon(
isLiked ? Icons.favorite : Icons.favorite_border,
color: isLiked ? Colors.red : Colors.grey,
size: PageCardStyles.footerButtonInnerSize,
);
},
),
),
),
const SizedBox(width: 18),
//
_actionButtonSkeleton(
c.isLoading.value,
SizedBox(
height: PageCardStyles.footerButtonSize,
width: PageCardStyles.footerButtonSize,
child: PullDownButton(
routeTheme: PullDownMenuRouteTheme(
endShadow: BoxShadow(
color: Colors.grey.withOpacity(0.6),
spreadRadius: 1,
blurRadius: 20,
offset: const Offset(0, 2),
),
),
itemBuilder: (context) => [
c.isFavorite.value
? PullDownMenuItem(
title: '取消收藏',
icon: CupertinoIcons.heart_fill,
onTap: c.handleRemoveFavoriteMenuItemClick,
)
: PullDownMenuItem(
title: '收藏',
icon: CupertinoIcons.heart,
onTap: c.handleRemoveFavoriteMenuItemClick,
),
const PullDownMenuDivider(),
PullDownMenuItem(
title: '分享',
icon: CupertinoIcons.share,
onTap: c.handleShareClick,
),
const PullDownMenuDivider.large(),
/*
PullDownMenuItem(
title: '相似推荐',
onTap: () {},
icon: CupertinoIcons.ellipsis_circle,
),
const PullDownMenuDivider.large(),
*/
PullDownMenuItem(
title: '页面详情',
onTap: c.handlePageInfoClick,
icon: CupertinoIcons.info_circle,
),
],
position: PullDownMenuPosition.under,
buttonBuilder: (context, showMenu) => IconButton(
onPressed: showMenu,
padding: const EdgeInsets.all(0.0),
splashRadius: PageCardStyles.footerButtonInnerSize - 4,
iconSize: PageCardStyles.footerButtonInnerSize,
icon: const Icon(
Icons.more_horiz,
color: Colors.grey,
),
),
),
),
),
],
),
);
}
Widget _buildCard(BuildContext context) {
return Card(
elevation: 4.0,
//
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(Styles.isXs ? 0 : 14.0)),
),
// 齿
clipBehavior: Clip.antiAlias,
semanticContainer: false,
child: Padding(
padding: PageCardStyles.cardInnerPadding,
child: SizedBox(
height: PageCardStyles.cardInnerHeight * textScale,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
_buildCardHeader(context),
//
_buildCardBody(context),
// Footer
_buildCardFooter(context),
],
),
),
),
);
}
@override
Widget render(BuildContext context) {
textScale = MediaQuery.of(context).textScaleFactor;
/*
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Obx(
() => ScaleTap(
enableFeedback: !c.isLoading.value,
onPressed: c.handleCardClick,
child: GetBuilder<PageCardController>(
init: c,
builder: (GetxController c) => _buildCard(context),
),
),
),
);
*/
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: ScaleTap(
onPressed: c.handleCardClick,
child: GetBuilder<PageCardController>(
init: c,
builder: (GetxController c) => _buildCard(context),
),
),
);
}
}

@ -0,0 +1,201 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:isekai_wiki/api/mw/list.dart';
import 'package:isekai_wiki/api/mw/mw_api.dart';
import 'package:isekai_wiki/api/response/page_info.dart';
import 'package:isekai_wiki/components/page_card.dart';
import 'package:isekai_wiki/models/favorite_list.dart';
import 'package:isekai_wiki/pages/home.dart';
import 'package:isekai_wiki/extension/map.dart';
class RecentPageListController extends GetxController {
Function? _handleReload;
ScrollController? scrollController;
var shouldRefresh = true;
var pageList = RxList<PageInfo>();
var continueInfo = RxMap<String, String>();
var errorList = RxList<MWError>();
var hasNextPage = true.obs;
var isLoading = false.obs;
RecentPageListController({this.scrollController});
@override
void onInit() {
scrollController?.addListener(() {
if (scrollController!.position.atEdge &&
scrollController!.position.pixels != 0) {
//
if (hasNextPage.value && !isLoading.value) {
loadNextPages();
}
}
});
super.onInit();
loadNextPages();
var homeController = Get.find<HomeController>();
homeController.onRefresh = refreshPages;
}
@override
void dispose() {
var homeController = Get.find<HomeController>();
homeController.onRefresh = refreshPages;
super.dispose();
}
Future<void> refreshPages() async {
continueInfo.clear();
errorList.clear();
hasNextPage.value = true;
shouldRefresh = true;
await loadNextPages();
}
Future<void> loadNextPages() async {
//
if (!hasNextPage.value) return;
isLoading.value = true;
var rcListRes = await MWApiList.getMixedRecentChanges(
limit: 10, continueInfo: continueInfo.isNotEmpty ? continueInfo : null);
if (!rcListRes.ok) {
//
errorList.value = rcListRes.errorList ?? [];
isLoading.value = false;
hasNextPage.value = false;
if (shouldRefresh) {
pageList.clear();
}
return;
}
var pageIds = rcListRes.data!.map((rcInfo) => rcInfo.pageid).toList();
var pageListRes =
await MWApiList.getPageInfoList(pageids: pageIds, extractChars: 515);
if (!pageListRes.ok) {
//
errorList.value = pageListRes.errorList ?? [];
isLoading.value = false;
hasNextPage.value = false;
if (shouldRefresh) {
pageList.clear();
}
return;
}
//
if (shouldRefresh) {
//
pageList.value = pageListRes.data!;
shouldRefresh = false;
} else {
pageList.addAll(pageListRes.data!);
}
hasNextPage.value = rcListRes.continueInfo != null;
continueInfo.value = rcListRes.continueInfo ?? {};
isLoading.value = false;
}
Future<void> reload() async {
if (_handleReload != null) {
await _handleReload!();
}
}
void setReloadHandler(Function? reloadHandler) {
_handleReload = reloadHandler;
}
}
class RecentPageList extends StatelessWidget {
final ScrollController? scrollController;
const RecentPageList({
super.key,
this.scrollController,
});
Widget _buildSkeletonList() {
return Column(
key: key,
children: [
for (var i = 0; i < 6; i++) PageCard(key: ValueKey(i), isLoading: true),
],
);
}
Widget _buildPageCard(int index, PageInfo pageInfo,
RecentPageListController c, FavoriteListController fc) {
return PageCard(
key: ValueKey(index),
pageInfo: c.pageList[index],
isFavorite: fc.isFavorite(c.pageList[index]),
onSetFavorite: fc.setFavorite,
);
}
SliverChildBuilderDelegate _buildPageListDelegate() {
var c = Get.find<RecentPageListController>();
var fc = Get.find<FavoriteListController>();
return SliverChildBuilderDelegate(
childCount: c.pageList.length + 1,
(context, index) {
if (index == 0) {
//
return Obx(() {
if (c.pageList.isEmpty) {
return _buildSkeletonList();
} else {
return _buildPageCard(index, c.pageList[index], c, fc);
}
});
}
//
if (index < c.pageList.length) {
return _buildPageCard(index, c.pageList[index], c, fc);
} else if (index == c.pageList.length) {
//
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Center(
child: Obx(
() => c.isLoading.value
? const CupertinoActivityIndicator(
radius: 14,
)
: const SizedBox(
width: 28,
height: 28,
),
),
),
);
}
return null;
},
);
}
@override
Widget build(BuildContext context) {
Get.put(RecentPageListController(scrollController: scrollController));
return Obx(() => SliverList(
delegate: _buildPageListDelegate(),
));
}
}

@ -0,0 +1,61 @@
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:isekai_wiki/reactive/reactive.dart';
class StateTestController extends GetxController {
var count = 0.obs;
@override
void onInit() {
debugPrint("StateTestController onInit");
super.onInit();
}
void updateState({required int count}) {
debugPrint("StateTestController updateState");
this.count.value = count;
}
}
class StateTest extends StatefulWidget {
final VoidCallback? onPressed;
final int count;
const StateTest({super.key, this.onPressed, required this.count});
@override
State<StatefulWidget> createState() {
return StateTestState();
}
}
class StateTestState extends ReactiveState<StateTest> {
StateTestController c = StateTestController();
@override
void initState() {
super.initState();
Get.put(c);
}
@override
void receiveProps() {
c.updateState(count: widget.count);
}
@override
Widget render(BuildContext context) {
return Column(children: [
Center(
child: Obx(
() => Text(c.count.value.toString()),
),
),
CupertinoButton.filled(
child: const Text("增加"),
onPressed: () {
widget.onPressed?.call();
}),
]);
}
}

@ -0,0 +1,35 @@
import 'package:flutter/cupertino.dart';
import 'package:isekai_wiki/styles.dart';
class IsekaiPageLargeTitle extends StatelessWidget {
final String text;
const IsekaiPageLargeTitle(this.text, { super.key });
@override
Widget build(BuildContext context) {
return Text(text,
style: const TextStyle(
color: Styles.themeNavTitleColor,
fontWeight: FontWeight.normal
),
);
}
}
class IsekaiPageNavTitle extends StatelessWidget {
final String text;
const IsekaiPageNavTitle(this.text, { super.key });
@override
Widget build(BuildContext context) {
return Text(text,
style: const TextStyle(
color: Styles.themeNavTitleColor,
fontWeight: FontWeight.normal,
fontSize: Styles.navTitleFontSize
),
);
}
}

@ -0,0 +1,14 @@
class Utils {
static String getFriendDate(DateTime time) {
var currentTime = DateTime.now();
var timeDelta = currentTime.millisecondsSinceEpoch - time.millisecondsSinceEpoch;
if (timeDelta <= 86400) {
return "刚刚更新";
}
String timeStr = "${time.month}${time.day}";
if (time.year != currentTime.year) {
timeStr = "${time.year}$timeStr";
}
return timeStr;
}
}

@ -0,0 +1,3 @@
import './string.dart';
import './list.dart';
import './map.dart';

@ -0,0 +1,47 @@
import 'package:get/get.dart';
extension ListExtension<T> on List<T> {
bool addOrSet(int index, T value) {
if (length == index) {
add(value);
return true;
} else if (length > index) {
this[index] = value;
return true;
}
return false;
}
T getWithSetCallback(int index, T Function() callback) {
if (length > index) {
return this[index];
} else {
var data = callback();
addOrSet(index, data);
return data;
}
}
}
extension RxListExtension<T> on RxList<T> {
bool addOrSet(int index, T value) {
if (length == index) {
add(value);
return true;
} else if (length > index) {
this[index] = value;
return true;
}
return false;
}
T getWithSetCallback(int index, T Function() callback) {
if (length > index) {
return this[index];
} else {
var data = callback();
addOrSet(index, data);
return data;
}
}
}

@ -0,0 +1,25 @@
import 'package:get/get.dart';
extension MapExtension<K, T> on Map<K, T> {
T getWithSetCallback(K key, T Function() callback) {
if (containsKey(key)) {
return this[key]!;
} else {
var data = callback();
this[key] = data;
return data;
}
}
}
extension RxMapExtension<K, T> on RxMap<K, T> {
T getWithSetCallback(K key, T Function() callback) {
if (containsKey(key)) {
return this[key]!;
} else {
var data = callback();
this[key] = data;
return data;
}
}
}

@ -0,0 +1,9 @@
extension StringExtension on String {
String capitalize() {
if (length > 1) {
return toUpperCase();
} else {
return this[0].toUpperCase() + substring(1);
}
}
}

@ -0,0 +1,14 @@
import 'package:package_info_plus/package_info_plus.dart';
typedef VoidFutureCallback = Future<void> Function();
typedef BoolFutureCallback = Future<bool> Function();
class Global {
static String isekaiWikiHomeUrl = "https://www.isekai.cn/";
static PackageInfo? packageInfo;
static String wikiLang = "zh-cn";
static String? webOrigin;
}

@ -0,0 +1,55 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:isekai_wiki/global.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'app.dart';
Future<void> init() async {
//
/*SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);*/
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(statusBarColor: Colors.transparent));
if (kIsWeb) {
// web origin
Global.webOrigin = Uri.base.origin;
}
}
Future<void> postInit() async {
if (!kIsWeb) {
//
try {
if (kDebugMode) {
var modes = await FlutterDisplayMode.supported;
print("Refresh rate list:");
// ignore: avoid_print
modes.forEach(print);
print("Preferred refresh rate:");
print(await FlutterDisplayMode.preferred);
}
await FlutterDisplayMode.setHighRefreshRate();
} catch (err) {
if (kDebugMode) {
print("Cannot set to high refresh rate: ");
print(err);
}
}
}
Global.packageInfo = await PackageInfo.fromPlatform();
}
void main() {
init();
runApp(const IsekaiWikiApp());
postInit();
}

@ -0,0 +1,62 @@
import 'package:get/get.dart';
import 'package:isekai_wiki/api/response/page_info.dart';
import 'package:isekai_wiki/models/user.dart';
import 'package:isekai_wiki/utils/dialog.dart';
class FavoriteListController extends GetxController {
var pageIds = RxList<int>();
void updateFromPageList(List<PageInfo> list) {
List<int> addList = [];
List<int> removeList = [];
for (var pageInfo in list) {
if (pageInfo.inwatchlist != null) {
if (pageInfo.inwatchlist!) {
addList.add(pageInfo.pageid);
} else {
removeList.add(pageInfo.pageid);
}
}
}
var newPageIds =
pageIds.where((pageId) => !removeList.contains(pageId)).toList();
for (var pageId in addList) {
if (!newPageIds.contains(pageId)) {
newPageIds.add(pageId);
}
}
pageIds.value = newPageIds;
}
void updateFromWatchList(List<PageInfo> list) {
List<int> addList = [];
for (var pageInfo in list) {
addList.add(pageInfo.pageid);
}
for (var pageId in addList) {
if (!pageIds.contains(pageId)) {
pageIds.add(pageId);
}
}
}
bool isFavorite(PageInfo pageInfo) {
return pageIds.contains(pageInfo.pageid);
}
Future<bool> setFavorite(
PageInfo pageInfo, bool isFavorite, bool showToast) async {
//
var uc = Get.find<UserController>();
if (!uc.isLoggedIn) {
var result = await confirm(Get.overlayContext!, "使用收藏功能需要登录",
title: "提示", positiveText: "登录");
return false;
}
return false;
}
}

@ -0,0 +1,5 @@
import 'package:get/get.dart';
class HistoryListController extends GetxController {
var pageIds = RxList<int>();
}

@ -0,0 +1,14 @@
import 'package:get/get.dart';
import 'package:isekai_wiki/models/favorite_list.dart';
import 'package:isekai_wiki/models/history_list.dart';
import 'package:isekai_wiki/models/user.dart';
class InitialBinding extends Bindings {
@override
void dependencies() {
Get.put<UserController>(UserController());
Get.lazyPut<FavoriteListController>(() => FavoriteListController());
Get.lazyPut<HistoryListController>(() => HistoryListController());
}
}

@ -0,0 +1,104 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'user.g.dart';
@JsonSerializable()
class UserInfo {
int userId;
String userName;
String? nickName;
String? avatarUrl;
UserInfo(
{required this.userId,
required this.userName,
this.nickName,
this.avatarUrl});
factory UserInfo.fromJson(Map<String, dynamic> json) =>
_$UserInfoFromJson(json);
Map<String, dynamic> toJson() => _$UserInfoToJson(this);
}
class UserController extends GetxController {
bool isReady = false;
var userId = 0.obs;
bool get isLoggedIn {
return userId.value > 0;
}
var userName = "".obs;
var nickName = "".obs;
var avatarUrl = "".obs;
String get getDisplayName {
return nickName.isNotEmpty ? nickName.string : userName.string;
}
Future<void> initialize() async {
if (!isReady) {
await loadFromStorage();
isReady = true;
postInit().catchError((err) {
if (kDebugMode) {
print(err);
}
});
}
}
Future<void> postInit() async {
await updateProfile();
}
///
Future<void> updateProfile() async {}
///
Future<void> loadFromStorage() async {
try {
final prefs = await SharedPreferences.getInstance();
var userInfoJson = prefs.getString("userInfo");
if (userInfoJson == null) return;
var userInfoObject = jsonDecode(userInfoJson);
if (userInfoObject == null) return;
var userInfo = UserInfo.fromJson(userInfoObject);
userId.value = userInfo.userId;
userName.value = userInfo.userName;
nickName.value = userInfo.nickName ?? "";
avatarUrl.value = userInfo.avatarUrl ?? "";
} catch (ex) {
if (kDebugMode) {
print(ex);
}
}
}
///
Future<void> saveToStorage() async {
final prefs = await SharedPreferences.getInstance();
var userInfo = UserInfo(
userId: userId.value,
userName: userName.value,
nickName: nickName.isNotEmpty ? nickName.value : null,
avatarUrl: avatarUrl.isNotEmpty ? avatarUrl.value : null,
);
var userInfoJson = jsonEncode(userInfo.toJson());
prefs.setString("userInfo", userInfoJson);
}
}

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
UserInfo _$UserInfoFromJson(Map<String, dynamic> json) => UserInfo(
userId: json['userId'] as int,
userName: json['userName'] as String,
nickName: json['nickName'] as String?,
avatarUrl: json['avatarUrl'] as String?,
);
Map<String, dynamic> _$UserInfoToJson(UserInfo instance) => <String, dynamic>{
'userId': instance.userId,
'userName': instance.userName,
'nickName': instance.nickName,
'avatarUrl': instance.avatarUrl,
};

@ -0,0 +1,79 @@
import 'package:cupertino_lists/cupertino_lists.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_web_browser/flutter_web_browser.dart';
import 'package:get/get.dart';
import '../components/dummy_icon.dart';
import '../components/isekai_nav_bar.dart';
import '../components/isekai_page_scaffold.dart';
import '../global.dart';
import '../styles.dart';
class AboutPageController extends GetxController {
Future<void> handleMainPageLinkClick() async {
if (GetPlatform.isAndroid || GetPlatform.isIOS) {
await FlutterWebBrowser.openWebPage(
url: Global.isekaiWikiHomeUrl,
customTabsOptions: const CustomTabsOptions(
defaultColorSchemeParams: CustomTabsColorSchemeParams(
toolbarColor: Colors.black87,
),
),
);
} else {}
}
}
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
var c = Get.put(AboutPageController());
return IsekaiPageScaffold(
navigationBar: const IsekaiNavigationBar(
middle: Text('关于'), previousPageTitle: "设置"),
child: ListView(
children: [
const SizedBox(height: 18),
Container(
color: Styles.panelBackgroundColor,
child: SafeArea(
top: false,
bottom: false,
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: Column(
children: const <Widget>[
Text("异世界百科APP", style: Styles.articleTitle),
SizedBox(height: 18),
Text("使用Flutter构建"),
],
),
),
),
),
const SizedBox(height: 18),
CupertinoListSection.insetGrouped(
backgroundColor: Styles.themePageBackgroundColor,
children: <CupertinoListTile>[
CupertinoListTile.notched(
title: const Text('异世界百科',
style: TextStyle(color: Styles.linkColor)),
leading: const DummyIcon(
color: CupertinoColors.systemBlue,
icon: CupertinoIcons.globe,
),
trailing: const CupertinoListTileChevron(),
onTap: c.handleMainPageLinkClick,
),
],
),
],
),
);
}
}

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

Loading…
Cancel
Save