├── android ├── .idea │ ├── .name │ ├── misc.xml │ ├── gradle.xml │ └── codeStyles │ │ └── Project.xml ├── app │ ├── src │ │ ├── main │ │ │ ├── ic_launcher-web.png │ │ │ ├── res │ │ │ │ ├── xml │ │ │ │ │ └── file_paths.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── values-zh │ │ │ │ │ └── strings.xml │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ └── drawable-night │ │ │ │ │ └── launch_background.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── yaerin │ │ │ │ │ └── dailypics │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ ├── PlatformPluginImpl.kt │ │ │ │ │ ├── PlatformPluginApi24Impl.kt │ │ │ │ │ ├── UseAsWallpaperActivity.kt │ │ │ │ │ └── PlatformPlugin.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── signingConfigs.gradle.example │ └── build.gradle ├── gradle.properties ├── .gitignore ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── build.gradle ├── README.md ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── Runner.entitlements │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ └── AppDelegate.swift ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── TodayExtension │ ├── TodayExtension.entitlements │ ├── Info.plist │ ├── TodayViewController.swift │ └── Base.lproj │ │ └── MainInterface.storyboard ├── .gitignore ├── Podfile └── Podfile.lock ├── res ├── ic_launcher.png ├── placeholder.jpg ├── ionicons-v5.5.1.ttf ├── placeholder-night.jpg └── contributors.json ├── windows ├── runner │ ├── resources │ │ └── app_icon.ico │ ├── resource.h │ ├── CMakeLists.txt │ ├── utils.h │ ├── runner.exe.manifest │ ├── run_loop.h │ ├── flutter_window.h │ ├── main.cpp │ ├── utils.cpp │ ├── flutter_window.cpp │ ├── run_loop.cpp │ ├── Runner.rc │ └── win32_window.h ├── .gitignore ├── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugin_registrant.cc │ ├── generated_plugins.cmake │ └── CMakeLists.txt └── CMakeLists.txt ├── lib ├── misc │ ├── config.g.dart │ ├── bean.g.dart │ └── bean.dart ├── utils │ ├── http.dart │ ├── windows.dart │ ├── api.dart │ └── utils.dart ├── widget │ ├── error.dart │ ├── buttons.dart │ ├── qrcode.dart │ ├── optimized_image.dart │ ├── hightlight.dart │ ├── adaptive_scaffold.dart │ ├── animated_transform.dart │ ├── slivers.dart │ ├── search.dart │ ├── toast.dart │ ├── photo_card.dart │ └── panel_view.dart ├── extension.dart ├── components │ ├── settings.dart │ └── suggest.dart ├── model │ └── app.dart ├── main.dart └── pages │ ├── splash.dart │ ├── home.dart │ └── search.dart ├── .idea ├── vcs.xml ├── encodings.xml ├── statistic.xml ├── dictionaries │ └── KagurazakaHanabi.xml ├── codeStyles │ └── Project.xml └── misc.xml ├── .metadata ├── .github └── no-response.yml ├── .gitignore ├── .gitattributes ├── pubspec.yaml └── analysis_options.yaml /android/.idea/.name: -------------------------------------------------------------------------------- 1 | _android -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 图鉴日图 - 精选壁纸推荐 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /res/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/res/ic_launcher.png -------------------------------------------------------------------------------- /res/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/res/placeholder.jpg -------------------------------------------------------------------------------- /res/ionicons-v5.5.1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/res/ionicons-v5.5.1.ttf -------------------------------------------------------------------------------- /res/placeholder-night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/res/placeholder-night.jpg -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /app/signingConfigs.gradle 4 | /captures/ 5 | /gradlew 6 | /gradlew.bat 7 | /local.properties 8 | GeneratedPluginRegistrant.java 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /lib/misc/config.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | class Config { 4 | static const int buildNumber = 200220; 5 | 6 | static const String version = '2.1.1'; 7 | } 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KagurazakaHanabi/dailypics/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/statistic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 图鉴日图 4 | 用作壁纸 5 | 6 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Daily Pics 4 | Use As Wallpaper 5 | 6 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/signingConfigs.gradle.example: -------------------------------------------------------------------------------- 1 | android { 2 | signingConfigs { 3 | release { 4 | storeFile file("") 5 | keyAlias "" 6 | storePassword "" 7 | keyPassword "" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 740fb2a8bb6d43a475ce76f7fe3e5fc10bbe24ff 8 | channel: master 9 | 10 | project_type: app 11 | 12 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/TodayExtension/TodayExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.dailypics.Today 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 7 5 | # Label requiring a response 6 | responseRequiredLabel: "waiting for customer response" 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: false 9 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void RegisterPlugins(flutter::PluginRegistry* registry) { 12 | UrlLauncherPluginRegisterWithRegistrar( 13 | registry->GetRegistrarForPlugin("UrlLauncherPlugin")); 14 | } 15 | -------------------------------------------------------------------------------- /.idea/dictionaries/KagurazakaHanabi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | cerasus 5 | chimon 6 | cupertino 7 | dailypics 8 | hanabi 9 | hitokoto 10 | ionicons 11 | kagurazaka 12 | recents 13 | tujian 14 | vsync 15 | yaerin 16 | 17 | 18 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.associated-domains 6 | 7 | applinks:dailypics.cn 8 | applinks:www.dailypics.cn 9 | 10 | com.apple.security.application-groups 11 | 12 | group.dailypics.Today 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | url_launcher_windows 7 | ) 8 | 9 | set(PLUGIN_BUNDLED_LIBRARIES) 10 | 11 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 12 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 13 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 14 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 15 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 16 | endforeach(plugin) 17 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "run_loop.cpp" 8 | "utils.cpp" 9 | "win32_window.cpp" 10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 11 | "Runner.rc" 12 | "runner.exe.manifest" 13 | ) 14 | apply_standard_settings(${BINARY_NAME}) 15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 18 | add_dependencies(${BINARY_NAME} flutter_assemble) 19 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = "1.3.50" 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.3' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = "../build" 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(":app") 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .idea/ 11 | .svn/ 12 | .vscode/ 13 | test/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | **/.idea/caches 20 | **/.idea/libraries 21 | **/.idea/misc.xml 22 | **/.idea/modules.xml 23 | **/.idea/workspace.xml 24 | **/.idea/navEditor.xml 25 | **/.idea/assetWizardSettings.xml 26 | 27 | # Flutter/Dart/Pub related 28 | **/doc/api/ 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .packages 33 | .packages.generated 34 | .pub-cache/ 35 | .pub/ 36 | /build/ 37 | 38 | # Web related 39 | lib/generated_plugin_registrant.dart 40 | 41 | # Exceptions to above rules. 42 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 43 | -------------------------------------------------------------------------------- /lib/utils/http.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:dailypics/misc/config.g.dart'; 16 | import 'package:dio/dio.dart'; 17 | 18 | Dio http = Dio(BaseOptions( 19 | headers: { 20 | 'user-agent': 'Dailypics/${Config.version} Version/${Config.buildNumber}', 21 | }, 22 | )); 23 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Always perform LF normalization on these files 5 | *.dart text 6 | *.gradle text 7 | *.html text 8 | *.java text 9 | *.json text 10 | *.md text 11 | *.py text 12 | *.sh text 13 | *.txt text 14 | *.xml text 15 | *.yaml text 16 | 17 | # Make sure that these Windows files always have CRLF line endings in checkout 18 | *.bat text eol=crlf 19 | *.ps1 text eol=crlf 20 | *.rc text eol=crlf 21 | *.sln text eol=crlf 22 | *.props text eol=crlf 23 | *.vcxproj text eol=crlf 24 | *.vcxproj.filters text eol=crlf 25 | 26 | # Never perform LF normalization on these files 27 | *.ico binary 28 | *.jar binary 29 | *.png binary 30 | *.zip binary 31 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dailypics 2 | description: A new Flutter application. 3 | version: 2.2.7+200325 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ">=2.6.0 <3.0.0" 8 | flutter: ">=2.0.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | flutter_localizations: 14 | sdk: flutter 15 | flutter_markdown: 16 | git: https://github.com/KagurazakaHanabi/flutter_markdown.git 17 | cached_network_image: ^2.0.0 18 | cupertino_icons: ^0.1.3 19 | dio: ^3.0.9 20 | flutter_staggered_grid_view: ^0.3.0 21 | image_picker: ^0.6.3+1 22 | json_annotation: ^3.0.1 23 | package_info: ^0.4.0+13 24 | palette_generator: ^0.2.0 25 | path_provider: ^1.5.1 26 | photo_view: ^0.9.1 27 | qr: ^1.2.0 28 | scoped_model: ^1.0.1 29 | shared_preferences: ^0.5.6 30 | uni_links: ^0.2.0 31 | url_launcher: ^5.4.1 32 | win32: ^2.0.5 33 | 34 | dev_dependencies: 35 | build_runner: ^1.7.4 36 | json_serializable: ^3.2.5 37 | 38 | flutter: 39 | uses-material-design: true 40 | assets: 41 | - res/ 42 | fonts: 43 | - family: Ionicons 44 | fonts: 45 | - asset: res/Ionicons-v5.5.1.ttf 46 | -------------------------------------------------------------------------------- /ios/TodayExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | 图鉴日图 Today 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | NSExtension 24 | 25 | NSExtensionMainStoryboard 26 | MainInterface 27 | NSExtensionPointIdentifier 28 | com.apple.widget-extension 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /windows/runner/run_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_RUN_LOOP_H_ 2 | #define RUNNER_RUN_LOOP_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // A runloop that will service events for Flutter instances as well 10 | // as native messages. 11 | class RunLoop { 12 | public: 13 | RunLoop(); 14 | ~RunLoop(); 15 | 16 | // Prevent copying 17 | RunLoop(RunLoop const&) = delete; 18 | RunLoop& operator=(RunLoop const&) = delete; 19 | 20 | // Runs the run loop until the application quits. 21 | void Run(); 22 | 23 | // Registers the given Flutter instance for event servicing. 24 | void RegisterFlutterInstance( 25 | flutter::FlutterEngine* flutter_instance); 26 | 27 | // Unregisters the given Flutter instance from event servicing. 28 | void UnregisterFlutterInstance( 29 | flutter::FlutterEngine* flutter_instance); 30 | 31 | private: 32 | using TimePoint = std::chrono::steady_clock::time_point; 33 | 34 | // Processes all currently pending messages for registered Flutter instances. 35 | TimePoint ProcessFlutterMessages(); 36 | 37 | std::set flutter_instances_; 38 | }; 39 | 40 | #endif // RUNNER_RUN_LOOP_H_ 41 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/yaerin/dailypics/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 KagurazakaHanabi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.yaerin.dailypics 18 | 19 | import androidx.annotation.NonNull 20 | import io.flutter.embedding.android.FlutterActivity 21 | import io.flutter.embedding.engine.FlutterEngine 22 | import io.flutter.plugins.GeneratedPluginRegistrant 23 | 24 | class MainActivity : FlutterActivity() { 25 | override fun configureFlutterEngine(@NonNull engine: FlutterEngine) { 26 | GeneratedPluginRegistrant.registerWith(engine) 27 | engine.plugins.add(PlatformPlugin()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "run_loop.h" 10 | #include "win32_window.h" 11 | 12 | // A window that does nothing but host a Flutter view. 13 | class FlutterWindow : public Win32Window { 14 | public: 15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a 16 | // Flutter view running |project|. 17 | explicit FlutterWindow(RunLoop* run_loop, 18 | const flutter::DartProject& project); 19 | virtual ~FlutterWindow(); 20 | 21 | protected: 22 | // Win32Window: 23 | bool OnCreate() override; 24 | void OnDestroy() override; 25 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 26 | LPARAM const lparam) noexcept override; 27 | 28 | private: 29 | // The run loop driving events for this window. 30 | RunLoop* run_loop_; 31 | 32 | // The project to run. 33 | flutter::DartProject project_; 34 | 35 | // The Flutter instance hosted by this window. 36 | std::unique_ptr flutter_controller_; 37 | }; 38 | 39 | #endif // RUNNER_FLUTTER_WINDOW_H_ 40 | -------------------------------------------------------------------------------- /lib/utils/windows.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 chimon89 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:ffi'; 16 | import 'dart:io'; 17 | 18 | import 'package:ffi/ffi.dart'; 19 | import 'package:win32/win32.dart'; 20 | 21 | class Windows { 22 | static void useAsWallpaper(File wallpaperFile) { 23 | final hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); 24 | if (FAILED(hr)) throw WindowsException(hr); 25 | 26 | final wallpaper = DesktopWallpaper.createInstance(); 27 | 28 | final pathPtr = TEXT(wallpaperFile.path); 29 | wallpaper.SetWallpaper(nullptr, pathPtr); 30 | if (FAILED(hr)) throw WindowsException(hr); 31 | 32 | calloc.free(pathPtr); 33 | calloc.free(wallpaper.ptr); 34 | 35 | CoUninitialize(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #pragma execution_character_set("utf-8") 5 | 6 | #include "flutter_window.h" 7 | #include "run_loop.h" 8 | #include "utils.h" 9 | 10 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 11 | _In_ wchar_t *command_line, _In_ int show_command) { 12 | // Attach to console when present (e.g., 'flutter run') or create a 13 | // new console when running with a debugger. 14 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 15 | CreateAndAttachConsole(); 16 | } 17 | 18 | // Initialize COM, so that it is available for use in the library and/or 19 | // plugins. 20 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 21 | 22 | RunLoop run_loop; 23 | 24 | flutter::DartProject project(L"data"); 25 | 26 | std::vector command_line_arguments = 27 | GetCommandLineArguments(); 28 | 29 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 30 | 31 | FlutterWindow window(&run_loop, project); 32 | Win32Window::Point origin(10, 10); 33 | Win32Window::Size size(480, 800); 34 | if (!window.CreateAndShow(L"Dailypics Desktop", origin, size)) { 35 | return EXIT_FAILURE; 36 | } 37 | window.SetQuitOnClose(true); 38 | 39 | run_loop.Run(); 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/yaerin/dailypics/PlatformPluginImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 KagurazakaHanabi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.yaerin.dailypics 18 | 19 | import io.flutter.plugin.common.MethodCall 20 | import io.flutter.plugin.common.MethodChannel.Result 21 | import java.io.IOException 22 | 23 | internal interface PlatformPluginImpl { 24 | companion object { 25 | const val PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".file_provider" 26 | } 27 | 28 | @Throws(IOException::class) 29 | fun share(path: String, result: Result?) 30 | 31 | fun useAsWallpaper(file: String, result: Result?) 32 | 33 | fun requestReview(result: Result?) 34 | 35 | fun isAlbumAuthorized(result: Result?) 36 | 37 | fun openAppSettings(result: Result?) 38 | 39 | @Throws(IOException::class) 40 | fun syncAlbum(call: MethodCall, result: Result) 41 | } 42 | -------------------------------------------------------------------------------- /lib/widget/error.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/cupertino.dart'; 16 | import 'package:flutter/material.dart' show Colors; 17 | 18 | class CustomErrorWidget extends StatelessWidget { 19 | const CustomErrorWidget( 20 | this.details, { 21 | Key key, 22 | }) : super(key: key); 23 | 24 | final FlutterErrorDetails details; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Container( 29 | color: CupertinoTheme.of(context).scaffoldBackgroundColor, 30 | alignment: Alignment.center, 31 | padding: const EdgeInsets.all(16), 32 | child: Text( 33 | details.exceptionAsString(), 34 | style: TextStyle( 35 | color: const CupertinoDynamicColor.withBrightness( 36 | color: Colors.black54, 37 | darkColor: Colors.white70, 38 | ).resolveFrom(context), 39 | ), 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/extension.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/widgets.dart'; 16 | 17 | extension ColorX on Color { 18 | static Color fromHexString(String source) { 19 | String s = source.toUpperCase().replaceAll('#', ''); 20 | if (s.length == 6) { 21 | s = 'FF$s'; 22 | } else if (s.length == 3) { 23 | s = 'FF${s[0] * 2}${s[1] * 2}${s[2] * 2}'; 24 | } 25 | return Color(int.parse(s, radix: 16)); 26 | } 27 | 28 | String get hexString { 29 | return '#${value.toRadixString(16).padLeft(8, '0')}'; 30 | } 31 | 32 | bool get isDark { 33 | // See https://github.com/FooStudio/tinycolor 34 | return (red * 299 + green * 587 + blue * 114) / 1000 < 128; 35 | } 36 | } 37 | 38 | extension StringX on String { 39 | bool get isUuid { 40 | if (isEmpty) return false; 41 | return RegExp( 42 | r'^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$', 43 | caseSensitive: false, 44 | ).hasMatch(this); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/components/settings.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:dailypics/pages/upload.dart'; 16 | import 'package:flutter/cupertino.dart'; 17 | 18 | class SettingsComponent extends StatefulWidget { 19 | @override 20 | _SettingsComponentState createState() => _SettingsComponentState(); 21 | } 22 | 23 | class _SettingsComponentState extends State { 24 | @override 25 | Widget build(BuildContext context) { 26 | return CupertinoPageScaffold( 27 | navigationBar: CupertinoNavigationBar( 28 | middle: const Text('更多'), 29 | trailing: CupertinoButton( 30 | padding: EdgeInsets.zero, 31 | child: const Text('投稿'), 32 | onPressed: () { 33 | Navigator.of(context, rootNavigator: true).push( 34 | CupertinoPageRoute(builder: (_) => UploadPage()), 35 | ); 36 | }, 37 | ), 38 | ), 39 | child: ListView( 40 | children: [], 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | linter: 2 | rules: 3 | - avoid_empty_else 4 | - avoid_init_to_null 5 | - avoid_relative_lib_imports 6 | - avoid_return_types_on_setters 7 | - avoid_shadowing_type_parameters 8 | - avoid_types_as_parameter_names 9 | - avoid_unused_constructor_parameters 10 | - avoid_print 11 | - always_declare_return_types 12 | - await_only_futures 13 | - camel_case_types 14 | - cancel_subscriptions 15 | - close_sinks 16 | - curly_braces_in_flow_control_structures 17 | - empty_catches 18 | - empty_constructor_bodies 19 | - empty_statements 20 | - file_names 21 | - hash_and_equals 22 | - implementation_imports 23 | - library_names 24 | - library_prefixes 25 | - literal_only_boolean_expressions 26 | - no_adjacent_strings_in_list 27 | - no_duplicate_case_values 28 | - non_constant_identifier_names 29 | - null_closures 30 | - prefer_const_constructors 31 | - prefer_const_declarations 32 | - prefer_contains 33 | - prefer_equal_for_default_values 34 | - prefer_is_empty 35 | - prefer_is_not_empty 36 | - prefer_is_not_operator 37 | - prefer_iterable_whereType 38 | - recursive_getters 39 | - slash_for_doc_comments 40 | - sort_constructors_first 41 | - sort_unnamed_constructors_first 42 | - type_init_formals 43 | - unnecessary_const 44 | - unnecessary_getters_setters 45 | - unnecessary_new 46 | - unnecessary_null_in_if_null_operators 47 | - unnecessary_parenthesis 48 | - unnecessary_statements 49 | - unnecessary_this 50 | - unrelated_type_equality_checks 51 | - use_rethrow_when_possible 52 | - valid_regexps 53 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/yaerin/dailypics/PlatformPluginApi24Impl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 KagurazakaHanabi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.yaerin.dailypics 18 | 19 | import android.content.Context 20 | import android.content.Intent 21 | import android.net.Uri 22 | import androidx.core.content.FileProvider 23 | import com.yaerin.dailypics.PlatformPluginImpl.Companion.PROVIDER_AUTHORITY 24 | import io.flutter.plugin.common.MethodChannel 25 | import java.io.File 26 | 27 | class PlatformPluginApi24Impl(context: Context) : PlatformPluginBaseImpl(context) { 28 | override fun share(path: String, result: MethodChannel.Result?) { 29 | val uri: Uri = FileProvider.getUriForFile(context, PROVIDER_AUTHORITY, File(path)) 30 | val intent = Intent(Intent.ACTION_SEND) 31 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 32 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 33 | intent.putExtra(Intent.EXTRA_STREAM, uri) 34 | context.startActivity(Intent.createChooser(intent, null)) 35 | result?.success(null) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/widget/buttons.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/cupertino.dart'; 16 | 17 | class Button extends StatelessWidget { 18 | const Button({ 19 | Key key, 20 | @required this.child, 21 | this.color, 22 | @required this.onPressed, 23 | }) : super(key: key); 24 | 25 | final Widget child; 26 | 27 | final Color color; 28 | 29 | final GestureTapCallback onPressed; 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return GestureDetector( 34 | onTap: onPressed, 35 | child: Container( 36 | margin: const EdgeInsets.all(8), 37 | padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 20), 38 | decoration: BoxDecoration( 39 | color: color ?? CupertinoTheme.of(context).primaryColor, 40 | borderRadius: BorderRadius.circular(46), 41 | ), 42 | child: DefaultTextStyle( 43 | child: child, 44 | style: TextStyle( 45 | fontSize: 14, 46 | color: CupertinoTheme.of(context).primaryContrastingColor, 47 | ), 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/yaerin/dailypics/UseAsWallpaperActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 KagurazakaHanabi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.yaerin.dailypics 18 | 19 | import android.app.Activity 20 | import android.app.WallpaperManager 21 | import android.content.Intent 22 | import android.net.Uri 23 | import android.os.Bundle 24 | import java.io.IOException 25 | 26 | class UseAsWallpaperActivity : Activity() { 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | val data: String? = intent?.extras?.getString(Intent.EXTRA_STREAM) 30 | if (data == null) { 31 | finish() 32 | } 33 | 34 | val uri = Uri.parse(data) 35 | try { 36 | val intent: Intent? = PlatformPluginBaseImpl.getCropAndSetWallpaperIntent(this, uri) 37 | startActivity(Intent.createChooser(intent, "设置为壁纸")) 38 | } catch (e: Exception) { 39 | val wm = WallpaperManager.getInstance(this) 40 | try { 41 | wm.setStream(contentResolver.openInputStream(uri)) 42 | } catch (ex: IOException) { 43 | } 44 | } 45 | finish() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/model/app.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:dailypics/misc/bean.dart'; 16 | import 'package:flutter/cupertino.dart'; 17 | import 'package:scoped_model/scoped_model.dart'; 18 | 19 | class AppModel extends Model { 20 | Map _types; 21 | Map get types => _types; 22 | set types(Map data) { 23 | _types = data; 24 | notifyListeners(); 25 | } 26 | 27 | List _today = []; 28 | List get today => _today; 29 | set today(List data) { 30 | _today = data; 31 | notifyListeners(); 32 | } 33 | 34 | List _recent = []; 35 | List get recent => _recent; 36 | set recent(List data) { 37 | _recent = data; 38 | notifyListeners(); 39 | } 40 | 41 | List _collections = []; 42 | List get collections => _collections; 43 | set collections(List data) { 44 | _collections = data; 45 | notifyListeners(); 46 | } 47 | 48 | @override 49 | void notifyListeners() { 50 | super.notifyListeners(); 51 | } 52 | 53 | static AppModel of(BuildContext context) { 54 | return ScopedModel.of(context, rebuildOnChange: true); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file("local.properties") 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader("UTF-8") { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty("flutter.sdk") 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty("flutter.versionCode") 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = "1" 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty("flutter.versionName") 20 | if (flutterVersionName == null) { 21 | flutterVersionName = "1.0" 22 | } 23 | 24 | apply plugin: "com.android.application" 25 | apply plugin: "kotlin-android" 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | apply from: "signingConfigs.gradle" 28 | 29 | android { 30 | compileSdkVersion 29 31 | 32 | sourceSets { 33 | main.java.srcDirs += "src/main/kotlin" 34 | } 35 | 36 | lintOptions { 37 | disable "InvalidPackage" 38 | } 39 | 40 | defaultConfig { 41 | applicationId "cn.dailypics.android" 42 | minSdkVersion 16 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | signingConfig signingConfigs.release 51 | } 52 | } 53 | } 54 | 55 | flutter { 56 | source "../.." 57 | } 58 | 59 | dependencies { 60 | implementation "androidx.legacy:legacy-support-v4:1.0.0" 61 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 62 | } 63 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /res/contributors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "avatar": "https://qlogo1.store.qq.com/qzone/1461397456/1461397456/100", 4 | "name": "Chimon", 5 | "position": "执行 / 开发", 6 | "url": "mailto:Chimon@Chimon.me" 7 | }, 8 | { 9 | "avatar": "https://qlogo1.store.qq.com/qzone/3314176520/3314176520/100", 10 | "name": "Createlite", 11 | "position": "执行", 12 | "url": "https://github.com/Createlite" 13 | }, 14 | { 15 | "avatar": "https://qlogo1.store.qq.com/qzone/2598136787/2598136787/100", 16 | "name": "Copyright³", 17 | "position": "后端开发", 18 | "url": "https://github.com/unstartdev" 19 | }, 20 | { 21 | "avatar": "https://qlogo1.store.qq.com/qzone/2433146453/2433146453/100", 22 | "name": "神楽坂花火", 23 | "position": "iOS 开发", 24 | "url": "https://github.com/KagurazakaHanabi" 25 | }, 26 | { 27 | "avatar": "https://qlogo1.store.qq.com/qzone/2331490629/2331490629/100", 28 | "name": "Gadgetry", 29 | "position": "Python 开发", 30 | "url": "https://github.com/gggxbbb" 31 | }, 32 | { 33 | "avatar": "https://qlogo1.store.qq.com/qzone/727526713/727526713/100", 34 | "name": "Kiyoi Bread", 35 | "position": "UI 设计", 36 | "url": null 37 | }, 38 | { 39 | "avatar": "https://dl.chimon.work/head/galentwww_20210414_094622.png", 40 | "name": "Galentwww", 41 | "position": "运营", 42 | "url": "mailto:galentwww@mail.dailypics.cn" 43 | }, 44 | { 45 | "avatar": "https://qlogo1.store.qq.com/qzone/2838925057/2838925057/100", 46 | "name": "Night Glow.", 47 | "position": "运营", 48 | "url": null 49 | }, 50 | { 51 | "avatar": "https://qlogo1.store.qq.com/qzone/2289582155/2289582155/100", 52 | "name": "Delsart", 53 | "position": "小程序", 54 | "url": null 55 | }, 56 | { 57 | "avatar": "https://qlogo1.store.qq.com/qzone/2598487949/2598487949/100", 58 | "name": "Recheast", 59 | "position": "公关", 60 | "url": null 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(RunLoop* run_loop, 8 | const flutter::DartProject& project) 9 | : run_loop_(run_loop), project_(project) {} 10 | 11 | FlutterWindow::~FlutterWindow() {} 12 | 13 | bool FlutterWindow::OnCreate() { 14 | if (!Win32Window::OnCreate()) { 15 | return false; 16 | } 17 | 18 | RECT frame = GetClientArea(); 19 | 20 | // The size here must match the window dimensions to avoid unnecessary surface 21 | // creation / destruction in the startup path. 22 | flutter_controller_ = std::make_unique( 23 | frame.right - frame.left, frame.bottom - frame.top, project_); 24 | // Ensure that basic setup of the controller was successful. 25 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 26 | return false; 27 | } 28 | RegisterPlugins(flutter_controller_->engine()); 29 | run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); 30 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 31 | return true; 32 | } 33 | 34 | void FlutterWindow::OnDestroy() { 35 | if (flutter_controller_) { 36 | run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); 37 | flutter_controller_ = nullptr; 38 | } 39 | 40 | Win32Window::OnDestroy(); 41 | } 42 | 43 | LRESULT 44 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 45 | WPARAM const wparam, 46 | LPARAM const lparam) noexcept { 47 | // Give Flutter, including plugins, an opporutunity to handle window messages. 48 | if (flutter_controller_) { 49 | std::optional result = 50 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 51 | lparam); 52 | if (result) { 53 | return *result; 54 | } 55 | } 56 | 57 | switch (message) { 58 | case WM_FONTCHANGE: 59 | flutter_controller_->engine()->ReloadSystemFonts(); 60 | break; 61 | } 62 | 63 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 64 | } 65 | -------------------------------------------------------------------------------- /lib/widget/qrcode.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/cupertino.dart'; 16 | import 'package:qr/qr.dart'; 17 | 18 | class QrCodeView extends StatefulWidget { 19 | const QrCodeView(this.data); 20 | 21 | final String data; 22 | 23 | @override 24 | _QrCodeViewState createState() => _QrCodeViewState(); 25 | } 26 | 27 | class _QrCodeViewState extends State { 28 | QrCode code; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | code = QrCode.fromData( 34 | data: widget.data, 35 | errorCorrectLevel: QrErrorCorrectLevel.L, 36 | ); 37 | code.make(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | double width = (code.moduleCount * 1.5 + 3).toDouble(); 43 | return CustomPaint( 44 | size: Size(width, width), 45 | painter: _QrCodePainter(code), 46 | ); 47 | } 48 | } 49 | 50 | class _QrCodePainter extends CustomPainter { 51 | _QrCodePainter(this.code); 52 | 53 | final QrCode code; 54 | 55 | @override 56 | void paint(Canvas canvas, Size size) { 57 | canvas.drawRect( 58 | Rect.fromLTWH(0, 0, size.width, size.height), 59 | Paint()..color = const Color(0xFFFFFFFF), 60 | ); 61 | canvas.translate(1.5, 1.5); 62 | for (int x = 0; x < code.moduleCount; x++) { 63 | for (int y = 0; y < code.moduleCount; y++) { 64 | if (code.isDark(y, x)) { 65 | canvas.drawRect( 66 | Rect.fromLTWH(x * 1.5, y * 1.5, 1.5, 1.5), 67 | Paint()..color = const Color(0xFF000000), 68 | ); 69 | } 70 | } 71 | } 72 | } 73 | 74 | @override 75 | bool shouldRepaint(CustomPainter oldDelegate) => true; 76 | } 77 | -------------------------------------------------------------------------------- /lib/widget/optimized_image.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:cached_network_image/cached_network_image.dart'; 16 | import 'package:flutter/cupertino.dart'; 17 | 18 | class OptimizedImage extends StatelessWidget { 19 | OptimizedImage( 20 | this.imageUrl, { 21 | Key key, 22 | this.fit = BoxFit.cover, 23 | this.borderRadius = BorderRadius.zero, 24 | this.heroTag, 25 | }) : assert(imageUrl != null), 26 | assert(borderRadius != null), 27 | super(key: key); 28 | 29 | final String imageUrl; 30 | 31 | final BoxFit fit; 32 | 33 | final BorderRadius borderRadius; 34 | 35 | final Object heroTag; 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | bool isDark = CupertinoTheme.of(context).brightness == Brightness.dark; 40 | Widget result = ClipRRect( 41 | borderRadius: borderRadius, 42 | child: CachedNetworkImage( 43 | imageUrl: imageUrl, 44 | fit: fit, 45 | placeholderFadeInDuration: Duration.zero, 46 | fadeInDuration: const Duration(milliseconds: 700), 47 | fadeInCurve: Curves.easeIn, 48 | fadeOutDuration: const Duration(milliseconds: 300), 49 | fadeOutCurve: Curves.easeOut, 50 | placeholder: (_, __) { 51 | return Container( 52 | alignment: Alignment.center, 53 | color: isDark ? const Color(0xFF1F1F1F) : const Color(0xFFE0E0E0), 54 | child: Image.asset('res/placeholder${isDark ? '-night' : ''}.jpg'), 55 | ); 56 | }, 57 | ), 58 | ); 59 | if (heroTag != null) { 60 | result = Hero( 61 | tag: heroTag, 62 | transitionOnUserGestures: true, 63 | child: result, 64 | ); 65 | } 66 | return result; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | 图鉴日图 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | dailypics 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleURLName 27 | $(PRODUCT_BUNDLE_IDENTIFIER) 28 | CFBundleURLSchemes 29 | 30 | dailypics 31 | 32 | 33 | 34 | CFBundleURLName 35 | $(PRODUCT_BUNDLE_IDENTIFIER) 36 | CFBundleURLSchemes 37 | 38 | https 39 | 40 | 41 | 42 | CFBundleVersion 43 | $(FLUTTER_BUILD_NUMBER) 44 | ITSAppUsesNonExemptEncryption 45 | 46 | LSRequiresIPhoneOS 47 | 48 | NSPhotoLibraryUsageDescription 49 | 需要访问相册以保存图片 50 | UILaunchStoryboardName 51 | LaunchScreen 52 | UIMainStoryboardFile 53 | Main 54 | UISupportedInterfaceOrientations 55 | 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | UIInterfaceOrientationPortrait 59 | 60 | UISupportedInterfaceOrientations~ipad 61 | 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationPortraitUpsideDown 64 | UIInterfaceOrientationLandscapeRight 65 | UIInterfaceOrientationPortrait 66 | 67 | UIViewControllerBasedStatusBarAppearance 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:dailypics/model/app.dart'; 16 | import 'package:dailypics/pages/splash.dart'; 17 | import 'package:dailypics/widget/error.dart'; 18 | import 'package:flutter/cupertino.dart'; 19 | import 'package:flutter_localizations/flutter_localizations.dart'; 20 | import 'package:scoped_model/scoped_model.dart'; 21 | 22 | void main() async { 23 | ErrorWidget.builder = (FlutterErrorDetails details) { 24 | return CustomErrorWidget(details); 25 | }; 26 | runApp(TujianApp()); 27 | } 28 | 29 | class TujianApp extends StatelessWidget { 30 | final AppModel model = AppModel(); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return ScopedModel( 35 | model: model, 36 | child: CupertinoApp( 37 | title: '图鉴日图', 38 | home: SplashPage(), 39 | debugShowCheckedModeBanner: false, 40 | builder: (BuildContext context, Widget child) { 41 | CupertinoThemeData theme = CupertinoTheme.of(context).copyWith( 42 | brightness: MediaQuery.platformBrightnessOf(context), 43 | ); 44 | CupertinoTextThemeData textTheme = theme.textTheme; 45 | return CupertinoTheme( 46 | data: theme, 47 | child: DefaultTextStyle( 48 | style: textTheme.textStyle, 49 | child: child, 50 | ), 51 | ); 52 | }, 53 | supportedLocales: const [ 54 | Locale('zh'), 55 | Locale('ja'), 56 | Locale('en'), 57 | ], 58 | localizationsDelegates: [ 59 | GlobalCupertinoLocalizations.delegate, 60 | GlobalMaterialLocalizations.delegate, 61 | GlobalWidgetsLocalizations.delegate, 62 | ], 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /windows/runner/run_loop.cpp: -------------------------------------------------------------------------------- 1 | #include "run_loop.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | RunLoop::RunLoop() {} 8 | 9 | RunLoop::~RunLoop() {} 10 | 11 | void RunLoop::Run() { 12 | bool keep_running = true; 13 | TimePoint next_flutter_event_time = TimePoint::clock::now(); 14 | while (keep_running) { 15 | std::chrono::nanoseconds wait_duration = 16 | std::max(std::chrono::nanoseconds(0), 17 | next_flutter_event_time - TimePoint::clock::now()); 18 | ::MsgWaitForMultipleObjects( 19 | 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), 20 | QS_ALLINPUT); 21 | bool processed_events = false; 22 | MSG message; 23 | // All pending Windows messages must be processed; MsgWaitForMultipleObjects 24 | // won't return again for items left in the queue after PeekMessage. 25 | while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { 26 | processed_events = true; 27 | if (message.message == WM_QUIT) { 28 | keep_running = false; 29 | break; 30 | } 31 | ::TranslateMessage(&message); 32 | ::DispatchMessage(&message); 33 | // Allow Flutter to process messages each time a Windows message is 34 | // processed, to prevent starvation. 35 | next_flutter_event_time = 36 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 37 | } 38 | // If the PeekMessage loop didn't run, process Flutter messages. 39 | if (!processed_events) { 40 | next_flutter_event_time = 41 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 42 | } 43 | } 44 | } 45 | 46 | void RunLoop::RegisterFlutterInstance( 47 | flutter::FlutterEngine* flutter_instance) { 48 | flutter_instances_.insert(flutter_instance); 49 | } 50 | 51 | void RunLoop::UnregisterFlutterInstance( 52 | flutter::FlutterEngine* flutter_instance) { 53 | flutter_instances_.erase(flutter_instance); 54 | } 55 | 56 | RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { 57 | TimePoint next_event_time = TimePoint::max(); 58 | for (auto instance : flutter_instances_) { 59 | std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); 60 | if (wait_duration != std::chrono::nanoseconds::max()) { 61 | next_event_time = 62 | std::min(next_event_time, TimePoint::clock::now() + wait_duration); 63 | } 64 | } 65 | return next_event_time; 66 | } 67 | -------------------------------------------------------------------------------- /lib/components/suggest.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:dailypics/misc/bean.dart'; 16 | import 'package:dailypics/utils/api.dart'; 17 | import 'package:dailypics/widget/slivers.dart'; 18 | import 'package:flutter/cupertino.dart'; 19 | 20 | class SuggestComponent extends StatefulWidget { 21 | @override 22 | _SuggestComponentState createState() => _SuggestComponentState(); 23 | } 24 | 25 | class _SuggestComponentState extends State with AutomaticKeepAliveClientMixin { 26 | ScrollController controller = ScrollController(); 27 | 28 | List data; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | _fetchData(); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | super.build(context); 39 | if (data == null) { 40 | return const Center( 41 | child: CupertinoActivityIndicator(), 42 | ); 43 | } else { 44 | return CupertinoScrollbar( 45 | controller: controller, 46 | child: CustomScrollView( 47 | controller: controller, 48 | physics: const AlwaysScrollableScrollPhysics(), 49 | slivers: [ 50 | const CupertinoSliverNavigationBar( 51 | largeTitle: Text('推荐'), 52 | ), 53 | CupertinoSliverRefreshControl(onRefresh: _fetchData), 54 | SliverSafeArea( 55 | top: false, 56 | sliver: SliverImageCardList( 57 | tagBuilder: (i) => '$i-${data[i].id}', 58 | data: data, 59 | ), 60 | ), 61 | ], 62 | ), 63 | ); 64 | } 65 | } 66 | 67 | Future _fetchData() async { 68 | data = await TujianApi.getRandom(count: 20); 69 | setState(() {}); 70 | } 71 | 72 | @override 73 | void dispose() { 74 | controller.dispose(); 75 | super.dispose(); 76 | } 77 | 78 | @override 79 | bool get wantKeepAlive => data != null; 80 | } 81 | -------------------------------------------------------------------------------- /lib/widget/hightlight.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/cupertino.dart'; 16 | import 'package:flutter/gestures.dart'; 17 | 18 | typedef RecognizerBuilder = GestureRecognizer Function(String match); 19 | 20 | class HighlightedText { 21 | const HighlightedText({this.recognizer, this.style}); 22 | 23 | final RecognizerBuilder recognizer; 24 | 25 | final TextStyle style; 26 | } 27 | 28 | class _LinkSpec { 29 | _LinkSpec(this.pattern, this.origin, this.text, this.start, this.end); 30 | 31 | final HighlightedText pattern; 32 | 33 | final RegExpMatch origin; 34 | 35 | final String text; 36 | 37 | final int start; 38 | 39 | final int end; 40 | 41 | @override 42 | String toString() { 43 | return '_LinkSpec { text: $text, start: $start, end: $end }'; 44 | } 45 | } 46 | 47 | class Highlight extends StatelessWidget { 48 | const Highlight({ 49 | Key key, 50 | @required this.text, 51 | @required this.defaultStyle, 52 | @required this.style, 53 | @required this.patterns, 54 | }) : super(key: key); 55 | 56 | final String text; 57 | 58 | final TextStyle defaultStyle; 59 | 60 | final TextStyle style; 61 | 62 | final Map patterns; 63 | 64 | @override 65 | Widget build(BuildContext context) { 66 | List<_LinkSpec> links = []; 67 | patterns.forEach((Pattern p, HighlightedText h) { 68 | p.allMatches(text).forEach((Match e) { 69 | int start = e.start, end = e.end; 70 | links.add(_LinkSpec(h, e, e.input.substring(start, end), start, end)); 71 | }); 72 | }); 73 | links.sort((a, b) => a.start - b.start); 74 | return Text.rich( 75 | TextSpan( 76 | text: links.isEmpty ? text : text.substring(0, links[0].start), 77 | style: defaultStyle, 78 | children: links.map((_LinkSpec e) { 79 | HighlightedText p = e.pattern; 80 | int i = links.indexOf(e); 81 | bool last = i == links.length - 1; 82 | return TextSpan( 83 | text: e.text, 84 | recognizer: p.recognizer != null ? p.recognizer(e.text) : null, 85 | style: p.style ?? style, 86 | children: [ 87 | TextSpan( 88 | style: defaultStyle, 89 | text: text.substring(e.end, last ? null : links[i + 1].start), 90 | ) 91 | ], 92 | ); 93 | }).toList(), 94 | ), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/widget/adaptive_scaffold.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:ui'; 16 | 17 | import 'package:dailypics/utils/utils.dart'; 18 | import 'package:flutter/cupertino.dart'; 19 | 20 | class AdaptiveScaffold extends StatelessWidget { 21 | const AdaptiveScaffold({ 22 | Key key, 23 | this.navigationBar, 24 | this.backgroundColor, 25 | this.resizeToAvoidBottomInset = true, 26 | this.padding = const EdgeInsets.fromLTRB(80, 48, 80, 0), 27 | @required this.child, 28 | }) : assert(child != null), 29 | assert(resizeToAvoidBottomInset != null), 30 | super(key: key); 31 | 32 | final ObstructingPreferredSizeWidget navigationBar; 33 | 34 | final Widget child; 35 | 36 | final Color backgroundColor; 37 | 38 | final bool resizeToAvoidBottomInset; 39 | 40 | final EdgeInsets padding; 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | Widget result = CupertinoPageScaffold( 45 | child: child, 46 | navigationBar: navigationBar, 47 | backgroundColor: backgroundColor, 48 | resizeToAvoidBottomInset: resizeToAvoidBottomInset, 49 | ); 50 | if (SystemUtils.isIPad(context)) { 51 | Size size = MediaQuery.of(context).size; 52 | double left = padding.left, right = padding.right; 53 | if (!SystemUtils.isPortrait(context)) { 54 | left = (size.width - size.height) / 2; 55 | right = (size.width - size.height) / 2; 56 | } 57 | if (SystemUtils.isIPad(context, true)) { 58 | left += left / 3; 59 | right += right / 3; 60 | } 61 | result = BackdropFilter( 62 | filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), 63 | child: Stack( 64 | children: [ 65 | GestureDetector( 66 | behavior: HitTestBehavior.opaque, 67 | onTap: () => Navigator.of(context).pop(), 68 | child: Container(), 69 | ), 70 | Padding( 71 | padding: EdgeInsets.only( 72 | left: left, 73 | top: padding.top, 74 | right: right, 75 | bottom: padding.bottom, 76 | ), 77 | child: ClipRRect( 78 | borderRadius: const BorderRadius.vertical( 79 | top: Radius.circular(16), 80 | ), 81 | child: result, 82 | ), 83 | ) 84 | ], 85 | ), 86 | ); 87 | } 88 | return result; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/TodayExtension/TodayViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 KagurazakaHanabi 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Alamofire 18 | import AlamofireImage 19 | import UIKit 20 | import NotificationCenter 21 | 22 | class TodayViewController: UIViewController, NCWidgetProviding { 23 | @IBOutlet weak var imageView: UIImageView! 24 | 25 | var current: Picture? 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | self.extensionContext?.widgetLargestAvailableDisplayMode = .expanded 30 | } 31 | 32 | func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) { 33 | AF.request("https://v2.api.dailypics.cn/today").responseJSON { response in 34 | switch response.result { 35 | case .success: 36 | if let data = response.value as? Array { 37 | let random = data[Int.random(in: 0.., with event: UIEvent?) { 62 | if self.current != nil { 63 | let uri = URL.init(string: "dailypics://p/\(current!.id)") 64 | self.extensionContext?.open(uri!, completionHandler: nil) 65 | } 66 | } 67 | } 68 | 69 | struct Picture { 70 | let id: String 71 | let url: String 72 | 73 | init?(source: [String: Any]) { 74 | guard let id = source["PID"] as? String, 75 | let path = source["nativePath"] as? String 76 | else { 77 | return nil 78 | } 79 | 80 | self.id = id 81 | self.url = "https://s1.images.dailypics.cn/\(path)!w1080_jpg" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 71 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 39 | 40 | -------------------------------------------------------------------------------- /lib/widget/animated_transform.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/cupertino.dart'; 16 | 17 | class AnimatedTransform extends ImplicitlyAnimatedWidget { 18 | const AnimatedTransform({ 19 | Key key, 20 | this.child, 21 | @required this.transform, 22 | this.alignment = Alignment.center, 23 | this.transformHitTests = true, 24 | Curve curve = Curves.linear, 25 | @required Duration duration, 26 | }) : assert(transform != null), 27 | super(key: key, curve: curve, duration: duration); 28 | 29 | AnimatedTransform.rotate({ 30 | Key key, 31 | this.child, 32 | @required double angle, 33 | this.alignment = Alignment.center, 34 | this.transformHitTests = true, 35 | Curve curve = Curves.linear, 36 | @required Duration duration, 37 | }) : transform = Matrix4.rotationZ(angle), 38 | super(key: key, curve: curve, duration: duration); 39 | 40 | AnimatedTransform.translate({ 41 | Key key, 42 | this.child, 43 | @required Offset offset, 44 | this.transformHitTests = true, 45 | Curve curve = Curves.linear, 46 | @required Duration duration, 47 | }) : transform = Matrix4.translationValues(offset.dx, offset.dy, 0.0), 48 | alignment = null, 49 | super(key: key, curve: curve, duration: duration); 50 | 51 | AnimatedTransform.scale({ 52 | Key key, 53 | this.child, 54 | @required double scale, 55 | this.alignment = Alignment.center, 56 | this.transformHitTests = true, 57 | Curve curve = Curves.linear, 58 | @required Duration duration, 59 | }) : transform = Matrix4.diagonal3Values(scale, scale, 1), 60 | super(key: key, curve: curve, duration: duration); 61 | 62 | final Widget child; 63 | 64 | final Matrix4 transform; 65 | 66 | final AlignmentGeometry alignment; 67 | 68 | final bool transformHitTests; 69 | 70 | @override 71 | _AnimatedTransformState createState() => _AnimatedTransformState(); 72 | } 73 | 74 | class _AnimatedTransformState extends AnimatedWidgetBaseState { 75 | AlignmentGeometryTween _alignment; 76 | Matrix4Tween _transform; 77 | 78 | @override 79 | void forEachTween(TweenVisitor visitor) { 80 | _alignment = visitor(_alignment, widget.alignment, (dynamic value) => AlignmentGeometryTween(begin: value)); 81 | _transform = visitor(_transform, widget.transform, (dynamic value) => Matrix4Tween(begin: value)); 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | return Transform( 87 | child: widget.child, 88 | transform: _transform.evaluate(animation), 89 | alignment: _alignment?.evaluate(animation), 90 | transformHitTests: widget.transformHitTests, 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/widget/slivers.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:dailypics/misc/bean.dart'; 16 | import 'package:dailypics/utils/utils.dart'; 17 | import 'package:flutter/cupertino.dart'; 18 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 19 | 20 | import 'photo_card.dart'; 21 | 22 | class SliverImageCardList extends StatelessWidget { 23 | const SliverImageCardList({ 24 | Key key, 25 | this.header, 26 | this.footer, 27 | @required this.data, 28 | this.tagBuilder, 29 | this.adaptiveTablet = false, 30 | }) : assert(data != null), 31 | assert(adaptiveTablet != null), 32 | super(key: key); 33 | 34 | final Widget header; 35 | 36 | final Widget footer; 37 | 38 | final List data; 39 | 40 | final String Function(int index) tagBuilder; 41 | 42 | final bool adaptiveTablet; 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | bool iPad = SystemUtils.isIPad(context, true); 47 | bool portrait = SystemUtils.isPortrait(context); 48 | int cnt = SystemUtils.isIPad(context) ? iPad && !portrait && adaptiveTablet ? 6 : 2 : 1; 49 | return SliverPadding( 50 | padding: SystemUtils.isIPad(context, true) 51 | ? const EdgeInsets.fromLTRB(12, 12, 12, 0) 52 | : const EdgeInsets.only(left: 4, top: 15, right: 4), 53 | sliver: SliverStaggeredGrid.countBuilder( 54 | crossAxisCount: cnt, 55 | itemCount: data.length + 2, 56 | staggeredTileBuilder: (i) { 57 | if (i == 0 || i == data.length + 1) { 58 | return StaggeredTile.fit(cnt); 59 | } else if (iPad && !portrait && adaptiveTablet) { 60 | if (_needWiden(i)) { 61 | return const StaggeredTile.count(4, 3); 62 | } else { 63 | return const StaggeredTile.count(2, 3); 64 | } 65 | } else { 66 | return const StaggeredTile.fit(1); 67 | } 68 | }, 69 | itemBuilder: (_, int i) { 70 | if (i == 0) { 71 | return header ?? Container(); 72 | } else if (i == data.length + 1) { 73 | return footer ?? Container(); 74 | } else if (iPad && !portrait && adaptiveTablet) { 75 | return PhotoCard( 76 | data[i - 1], 77 | tagBuilder != null ? tagBuilder(i - 1) : '#$i', 78 | aspectRatio: _needWiden(i) ? 4 / 3 : 2 / 3.15, 79 | ); 80 | } else { 81 | return PhotoCard( 82 | data[i - 1], 83 | tagBuilder != null ? tagBuilder(i - 1) : '#$i', 84 | ); 85 | } 86 | }, 87 | ), 88 | ); 89 | } 90 | 91 | bool _needWiden(int index) { 92 | return index % 4 == 1 || index % 4 == 0; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /ios/TodayExtension/Base.lproj/MainInterface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "cn.dailypics" "\0" 93 | VALUE "FileDescription", "Dailypics Desktop" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "dailypics" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2021 图鉴事务所 All rights reserved." "\0" 97 | VALUE "OriginalFilename", "dailypics.exe" "\0" 98 | VALUE "ProductName", "dailypics" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /lib/widget/search.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/cupertino.dart'; 16 | 17 | const CupertinoDynamicColor _kClearColor = CupertinoDynamicColor.withBrightness( 18 | color: Color(0xFF636366), 19 | darkColor: Color(0xFFAEAEB2), 20 | ); 21 | 22 | class CupertinoSearchBar extends StatelessWidget { 23 | CupertinoSearchBar({ 24 | Key key, 25 | this.controller, 26 | this.padding = const EdgeInsets.fromLTRB(16, 10, 16, 10), 27 | this.readOnly = false, 28 | this.autofocus = false, 29 | this.showCancelButton = false, 30 | this.onChanged, 31 | this.onSubmitted, 32 | this.onTap, 33 | }) : super(key: key); 34 | 35 | final TextEditingController controller; 36 | 37 | final EdgeInsets padding; 38 | 39 | final bool readOnly; 40 | 41 | final bool autofocus; 42 | 43 | final bool showCancelButton; 44 | 45 | final ValueChanged onChanged; 46 | 47 | final ValueChanged onSubmitted; 48 | 49 | final GestureTapCallback onTap; 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return Container( 54 | color: CupertinoTheme.of(context).barBackgroundColor, 55 | padding: padding, 56 | child: Row( 57 | children: [ 58 | Expanded( 59 | child: Hero( 60 | tag: 'CupertinoSearchBar', 61 | child: CupertinoTextField( 62 | controller: controller, 63 | readOnly: readOnly, 64 | autofocus: autofocus, 65 | onChanged: onChanged, 66 | onSubmitted: onSubmitted, 67 | onTap: onTap, 68 | clearButtonMode: OverlayVisibilityMode.editing, 69 | textInputAction: TextInputAction.search, 70 | keyboardAppearance: CupertinoTheme.of(context).brightness, 71 | placeholder: '搜索', 72 | decoration: BoxDecoration( 73 | borderRadius: BorderRadius.circular(10), 74 | color: CupertinoColors.tertiarySystemFill, 75 | ), 76 | prefix: Padding( 77 | padding: const EdgeInsets.only(left: 8), 78 | child: Icon( 79 | CupertinoIcons.search, 80 | color: _kClearColor.resolveFrom(context), 81 | size: 22, 82 | ), 83 | ), 84 | ), 85 | ), 86 | ), 87 | Hero( 88 | tag: 'CupertinoSearchBar.showCancelButton', 89 | child: Offstage( 90 | offstage: !showCancelButton, 91 | child: CupertinoButton( 92 | padding: const EdgeInsets.only(left: 16), 93 | child: const Text('取消', softWrap: false), 94 | onPressed: () => Navigator.of(context).pop(), 95 | ), 96 | ), 97 | ), 98 | ], 99 | ), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | xmlns:android 11 | 12 | ^$ 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | xmlns:.* 22 | 23 | ^$ 24 | 25 | 26 | BY_NAME 27 | 28 |
29 |
30 | 31 | 32 | 33 | .*:id 34 | 35 | http://schemas.android.com/apk/res/android 36 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | .*:name 45 | 46 | http://schemas.android.com/apk/res/android 47 | 48 | 49 | 50 |
51 |
52 | 53 | 54 | 55 | name 56 | 57 | ^$ 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 | style 67 | 68 | ^$ 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | 77 | .* 78 | 79 | ^$ 80 | 81 | 82 | BY_NAME 83 | 84 |
85 |
86 | 87 | 88 | 89 | .* 90 | 91 | http://schemas.android.com/apk/res/android 92 | 93 | 94 | ANDROID_ATTRIBUTE_ORDER 95 | 96 |
97 |
98 | 99 | 100 | 101 | .* 102 | 103 | .* 104 | 105 | 106 | BY_NAME 107 | 108 |
109 |
110 |
111 |
112 |
113 |
-------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(dailypics LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "dailypics") 5 | 6 | cmake_policy(SET CMP0063 NEW) 7 | 8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 9 | 10 | # Configure build options. 11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 12 | if(IS_MULTICONFIG) 13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 14 | CACHE STRING "" FORCE) 15 | else() 16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 17 | set(CMAKE_BUILD_TYPE "Debug" CACHE 18 | STRING "Flutter build mode" FORCE) 19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 20 | "Debug" "Profile" "Release") 21 | endif() 22 | endif() 23 | 24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 28 | 29 | # Use Unicode for all projects. 30 | add_definitions(-DUNICODE -D_UNICODE) 31 | 32 | # Compilation settings that should be applied to most targets. 33 | function(APPLY_STANDARD_SETTINGS TARGET) 34 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 36 | target_compile_options(${TARGET} PRIVATE /EHsc) 37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 39 | endfunction() 40 | 41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 42 | 43 | # Flutter library and tool build rules. 44 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 45 | 46 | # Application build 47 | add_subdirectory("runner") 48 | 49 | # Generated plugin build rules, which manage building the plugins and adding 50 | # them to the application. 51 | include(flutter/generated_plugins.cmake) 52 | 53 | 54 | # === Installation === 55 | # Support files are copied into place next to the executable, so that it can 56 | # run in place. This is done instead of making a separate bundle (as on Linux) 57 | # so that building and running from within Visual Studio will work. 58 | set(BUILD_BUNDLE_DIR "$") 59 | # Make the "install" step default, as it's required to run. 60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 63 | endif() 64 | 65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 67 | 68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 69 | COMPONENT Runtime) 70 | 71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 75 | COMPONENT Runtime) 76 | 77 | if(PLUGIN_BUNDLED_LIBRARIES) 78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 80 | COMPONENT Runtime) 81 | endif() 82 | 83 | # Fully re-copy the assets directory on each build to avoid having stale files 84 | # from a previous install. 85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 86 | install(CODE " 87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 88 | " COMPONENT Runtime) 89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 91 | 92 | # Install the AOT library on non-Debug builds only. 93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 94 | CONFIGURATIONS Profile;Release 95 | COMPONENT Runtime) 96 | -------------------------------------------------------------------------------- /lib/widget/toast.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | import 'dart:collection'; 17 | 18 | import 'package:flutter/material.dart'; 19 | 20 | class Toast extends StatelessWidget { 21 | Toast( 22 | this.context, 23 | this.text, { 24 | this.duration = length_short, 25 | this.alignment = const Alignment(0, 0.75), 26 | }) : assert(context != null), 27 | assert(duration != null), 28 | assert(alignment != null); 29 | 30 | static const Duration length_short = Duration(seconds: 4); 31 | 32 | static const Duration length_long = Duration(seconds: 7); 33 | 34 | static const Duration _animationDuration = Duration(milliseconds: 500); 35 | 36 | static Queue _toasts = Queue(); 37 | 38 | static Timer _toastTimer; 39 | 40 | final BuildContext context; 41 | 42 | final String text; 43 | 44 | final Duration duration; 45 | 46 | final Alignment alignment; 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | TextStyle textStyle = const TextStyle( 51 | color: Color(0xDE000000), 52 | fontSize: 14, 53 | ); 54 | return Container( 55 | width: MediaQuery.of(context).size.width, 56 | height: MediaQuery.of(context).size.height, 57 | padding: MediaQuery.of(context).padding, 58 | alignment: alignment, 59 | child: DefaultTextStyle( 60 | style: Theme.of(context).textTheme.subtitle1.merge(textStyle), 61 | child: AnimatedOpacity( 62 | opacity: _toasts.contains(this) ? 1 : 0, 63 | duration: const Duration(milliseconds: 500), 64 | curve: Curves.easeInOut, 65 | child: Container( 66 | margin: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), 67 | padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), 68 | decoration: BoxDecoration( 69 | borderRadius: BorderRadius.circular(22), 70 | color: const Color(0xE6EEEEEE), 71 | ), 72 | child: Text(text ?? 'null'), 73 | ), 74 | ), 75 | ), 76 | ); 77 | } 78 | 79 | void show() async { 80 | if (_toasts.isNotEmpty) { 81 | _toasts.addLast(this); 82 | return; 83 | } 84 | _toasts.addLast(this); 85 | OverlayState overlay = Overlay.of(context); 86 | OverlayEntry entry = OverlayEntry(builder: (_) => this); 87 | overlay.insert(entry); 88 | entry.markNeedsBuild(); 89 | await Future.delayed(_animationDuration); 90 | _toastTimer = Timer.periodic(duration, (_) async { 91 | _toasts.removeFirst(); 92 | entry.markNeedsBuild(); 93 | await Future.delayed(_animationDuration); 94 | entry.remove(); 95 | if (_toasts.isNotEmpty) { 96 | entry = OverlayEntry(builder: (_) => _toasts.first); 97 | overlay.insert(entry); 98 | entry.markNeedsBuild(); 99 | await Future.delayed(_animationDuration); 100 | } else { 101 | _toastTimer.cancel(); 102 | _toastTimer = null; 103 | } 104 | }); 105 | } 106 | 107 | void cancel() => _toasts.remove(this); 108 | } 109 | -------------------------------------------------------------------------------- /android/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '10.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | target 'TodayExtension' do 82 | use_frameworks! 83 | 84 | pod 'Alamofire', '~> 5.0' 85 | pod 'AlamofireImage', '~> 4.0' 86 | end 87 | 88 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 89 | install! 'cocoapods', :disable_input_output_paths => true 90 | 91 | post_install do |installer| 92 | installer.pods_project.targets.each do |target| 93 | target.build_configurations.each do |config| 94 | config.build_settings['ENABLE_BITCODE'] = 'NO' 95 | end 96 | end 97 | end 98 | 99 | -------------------------------------------------------------------------------- /lib/pages/splash.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | import 'dart:io'; 17 | import 'dart:ui'; 18 | 19 | import 'package:dailypics/misc/bean.dart'; 20 | import 'package:dailypics/model/app.dart'; 21 | import 'package:dailypics/pages/home.dart'; 22 | import 'package:dailypics/utils/api.dart'; 23 | import 'package:dailypics/utils/utils.dart'; 24 | import 'package:flutter/cupertino.dart'; 25 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 26 | 27 | class SplashPage extends StatefulWidget { 28 | @override 29 | _SplashPageState createState() => _SplashPageState(); 30 | } 31 | 32 | class _SplashPageState extends State { 33 | ImageFrameBuilder _frameBuilder = (_, Widget child, int frame, bool loaded) { 34 | if (loaded) return child; 35 | return AnimatedOpacity( 36 | child: child, 37 | curve: Curves.easeOut, 38 | opacity: frame == null ? 0 : 1, 39 | duration: const Duration(milliseconds: 300), 40 | ); 41 | }; 42 | 43 | File _file; 44 | Timer _timer; 45 | 46 | @override 47 | void initState() { 48 | super.initState(); 49 | _initialize(); 50 | } 51 | 52 | Future _initialize() async { 53 | _timer = Timer(const Duration(seconds: 3), () => HomePage.push(context)); 54 | List results = await Future.wait([ 55 | TujianApi.getTypes(), 56 | Settings.initialize(), 57 | _fetchOrShowSplash(), 58 | ]); 59 | AppModel.of(context).types = results[0]; 60 | if (_file == null) { 61 | _timer.cancel(); 62 | HomePage.push(context); 63 | } 64 | } 65 | 66 | Future _fetchOrShowSplash() async { 67 | Splash splash = await TujianApi.getSplash(); 68 | String url = splash.imageUrl; 69 | DateTime now = DateTime.now(); 70 | if (now.isAfter(splash.effectiveAt) && now.isBefore(splash.expiresAt)) { 71 | DefaultCacheManager manager = DefaultCacheManager(); 72 | FileInfo info = await manager.getFileFromCache(url); 73 | if (info != null) { 74 | return setState(() => _file = info.file); 75 | } else { 76 | manager.downloadFile(url); 77 | } 78 | } 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | if (_file == null) { 84 | return CupertinoPageScaffold( 85 | child: Container(), 86 | ); 87 | } 88 | return CupertinoPageScaffold( 89 | child: Stack( 90 | fit: StackFit.expand, 91 | alignment: Alignment.center, 92 | children: [ 93 | Image.file( 94 | _file, 95 | fit: BoxFit.cover, 96 | frameBuilder: _frameBuilder, 97 | ), 98 | if (SystemUtils.isIPad(context)) 99 | ClipRect( 100 | child: BackdropFilter( 101 | filter: ImageFilter.blur(sigmaX: 32, sigmaY: 32), 102 | child: Image.file( 103 | _file, 104 | fit: BoxFit.contain, 105 | frameBuilder: _frameBuilder, 106 | ), 107 | ), 108 | ), 109 | ], 110 | ), 111 | ); 112 | } 113 | 114 | @override 115 | void dispose() { 116 | _timer?.cancel(); 117 | super.dispose(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 11 | 12 | # === Flutter Library === 13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 14 | 15 | # Published to parent scope for install step. 16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 20 | 21 | list(APPEND FLUTTER_LIBRARY_HEADERS 22 | "flutter_export.h" 23 | "flutter_windows.h" 24 | "flutter_messenger.h" 25 | "flutter_plugin_registrar.h" 26 | "flutter_texture_registrar.h" 27 | ) 28 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 29 | add_library(flutter INTERFACE) 30 | target_include_directories(flutter INTERFACE 31 | "${EPHEMERAL_DIR}" 32 | ) 33 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 34 | add_dependencies(flutter flutter_assemble) 35 | 36 | # === Wrapper === 37 | list(APPEND CPP_WRAPPER_SOURCES_CORE 38 | "core_implementations.cc" 39 | "standard_codec.cc" 40 | ) 41 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 42 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 43 | "plugin_registrar.cc" 44 | ) 45 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 46 | list(APPEND CPP_WRAPPER_SOURCES_APP 47 | "flutter_engine.cc" 48 | "flutter_view_controller.cc" 49 | ) 50 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 51 | 52 | # Wrapper sources needed for a plugin. 53 | add_library(flutter_wrapper_plugin STATIC 54 | ${CPP_WRAPPER_SOURCES_CORE} 55 | ${CPP_WRAPPER_SOURCES_PLUGIN} 56 | ) 57 | apply_standard_settings(flutter_wrapper_plugin) 58 | set_target_properties(flutter_wrapper_plugin PROPERTIES 59 | POSITION_INDEPENDENT_CODE ON) 60 | set_target_properties(flutter_wrapper_plugin PROPERTIES 61 | CXX_VISIBILITY_PRESET hidden) 62 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 63 | target_include_directories(flutter_wrapper_plugin PUBLIC 64 | "${WRAPPER_ROOT}/include" 65 | ) 66 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 67 | 68 | # Wrapper sources needed for the runner. 69 | add_library(flutter_wrapper_app STATIC 70 | ${CPP_WRAPPER_SOURCES_CORE} 71 | ${CPP_WRAPPER_SOURCES_APP} 72 | ) 73 | apply_standard_settings(flutter_wrapper_app) 74 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 75 | target_include_directories(flutter_wrapper_app PUBLIC 76 | "${WRAPPER_ROOT}/include" 77 | ) 78 | add_dependencies(flutter_wrapper_app flutter_assemble) 79 | 80 | # === Flutter tool backend === 81 | # _phony_ is a non-existent file to force this command to run every time, 82 | # since currently there's no way to get a full input/output list from the 83 | # flutter tool. 84 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 85 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 86 | add_custom_command( 87 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 88 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 89 | ${CPP_WRAPPER_SOURCES_APP} 90 | ${PHONY_OUTPUT} 91 | COMMAND ${CMAKE_COMMAND} -E env 92 | ${FLUTTER_TOOL_ENVIRONMENT} 93 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 94 | windows-x64 $ 95 | VERBATIM 96 | ) 97 | add_custom_target(flutter_assemble DEPENDS 98 | "${FLUTTER_LIBRARY}" 99 | ${FLUTTER_LIBRARY_HEADERS} 100 | ${CPP_WRAPPER_SOURCES_CORE} 101 | ${CPP_WRAPPER_SOURCES_PLUGIN} 102 | ${CPP_WRAPPER_SOURCES_APP} 103 | ) 104 | -------------------------------------------------------------------------------- /lib/pages/home.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | 17 | import 'package:dailypics/extension.dart'; 18 | import 'package:dailypics/components/suggest.dart'; 19 | import 'package:dailypics/components/today.dart'; 20 | import 'package:dailypics/misc/ionicons.dart'; 21 | import 'package:dailypics/pages/about.dart'; 22 | import 'package:dailypics/pages/details.dart'; 23 | import 'package:dailypics/pages/recent.dart'; 24 | import 'package:flutter/cupertino.dart'; 25 | import 'package:flutter/services.dart'; 26 | import 'package:uni_links/uni_links.dart'; 27 | 28 | class HomePage extends StatefulWidget { 29 | @override 30 | _HomePageState createState() => _HomePageState(); 31 | 32 | static void push(BuildContext context) { 33 | Navigator.of(context).pushReplacement( 34 | PageRouteBuilder(pageBuilder: (_, Animation animation, __) { 35 | return FadeTransition( 36 | opacity: animation, 37 | child: HomePage(), 38 | ); 39 | }), 40 | ); 41 | } 42 | } 43 | 44 | class _HomePageState extends State { 45 | StreamSubscription _subscription; 46 | 47 | @override 48 | void initState() { 49 | super.initState(); 50 | _subscription = getUriLinksStream().listen(_handleUniLink); 51 | getInitialUri().then(_handleUniLink); 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return AnnotatedRegion( 57 | value: CupertinoTheme.of(context).barBackgroundColor.isDark 58 | ? SystemUiOverlayStyle.light 59 | : SystemUiOverlayStyle.dark, 60 | child: CupertinoTabScaffold( 61 | resizeToAvoidBottomInset: false, 62 | tabBar: CupertinoTabBar( 63 | items: [ 64 | _buildNavigationItem(Ionicons.today_outline, 'Today'), 65 | _buildNavigationItem(Ionicons.time_outline, '以往'), 66 | _buildNavigationItem(Ionicons.flame_outline, '推荐 '), 67 | _buildNavigationItem(Ionicons.settings_outline, '更多'), 68 | ], 69 | ), 70 | tabBuilder: (_, index) { 71 | switch (index) { 72 | case 0: 73 | return CupertinoTabView(builder: (_) => TodayComponent()); 74 | case 1: 75 | return CupertinoTabView(builder: (_) => RecentPage()); 76 | case 2: 77 | return CupertinoTabView(builder: (_) => SuggestComponent()); 78 | case 3: 79 | return CupertinoTabView(builder: (_) => AboutPage()); 80 | default: 81 | return null; 82 | } 83 | }, 84 | ), 85 | ); 86 | } 87 | 88 | @override 89 | void dispose() { 90 | _subscription.cancel(); 91 | super.dispose(); 92 | } 93 | 94 | BottomNavigationBarItem _buildNavigationItem(IconData icon, String title) { 95 | return BottomNavigationBarItem(icon: Icon(icon), label: title); 96 | } 97 | 98 | void _handleUniLink(Uri uri) { 99 | if (uri == null) return; 100 | if ((uri.scheme == 'dailypics' && uri.host == 'p') || 101 | ((uri.scheme == 'https' && uri.host.contains('dailypics.cn')) && 102 | (uri.path.startsWith('/p/') || uri.path.startsWith('/member/id/')))) { 103 | DetailsPage.push(context, pid: uri.pathSegments.last); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/misc/bean.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'bean.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Picture _$PictureFromJson(Map json) { 10 | return Picture( 11 | id: json['PID'] as String, 12 | tid: json['TID'] as String, 13 | user: json['username'] as String, 14 | title: json['p_title'] as String, 15 | content: json['p_content'] as String, 16 | width: json['width'] as int, 17 | height: json['height'] as int, 18 | cdnUrl: Picture._urlFromJson(json['nativePath'] as String), 19 | color: Picture._colorFromHex(json['theme_color'] as String), 20 | date: json['p_date'] as String, 21 | marked: json['marked'] as bool ?? false, 22 | ); 23 | } 24 | 25 | Map _$PictureToJson(Picture instance) => { 26 | 'PID': instance.id, 27 | 'TID': instance.tid, 28 | 'username': instance.user, 29 | 'p_title': instance.title, 30 | 'p_content': instance.content, 31 | 'width': instance.width, 32 | 'height': instance.height, 33 | 'nativePath': instance.cdnUrl, 34 | 'theme_color': Picture._colorToHex(instance.color), 35 | 'p_date': instance.date, 36 | 'marked': instance.marked, 37 | }; 38 | 39 | Recents _$RecentsFromJson(Map json) { 40 | return Recents( 41 | current: json['page'] as int, 42 | maximum: json['maxpage'] as int, 43 | option: json['op'] as String, 44 | data: (json['result'] as List) 45 | ?.map((e) => 46 | e == null ? null : Picture.fromJson(e as Map)) 47 | ?.toList(), 48 | ); 49 | } 50 | 51 | Map _$RecentsToJson(Recents instance) => { 52 | 'page': instance.current, 53 | 'maxpage': instance.maximum, 54 | 'op': instance.option, 55 | 'result': instance.data, 56 | }; 57 | 58 | Splash _$SplashFromJson(Map json) { 59 | return Splash( 60 | title: json['splash_title'] as String, 61 | imageUrl: json['splash_image'] as String, 62 | effectiveAt: Splash._parseDateTime(json['effective_at'] as String), 63 | expiresAt: Splash._parseDateTime(json['expires_at'] as String), 64 | ); 65 | } 66 | 67 | Map _$SplashToJson(Splash instance) => { 68 | 'splash_title': instance.title, 69 | 'splash_image': instance.imageUrl, 70 | 'effective_at': instance.effectiveAt?.toIso8601String(), 71 | 'expires_at': instance.expiresAt?.toIso8601String(), 72 | }; 73 | 74 | Hitokoto _$HitokotoFromJson(Map json) { 75 | return Hitokoto( 76 | source: json['source'] as String, 77 | content: json['hitokoto'] as String, 78 | ); 79 | } 80 | 81 | Map _$HitokotoToJson(Hitokoto instance) => { 82 | 'source': instance.source, 83 | 'hitokoto': instance.content, 84 | }; 85 | 86 | Contributor _$ContributorFromJson(Map json) { 87 | return Contributor( 88 | avatar: json['avatar'] as String, 89 | name: json['name'] as String, 90 | position: json['position'] as String, 91 | url: json['url'] as String, 92 | ); 93 | } 94 | 95 | Map _$ContributorToJson(Contributor instance) => 96 | { 97 | 'avatar': instance.avatar, 98 | 'name': instance.name, 99 | 'position': instance.position, 100 | 'url': instance.url, 101 | }; 102 | 103 | User _$UserFromJson(Map json) { 104 | return User( 105 | nickname: json['nickname'] as String, 106 | username: json['username'] as String, 107 | ); 108 | } 109 | 110 | Map _$UserToJson(User instance) => { 111 | 'nickname': instance.nickname, 112 | 'username': instance.username, 113 | }; 114 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.0.2) 3 | - AlamofireImage (4.0.2): 4 | - Alamofire (< 6.0.0, >= 5.0.2) 5 | - Flutter (1.0.0) 6 | - flutter_plugin_android_lifecycle (0.0.1): 7 | - Flutter 8 | - FMDB (2.7.5): 9 | - FMDB/standard (= 2.7.5) 10 | - FMDB/standard (2.7.5) 11 | - image_picker (0.0.1): 12 | - Flutter 13 | - package_info (0.0.1): 14 | - Flutter 15 | - path_provider (0.0.1): 16 | - Flutter 17 | - path_provider_macos (0.0.1): 18 | - Flutter 19 | - shared_preferences (0.0.1): 20 | - Flutter 21 | - shared_preferences_macos (0.0.1): 22 | - Flutter 23 | - shared_preferences_web (0.0.1): 24 | - Flutter 25 | - sqflite (0.0.1): 26 | - Flutter 27 | - FMDB (~> 2.7.2) 28 | - uni_links (0.0.1): 29 | - Flutter 30 | - url_launcher (0.0.1): 31 | - Flutter 32 | - url_launcher_macos (0.0.1): 33 | - Flutter 34 | - url_launcher_web (0.0.1): 35 | - Flutter 36 | 37 | DEPENDENCIES: 38 | - Alamofire (~> 5.0) 39 | - AlamofireImage (~> 4.0) 40 | - Flutter (from `Flutter`) 41 | - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) 42 | - image_picker (from `.symlinks/plugins/image_picker/ios`) 43 | - package_info (from `.symlinks/plugins/package_info/ios`) 44 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 45 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) 46 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 47 | - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) 48 | - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) 49 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 50 | - uni_links (from `.symlinks/plugins/uni_links/ios`) 51 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 52 | - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) 53 | - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) 54 | 55 | SPEC REPOS: 56 | trunk: 57 | - Alamofire 58 | - AlamofireImage 59 | - FMDB 60 | 61 | EXTERNAL SOURCES: 62 | Flutter: 63 | :path: Flutter 64 | flutter_plugin_android_lifecycle: 65 | :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" 66 | image_picker: 67 | :path: ".symlinks/plugins/image_picker/ios" 68 | package_info: 69 | :path: ".symlinks/plugins/package_info/ios" 70 | path_provider: 71 | :path: ".symlinks/plugins/path_provider/ios" 72 | path_provider_macos: 73 | :path: ".symlinks/plugins/path_provider_macos/ios" 74 | shared_preferences: 75 | :path: ".symlinks/plugins/shared_preferences/ios" 76 | shared_preferences_macos: 77 | :path: ".symlinks/plugins/shared_preferences_macos/ios" 78 | shared_preferences_web: 79 | :path: ".symlinks/plugins/shared_preferences_web/ios" 80 | sqflite: 81 | :path: ".symlinks/plugins/sqflite/ios" 82 | uni_links: 83 | :path: ".symlinks/plugins/uni_links/ios" 84 | url_launcher: 85 | :path: ".symlinks/plugins/url_launcher/ios" 86 | url_launcher_macos: 87 | :path: ".symlinks/plugins/url_launcher_macos/ios" 88 | url_launcher_web: 89 | :path: ".symlinks/plugins/url_launcher_web/ios" 90 | 91 | SPEC CHECKSUMS: 92 | Alamofire: 3ba7a4db18b4f62c4a1c0e1cb39d7f3d52e10ada 93 | AlamofireImage: ca7325440303d041a80b7caa55066ba945522378 94 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 95 | flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b 96 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 97 | image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f 98 | package_info: 48b108e75b8802c2d5e126f208ef540561c98aef 99 | path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d 100 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 101 | shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 102 | shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 103 | shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 104 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 105 | uni_links: d97da20c7701486ba192624d99bffaaffcfc298a 106 | url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 107 | url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 108 | url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c 109 | 110 | PODFILE CHECKSUM: aacc89576a3f3fb06a06647bd0e510aba02fc626 111 | 112 | COCOAPODS: 1.8.4 113 | -------------------------------------------------------------------------------- /lib/utils/api.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | import 'dart:convert'; 17 | import 'dart:io'; 18 | 19 | import 'package:dailypics/misc/bean.dart'; 20 | import 'package:dailypics/utils/http.dart'; 21 | import 'package:dio/dio.dart'; 22 | import 'package:flutter/cupertino.dart'; 23 | 24 | class TujianApiException implements Exception { 25 | const TujianApiException(this.message); 26 | 27 | final String message; 28 | 29 | @override 30 | String toString() => 'TujianApiException: $message'; 31 | } 32 | 33 | class TujianApi { 34 | static const String _kBaseUrl = 'https://v2.api.dailypics.cn'; 35 | 36 | static Future> getTypes() async { 37 | Response response = await http.getUri(Uri.parse('$_kBaseUrl/sort')); 38 | Map result = {}; 39 | (response.data['result'] as List).forEach((e) { 40 | result.addAll({e['TID']: e['T_NAME']}); 41 | }); 42 | return result; 43 | } 44 | 45 | static Future> getToday() async { 46 | Response response = await http.getUri(Uri.parse('$_kBaseUrl/today')); 47 | return Picture.parseList(response.data) ?? []; 48 | } 49 | 50 | static Future> getRandom({int count = 1}) async { 51 | Response response = await http.getUri(Uri.parse('$_kBaseUrl/random?count=$count')); 52 | return Picture.parseList(response.data) ?? []; 53 | } 54 | 55 | /// 获取分页归档 56 | /// 57 | /// page 页数,不得超过 [Recents.maximum] 58 | /// size 每页容量,位于区间 [3, 20] 59 | /// sort 分类 ID 60 | /// option 排序方式,取值为'asc'或'desc' 61 | static Future getRecents({ 62 | int page = 1, 63 | int size = 3, 64 | @required String sort, 65 | String option = 'desc', 66 | }) async { 67 | String url = '$_kBaseUrl/list?page=$page&size=$size&sort=$sort&op=$option'; 68 | Response response = await http.getUri(Uri.parse(url)); 69 | return Recents.fromJson(response.data); 70 | } 71 | 72 | static Future getDetails(String pid) async { 73 | Response response = await http.getUri(Uri.parse('$_kBaseUrl/member?id=$pid')); 74 | Map json = response.data; 75 | if (json['error_code'] != null) { 76 | throw TujianApiException(json['msg']); 77 | } else { 78 | return Picture.fromJson(json); 79 | } 80 | } 81 | 82 | static Future> search(String keyword) async { 83 | String encodedQuery = Uri.encodeQueryComponent(keyword); 84 | Response response = await http.getUri(Uri.parse('$_kBaseUrl/search/s/$encodedQuery')); 85 | return Picture.parseList(response.data['result']); 86 | } 87 | 88 | static Future getSplash() async { 89 | Response response = await http.getUri(Uri.parse('$_kBaseUrl/app/splash')); 90 | return Splash.fromJson(response.data); 91 | } 92 | 93 | static Future uploadFile( 94 | File file, 95 | ProgressCallback onProgress, 96 | ) async { 97 | Uri uri = Uri.parse('https://img.dpic.dev/upload'); 98 | HttpClient client = HttpClient(); 99 | HttpClientRequest request = await client.postUrl(uri); 100 | String subType = file.path.substring(file.path.lastIndexOf('.') + 1); 101 | request.headers.set('content-type', 'image/$subType'); 102 | int contentLength = file.statSync().size; 103 | int byteCount = 0; 104 | Stream> stream = file.openRead(); 105 | await request.addStream(stream.transform(StreamTransformer.fromHandlers( 106 | handleDone: (sink) => sink.close(), 107 | handleError: (_, __, ___) {}, 108 | handleData: (data, sink) { 109 | byteCount += data.length; 110 | sink.add(data); 111 | if (onProgress != null) { 112 | onProgress(byteCount, contentLength); 113 | } 114 | }, 115 | ))); 116 | HttpClientResponse response = await request.close(); 117 | return jsonDecode(await response.cast>().transform(utf8.decoder).join()); 118 | } 119 | 120 | static Future submit({ 121 | String title, 122 | String content, 123 | String url, 124 | String user, 125 | String type, 126 | String email, 127 | }) async { 128 | final Map data = { 129 | 'title': title, 130 | 'content': content, 131 | 'url': url, 132 | 'user': user, 133 | 'sort': type, 134 | 'hz': email, 135 | }; 136 | Response response = await http.postUri(Uri.parse('$_kBaseUrl/tg'), data: data); 137 | return response.data; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/misc/bean.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:convert'; 16 | 17 | import 'package:dailypics/extension.dart'; 18 | import 'package:flutter/widgets.dart'; 19 | import 'package:json_annotation/json_annotation.dart'; 20 | 21 | part 'bean.g.dart'; 22 | 23 | @JsonSerializable() 24 | class Picture { 25 | Picture({ 26 | this.id, 27 | this.tid, 28 | this.user, 29 | this.title, 30 | this.content, 31 | this.width, 32 | this.height, 33 | this.url, 34 | this.cdnUrl, 35 | this.color, 36 | this.date, 37 | this.marked, 38 | }); 39 | 40 | factory Picture.fromJson(Map json) { 41 | return _$PictureFromJson(json); 42 | } 43 | 44 | @JsonKey(name: 'PID') 45 | String id; 46 | 47 | @JsonKey(name: 'TID') 48 | String tid; 49 | 50 | @JsonKey(name: 'username') 51 | String user; 52 | 53 | @JsonKey(name: 'p_title') 54 | String title; 55 | 56 | @JsonKey(name: 'p_content') 57 | String content; 58 | 59 | int width; 60 | 61 | int height; 62 | 63 | @JsonKey(ignore: true) 64 | String url; 65 | 66 | @JsonKey(name: 'nativePath', fromJson: _urlFromJson) 67 | String cdnUrl; 68 | 69 | @JsonKey(name: 'theme_color', fromJson: _colorFromHex, toJson: _colorToHex) 70 | Color color; 71 | 72 | @JsonKey(name: 'p_date') 73 | String date; 74 | 75 | @JsonKey(defaultValue: false) 76 | bool marked; 77 | 78 | static List parseList(List json) { 79 | return json.map((e) { 80 | return e == null ? null : Picture.fromJson(e as Map); 81 | })?.toList(); 82 | } 83 | 84 | String getCompressedUrl([String style = 'w1080']) { 85 | if (url != null) return url; 86 | return '${cdnUrl}!$style'; 87 | } 88 | 89 | String toJson() => jsonEncode(_$PictureToJson(this)); 90 | 91 | static String _urlFromJson(String s) { 92 | return 'https://s1.images.dailypics.cn$s'; 93 | } 94 | 95 | static Color _colorFromHex(String source) { 96 | return ColorX.fromHexString(source); 97 | } 98 | 99 | static String _colorToHex(Color color) { 100 | return color?.hexString; 101 | } 102 | } 103 | 104 | @JsonSerializable() 105 | class Recents { 106 | Recents({this.current, this.maximum, this.option, this.data}); 107 | 108 | factory Recents.fromJson(Map json) { 109 | return _$RecentsFromJson(json); 110 | } 111 | 112 | @JsonKey(name: 'page') 113 | int current; 114 | 115 | @JsonKey(name: 'maxpage') 116 | int maximum; 117 | 118 | @JsonKey(name: 'op') 119 | String option; 120 | 121 | @JsonKey(name: 'result') 122 | List data; 123 | 124 | String toJson() => jsonEncode(_$RecentsToJson(this)); 125 | } 126 | 127 | @JsonSerializable() 128 | class Splash { 129 | Splash({this.title, this.imageUrl, this.effectiveAt, this.expiresAt}); 130 | 131 | factory Splash.fromJson(Map json) { 132 | return _$SplashFromJson(json); 133 | } 134 | 135 | @JsonKey(name: 'splash_title') 136 | String title; 137 | 138 | @JsonKey(name: 'splash_image') 139 | String imageUrl; 140 | 141 | @JsonKey(name: 'effective_at', fromJson: _parseDateTime) 142 | DateTime effectiveAt; 143 | 144 | @JsonKey(name: 'expires_at', fromJson: _parseDateTime) 145 | DateTime expiresAt; 146 | 147 | String toJson() => jsonEncode(_$SplashToJson(this)); 148 | 149 | static DateTime _parseDateTime(String s) => DateTime.parse(s); 150 | } 151 | 152 | @JsonSerializable() 153 | class Hitokoto { 154 | Hitokoto({this.source, this.content}); 155 | 156 | factory Hitokoto.fromJson(Map json) { 157 | return _$HitokotoFromJson(json); 158 | } 159 | 160 | String source; 161 | 162 | @JsonKey(name: 'hitokoto') 163 | String content; 164 | 165 | String toJson() => jsonEncode(_$HitokotoToJson(this)); 166 | } 167 | 168 | @JsonSerializable() 169 | class Contributor { 170 | Contributor({this.avatar, this.name, this.position, this.url}); 171 | 172 | factory Contributor.fromJson(Map json) { 173 | return _$ContributorFromJson(json); 174 | } 175 | 176 | String avatar; 177 | 178 | String name; 179 | 180 | String position; 181 | 182 | String url; 183 | 184 | String toJson() => jsonEncode(_$ContributorToJson(this)); 185 | } 186 | 187 | @JsonSerializable() 188 | class User { 189 | User({this.nickname, this.username}); 190 | 191 | factory User.fromJson(Map json) { 192 | return _$UserFromJson(json); 193 | } 194 | 195 | String nickname; 196 | 197 | String username; 198 | 199 | String toJson() => jsonEncode(_$UserToJson(this)); 200 | } 201 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/yaerin/dailypics/PlatformPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2021 KagurazakaHanabi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.yaerin.dailypics 18 | 19 | import android.Manifest.permission.WRITE_EXTERNAL_STORAGE 20 | import android.app.Activity 21 | import android.content.pm.PackageManager.PERMISSION_DENIED 22 | import android.content.pm.PackageManager.PERMISSION_GRANTED 23 | import android.os.Build 24 | import io.flutter.embedding.engine.plugins.FlutterPlugin 25 | import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding 26 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 27 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 28 | import io.flutter.plugin.common.BinaryMessenger 29 | import io.flutter.plugin.common.MethodCall 30 | import io.flutter.plugin.common.MethodChannel 31 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 32 | import io.flutter.plugin.common.MethodChannel.Result 33 | import io.flutter.plugin.common.PluginRegistry.Registrar 34 | import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener 35 | import java.io.IOException 36 | 37 | internal class PlatformPlugin : ActivityAware, FlutterPlugin, MethodCallHandler, RequestPermissionsResultListener { 38 | private var pluginImpl: PlatformPluginImpl? = null 39 | 40 | private var activity: Activity? = null 41 | private var methodChannel: MethodChannel? = null 42 | 43 | private var methodCall: MethodCall? = null 44 | private var methodResult: Result? = null 45 | 46 | companion object { 47 | 48 | @Suppress("UNUSED") 49 | fun registerWith(registrar: Registrar) { 50 | val instance = PlatformPlugin() 51 | instance.onAttachedToEngine(registrar.messenger()) 52 | } 53 | } 54 | 55 | override fun onAttachedToEngine(binding: FlutterPluginBinding) { 56 | val context = binding.applicationContext 57 | pluginImpl = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 58 | PlatformPluginApi24Impl(context) 59 | } else { 60 | PlatformPluginBaseImpl(context) 61 | } 62 | onAttachedToEngine(binding.binaryMessenger) 63 | } 64 | 65 | fun onAttachedToEngine(messenger: BinaryMessenger) { 66 | methodChannel = MethodChannel(messenger, "ml.cerasus.pics") 67 | methodChannel!!.setMethodCallHandler(this) 68 | } 69 | 70 | override fun onAttachedToActivity(binding: ActivityPluginBinding) { 71 | activity = binding.activity 72 | binding.addRequestPermissionsResultListener(this) 73 | } 74 | 75 | override fun onDetachedFromActivity() { 76 | activity = null 77 | } 78 | 79 | override fun onDetachedFromEngine(binding: FlutterPluginBinding) { 80 | methodChannel!!.setMethodCallHandler(null) 81 | methodChannel = null 82 | pluginImpl = null 83 | } 84 | 85 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { 86 | onAttachedToActivity(binding) 87 | } 88 | 89 | override fun onDetachedFromActivityForConfigChanges() { 90 | onDetachedFromActivity() 91 | } 92 | 93 | override fun onMethodCall(call: MethodCall, result: Result) { 94 | when (call.method) { 95 | "share" -> try { 96 | pluginImpl!!.share(call.argument("file")!!, result) 97 | } catch (e: IOException) { 98 | result.error(e.javaClass.name, e.localizedMessage, null) 99 | } 100 | 101 | "useAsWallpaper" -> pluginImpl!!.useAsWallpaper(call.arguments as String, result) 102 | 103 | "requestReview" -> pluginImpl!!.requestReview(result) 104 | 105 | "isAlbumAuthorized" -> pluginImpl!!.isAlbumAuthorized(result) 106 | 107 | "openAppSettings" -> pluginImpl!!.openAppSettings(result) 108 | 109 | "syncAlbum" -> { 110 | methodCall = call 111 | methodResult = result 112 | val requestCode = 1080 113 | val permissions = arrayOf(WRITE_EXTERNAL_STORAGE) 114 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 115 | activity!!.requestPermissions(permissions, requestCode) 116 | } else { 117 | val grantResults = intArrayOf(PERMISSION_GRANTED) 118 | onRequestPermissionsResult(requestCode, permissions, grantResults) 119 | } 120 | } 121 | 122 | else -> result.notImplemented() 123 | } 124 | } 125 | 126 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray): Boolean { 127 | if (requestCode != 1080) { 128 | return false 129 | } 130 | if (grantResults[0] == PERMISSION_DENIED) { 131 | methodResult!!.error("-1", "Permission denied", null) 132 | return true 133 | } 134 | return try { 135 | pluginImpl!!.syncAlbum(methodCall!!, methodResult!!) 136 | true 137 | } catch (e: IOException) { 138 | methodResult!!.error(e.javaClass.name, e.localizedMessage, null) 139 | false 140 | } 141 | 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /lib/pages/search.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | import 'dart:ui' show ImageFilter, window; 17 | 18 | import 'package:dailypics/extension.dart'; 19 | import 'package:dailypics/misc/bean.dart'; 20 | import 'package:dailypics/utils/api.dart'; 21 | import 'package:dailypics/utils/utils.dart'; 22 | import 'package:dailypics/widget/photo_card.dart'; 23 | import 'package:dailypics/widget/search.dart'; 24 | import 'package:flutter/cupertino.dart'; 25 | import 'package:flutter/material.dart' show LinearProgressIndicator; 26 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 27 | 28 | const double kSearchBarHeight = 44; 29 | 30 | class SearchPage extends StatefulWidget { 31 | @override 32 | _SearchPageState createState() => _SearchPageState(); 33 | 34 | static Future push(BuildContext context, {String query}) { 35 | return Navigator.of(context).push( 36 | PageRouteBuilder( 37 | pageBuilder: (_, __, ___) => SearchPage(), 38 | transitionsBuilder: (_, Animation animation, __, Widget child) { 39 | return FadeTransition(opacity: animation, child: child); 40 | }, 41 | ), 42 | ); 43 | } 44 | } 45 | 46 | class _SearchPageState extends State { 47 | ScrollController controller = ScrollController(); 48 | 49 | List data = []; 50 | bool doing = false; 51 | String query = ''; 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | MediaQueryData queryData = MediaQuery.of(context); 56 | EdgeInsets windowPadding = queryData.padding; 57 | windowPadding = windowPadding.copyWith(top: 90); 58 | return CupertinoPageScaffold( 59 | resizeToAvoidBottomInset: false, 60 | child: MediaQuery( 61 | data: queryData.copyWith(padding: windowPadding), 62 | child: Stack( 63 | children: [ 64 | CupertinoScrollbar( 65 | controller: controller, 66 | child: StaggeredGridView.countBuilder( 67 | controller: controller, 68 | padding: SystemUtils.isIPad(context) 69 | ? const EdgeInsets.fromLTRB(12, 12, 12, 0) + windowPadding 70 | : const EdgeInsets.fromLTRB(4, 0, 4, 0) + windowPadding, 71 | crossAxisCount: SystemUtils.isIPad(context) ? 2 : 1, 72 | staggeredTileBuilder: (_) => const StaggeredTile.fit(1), 73 | itemCount: data.isEmpty ? 1 : data.length, 74 | itemBuilder: (_, i) { 75 | if (data.isNotEmpty) { 76 | return PhotoCard(data[i], '$query-${data[i].id}'); 77 | } else if (query.isNotEmpty && !doing) { 78 | return Container( 79 | padding: const EdgeInsets.only(top: 16), 80 | alignment: Alignment.center, 81 | child: const Text( 82 | '未找到相关内容', 83 | style: TextStyle( 84 | fontSize: 14, 85 | color: Color(0xB3000000), 86 | ), 87 | ), 88 | ); 89 | } else { 90 | return Container(); 91 | } 92 | }, 93 | ), 94 | ), 95 | ClipRect( 96 | child: BackdropFilter( 97 | filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), 98 | child: _buildSearchBar(), 99 | ), 100 | ), 101 | ], 102 | ), 103 | ), 104 | ); 105 | } 106 | 107 | Widget _buildSearchBar() { 108 | EdgeInsets padding = MediaQuery.of(context).padding; 109 | Color barBackgroundColor = CupertinoTheme.of(context).barBackgroundColor; 110 | return Column( 111 | mainAxisSize: MainAxisSize.min, 112 | children: [ 113 | SizedBox( 114 | width: 500, 115 | child: CupertinoSearchBar( 116 | padding: EdgeInsets.fromLTRB(16, padding.top + 10, 16, 10), 117 | showCancelButton: true, 118 | autofocus: true, 119 | onSubmitted: (value) { 120 | if (value.isUuid) { 121 | _fetchData(value); 122 | } else if (value.isNotEmpty) { 123 | query = value; 124 | _fetchData(); 125 | } 126 | }, 127 | ), 128 | ), 129 | SizedBox( 130 | height: doing ? 2 : 1 / window.devicePixelRatio, 131 | child: LinearProgressIndicator( 132 | backgroundColor: barBackgroundColor, 133 | valueColor: const AlwaysStoppedAnimation(Color(0x4C000000)), 134 | value: (doing ?? false) ? null : 1, 135 | ), 136 | ), 137 | ], 138 | ); 139 | } 140 | 141 | Future _fetchData([String uuid]) async { 142 | setState(() => doing = true); 143 | List result = []; 144 | if (uuid != null) { 145 | Picture detail = await TujianApi.getDetails(uuid); 146 | if (detail.id != null) { 147 | result = [detail]; 148 | } 149 | } else { 150 | result = await TujianApi.search(query); 151 | } 152 | setState(() { 153 | doing = false; 154 | data = result; 155 | }); 156 | if (controller.position.pixels > 320) { 157 | controller.jumpTo(0); 158 | } 159 | } 160 | 161 | @override 162 | void dispose() { 163 | controller.dispose(); 164 | super.dispose(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/widget/photo_card.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:dailypics/extension.dart'; 16 | import 'package:dailypics/misc/bean.dart'; 17 | import 'package:dailypics/pages/details.dart'; 18 | import 'package:dailypics/widget/animated_transform.dart'; 19 | import 'package:dailypics/widget/optimized_image.dart'; 20 | import 'package:dailypics/widget/qrcode.dart'; 21 | import 'package:flutter/cupertino.dart'; 22 | import 'package:flutter/material.dart' show Colors; 23 | import 'package:markdown/markdown.dart' hide Text; 24 | 25 | class PhotoCard extends StatefulWidget { 26 | const PhotoCard( 27 | this.data, 28 | this.heroTag, { 29 | this.padding = const EdgeInsets.all(16), 30 | this.aspectRatio = 4 / 5, 31 | this.boxShadow = const [ 32 | BoxShadow( 33 | color: Colors.black26, 34 | offset: Offset(0, 4), 35 | spreadRadius: -24, 36 | blurRadius: 32, 37 | ) 38 | ], 39 | this.showTexts = true, 40 | this.showQrCode = false, 41 | this.repaintKey, 42 | }) : assert(aspectRatio != null); 43 | 44 | final Picture data; 45 | 46 | final String heroTag; 47 | 48 | final EdgeInsets padding; 49 | 50 | final double aspectRatio; 51 | 52 | final List boxShadow; 53 | 54 | final bool showQrCode; 55 | 56 | final bool showTexts; 57 | 58 | final GlobalKey repaintKey; 59 | 60 | @override 61 | State createState() => _PhotoCardState(); 62 | } 63 | 64 | class _PhotoCardState extends State { 65 | final Duration duration = const Duration(milliseconds: 150); 66 | 67 | double scale = 1; 68 | DateTime tapDown; 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | Color textColor = widget.data.color?.isDark ?? false ? Colors.white : Colors.black; 73 | return AnimatedTransform.scale( 74 | scale: scale, 75 | duration: duration, 76 | curve: Curves.easeInOut, 77 | alignment: Alignment.center, 78 | child: AspectRatio( 79 | aspectRatio: widget.aspectRatio ?? 4 / 5, 80 | child: Container( 81 | padding: widget.padding, 82 | decoration: BoxDecoration( 83 | borderRadius: BorderRadius.circular(16), 84 | boxShadow: widget.boxShadow, 85 | ), 86 | child: GestureDetector( 87 | onTapDown: (_) { 88 | tapDown = DateTime.now(); 89 | setState(() => scale = 0.97); 90 | }, 91 | onTapCancel: () => setState(() => scale = 1.0), 92 | onTapUp: (_) async { 93 | if (DateTime.now().difference(tapDown) < duration) { 94 | await Future.delayed(duration); 95 | } 96 | setState(() => scale = 1.0); 97 | DetailsPage.push( 98 | context, 99 | data: widget.data, 100 | heroTag: widget.heroTag, 101 | ); 102 | }, 103 | child: RepaintBoundary( 104 | key: widget.repaintKey, 105 | child: Stack( 106 | children: [ 107 | AspectRatio( 108 | aspectRatio: widget.aspectRatio, 109 | child: OptimizedImage( 110 | widget.data.getCompressedUrl(), 111 | heroTag: widget.heroTag, 112 | borderRadius: BorderRadius.circular(16), 113 | ), 114 | ), 115 | if (widget.showTexts) 116 | Padding( 117 | padding: const EdgeInsets.fromLTRB(16, 32, 24, 0), 118 | child: Column( 119 | crossAxisAlignment: CrossAxisAlignment.start, 120 | children: [ 121 | Text( 122 | widget.data.title, 123 | style: TextStyle( 124 | color: textColor, 125 | fontWeight: FontWeight.w500, 126 | fontSize: 28, 127 | ), 128 | ), 129 | Padding( 130 | padding: const EdgeInsets.only(top: 4), 131 | child: Text( 132 | markdownToHtml(widget.data.content.split('\n')[0]) 133 | .replaceAll(RegExp(r'<[^>]+>'), ''), 134 | maxLines: 2, 135 | overflow: TextOverflow.ellipsis, 136 | style: TextStyle( 137 | color: textColor.withAlpha(0xB3), 138 | fontSize: 13, 139 | ), 140 | ), 141 | ), 142 | ], 143 | ), 144 | ), 145 | if (widget.showQrCode) 146 | AspectRatio( 147 | aspectRatio: widget.aspectRatio, 148 | child: Container( 149 | alignment: Alignment.bottomRight, 150 | padding: const EdgeInsets.only(right: 8, bottom: 8), 151 | child: QrCodeView( 152 | widget.data.url?.contains('bing.com/') ?? false 153 | ? 'https://cn.bing.com/' 154 | : 'https://dailypics.cn/p/${widget.data.id}', 155 | ), 156 | ), 157 | ), 158 | ], 159 | ), 160 | ), 161 | ), 162 | ), 163 | ), 164 | ); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/widget/panel_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:ui'; 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | enum PanelLockMode { 20 | unlock, 21 | closed, 22 | opened, 23 | } 24 | 25 | class PanelView extends StatefulWidget { 26 | PanelView({ 27 | Key key, 28 | this.alignment = Alignment.bottomCenter, 29 | @required this.panel, 30 | @required this.child, 31 | this.minHeight = kToolbarHeight, 32 | this.maxHeight = 320, 33 | this.radius = 8, 34 | this.color = Colors.white, 35 | this.boxShadow = const [ 36 | BoxShadow( 37 | blurRadius: 8, 38 | color: Color.fromRGBO(0, 0, 0, 0.25), 39 | ) 40 | ], 41 | this.lockMode = PanelLockMode.unlock, 42 | this.scrimColor = Colors.black, 43 | this.scrimOpacity = 0.5, 44 | this.ignorePointer = true, 45 | this.onPanelSlide, 46 | this.onPanelOpened, 47 | this.onPanelClosed, 48 | }) : assert(alignment.x == 0), 49 | assert(alignment.y == -1 || alignment.y == 1), 50 | assert(panel != null && child != null), 51 | assert(color != null), 52 | assert(scrimOpacity >= 0 && scrimOpacity <= 1), 53 | super(key: key); 54 | 55 | final Alignment alignment; 56 | 57 | final Widget panel; 58 | 59 | final Widget child; 60 | 61 | final double minHeight; 62 | 63 | final double maxHeight; 64 | 65 | final double radius; 66 | 67 | final Color color; 68 | 69 | final List boxShadow; 70 | 71 | final PanelLockMode lockMode; 72 | 73 | final Color scrimColor; 74 | 75 | final double scrimOpacity; 76 | 77 | final bool ignorePointer; 78 | 79 | final void Function(double offset) onPanelSlide; 80 | 81 | final VoidCallback onPanelOpened; 82 | 83 | final VoidCallback onPanelClosed; 84 | 85 | @override 86 | PanelViewState createState() => PanelViewState(); 87 | } 88 | 89 | class PanelViewState extends State with SingleTickerProviderStateMixin { 90 | AnimationController _anim; 91 | 92 | @override 93 | void initState() { 94 | super.initState(); 95 | _anim = AnimationController( 96 | vsync: this, 97 | duration: const Duration(milliseconds: 246), 98 | )..addListener(_dispatchEvents); 99 | } 100 | 101 | @override 102 | Widget build(BuildContext context) { 103 | Size size = MediaQuery.of(context).size; 104 | Radius radius = Radius.circular(widget.radius); 105 | double extent = widget.maxHeight - widget.minHeight; 106 | return Stack( 107 | children: [ 108 | SizedBox( 109 | width: size.width, 110 | height: size.height, 111 | child: widget.child, 112 | ), 113 | Offstage( 114 | offstage: _anim.value == 0 || widget.scrimColor == null, 115 | child: GestureDetector( 116 | onTap: !widget.ignorePointer ? close : null, 117 | child: Container( 118 | width: size.width, 119 | height: size.height, 120 | color: widget.scrimColor?.withOpacity( 121 | _anim.value * widget.scrimOpacity, 122 | ), 123 | ), 124 | ), 125 | ), 126 | Positioned( 127 | top: widget.alignment.y == -1 ? extent * (_anim.value - 1) : null, 128 | bottom: widget.alignment.y == 1 ? extent * (_anim.value - 1) : null, 129 | child: GestureDetector( 130 | onVerticalDragUpdate: _onVerticalDragUpdate, 131 | onVerticalDragEnd: _onVerticalDragEnd, 132 | child: Container( 133 | width: size.width, 134 | height: widget.maxHeight, 135 | child: widget.panel, 136 | decoration: BoxDecoration( 137 | color: widget.color, 138 | boxShadow: widget.boxShadow, 139 | borderRadius: BorderRadius.vertical( 140 | top: widget.alignment.y == 1 ? radius : Radius.zero, 141 | bottom: widget.alignment.y == -1 ? radius : Radius.zero, 142 | ), 143 | ), 144 | ), 145 | ), 146 | ), 147 | ], 148 | ); 149 | } 150 | 151 | @override 152 | void dispose() { 153 | _anim.dispose(); 154 | super.dispose(); 155 | } 156 | 157 | void _dispatchEvents() { 158 | setState(() {}); 159 | if (widget.onPanelSlide != null) { 160 | widget.onPanelSlide(_anim.value); 161 | } 162 | if (widget.onPanelOpened != null && _anim.value == 1) { 163 | widget.onPanelOpened(); 164 | } 165 | if (widget.onPanelClosed != null && _anim.value == 0) { 166 | widget.onPanelClosed(); 167 | } 168 | } 169 | 170 | void _onVerticalDragUpdate(DragUpdateDetails details) { 171 | if (widget.lockMode != PanelLockMode.unlock) return; 172 | double extent = widget.maxHeight - widget.minHeight; 173 | double newValue = details.primaryDelta / extent; 174 | _anim.value -= newValue * widget.alignment.y; 175 | } 176 | 177 | void _onVerticalDragEnd(DragEndDetails details) { 178 | if (widget.lockMode != PanelLockMode.unlock || _anim.isAnimating) return; 179 | double minFlingVelocity = 365; 180 | double extent = widget.maxHeight - widget.minHeight; 181 | double velocity = details.velocity.pixelsPerSecond.dy; 182 | if (velocity.abs() >= minFlingVelocity) { 183 | _anim.fling(velocity: -velocity / extent * widget.alignment.y); 184 | } else { 185 | if (_anim.value > 0.5) { 186 | open(); 187 | } else { 188 | close(); 189 | } 190 | } 191 | } 192 | 193 | void open() => _anim.fling(velocity: 1); 194 | 195 | void close() => _anim.fling(velocity: -1); 196 | 197 | static PanelViewState of(BuildContext context, {bool nullOk = false}) { 198 | assert(context != null); 199 | assert(nullOk != null); 200 | final PanelViewState result = context.findAncestorStateOfType(); 201 | if (nullOk || result != null) { 202 | return result; 203 | } 204 | throw FlutterError( 205 | 'PanelView.of() called with a context that does not contain a PanelView.', 206 | ); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /lib/utils/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2021 KagurazakaHanabi 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | import 'dart:io'; 17 | 18 | import 'package:dailypics/misc/bean.dart'; 19 | import 'package:dailypics/utils/http.dart'; 20 | import 'package:dailypics/utils/windows.dart'; 21 | import 'package:dio/dio.dart'; 22 | import 'package:flutter/cupertino.dart'; 23 | import 'package:flutter/services.dart'; 24 | import 'package:path_provider/path_provider.dart'; 25 | import 'package:shared_preferences/shared_preferences.dart'; 26 | import 'package:url_launcher/url_launcher.dart'; 27 | 28 | const MethodChannel _channel = MethodChannel('ml.cerasus.pics'); 29 | 30 | class SystemUtils { 31 | static Future share(File file, [Rect originRect]) async { 32 | Map params = {'file': file.path}; 33 | if (originRect != null) { 34 | params['originX'] = originRect.left; 35 | params['originY'] = originRect.top; 36 | params['originWidth'] = originRect.width; 37 | params['originHeight'] = originRect.height; 38 | } 39 | await _channel.invokeMethod('share', params); 40 | } 41 | 42 | static Future useAsWallpaper(File file) async { 43 | switch (Platform.operatingSystem) { 44 | case "android": 45 | await _channel.invokeMethod('useAsWallpaper', file.path); 46 | break; 47 | case "windows": 48 | Windows.useAsWallpaper(file); 49 | break; 50 | } 51 | } 52 | 53 | static Future useAsWallpaperForWindows(File file) async { 54 | 55 | } 56 | 57 | static Future requestReview(bool inApp) async { 58 | await _channel.invokeMethod('requestReview', inApp); 59 | } 60 | 61 | static Future isAlbumAuthorized() { 62 | return _channel.invokeMethod('isAlbumAuthorized'); 63 | } 64 | 65 | static Future openAppSettings() async { 66 | await _channel.invokeMethod('openAppSettings'); 67 | } 68 | 69 | /*static Future makeH2Wallpaper( 70 | Size size, 71 | Offset offset, 72 | Color backgroundColor, 73 | File backgroundImage, 74 | double backgroundBlurRadius, 75 | Color dockBarColor, 76 | Color shadowColor, 77 | double shadowRadius, 78 | Offset shadowOffset, 79 | double borderRadius, 80 | ) async { 81 | await _channel.invokeMethod('makeH2Wallpaper', { 82 | 'width': size.width, 83 | 'height': size.height, 84 | 'offsetX': offset.dx, 85 | 'offsetY': offset.dy, 86 | 'background': backgroundColor != null ? backgroundColor.hexString : backgroundImage.path, 87 | 'backgroundBlurRadius': backgroundColor != null ? null : backgroundBlurRadius, 88 | 'dockBarColor': dockBarColor.hexString, 89 | 'shadowColor': shadowColor.hexString, 90 | 'shadowRadius': shadowRadius, 91 | 'shadowOffsetX': shadowOffset.dx, 92 | 'shadowOffsetY': shadowOffset.dy, 93 | 'borderRadius': borderRadius, 94 | }); 95 | }*/ 96 | 97 | static Future openUrl(String url) { 98 | return launch(url, forceSafariVC: false, forceWebView: false); 99 | } 100 | 101 | static bool isIPad(BuildContext context, [bool strict = false]) { 102 | Size size = MediaQuery.of(context).size; 103 | if (strict) { 104 | return size.shortestSide >= 600; 105 | } 106 | return size.width >= 600; 107 | } 108 | 109 | static bool isPortrait(BuildContext context) { 110 | Size size = MediaQuery.of(context).size; 111 | return size.width < size.height; 112 | } 113 | } 114 | 115 | class Settings { 116 | static SharedPreferences _prefs; 117 | 118 | static Future initialize() async { 119 | _prefs = await SharedPreferences.getInstance(); 120 | } 121 | 122 | static List get marked => _prefs.getStringList('marked') ?? []; 123 | 124 | static set marked(List list) => _prefs.setStringList('marked', list); 125 | } 126 | 127 | class DownloadManager { 128 | static DownloadManager _instance; 129 | 130 | static DownloadManager get instance { 131 | if (_instance == null) { 132 | _instance = DownloadManager(); 133 | } 134 | return _instance; 135 | } 136 | 137 | List _tasks = []; 138 | 139 | Future runTask(Picture data, ValueNotifier onProgress) async { 140 | String url = data.url ?? data.cdnUrl; 141 | String dest = Platform.isWindows ? (await getDownloadsDirectory()).path : (await getTemporaryDirectory()).path; 142 | File file; 143 | String name; 144 | if (url.contains('bing.com/')) { 145 | name = url.substring(url.lastIndexOf('=') + 1); 146 | } else { 147 | name = url.substring(url.lastIndexOf('/') + 1); 148 | } 149 | file = File('$dest/$name'); 150 | if (file.existsSync()) { 151 | file.deleteSync(); 152 | } 153 | 154 | CancelToken token = CancelToken(); 155 | DownloadTask task = DownloadTask( 156 | url: url, 157 | destFile: file, 158 | progress: onProgress, 159 | cancelToken: token, 160 | ); 161 | _tasks.add(task); 162 | http.downloadUri( 163 | Uri.parse(url), 164 | file.path, 165 | cancelToken: token, 166 | onReceiveProgress: (int count, int total) { 167 | // DownloadTask task = tasks.singleWhere((e) => e.url == url); 168 | task.progress.value = count / total; 169 | }, 170 | ).then((value) async { 171 | if (!Platform.isWindows) { 172 | await _channel.invokeMethod('syncAlbum', { 173 | 'file': file.path, 174 | 'title': data.title, 175 | 'content': data.content, 176 | }); 177 | } 178 | task.progress.value = -1; 179 | _tasks.remove(task); 180 | }, onError: (err) => _tasks.remove(task)); 181 | return task; 182 | } 183 | 184 | List queryTask(String url) { 185 | return _tasks.where((e) => e.url == url).toList(); 186 | } 187 | 188 | void cancel(String url) { 189 | Iterable tasks = _tasks.where((e) => e.url == url); 190 | for (DownloadTask task in tasks) { 191 | task.cancelToken.cancel("Abort by user!"); 192 | } 193 | } 194 | } 195 | 196 | class DownloadTask { 197 | DownloadTask({this.url, this.destFile, this.progress, this.cancelToken}); 198 | 199 | String url; 200 | File destFile; 201 | ValueNotifier progress; 202 | CancelToken cancelToken; 203 | } 204 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 37 | 38 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2021 KagurazakaHanabi 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | import Flutter 18 | import Photos 19 | import UIKit 20 | import StoreKit 21 | 22 | @UIApplicationMain 23 | class AppDelegate: FlutterAppDelegate { 24 | override func application( 25 | _ application: UIApplication, 26 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 27 | ) -> Bool { 28 | let controller: FlutterViewController = window.rootViewController as! FlutterViewController 29 | let channel = FlutterMethodChannel(name: "ml.cerasus.pics", binaryMessenger: controller.binaryMessenger) 30 | channel.setMethodCallHandler({ 31 | (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in 32 | switch call.method { 33 | case "share": 34 | let arguments = call.arguments as! Dictionary; 35 | let originX = arguments["originX"], originY = arguments["originY"]; 36 | let originWidth = arguments["originWidth"], originHeight = arguments["originHeight"]; 37 | var originRect: CGRect? = nil; 38 | if originX != nil && originY != nil && originWidth != nil && originHeight != nil { 39 | originRect = CGRect.init( 40 | x: originX as! Double, y: originY as! Double, 41 | width: originWidth as! Double, height: originHeight as! Double) 42 | } 43 | self.share(file: arguments["file"]! as! String, atSource: originRect, result: result) 44 | case "useAsWallpaper": 45 | self.useAsWallpaper(file: call.arguments as! String, result: result) 46 | case "requestReview": 47 | self.requestReview(inApp: call.arguments as! Bool, result: result) 48 | case "isAlbumAuthorized": 49 | self.isAlbumAuthorized(result: result) 50 | case "openAppSettings": 51 | self.openAppSettings(result: result) 52 | case "syncAlbum": 53 | self.syncAlbum(file: (call.arguments as! Dictionary)["file"]!, result: result) 54 | default: 55 | result(FlutterMethodNotImplemented) 56 | } 57 | }) 58 | GeneratedPluginRegistrant.register(with: self) 59 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 60 | } 61 | 62 | private func share(file: String, atSource: CGRect?, result: FlutterResult) { 63 | do { 64 | let data = try Data(contentsOf: URL.init(string: "file://" + file)!) 65 | let controller = UIActivityViewController.init(activityItems: [UIImage(data: data) as Any], applicationActivities: nil) 66 | controller.popoverPresentationController?.sourceView = controller.view; 67 | if atSource != nil { 68 | controller.popoverPresentationController?.sourceRect = atSource!; 69 | } 70 | window.rootViewController!.present(controller, animated: true, completion: nil) 71 | result(nil) 72 | } catch let error { 73 | result(FlutterError(code: "0", message: error.localizedDescription, details: nil)) 74 | } 75 | } 76 | 77 | private func useAsWallpaper(file: String, result: FlutterResult) { 78 | result(FlutterMethodNotImplemented) 79 | } 80 | 81 | private func requestReview(inApp: Bool, result: FlutterResult) { 82 | if inApp, #available(iOS 10.3, *) { 83 | SKStoreReviewController.requestReview(); 84 | result(nil) 85 | } else { 86 | let url = "itms-apps://itunes.apple.com/app/id1457009047?action=write-review" 87 | UIApplication.shared.open(URL.init(string: url)!) 88 | result(nil) 89 | } 90 | } 91 | 92 | private func isAlbumAuthorized(result: FlutterResult) { 93 | let status = PHPhotoLibrary.authorizationStatus() 94 | result(status == .authorized || status == .notDetermined) 95 | } 96 | 97 | private func openAppSettings(result: FlutterResult) { 98 | UIApplication.shared.open(URL.init(string: UIApplication.openSettingsURLString)!) 99 | result(nil) 100 | } 101 | 102 | private func syncAlbum(file: String, result: @escaping FlutterResult) { 103 | PHPhotoLibrary.requestAuthorization({status in 104 | if status == .authorized { 105 | let albums: PHFetchResult = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: nil) 106 | var album: PHAssetCollection? = nil; 107 | for i in 0..