diff --git a/README.md b/README.md index 78ab8c6..6e97046 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,160 @@ -# rogapp - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +# gifunavi + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + +# 更新履歴 + +0. flutter_compass は pub.dev cache で 34 に変更。キャッシュをクリアしたら修正が必要。 + +1. 2024-8-17 + ・新規にプロジェクトを作り直し。 + ・ユーザー登録で13歳以上でないと登録できない様にした。 + ・生年月日の選択カレンダーを導入。 + ・エントリーカテゴリはメンバー構成によって自動修正できる様にした。(修正中) + ・イベントの締め切りフィールドを追加し、この日以降のイベントの修正はできない様にした。 +  ・エントリーに参加し、ゴールしたら、イベントの修正はできない様にする。必然的にチーム構成を変更する際には、エントリーが終了していれば、変更ができない様にして、新規追加を促す。 +2. + + +#Server +# API仕様変更案 + +## 1. イベントAPI + +### GET /api/events +- 変更点: レスポンスに `deadline_datetime` フィールドを追加 +- レスポンス例: + ```json + { + "id": 1, + "event_name": "サマーマラソン2024", + "start_datetime": "2024-07-01T09:00:00Z", + "end_datetime": "2024-07-01T17:00:00Z", + "deadline_datetime": "2024-06-15T23:59:59Z" + } + ``` + +### POST /api/events +- 変更点: リクエストボディに `deadline_datetime` フィールドを追加 +- リクエスト例: + ```json + { + "event_name": "ウィンターラン2025", + "start_datetime": "2025-01-15T08:00:00Z", + "end_datetime": "2025-01-15T16:00:00Z", + "deadline_datetime": "2025-01-01T23:59:59Z" + } + ``` + +## 2. エントリーAPI + +### PUT /api/entries/{id} +- 変更点: + 1. エントリー更新前にイベントの締め切り日時をチェック + 2. 締め切りを過ぎている場合、400 Bad Requestを返す +- エラーレスポンス例: + ```json + { + "error": "entry_update_closed", + "message": "エントリーの締め切りが過ぎているため、更新できません。", + "deadline": "2024-06-15T23:59:59Z" + } + ``` + +## 3. チームAPI + +### GET /api/teams/{id}/entries +- 新規エンドポイント: 指定されたチームの全エントリーを取得 +- レスポンス例: + ```json + [ + { + "id": 1, + "team_id": 5, + "event": { + "id": 1, + "event_name": "サマーマラソン2024", + "start_datetime": "2024-07-01T09:00:00Z", + "end_datetime": "2024-07-01T17:00:00Z", + "deadline_datetime": "2024-06-15T23:59:59Z" + }, + "category": { + "id": 3, + "category_name": "一般-5時間" + }, + "date": "2024-07-01T09:00:00Z", + "zekken_number": "A-123" + } + ] + ``` + +### PUT /api/teams/{id} +- 変更点: チーム更新時に関連するエントリーの締め切りをチェック +- エラーレスポンス例: + ```json + { + "error": "team_update_restricted", + "message": "締め切りを過ぎたエントリーが存在するため、チームの更新が制限されています。", + "closed_entries": [1, 2, 3] + } + ``` + +## 4. 共通エラーレスポンス + +- すべてのエンドポイントで、より詳細なエラー情報を提供 +- エラーレスポンス構造: + + { + "error": "error_code", + "message": "人間が読める詳細なエラーメッセージ", + "details": { + ## エラーに関する追加情報(オプション) + } + } + + +## 5. 認証・認可 + +- すべてのエンドポイントで適切な認証・認可チェックを実施 +- 権限不足の場合は403 Forbiddenを返す + +マニュアル編集が必要な部分: +1. flutter_compass プラグインの build.gradle ファイルを直接編集します: +ファイルパス: /Volumes/PortableSSD1TB/main/flutter/.pub-cache/hosted/pub.dev/flutter_compass-0.8.0/android/build.gradle +このファイルを開き、jcenter() を mavenCentral() に置き換えます: + $ ./gradlew clean + $ ./gradlew clean build +2. image_gallery_saver の build.gradle ファイルを編集 + /Volumes/PortableSSD1TB/main/flutter/.pub-cache/git/image_gallery_saver-24fd8207a4491c42ed907060bb5bf40c2430131f/android/build.gradle ファイル + ext.kotlin_version = '1.8.22' #'1.7.10' + $ ./gradlew clean + $ ./gradlew clean build +3. qr_code_scanner の問題解決: + /Volumes/PortableSSD1TB/main/flutter/.pub-cache/hosted/pub.dev/qr_code_scanner-[version]/android/build.gradle + ext.kotlin_version = '1.8.22' //'1.7.10' +4. flutter_keyboard_visibility: + vi /Volumes/PortableSSD1TB/main/flutter/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility-6.0.0/android/build.gradle + minSDK = 21 ,SDKversion=34 にする。 +5. device_info_plus は使用不可 +6. geolocator_android: + vi /Volumes/PortableSSD1TB/main/flutter/.pub-cache/hosted/pub.dev/geolocator_android-4.6.1/android/build.gradle + minSdkVersion 21 + + +テスト用位置情報: +大垣駅: 35.36701369466119, 136.61783662683948 +大垣城: 35.36182698266251, 136.61558088722234 +関ケ原駅:35.36365422752628, 136.47061844402452 \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index a7acf24..0d29021 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,29 +1,28 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore index 5d99765..55afd91 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,13 +1,13 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java - -# Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app -key.properties -**/*.keystore -**/*.jks +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/.gradle/config.properties b/android/app/.gradle/config.properties new file mode 100644 index 0000000..5910486 --- /dev/null +++ b/android/app/.gradle/config.properties @@ -0,0 +1,2 @@ +#Thu Aug 22 09:42:29 JST 2024 +java.home=/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home diff --git a/android/app/build.gradle b/android/app/build.gradle index 2795483..34bd3ed 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,87 +1,144 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -def keystoreProperties = new Properties() -def keystorePropertiesFile = rootProject.file('key.properties') -if (keystorePropertiesFile.exists()) { - keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -} - -android { - - compileSdkVersion 33 - - lintOptions { - checkReleaseBuilds false - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.dvox.gifunavi" - minSdkVersion 23 - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - multiDexEnabled true - } - - signingConfigs { - release { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null - storePassword keystoreProperties['storePassword'] - } - } - buildTypes { - release { - signingConfig signingConfigs.release - } - } - -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +// added ここから +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} +// added ここまで + +android { + namespace = "com.dvox.gifunavi" + compileSdk 34 + ndkVersion = '27.0.12077973' + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' //JavaVersion.VERSION_1_8 + } + + // Added : Add this block to force all libraries to use the same Kotlin version + configurations.all { + resolutionStrategy { + //force "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + force "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + //force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + force 'com.google.android.gms:play-services-location:21.0.1' + } + } + // added ここまで + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.dvox.gifunavi" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 21 + targetSdk 34// 19 //flutter.minSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + multiDexEnabled true // Added + } + + // added for release + signingConfigs { + if (keystoreProperties['storeFile'] != null) { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + minifyEnabled true + shrinkResources true + } + } + + lint { + baseline = file("lint-baseline.xml") + } + + lintOptions { + disable 'MissingPermission' + } + + task wrapper(type: Wrapper){ + gradleVersion = '8.2.1' + } + + task prepareKotlinBuildScriptModel { + + } + + +} + +flutter { + source = "../.." +} + + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // added + //implementation project(':flutter') // added - 2 + implementation 'com.google.android.gms:play-services-location:21.0.1' //18.0.0' // このバージョンは最新のものにしてください + //implementation 'androidx.core:core-ktx:1.13.1' // added + implementation 'androidx.core:core-ktx:1.10.1' // added + implementation 'androidx.multidex:multidex:2.0.1' // added + implementation 'com.google.android.material:material:1.5.0' + + // Update AndroidX libraries + // implementation "androidx.core:core-ktx:1.12.0" + implementation "androidx.appcompat:appcompat:1.6.1" + implementation "androidx.fragment:fragment-ktx:1.7.0" + implementation "androidx.activity:activity-ktx:1.8.2" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0" +} + +// Force all Kotlin dependencies to use the same version +configurations.all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + if (details.requested.group == 'org.jetbrains.kotlin') { + details.useVersion kotlin_version + } + } +} \ No newline at end of file diff --git a/android/app/gradle/wrapper/gradle-wrapper.properties b/android/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..229531a --- /dev/null +++ b/android/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/app/gradlew b/android/app/gradlew new file mode 100755 index 0000000..fcb6fca --- /dev/null +++ b/android/app/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/app/gradlew.bat b/android/app/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/android/app/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/app/lint-baseline.xml b/android/app/lint-baseline.xml new file mode 100644 index 0000000..2f84a0f --- /dev/null +++ b/android/app/lint-baseline.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/local.properties b/android/app/local.properties new file mode 100644 index 0000000..f5a3417 --- /dev/null +++ b/android/app/local.properties @@ -0,0 +1,8 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Thu Aug 22 08:15:46 JST 2024 +sdk.dir=/Users/akira/Library/Android/sdk diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index a42e013..399f698 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e30c45d..7b4b2db 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,42 +1,68 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/dvox/gifunavi/LocationService.kt b/android/app/src/main/kotlin/com/dvox/gifunavi/LocationService.kt new file mode 100644 index 0000000..3594ebb --- /dev/null +++ b/android/app/src/main/kotlin/com/dvox/gifunavi/LocationService.kt @@ -0,0 +1,250 @@ +package com.dvox.gifunavi + + +import android.location.Location +import android.Manifest +import android.annotation.TargetApi +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.location.LocationManager +import android.os.Build +import android.os.IBinder +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.Priority +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import android.app.Notification + + +data class GpsData( + val id: Int, + val teamName: String, + val eventCode: String, + val lat: Double, + val lon: Double, + val isCheckin: Int, + val createdAt: Long +) + +class GpsDatabaseHelper(private val context: Context) { + fun insertGps(gpsData: GpsData) { + Log.d("LocationService", "Android: insertGps.") + + // ここにデータベースへの挿入処理を実装する + } + + companion object { + fun getInstance(context: Context): GpsDatabaseHelper { + Log.d("LocationService", "Android: GpsDatabaseHelper.") + return GpsDatabaseHelper(context) + } + } +} + +class LocationService : Service() { + private lateinit var fusedLocationClient: FusedLocationProviderClient + private lateinit var gpsDatabaseHelper: GpsDatabaseHelper + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + override fun onCreate() { + super.onCreate() + Log.d("LocationService", "Android: onCreate.") + + fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) + gpsDatabaseHelper = GpsDatabaseHelper.getInstance(applicationContext) + + // 位置情報の権限チェックとGPS有効化の確認を行う + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE) == PackageManager.PERMISSION_GRANTED) { + //ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + Log.d("LocationService", "Android: onCreate : 位置情報の権限チェックとGPS有効化の確認") + + val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager + if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10000) + .setMinUpdateIntervalMillis(5000) + .build() +/* +val locationRequest = LocationRequest.create().apply { + priority = LocationRequest.PRIORITY_HIGH_ACCURACY + interval = 10000 + fastestInterval = 5000 +} + */ + + fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null) + + // フォアグラウンドサービスの設定 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel(CHANNEL_ID, "Location", NotificationManager.IMPORTANCE_DEFAULT) + val notificationManager = getSystemService(NotificationManager::class.java) + notificationManager?.createNotificationChannel(channel) + } + val notification = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("Tracking location...") + .setContentText("Location: null") + .setSmallIcon(android.R.drawable.ic_menu_mylocation) + .setOngoing(true) + .build() + + startForeground(NOTIFICATION_ID, notification) + } else { + Log.d("LocationService", "GPS is disabled.") + // GPSが無効の場合の処理を追加する(例: ユーザーにGPSを有効にするように促すなど) + stopSelf() // サービスを停止する + } + } else { + Log.d("LocationService", "Location permission or Foreground service location permission is not granted.") + // 位置情報の権限またはフォアグラウンドサービスの位置情報の権限が許可されていない場合の処理を追加する + stopSelf() // サービスを停止する + } + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + return START_STICKY + } + + @TargetApi(Build.VERSION_CODES.N) + override fun onDestroy() { + super.onDestroy() + Log.d("LocationService", "Android: onDestroy.") + fusedLocationClient.removeLocationUpdates(locationCallback) + stopForeground(STOP_FOREGROUND_REMOVE) + stopSelf() + } + + companion object { + private const val NOTIFICATION_ID = 1 + private const val CHANNEL_ID = "location_service_channel" + } + + private fun createNotification(): Notification { + Log.d("LocationService", "Android: createNotification Notification.") + val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("Location Service") + .setContentText("Running...") + .setSmallIcon(android.R.drawable.ic_menu_mylocation) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channelName = "Location Service Channel" + val channelDescription = "Channel for Location Service" + val importance = NotificationManager.IMPORTANCE_DEFAULT + val channel = NotificationChannel(CHANNEL_ID, channelName, importance).apply { + description = channelDescription + } + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + + return notificationBuilder.build() + } + + private fun createNotificationChannel() { + Log.d("LocationService", "Android: createNotificationChannel.") + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + CHANNEL_ID, + "Location Service Channel", + NotificationManager.IMPORTANCE_DEFAULT + ) + val manager = getSystemService(NotificationManager::class.java) + manager.createNotificationChannel(channel) + } + } + + private var lastLocation: Location? = null + + private val locationCallback = object : LocationCallback() { + override fun onLocationResult(locationResult: LocationResult) { + val currentLocation = locationResult.lastLocation + if (currentLocation != null) { + val accuracy = currentLocation.accuracy + if (accuracy <= 30) { + var lastLocation = lastLocation + if (lastLocation == null || currentLocation.distanceTo(lastLocation) >= 10) { + val lat = currentLocation.latitude + val lon = currentLocation.longitude + val currentTime = System.currentTimeMillis() + + // GPSデータをデバッグ用に表示 + val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + val formattedTime = sdf.format(Date(currentTime)) + //Log.d("LocationService", "Android: {$formattedTime}") + + + // GPSデータをデータベースに保存 + GlobalScope.launch(Dispatchers.IO) { + addGPStoDB(lat, lon, currentTime) + } + + lastLocation = currentLocation + } + } else { + Log.d("LocationService", "Android: GPS accuracy is above 30m. Skipping data saving.") + } + } else { + Log.d("LocationService", "Android: No GPS signal received.") + } + } + } + + + /* + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.d("LocationService", "Android: onStartCommand.") + + // Foregroundサービスを開始 + startForeground(NOTIFICATION_ID, createNotification()) + + return START_STICKY + } + */ + + private suspend fun addGPStoDB(lat: Double, lng: Double, timestamp: Long, isCheckin: Int = 0) { + try { + val context = applicationContext + val preferences = context.getSharedPreferences("RogPreferences", Context.MODE_PRIVATE) + val teamName = preferences.getString("team_name", "") ?: "" + val eventCode = preferences.getString("event_code", "") ?: "" + + if (teamName.isNotEmpty() && eventCode.isNotEmpty()) { + val gpsData = GpsData( + id = 0, + teamName = teamName, + eventCode = eventCode, + lat = lat, + lon = lng, + isCheckin = isCheckin, + createdAt = timestamp + ) + + gpsDatabaseHelper.insertGps(gpsData) + Log.d("LocationService", "Android: addGPStoDB.") + } + } catch (e: Exception) { + Log.e("LocationService", "Error adding GPS data to DB", e) + // エラーメッセージをユーザーに表示するなどの処理を追加 + } + } +} + diff --git a/android/app/src/main/kotlin/com/dvox/gifunavi/MainActivity.kt b/android/app/src/main/kotlin/com/dvox/gifunavi/MainActivity.kt new file mode 100644 index 0000000..62f4dfe --- /dev/null +++ b/android/app/src/main/kotlin/com/dvox/gifunavi/MainActivity.kt @@ -0,0 +1,142 @@ +package com.dvox.gifunavi + +import android.Manifest +import android.app.ActivityManager +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import android.widget.Toast +import androidx.annotation.NonNull +import androidx.core.content.ContextCompat +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel +import android.util.Log + +class MainActivity: FlutterActivity() { + private val CHANNEL = "location" + + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, + CHANNEL + ).setMethodCallHandler { call, result -> + when (call.method) { + "startLocationService" -> { + Log.d("MainActivity", "Android: called startLocationService.") + //val intent = Intent(this, LocationService::class.java) + startLocationService() + result.success(null) + } + + "stopLocationService" -> { + Log.d("MainActivity", "Android: called stopLocationService.") + //val intent = Intent(this, LocationService::class.java) + stopLocationService() + result.success(null) + } + + "isLocationServiceRunning" -> { + Log.d("MainActivity", "Android: called isLocationServiceRunnung.") + val isRunning = isServiceRunning(LocationService::class.java) + result.success(isRunning) + } + + else -> { + result.notImplemented() + } + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Log.d("MainActivity", "Android: onCreate.") + + // 位置情報の権限をリクエストする==> main() の前にコールされるので除外 2024-7-19 + /* + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_REQUEST_CODE) + } else { + // startLocationService() // アプリ起動時にLocationServiceを開始する ==> main.dartで制御する。 + } + */ + } + + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == PERMISSION_REQUEST_CODE) { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // startLocationService() + + Log.d("MainActivity", "Android: PERMISSION_GRANTED.") + } else { + // 位置情報の権限が拒否された場合の処理 + Toast.makeText(this, "Location permission denied.", Toast.LENGTH_SHORT).show() + } + } + } + + companion object { + private const val PERMISSION_REQUEST_CODE = 1 + } + + private fun startLocationService() { + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission( + this, + Manifest.permission.FOREGROUND_SERVICE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + if (!isServiceRunning(LocationService::class.java)) { + val intent = Intent(this, LocationService::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Log.d("MainActivity", "startForegroundService") + startForegroundService(intent) + } else { + Log.d("MainActivity", "startService") + startService(intent) + } + } else { + Log.d("MainActivity", "Location service is already running.") + } + } else { + Log.d( + "MainActivity", + "Location permission or Foreground service location permission is not granted." + ) + // 位置情報の権限またはフォアグラウンドサービスの位置情報の権限が許可されていない場合の処理を追加する + // 例: ユーザーに権限の必要性を説明し、許可を求めるダイアログを表示するなど + } + } + + private fun stopLocationService() { + if (isServiceRunning(LocationService::class.java)) { + val intent = Intent(this, LocationService::class.java) + stopService(intent) + } else { + Log.d("MainActivity", "Location service is not running.") + } + } + + private fun isServiceRunning(serviceClass: Class<*>): Boolean { + val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + for (service in manager.getRunningServices(Int.MAX_VALUE)) { + if (serviceClass.name == service.service.className) { + return true + } + } + return false + } +} diff --git a/android/app/src/main/kotlin/com/example/rogapp/MainActivity.kt b/android/app/src/main/kotlin/com/example/rogapp/MainActivity.kt deleted file mode 100644 index 9c7fd0b..0000000 --- a/android/app/src/main/kotlin/com/example/rogapp/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.rogapp - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml index 1cb7aa2..f74085f 100644 --- a/android/app/src/main/res/drawable-v21/launch_background.xml +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -1,12 +1,12 @@ - - - - - - - - + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml index 8403758..304732f 100644 --- a/android/app/src/main/res/drawable/launch_background.xml +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -1,12 +1,12 @@ - - - - - - - - + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 899012d..db77bb4 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png new file mode 100644 index 0000000..899012d Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 94993e4..17987b7 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png new file mode 100644 index 0000000..94993e4 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 0415a95..09d4391 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png new file mode 100644 index 0000000..0415a95 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index c94b6f2..d5f1c8d 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png new file mode 100644 index 0000000..c94b6f2 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 39d5d8e..4d6372e 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png new file mode 100644 index 0000000..39d5d8e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 6b2369f..06952be 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -1,18 +1,18 @@ - - - - - - - + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 3e29b54..cb1ef88 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - - - - - + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index a42e013..399f698 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/android/build.gradle b/android/build.gradle index f7eb7f6..728b34c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,15 +1,16 @@ +// added buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.8.22' // Use the latest version repositories { google() mavenCentral() } - dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' + classpath 'com.android.tools.build:gradle:8.3.0' //8.3.0' //7.3.0' // Use the version compatible with your Android Studio classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } +// added ここまで allprojects { repositories { @@ -18,14 +19,16 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } tasks.register("clean", Delete) { delete rootProject.buildDir } + + diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/Flutter.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/Flutter.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/Flutter.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/MTBBarcodeScanner.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/MTBBarcodeScanner.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/MTBBarcodeScanner.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/Pods-RunnerTests.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/Pods-RunnerTests.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/Pods-RunnerTests.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/camera_avfoundation-camera_avfoundation_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/camera_avfoundation-camera_avfoundation_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/camera_avfoundation-camera_avfoundation_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/camera_avfoundation.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/camera_avfoundation.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/camera_avfoundation.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus-connectivity_plus_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus-connectivity_plus_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus-connectivity_plus_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/device_info_plus-device_info_plus_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/device_info_plus-device_info_plus_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/device_info_plus-device_info_plus_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/device_info_plus.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/device_info_plus.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/device_info_plus.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/flutter_compass.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/flutter_compass.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/flutter_compass.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/flutter_keyboard_visibility.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/flutter_keyboard_visibility.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/flutter_keyboard_visibility.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/geolocator_apple-geolocator_apple_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/geolocator_apple-geolocator_apple_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/geolocator_apple-geolocator_apple_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/geolocator_apple.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/geolocator_apple.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/geolocator_apple.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/image_gallery_saver.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/image_gallery_saver.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/image_gallery_saver.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/image_picker_ios-image_picker_ios_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/image_picker_ios-image_picker_ios_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/image_picker_ios-image_picker_ios_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/image_picker_ios.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/image_picker_ios.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/image_picker_ios.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/package_info_plus-package_info_plus_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/package_info_plus-package_info_plus_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/package_info_plus-package_info_plus_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/package_info_plus.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/package_info_plus.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/package_info_plus.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/path_provider_foundation-path_provider_foundation_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/path_provider_foundation-path_provider_foundation_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/path_provider_foundation-path_provider_foundation_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/path_provider_foundation.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/path_provider_foundation.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/path_provider_foundation.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/permission_handler_apple-permission_handler_apple_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/permission_handler_apple-permission_handler_apple_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/permission_handler_apple-permission_handler_apple_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/permission_handler_apple.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/permission_handler_apple.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/permission_handler_apple.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/pointer_interceptor_ios-pointer_interceptor_ios_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/pointer_interceptor_ios-pointer_interceptor_ios_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/pointer_interceptor_ios-pointer_interceptor_ios_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/pointer_interceptor_ios.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/pointer_interceptor_ios.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/pointer_interceptor_ios.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/qr_code_scanner.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/qr_code_scanner.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/qr_code_scanner.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation-shared_preferences_foundation_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation-shared_preferences_foundation_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation-shared_preferences_foundation_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/shared_preferences_foundation.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/sqflite-sqflite_darwin_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/sqflite-sqflite_darwin_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/sqflite-sqflite_darwin_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/sqflite.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/sqflite.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/sqflite.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios-url_launcher_ios_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios-url_launcher_ios_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios-url_launcher_ios_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/url_launcher_ios.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/webview_flutter_wkwebview-webview_flutter_wkwebview_privacy.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/webview_flutter_wkwebview-webview_flutter_wkwebview_privacy.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/webview_flutter_wkwebview-webview_flutter_wkwebview_privacy.build/dgph differ diff --git a/android/build/ios/Pods.build/Release-iphonesimulator/webview_flutter_wkwebview.build/dgph b/android/build/ios/Pods.build/Release-iphonesimulator/webview_flutter_wkwebview.build/dgph new file mode 100644 index 0000000..aff52a3 Binary files /dev/null and b/android/build/ios/Pods.build/Release-iphonesimulator/webview_flutter_wkwebview.build/dgph differ diff --git a/android/gradle.properties b/android/gradle.properties index 46c1f16..253486f 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,5 @@ -org.gradle.jvmargs=-Xmx1536M -android.useAndroidX=true -android.enableJetifier=true +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +#org.gradle.jvmargs=-Xmx6G -XX:MaxMetaspaceSize=3G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true +kotlin.code.style=official \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 3c472b9..9f4197d 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 33f0745..b40caf8 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,39 @@ -include ':app' - -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() - -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } - -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.4.2" apply false + //id "org.jetbrains.kotlin.android" version "1.9.0" apply false + //id "org.jetbrains.kotlin.android" version "1.7.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" + +// added +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +// added ここまで diff --git a/assets/customIcon/SVG/gps_signal_high.svg b/assets/customIcon/SVG/gps_signal_high.svg new file mode 100644 index 0000000..f218fb8 --- /dev/null +++ b/assets/customIcon/SVG/gps_signal_high.svg @@ -0,0 +1,262 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/customIcon/SVG/gps_signal_low.svg b/assets/customIcon/SVG/gps_signal_low.svg new file mode 100644 index 0000000..366fade --- /dev/null +++ b/assets/customIcon/SVG/gps_signal_low.svg @@ -0,0 +1,330 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/customIcon/SVG/gps_signal_middle.svg b/assets/customIcon/SVG/gps_signal_middle.svg new file mode 100644 index 0000000..d7ec422 --- /dev/null +++ b/assets/customIcon/SVG/gps_signal_middle.svg @@ -0,0 +1,223 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/customIcon/gps_signal.psd b/assets/customIcon/gps_signal.psd new file mode 100644 index 0000000..f769c9f Binary files /dev/null and b/assets/customIcon/gps_signal.psd differ diff --git a/assets/customIcon/gps_signal_high.png b/assets/customIcon/gps_signal_high.png new file mode 100644 index 0000000..b2ea965 Binary files /dev/null and b/assets/customIcon/gps_signal_high.png differ diff --git a/assets/customIcon/gps_signal_low.png b/assets/customIcon/gps_signal_low.png new file mode 100644 index 0000000..104c76f Binary files /dev/null and b/assets/customIcon/gps_signal_low.png differ diff --git a/assets/customIcon/gps_signal_middle.png b/assets/customIcon/gps_signal_middle.png new file mode 100644 index 0000000..e206146 Binary files /dev/null and b/assets/customIcon/gps_signal_middle.png differ diff --git a/assets/customIcon/icomoon-old/Read Me.txt b/assets/customIcon/icomoon-old/Read Me.txt new file mode 100644 index 0000000..723a49e --- /dev/null +++ b/assets/customIcon/icomoon-old/Read Me.txt @@ -0,0 +1,7 @@ +Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. + +To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/docs/#local-fonts + +You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. + +You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. diff --git a/assets/customIcon/icomoon-old/demo-files/demo.css b/assets/customIcon/icomoon-old/demo-files/demo.css new file mode 100644 index 0000000..39b8991 --- /dev/null +++ b/assets/customIcon/icomoon-old/demo-files/demo.css @@ -0,0 +1,152 @@ +body { + padding: 0; + margin: 0; + font-family: sans-serif; + font-size: 1em; + line-height: 1.5; + color: #555; + background: #fff; +} +h1 { + font-size: 1.5em; + font-weight: normal; +} +small { + font-size: .66666667em; +} +a { + color: #e74c3c; + text-decoration: none; +} +a:hover, a:focus { + box-shadow: 0 1px #e74c3c; +} +.bshadow0, input { + box-shadow: inset 0 -2px #e7e7e7; +} +input:hover { + box-shadow: inset 0 -2px #ccc; +} +input, fieldset { + font-family: sans-serif; + font-size: 1em; + margin: 0; + padding: 0; + border: 0; +} +input { + color: inherit; + line-height: 1.5; + height: 1.5em; + padding: .25em 0; +} +input:focus { + outline: none; + box-shadow: inset 0 -2px #449fdb; +} +.glyph { + font-size: 16px; + width: 15em; + padding-bottom: 1em; + margin-right: 4em; + margin-bottom: 1em; + float: left; + overflow: hidden; +} +.liga { + width: 80%; + width: calc(100% - 2.5em); +} +.talign-right { + text-align: right; +} +.talign-center { + text-align: center; +} +.bgc1 { + background: #f1f1f1; +} +.fgc1 { + color: #999; +} +.fgc0 { + color: #000; +} +p { + margin-top: 1em; + margin-bottom: 1em; +} +.mvm { + margin-top: .75em; + margin-bottom: .75em; +} +.mtn { + margin-top: 0; +} +.mtl, .mal { + margin-top: 1.5em; +} +.mbl, .mal { + margin-bottom: 1.5em; +} +.mal, .mhl { + margin-left: 1.5em; + margin-right: 1.5em; +} +.mhmm { + margin-left: 1em; + margin-right: 1em; +} +.mls { + margin-left: .25em; +} +.ptl { + padding-top: 1.5em; +} +.pbs, .pvs { + padding-bottom: .25em; +} +.pvs, .pts { + padding-top: .25em; +} +.unit { + float: left; +} +.unitRight { + float: right; +} +.size1of2 { + width: 50%; +} +.size1of1 { + width: 100%; +} +.clearfix:before, .clearfix:after { + content: " "; + display: table; +} +.clearfix:after { + clear: both; +} +.hidden-true { + display: none; +} +.textbox0 { + width: 3em; + background: #f1f1f1; + padding: .25em .5em; + line-height: 1.5; + height: 1.5em; +} +#testDrive { + display: block; + padding-top: 24px; + line-height: 1.5; +} +.fs0 { + font-size: 16px; +} +.fs1 { + font-size: 32px; +} + diff --git a/assets/customIcon/icomoon-old/demo-files/demo.js b/assets/customIcon/icomoon-old/demo-files/demo.js new file mode 100644 index 0000000..6f45f1c --- /dev/null +++ b/assets/customIcon/icomoon-old/demo-files/demo.js @@ -0,0 +1,30 @@ +if (!('boxShadow' in document.body.style)) { + document.body.setAttribute('class', 'noBoxShadow'); +} + +document.body.addEventListener("click", function(e) { + var target = e.target; + if (target.tagName === "INPUT" && + target.getAttribute('class').indexOf('liga') === -1) { + target.select(); + } +}); + +(function() { + var fontSize = document.getElementById('fontSize'), + testDrive = document.getElementById('testDrive'), + testText = document.getElementById('testText'); + function updateTest() { + testDrive.innerHTML = testText.value || String.fromCharCode(160); + if (window.icomoonLiga) { + window.icomoonLiga(testDrive); + } + } + function updateSize() { + testDrive.style.fontSize = fontSize.value + 'px'; + } + fontSize.addEventListener('change', updateSize, false); + testText.addEventListener('input', updateTest, false); + testText.addEventListener('change', updateTest, false); + updateSize(); +}()); diff --git a/assets/customIcon/icomoon-old/demo.html b/assets/customIcon/icomoon-old/demo.html new file mode 100644 index 0000000..edf3669 --- /dev/null +++ b/assets/customIcon/icomoon-old/demo.html @@ -0,0 +1,80 @@ + + + + + IcoMoon Demo + + + + + +
+

Font Name: icomoon (Glyphs: 3)

+
+
+

Grid Size: Unknown

+
+
+ + icon-gps_signal_middle +
+
+ + +
+
+ liga: + +
+
+
+
+ + icon-gps_signal_low +
+
+ + +
+
+ liga: + +
+
+
+
+ + icon-gps_signal_high +
+
+ + +
+
+ liga: + +
+
+
+ + +
+

Font Test Drive

+ + +
  +
+
+ +
+

Generated by IcoMoon

+
+ + + + diff --git a/assets/customIcon/icomoon-old/fonts/icomoon.eot b/assets/customIcon/icomoon-old/fonts/icomoon.eot new file mode 100644 index 0000000..74b0f40 Binary files /dev/null and b/assets/customIcon/icomoon-old/fonts/icomoon.eot differ diff --git a/assets/customIcon/icomoon-old/fonts/icomoon.svg b/assets/customIcon/icomoon-old/fonts/icomoon.svg new file mode 100644 index 0000000..02cb400 --- /dev/null +++ b/assets/customIcon/icomoon-old/fonts/icomoon.svg @@ -0,0 +1,39 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/customIcon/icomoon-old/fonts/icomoon.woff b/assets/customIcon/icomoon-old/fonts/icomoon.woff new file mode 100644 index 0000000..351d4f9 Binary files /dev/null and b/assets/customIcon/icomoon-old/fonts/icomoon.woff differ diff --git a/assets/customIcon/icomoon-old/selection.json b/assets/customIcon/icomoon-old/selection.json new file mode 100644 index 0000000..ceea3de --- /dev/null +++ b/assets/customIcon/icomoon-old/selection.json @@ -0,0 +1 @@ +{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M476.771 35.229c199.666-13.278 355.46 61.878 467.376 225.468 108.605 206.743 88.252 399.332-61.064 577.761-127.939 124.994-280.599 169.618-457.982 133.872-231.133-67.546-353.261-221.776-366.385-462.679 9.428-217.043 114.333-366.572 314.716-448.587 34.697-10.24 69.143-18.852 103.339-25.835z","M504.954 218.422c110.963-8.694 205.688 25.752 284.183 103.339 4.721 17.168-1.541 28.129-18.789 32.881-30.823-26.775-64.489-49.478-100.991-68.11-81.37-34.44-162.788-34.44-244.257 0-35.044 21.006-68.708 43.709-100.991 68.11-11.273-4.217-18.319-12.046-21.138-23.486 29.204-38.979 66.782-67.945 112.734-86.899 29.725-10.931 59.477-19.542 89.248-25.835z","M514.349 302.972c80.403-7.114 148.513 17.937 204.33 75.156 6.247 23.475-2.367 33.652-25.835 30.532-96.19-87.544-193.264-89.11-291.229-4.697-18.12 11.933-30.646 8.019-37.578-11.743 2.069-12.747 8.332-22.925 18.789-30.532 39.091-31.31 82.932-50.882 131.523-58.716z","M260.697 453.284c184.765-0.783 369.523 0 554.275 2.349 26.431 4.496 44.436 19.371 54.018 44.624 4.462 91.056 2.894 181.873-4.697 272.44-9.131 15.191-22.443 25.365-39.927 30.532-191.023 3.133-382.043 3.133-573.064 0-26.722-3.227-43.163-18.098-49.321-44.624-3.132-84.55-3.132-169.101 0-253.651 8.958-30.138 28.53-47.362 58.716-51.67z","M237.211 467.376c197.291-0.783 394.574 0 591.853 2.349 15.294 7.455 25.473 19.198 30.532 35.229 3.133 84.55 3.133 169.101 0 253.651-6.017 17.554-17.76 29.297-35.229 35.229-192.587 3.133-385.174 3.133-577.761 0-17.469-5.933-29.212-17.676-35.229-35.229-3.132-86.115-3.132-172.234 0-258.349 7.828-11.724 16.439-22.683 25.835-32.881z","M345.248 519.046c28.354-0.658 56.537 0.907 84.55 4.697 0 14.092 0 28.183 0 42.275-4.942 0.7-9.639-0.080-14.092-2.349-12.498-31.307-34.419-40.702-65.761-28.183-25.779 24.083-36.739 53.835-32.881 89.248-3.754 35.765 7.207 65.513 32.881 89.248 49.484 5.209 65.924-15.928 49.321-63.413-4.239-5.641-9.72-9.559-16.44-11.743 20.355-3.133 40.71-3.133 61.064 0-5.643 4.242-9.558 9.719-11.743 16.44-1.566 23.486-3.132 46.972-4.697 70.459-1.566 1.564-3.132 3.133-4.697 4.697-6.167-5.040-13.213-8.173-21.138-9.394-77.32 18.3-116.464-13.016-117.431-93.945-1.181-48.762 19.174-84.776 61.064-108.037z","M472.073 523.743c30.471-5.421 61.788-6.205 93.945-2.349 45.267 6.67 61.708 32.505 49.321 77.505-5.482 18.005-17.225 29.748-35.229 35.229-19.977 0.939-39.551 3.288-58.716 7.046-3.133 21.922-3.133 43.839 0 65.761 5.702 7.276 12.748 12.753 21.138 16.44-23.486 3.133-46.972 3.133-70.459 0 5.068-4.491 10.545-8.403 16.44-11.743 3.133-57.931 3.133-115.867 0-173.798-4.88-5.684-10.362-10.381-16.44-14.092z","M692.844 519.046c27.066-1.649 53.68 0.7 79.853 7.046-4.434 12.772-5.998 26.079-4.697 39.927-4.697 0-9.394 0-14.092 0-7.765-33.707-28.118-43.886-61.064-30.532-18.939 21.626-17.375 41.979 4.697 61.064 22.326 11.546 43.464 24.858 63.413 39.927 24.599 57.264 5.81 88.581-56.367 93.945-17.925-3.791-35.934-6.924-54.018-9.394-0.77-12.622 0.014-25.149 2.349-37.578 1.945-2.147 4.293-3.716 7.046-4.697 6.656 15.285 17.615 27.028 32.881 35.229 43.637 7.619 60.078-10.386 49.321-54.018-22.984-18.277-47.254-34.717-72.807-49.321-24.567-39.56-16.741-70.092 23.486-91.596z","M519.046 533.138c58.077-5.369 79.214 19.681 63.413 75.156-17.779 16.271-38.917 22.537-63.413 18.789 0-31.317 0-62.628 0-93.945z"],"attrs":[{"fill":"rgb(255, 254, 11)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(0, 0, 0)"},{"fill":"rgb(5, 5, 5)"},{"fill":"rgb(254, 255, 0)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(3, 3, 3)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(254, 255, 0)"}],"width":1071,"isMulticolor":true,"isMulticolor2":false,"grid":0,"tags":["gps_signal_middle"],"colorPermutations":{"1101010111111415721312215821312221254255012552541112556537133315551":[{"f":9},{"f":0},{"f":1},{"f":4},{"f":8},{"f":2},{"f":3},{"f":2},{"f":8}]}},"attrs":[{"fill":"rgb(255, 254, 11)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(0, 0, 0)"},{"fill":"rgb(5, 5, 5)"},{"fill":"rgb(254, 255, 0)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(3, 3, 3)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(254, 255, 0)"}],"properties":{"order":2,"id":2,"name":"gps_signal_middle","prevSize":32,"code":59648,"codes":[59648,59649,59650,59651,59652,59653,59654,59655,59656]},"setIdx":0,"setId":2,"iconIdx":0},{"icon":{"paths":["M472.073 16.44c-62.2 9.509-121.698 29.081-178.495 58.716-183.411 111.384-267.179 275.005-251.303 490.862 33.091 203.748 146.608 340.752 340.55 411.009 213.466 56.080 392.746 2.062 537.835-162.055 123.012-171.919 138.667-353.547 46.972-544.881-113.96-181.861-279.148-266.412-495.56-253.651zM669.358 340.55c-67.809-41.065-139.837-48.111-216.073-21.138-25.823 10.547-49.309 24.639-70.459 42.275-14.050 10.059-19.531 23.368-16.44 39.927 3.777 6.128 9.257 9.259 16.44 9.394 24.303-17.197 49.355-33.637 75.156-49.321 62.177-27.915 123.242-25.567 183.193 7.046-26.619 28.183-53.234 56.367-79.853 84.55-100.22-0.783-200.428 0-300.624 2.349-30.974 2.785-50.545 19.226-58.716 49.321-3.132 84.55-3.132 169.101 0 253.651 5.542 11.086 11.023 22.044 16.44 32.881-6.072 8.436-13.118 16.267-21.138 23.486-117.899-133.054-150.78-284.93-98.642-455.633 79.099-195.473 224.714-298.813 436.844-310.018 117.098 2.714 218.089 43.424 302.972 122.128-54.901 58.031-111.268 114.398-169.101 169.101zM260.697 805.578c187.896 0.784 375.785 0 563.67-2.349 26.244-6.384 41.12-22.824 44.624-49.321 3.133-84.55 3.133-169.101 0-253.651-9.582-25.252-27.587-40.128-54.018-44.624-67.307-2.348-134.637-3.131-201.982-2.349 18.789-21.921 39.142-42.275 61.064-61.064 9.254 13.214 21.781 18.694 37.578 16.44 14.026-18.322 10.898-33.196-9.394-44.624 53.135-56.268 107.938-111.070 164.404-164.404 57.809 59.207 96.171 129.666 115.083 211.376 34.623 240.701-56.973 413.715-274.789 519.046-142.665 52.083-280.449 41.12-413.358-32.881-24.842-17.004-48.328-35.793-70.459-56.367 11.913-12.706 24.439-24.449 37.578-35.229zM542.532 472.073c-17.225 15.656-34.445 31.317-51.67 46.972-6.449-0.737-12.715 0.047-18.789 2.349 2.555 3.368 5.688 5.717 9.394 7.046-50.104 53.234-101.774 104.904-155.009 155.009-14.272-44.69-11.923-88.534 7.046-131.523 32.897-33.454 60.297-29.541 82.202 11.743 4.453 2.269 9.15 3.049 14.092 2.349 0-14.092 0-28.183 0-42.275-90.584-23.622-139.122 10.822-145.615 103.339 3.54 15.393 5.889 31.049 7.046 46.972 1.991 11.861 7.471 21.255 16.44 28.183-25.052 28.183-51.67 54.803-79.853 79.853-6.123-6.985-11.603-14.815-16.44-23.486-3.132-86.115-3.132-172.234 0-258.349 6.848-11.555 15.459-21.734 25.835-30.532 102.575-3.896 204.349-3.113 305.321 2.349zM598.899 467.376c76.739-0.783 153.459 0 230.165 2.349 9.977 5.27 18.587 12.316 25.835 21.138 7.647 88.942 9.216 178.19 4.697 267.743-6.017 17.554-17.76 29.297-35.229 35.229-184.752 2.349-369.51 3.133-554.275 2.349 21.921-25.050 45.407-48.537 70.459-70.459 20.407 4.105 40.762 2.541 61.064-4.697 7.925 1.221 14.971 4.354 21.138 9.394 6.026-24.224 9.157-49.274 9.394-75.156 2.185-6.722 6.1-12.199 11.743-16.44-4.453-2.269-9.15-3.049-14.092-2.349 18.789-21.922 39.144-42.275 61.064-61.064 0.78 45.432 0 90.84-2.349 136.22-5.895 3.34-11.372 7.253-16.44 11.743 23.486 3.133 46.972 3.133 70.459 0-8.389-3.687-15.435-9.164-21.138-16.44-3.133-21.922-3.133-43.839 0-65.761 19.165-3.758 38.738-6.106 58.716-7.046 33.999-17.168 45.742-44.567 35.229-82.202-15.665-24.379-38.372-35.342-68.11-32.881 15.656-18.789 32.881-36.014 51.67-51.67zM533.138 533.138c42.172-0.892 60.176 19.461 54.018 61.064-1.625 11.029-7.107 19.639-16.44 25.835-16.722 5.81-33.947 8.159-51.67 7.046 0-26.619 0-53.234 0-79.853 3.133-6.261 7.83-10.959 14.092-14.092zM401.615 664.661c7.172 39.809-10.052 57.034-51.67 51.67 15.657-18.789 32.881-36.014 51.67-51.67z","M669.358 340.55c-7.83 10.96-17.225 20.355-28.183 28.183-59.951-32.613-121.015-34.961-183.193-7.046-25.801 15.684-50.853 32.124-75.156 49.321-7.184-0.135-12.664-3.267-16.44-9.394-3.090-16.559 2.39-29.868 16.44-39.927 21.15-17.636 44.636-31.728 70.459-42.275 76.236-26.973 148.264-19.928 216.073 21.138z","M702.239 364.037c20.292 11.427 23.42 26.302 9.394 44.624-15.797 2.254-28.324-3.227-37.578-16.44 7.83-10.96 17.225-20.355 28.183-28.183z","M561.321 453.284c-4.697 7.829-10.959 14.092-18.789 18.789-100.973-5.462-202.746-6.245-305.321-2.349-10.376 8.798-18.987 18.977-25.835 30.532-3.132 86.115-3.132 172.234 0 258.349 4.837 8.671 10.317 16.501 16.44 23.486-1.566 4.697-4.697 7.83-9.394 9.394-5.418-10.837-10.898-21.795-16.44-32.881-3.132-84.55-3.132-169.101 0-253.651 8.17-30.095 27.742-46.537 58.716-49.321 100.196-2.349 200.404-3.131 300.624-2.349z","M612.991 453.284c67.344-0.783 134.675 0 201.982 2.349 26.431 4.496 44.436 19.371 54.018 44.624 3.133 84.55 3.133 169.101 0 253.651-3.504 26.497-18.38 42.938-44.624 49.321-187.885 2.349-375.773 3.133-563.67 2.349 1.566-4.697 4.697-7.83 9.394-9.394 184.765 0.784 369.523 0 554.275-2.349 17.469-5.933 29.212-17.676 35.229-35.229 4.519-89.553 2.95-178.801-4.697-267.743-7.248-8.821-15.858-15.867-25.835-21.138-76.706-2.348-153.426-3.131-230.165-2.349 3.133-6.263 7.83-10.96 14.092-14.092z","M326.459 683.45c-4.697 7.83-10.96 14.092-18.789 18.789-8.969-6.928-14.45-16.323-16.44-28.183-1.157-15.924-3.506-31.58-7.046-46.972 6.493-92.517 55.031-126.962 145.615-103.339 0 14.092 0 28.183 0 42.275-4.942 0.7-9.639-0.080-14.092-2.349-21.905-41.284-49.305-45.197-82.202-11.743-18.969 42.989-21.318 86.833-7.046 131.523z","M490.862 519.046c-1.564 4.697-4.697 7.83-9.394 9.394-3.706-1.329-6.839-3.678-9.394-7.046 6.074-2.302 12.34-3.086 18.789-2.349z","M547.229 519.046c29.738-2.461 52.445 8.502 68.11 32.881 10.512 37.634-1.231 65.033-35.229 82.202-19.977 0.939-39.551 3.288-58.716 7.046-3.133 21.922-3.133 43.839 0 65.761 5.702 7.276 12.748 12.753 21.138 16.44-23.486 3.133-46.972 3.133-70.459 0 5.068-4.491 10.545-8.403 16.44-11.743 2.349-45.38 3.128-90.788 2.349-136.22 7.83-10.959 17.225-20.353 28.183-28.183 0 26.619 0 53.234 0 79.853 17.723 1.113 34.948-1.235 51.67-7.046 9.333-6.196 14.815-14.806 16.44-25.835 6.158-41.604-11.846-61.957-54.018-61.064 3.133-6.261 7.83-10.959 14.092-14.092z","M692.844 519.046c27.066-1.649 53.68 0.7 79.853 7.046-4.434 12.772-5.998 26.079-4.697 39.927-4.697 0-9.394 0-14.092 0-7.765-33.707-28.118-43.886-61.064-30.532-18.939 21.626-17.375 41.979 4.697 61.064 22.326 11.546 43.464 24.858 63.413 39.927 24.599 57.264 5.81 88.581-56.367 93.945-17.925-3.791-35.934-6.924-54.018-9.394-0.77-12.622 0.014-25.149 2.349-37.578 1.945-2.147 4.293-3.716 7.046-4.697 6.656 15.285 17.615 27.028 32.881 35.229 43.637 7.619 60.078-10.386 49.321-54.018-22.984-18.277-47.254-34.717-72.807-49.321-24.567-39.56-16.741-70.092 23.486-91.596z","M429.798 636.477c4.942-0.7 9.639 0.080 14.092 2.349-5.643 4.242-9.558 9.719-11.743 16.44-0.237 25.882-3.369 50.932-9.394 75.156-6.167-5.040-13.213-8.173-21.138-9.394-20.302 7.238-40.657 8.803-61.064 4.697 1.566-4.697 4.697-7.83 9.394-9.394 41.618 5.364 58.841-11.861 51.67-51.67 7.829-10.959 17.223-20.353 28.183-28.183z"],"attrs":[{"fill":"rgb(255, 65, 37)"},{"fill":"rgb(0, 0, 0)"},{"fill":"rgb(0, 0, 0)"},{"fill":"rgb(10, 10, 10)"},{"fill":"rgb(3, 3, 3)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(5, 5, 5)"},{"fill":"rgb(3, 3, 3)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(2, 2, 2)"}],"width":1071,"isMulticolor":true,"isMulticolor2":false,"grid":0,"tags":["gps_signal_low"],"colorPermutations":{"1101010111111415721312215821312221254255012552541112556537133315551":[{"f":10},{"f":1},{"f":1},{"f":5},{"f":3},{"f":2},{"f":4},{"f":3},{"f":2},{"f":2}]}},"attrs":[{"fill":"rgb(255, 65, 37)"},{"fill":"rgb(0, 0, 0)"},{"fill":"rgb(0, 0, 0)"},{"fill":"rgb(10, 10, 10)"},{"fill":"rgb(3, 3, 3)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(5, 5, 5)"},{"fill":"rgb(3, 3, 3)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(2, 2, 2)"}],"properties":{"order":3,"id":1,"name":"gps_signal_low","prevSize":32,"code":59657,"codes":[59657,59658,59659,59660,59661,59662,59663,59664,59665,59666]},"setIdx":0,"setId":2,"iconIdx":1},{"icon":{"paths":["M476.771 30.532c203.992-13.476 361.35 64.811 472.073 234.862 84.55 161.272 84.55 322.546 0 483.817-130.461 195.927-311.305 268.734-542.532 218.422-195.946-64.418-311.029-198.29-345.248-401.615-15.798-232.312 80.496-399.065 288.881-500.257 41.888-15.544 84.163-27.287 126.826-35.229z","M519.046 119.78c131.297-2.777 241.683 42.629 331.156 136.22 3.119 23.469-7.055 32.081-30.532 25.835-147.423-132.183-308.694-150.972-483.817-56.367-22.969 13.877-43.324 31.1-61.064 51.67-30.235 15.35-41.978 6.738-35.229-25.835 77.581-77.549 170.743-121.39 279.486-131.523z","M504.954 218.422c110.963-8.694 205.688 25.752 284.183 103.339 4.721 17.168-1.541 28.129-18.789 32.881-30.823-26.775-64.489-49.478-100.991-68.11-81.37-34.44-162.788-34.44-244.257 0-35.044 21.006-68.708 43.709-100.991 68.11-11.273-4.217-18.319-12.046-21.138-23.486 29.204-38.979 66.782-67.945 112.734-86.899 29.725-10.931 59.477-19.542 89.248-25.835z","M514.349 302.972c80.403-7.114 148.513 17.937 204.33 75.156 6.247 23.475-2.367 33.652-25.835 30.532-96.19-87.544-193.264-89.11-291.229-4.697-18.12 11.933-30.646 8.019-37.578-11.743 2.069-12.747 8.332-22.925 18.789-30.532 39.091-31.31 82.932-50.882 131.523-58.716z","M260.697 453.284c184.765-0.783 369.523 0 554.275 2.349 26.431 4.496 44.436 19.371 54.018 44.624 4.462 91.056 2.894 181.873-4.697 272.44-9.131 15.191-22.443 25.365-39.927 30.532-191.023 3.133-382.043 3.133-573.064 0-26.722-3.227-43.163-18.098-49.321-44.624-3.132-84.55-3.132-169.101 0-253.651 8.958-30.138 28.53-47.362 58.716-51.67z","M237.211 467.376c197.291-0.783 394.574 0 591.853 2.349 15.294 7.455 25.473 19.198 30.532 35.229 3.133 84.55 3.133 169.101 0 253.651-6.017 17.554-17.76 29.297-35.229 35.229-192.587 3.133-385.174 3.133-577.761 0-17.469-5.933-29.212-17.676-35.229-35.229-3.132-86.115-3.132-172.234 0-258.349 7.828-11.724 16.439-22.683 25.835-32.881z","M345.248 519.046c28.354-0.658 56.537 0.907 84.55 4.697 0 14.092 0 28.183 0 42.275-4.942 0.7-9.639-0.080-14.092-2.349-12.498-31.307-34.419-40.702-65.761-28.183-25.779 24.083-36.739 53.835-32.881 89.248-3.754 35.765 7.207 65.513 32.881 89.248 49.484 5.209 65.924-15.928 49.321-63.413-4.239-5.641-9.72-9.559-16.44-11.743 20.355-3.133 40.71-3.133 61.064 0-5.643 4.242-9.558 9.719-11.743 16.44-1.566 23.486-3.132 46.972-4.697 70.459-1.566 1.564-3.132 3.133-4.697 4.697-6.167-5.040-13.213-8.173-21.138-9.394-77.32 18.3-116.464-13.016-117.431-93.945-1.181-48.762 19.174-84.776 61.064-108.037z","M472.073 523.743c30.471-5.421 61.788-6.205 93.945-2.349 45.267 6.67 61.708 32.505 49.321 77.505-5.482 18.005-17.225 29.748-35.229 35.229-19.977 0.939-39.551 3.288-58.716 7.046-3.133 21.922-3.133 43.839 0 65.761 5.702 7.276 12.748 12.753 21.138 16.44-23.486 3.133-46.972 3.133-70.459 0 5.068-4.491 10.545-8.403 16.44-11.743 3.133-57.931 3.133-115.867 0-173.798-4.88-5.684-10.362-10.381-16.44-14.092z","M692.844 519.046c27.066-1.649 53.68 0.7 79.853 7.046-4.434 12.772-5.998 26.079-4.697 39.927-4.697 0-9.394 0-14.092 0-7.765-33.707-28.118-43.886-61.064-30.532-18.939 21.626-17.375 41.979 4.697 61.064 22.326 11.546 43.464 24.858 63.413 39.927 24.599 57.264 5.81 88.581-56.367 93.945-17.925-3.791-35.934-6.924-54.018-9.394-0.77-12.622 0.014-25.149 2.349-37.578 1.945-2.147 4.293-3.716 7.046-4.697 6.656 15.285 17.615 27.028 32.881 35.229 43.637 7.619 60.078-10.386 49.321-54.018-22.984-18.277-47.254-34.717-72.807-49.321-24.567-39.56-16.741-70.092 23.486-91.596z","M519.046 533.138c58.077-5.369 79.214 19.681 63.413 75.156-17.779 16.271-38.917 22.537-63.413 18.789 0-31.317 0-62.628 0-93.945z"],"attrs":[{"fill":"rgb(22, 158, 213)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(0, 0, 0)"},{"fill":"rgb(5, 5, 5)"},{"fill":"rgb(14, 157, 213)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(3, 3, 3)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(14, 157, 213)"}],"width":1071,"isMulticolor":true,"isMulticolor2":false,"grid":0,"tags":["gps_signal_high"],"colorPermutations":{"1101010111111415721312215821312221254255012552541112556537133315551":[{"f":7},{"f":0},{"f":0},{"f":1},{"f":4},{"f":6},{"f":2},{"f":3},{"f":2},{"f":6}]}},"attrs":[{"fill":"rgb(22, 158, 213)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(0, 0, 0)"},{"fill":"rgb(5, 5, 5)"},{"fill":"rgb(14, 157, 213)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(3, 3, 3)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(14, 157, 213)"}],"properties":{"order":4,"id":0,"name":"gps_signal_high","prevSize":32,"code":59667,"codes":[59667,59668,59669,59670,59671,59672,59673,59674,59675,59676]},"setIdx":0,"setId":2,"iconIdx":2}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon"},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215,"classSelector":".icon"},"historySize":50,"showCodes":false,"gridSize":16,"showLiga":true}} \ No newline at end of file diff --git a/assets/customIcon/icomoon-old/style.css b/assets/customIcon/icomoon-old/style.css new file mode 100644 index 0000000..20b696c --- /dev/null +++ b/assets/customIcon/icomoon-old/style.css @@ -0,0 +1,169 @@ +@font-face { + font-family: 'icomoon'; + src: url('fonts/icomoon.eot?tueyzw'); + src: url('fonts/icomoon.eot?tueyzw#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?tueyzw') format('truetype'), + url('fonts/icomoon.woff?tueyzw') format('woff'), + url('fonts/icomoon.svg?tueyzw#icomoon') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +[class^="icon-"], [class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'icomoon' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-gps_signal_middle .path1:before { + content: "\e900"; + color: rgb(255, 254, 11); +} +.icon-gps_signal_middle .path2:before { + content: "\e901"; + margin-left: -1.0458984375em; + color: rgb(1, 1, 1); +} +.icon-gps_signal_middle .path3:before { + content: "\e902"; + margin-left: -1.0458984375em; + color: rgb(0, 0, 0); +} +.icon-gps_signal_middle .path4:before { + content: "\e903"; + margin-left: -1.0458984375em; + color: rgb(5, 5, 5); +} +.icon-gps_signal_middle .path5:before { + content: "\e904"; + margin-left: -1.0458984375em; + color: rgb(254, 255, 0); +} +.icon-gps_signal_middle .path6:before { + content: "\e905"; + margin-left: -1.0458984375em; + color: rgb(2, 2, 2); +} +.icon-gps_signal_middle .path7:before { + content: "\e906"; + margin-left: -1.0458984375em; + color: rgb(3, 3, 3); +} +.icon-gps_signal_middle .path8:before { + content: "\e907"; + margin-left: -1.0458984375em; + color: rgb(2, 2, 2); +} +.icon-gps_signal_middle .path9:before { + content: "\e908"; + margin-left: -1.0458984375em; + color: rgb(254, 255, 0); +} +.icon-gps_signal_low .path1:before { + content: "\e909"; + color: rgb(255, 65, 37); +} +.icon-gps_signal_low .path2:before { + content: "\e90a"; + margin-left: -1.0458984375em; + color: rgb(0, 0, 0); +} +.icon-gps_signal_low .path3:before { + content: "\e90b"; + margin-left: -1.0458984375em; + color: rgb(0, 0, 0); +} +.icon-gps_signal_low .path4:before { + content: "\e90c"; + margin-left: -1.0458984375em; + color: rgb(10, 10, 10); +} +.icon-gps_signal_low .path5:before { + content: "\e90d"; + margin-left: -1.0458984375em; + color: rgb(3, 3, 3); +} +.icon-gps_signal_low .path6:before { + content: "\e90e"; + margin-left: -1.0458984375em; + color: rgb(2, 2, 2); +} +.icon-gps_signal_low .path7:before { + content: "\e90f"; + margin-left: -1.0458984375em; + color: rgb(5, 5, 5); +} +.icon-gps_signal_low .path8:before { + content: "\e910"; + margin-left: -1.0458984375em; + color: rgb(3, 3, 3); +} +.icon-gps_signal_low .path9:before { + content: "\e911"; + margin-left: -1.0458984375em; + color: rgb(2, 2, 2); +} +.icon-gps_signal_low .path10:before { + content: "\e912"; + margin-left: -1.0458984375em; + color: rgb(2, 2, 2); +} +.icon-gps_signal_high .path1:before { + content: "\e913"; + color: rgb(22, 158, 213); +} +.icon-gps_signal_high .path2:before { + content: "\e914"; + margin-left: -1.0458984375em; + color: rgb(1, 1, 1); +} +.icon-gps_signal_high .path3:before { + content: "\e915"; + margin-left: -1.0458984375em; + color: rgb(1, 1, 1); +} +.icon-gps_signal_high .path4:before { + content: "\e916"; + margin-left: -1.0458984375em; + color: rgb(0, 0, 0); +} +.icon-gps_signal_high .path5:before { + content: "\e917"; + margin-left: -1.0458984375em; + color: rgb(5, 5, 5); +} +.icon-gps_signal_high .path6:before { + content: "\e918"; + margin-left: -1.0458984375em; + color: rgb(14, 157, 213); +} +.icon-gps_signal_high .path7:before { + content: "\e919"; + margin-left: -1.0458984375em; + color: rgb(2, 2, 2); +} +.icon-gps_signal_high .path8:before { + content: "\e91a"; + margin-left: -1.0458984375em; + color: rgb(3, 3, 3); +} +.icon-gps_signal_high .path9:before { + content: "\e91b"; + margin-left: -1.0458984375em; + color: rgb(2, 2, 2); +} +.icon-gps_signal_high .path10:before { + content: "\e91c"; + margin-left: -1.0458984375em; + color: rgb(14, 157, 213); +} diff --git a/assets/customIcon/icomoon.zip b/assets/customIcon/icomoon.zip new file mode 100644 index 0000000..f09032b Binary files /dev/null and b/assets/customIcon/icomoon.zip differ diff --git a/assets/customIcon/icomoon/Read Me.txt b/assets/customIcon/icomoon/Read Me.txt new file mode 100644 index 0000000..723a49e --- /dev/null +++ b/assets/customIcon/icomoon/Read Me.txt @@ -0,0 +1,7 @@ +Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. + +To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/docs/#local-fonts + +You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. + +You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. diff --git a/assets/customIcon/icomoon/demo-files/demo.css b/assets/customIcon/icomoon/demo-files/demo.css new file mode 100644 index 0000000..39b8991 --- /dev/null +++ b/assets/customIcon/icomoon/demo-files/demo.css @@ -0,0 +1,152 @@ +body { + padding: 0; + margin: 0; + font-family: sans-serif; + font-size: 1em; + line-height: 1.5; + color: #555; + background: #fff; +} +h1 { + font-size: 1.5em; + font-weight: normal; +} +small { + font-size: .66666667em; +} +a { + color: #e74c3c; + text-decoration: none; +} +a:hover, a:focus { + box-shadow: 0 1px #e74c3c; +} +.bshadow0, input { + box-shadow: inset 0 -2px #e7e7e7; +} +input:hover { + box-shadow: inset 0 -2px #ccc; +} +input, fieldset { + font-family: sans-serif; + font-size: 1em; + margin: 0; + padding: 0; + border: 0; +} +input { + color: inherit; + line-height: 1.5; + height: 1.5em; + padding: .25em 0; +} +input:focus { + outline: none; + box-shadow: inset 0 -2px #449fdb; +} +.glyph { + font-size: 16px; + width: 15em; + padding-bottom: 1em; + margin-right: 4em; + margin-bottom: 1em; + float: left; + overflow: hidden; +} +.liga { + width: 80%; + width: calc(100% - 2.5em); +} +.talign-right { + text-align: right; +} +.talign-center { + text-align: center; +} +.bgc1 { + background: #f1f1f1; +} +.fgc1 { + color: #999; +} +.fgc0 { + color: #000; +} +p { + margin-top: 1em; + margin-bottom: 1em; +} +.mvm { + margin-top: .75em; + margin-bottom: .75em; +} +.mtn { + margin-top: 0; +} +.mtl, .mal { + margin-top: 1.5em; +} +.mbl, .mal { + margin-bottom: 1.5em; +} +.mal, .mhl { + margin-left: 1.5em; + margin-right: 1.5em; +} +.mhmm { + margin-left: 1em; + margin-right: 1em; +} +.mls { + margin-left: .25em; +} +.ptl { + padding-top: 1.5em; +} +.pbs, .pvs { + padding-bottom: .25em; +} +.pvs, .pts { + padding-top: .25em; +} +.unit { + float: left; +} +.unitRight { + float: right; +} +.size1of2 { + width: 50%; +} +.size1of1 { + width: 100%; +} +.clearfix:before, .clearfix:after { + content: " "; + display: table; +} +.clearfix:after { + clear: both; +} +.hidden-true { + display: none; +} +.textbox0 { + width: 3em; + background: #f1f1f1; + padding: .25em .5em; + line-height: 1.5; + height: 1.5em; +} +#testDrive { + display: block; + padding-top: 24px; + line-height: 1.5; +} +.fs0 { + font-size: 16px; +} +.fs1 { + font-size: 32px; +} + diff --git a/assets/customIcon/icomoon/demo-files/demo.js b/assets/customIcon/icomoon/demo-files/demo.js new file mode 100644 index 0000000..6f45f1c --- /dev/null +++ b/assets/customIcon/icomoon/demo-files/demo.js @@ -0,0 +1,30 @@ +if (!('boxShadow' in document.body.style)) { + document.body.setAttribute('class', 'noBoxShadow'); +} + +document.body.addEventListener("click", function(e) { + var target = e.target; + if (target.tagName === "INPUT" && + target.getAttribute('class').indexOf('liga') === -1) { + target.select(); + } +}); + +(function() { + var fontSize = document.getElementById('fontSize'), + testDrive = document.getElementById('testDrive'), + testText = document.getElementById('testText'); + function updateTest() { + testDrive.innerHTML = testText.value || String.fromCharCode(160); + if (window.icomoonLiga) { + window.icomoonLiga(testDrive); + } + } + function updateSize() { + testDrive.style.fontSize = fontSize.value + 'px'; + } + fontSize.addEventListener('change', updateSize, false); + testText.addEventListener('input', updateTest, false); + testText.addEventListener('change', updateTest, false); + updateSize(); +}()); diff --git a/assets/customIcon/icomoon/demo.html b/assets/customIcon/icomoon/demo.html new file mode 100644 index 0000000..81d22fa --- /dev/null +++ b/assets/customIcon/icomoon/demo.html @@ -0,0 +1,80 @@ + + + + + IcoMoon Demo + + + + + +
+

Font Name: icomoon (Glyphs: 3)

+
+
+

Grid Size: Unknown

+
+
+ + icon-gps_signal_low +
+
+ + +
+
+ liga: + +
+
+
+
+ + icon-gps_signal_middle +
+
+ + +
+
+ liga: + +
+
+
+
+ + icon-gps_signal_high +
+
+ + +
+
+ liga: + +
+
+
+ + +
+

Font Test Drive

+ + +
  +
+
+ +
+

Generated by IcoMoon

+
+ + + + diff --git a/assets/customIcon/icomoon/fonts/icomoon.eot b/assets/customIcon/icomoon/fonts/icomoon.eot new file mode 100644 index 0000000..ef25f06 Binary files /dev/null and b/assets/customIcon/icomoon/fonts/icomoon.eot differ diff --git a/assets/customIcon/icomoon/fonts/icomoon.svg b/assets/customIcon/icomoon/fonts/icomoon.svg new file mode 100644 index 0000000..aeaaed4 --- /dev/null +++ b/assets/customIcon/icomoon/fonts/icomoon.svg @@ -0,0 +1,50 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/customIcon/icomoon/fonts/icomoon.ttf b/assets/customIcon/icomoon/fonts/icomoon.ttf new file mode 100644 index 0000000..f2e8bb4 Binary files /dev/null and b/assets/customIcon/icomoon/fonts/icomoon.ttf differ diff --git a/assets/customIcon/icomoon/fonts/icomoon.woff b/assets/customIcon/icomoon/fonts/icomoon.woff new file mode 100644 index 0000000..4ed8b4e Binary files /dev/null and b/assets/customIcon/icomoon/fonts/icomoon.woff differ diff --git a/assets/customIcon/icomoon/selection.json b/assets/customIcon/icomoon/selection.json new file mode 100644 index 0000000..de89dcd --- /dev/null +++ b/assets/customIcon/icomoon/selection.json @@ -0,0 +1 @@ +{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M4.697 371.083c0-123.694 0-245.040 0-366.385 356.991-0 713.982-0 1070.972-0 0 341.333 0 682.667 0 1024-356.991 0-713.982 0-1070.972 0-0-218.422-0-436.844-0-657.615zM54.676 595.488c8.121 29.927 13.467 60.954 24.814 89.603 73.114 184.603 210.166 292.309 405.758 315.852 266.402 32.066 503.041-151.136 541.779-416.589 26.568-182.062-36.314-334.611-177.010-450.896-110.474-91.306-241.173-124.942-383.508-103.957-127.29 18.767-232.431 79.646-312.212 180.371-88.664 111.94-121.433 240.139-99.62 385.616z","M54.001 593.523c-21.138-143.512 11.63-271.711 100.295-383.651 79.781-100.726 184.922-161.605 312.212-180.371 142.335-20.985 273.035 12.651 383.508 103.957 140.696 116.285 203.578 268.833 177.010 450.896-38.737 265.453-275.376 448.655-541.779 416.589-195.592-23.543-332.644-131.249-405.758-315.852-11.347-28.649-16.693-59.676-25.488-91.568zM493.216 582.955c0 0-1.199 1.237-4.108 3.501-15.138 15.638-30.275 31.276-47.497 49.623-6.517 6.27-13.034 12.539-22.199 19.893-20.165 20.664-40.33 41.329-62.409 64.715-4.767 4.63-9.535 9.261-17.027 14.967-17.098 17.578-34.196 35.156-53.412 55.506-3.397 3.166-6.794 6.333-12.766 10.681-15.251 15.398-30.503 30.797-45.735 46.176 161.781 159.584 456.337 177.301 643.164-16.003 193.416-200.12 149.585-488.401 5.346-626.396-56.661 54.941-113.278 109.839-171.898 167.468-9.587 9.377-19.174 18.754-31.401 29.287-17.031 17.498-34.062 34.995-53.17 55.23-4.967 4.743-9.934 9.485-17.489 15.395-18.525 19.010-37.051 38.020-57.67 59.748-6.536 6.316-13.073 12.632-21.576 19.475-1.581 2.108-3.163 4.216-6.521 9.011-6.080 5.961-12.16 11.922-20.487 18.182-1.455 0.761-2.314 1.951-2.211 3.625 0 0 0.034 0.37-0.935-0.085zM457.349 571.913c0 0-0.712 0.977 2.256-1.469 31.315-31.739 62.63-63.477 97.020-96.279 4.189-4.859 8.378-9.717 15.274-15.85 25.278-25.754 50.555-51.508 78.782-78.569 8.838-9.388 17.677-18.776 29.227-29.44 57.003-56.719 114.006-113.437 173.154-172.291-127.777-109.386-271.643-148.812-430.725-105.427-177.884 48.513-292.038 167.261-331.848 348.107-33.559 152.449 8.712 287.734 114.173 407.397 13.331-16.326 24.48-29.98 38.474-44.797 3.774-2.991 7.548-5.982 14.040-10.238 18.934-19.42 37.868-38.839 59.863-59.322 4.17-4.831 8.339-9.662 15.251-15.732 41.798-42.129 83.596-84.258 125.060-126.090z","M647.731 381.052c-25.278 25.754-50.555 51.508-78.41 76.23-85.383-1.219-168.198-2.262-250.989-1.289-54.246 0.638-104.545 39.561-115.433 92.715-6.635 32.394-3.208 66.74-5.864 100.084-4.093 51.377 6.421 97.585 43.256 135.665-11.149 13.654-22.298 27.308-35.629 43.635-105.46-119.663-147.732-254.948-114.173-407.397 39.81-180.846 153.964-299.594 331.848-348.107 159.081-43.385 302.948-3.959 430.725 105.427-59.149 58.854-116.151 115.572-175.322 171.472-52.719-33.54-106.686-44.272-167.197-35.86-49.736 6.914-90.483 28.306-128.083 58.454-11.706 9.386-24.523 22.034-10.979 38.809 12.334 15.278 25.445 6.171 35.552-3.391 47.505-44.942 103.381-65.857 167.97-54.371 25.104 4.464 48.535 18.337 72.729 27.924z","M706.678 370.357c56.617-54.898 113.234-109.796 169.895-164.737 144.239 137.995 188.070 426.275-5.346 626.396-186.827 193.303-481.382 175.587-643.164 16.003 15.232-15.379 30.484-30.777 48.077-45.472 16.687 3.827 31.019 9.601 45.377 9.666 148.48 0.671 296.967 0.883 445.447 0.224 69.745-0.309 124.21-55.525 125.408-125.287 0.577-33.594 0.331-67.208 0.085-100.81-0.56-76.639-54.843-130.298-131.996-130.73-46.094-0.258-92.187-0.496-138.281-0.743 17.031-17.498 34.062-34.995 53.601-52.013 6.812 3.35 10.774 7.623 15.497 8.774 7.82 1.905 21.142 5.075 23.229 1.883 4.929-7.54 6.615-18.719 5.65-28.014-0.567-5.454-8.732-10.12-13.479-15.14z","M288.683 788.389c17.098-17.578 34.196-35.156 54.055-52.173 25.691 0.936 49.255 4.88 71.414 0.931 43.88-7.82 43.56-9.622 43.559-53.704-0-12.444 1.298-25.097-0.649-37.229-0.782-4.871-8.698-8.597-13.367-12.844 15.138-15.638 30.275-31.276 47.258-47.226 1.845-0.312 3.424-1.259 3.58 1.081 0.156 51.265 0.156 100.191 0.156 149.031 10.116 0 17.547 0 26.374 0 0-12.799 0-24.391 0-35.982 0-12.228 0-24.456 0-35.717 22.424-2.396 42.894-2.377 62.18-7.144 31.628-7.817 48.024-32.983 47.054-66.477-0.906-31.284-17.309-50.117-50.913-57.192-10.573-2.226-21.551-2.53-32.344-3.714 18.525-19.010 37.051-38.020 58.45-56.409 55.319 0.696 107.77 0.285 160.207 0.974 60.482 0.795 106.686 46.886 107.85 107.17 0.605 31.3 0.184 62.62 0.12 93.931-0.152 73.981-44.235 118.075-118.264 118.112-138.552 0.068-277.104 0.207-415.655-0.22-17.027-0.052-34.044-3.385-51.065-5.197zM685.514 536.493c-9.389 11.917-22.983 22.491-27.307 36.027-9.514 29.78 6.364 55.983 37.202 65.543 19.283 5.977 39.588 8.571 59.224 13.52 14.668 3.697 25.308 11.705 25.972 28.854 0.681 17.589-8.526 28.124-23.865 33.383-8.692 2.98-18.715 5.807-27.408 4.348-24.887-4.178-49.4-10.58-78.679-17.143 5.071 9.826 7.662 24.504 15.266 27.874 29.575 13.11 61.062 18.449 93.416 8.941 29.759-8.745 48.729-31.477 48.58-58.593-0.153-27.827-15.741-45.238-49.196-53.785-15.864-4.053-32.090-6.666-48.039-10.411-17.044-4.002-31.058-15.2-27.148-32.21 2.68-11.657 15.84-25.504 27.342-29.37 14.379-4.833 32.251-2.436 47.889 0.59 13.633 2.638 26.26 10.48 39.331 16.022 1.731-1.056 3.462-2.112 5.193-3.168-4.75-9.855-7.118-25.43-14.664-28.573-32.18-13.401-65.977-19.331-103.108-1.85z","M553.549 475.228c-31.315 31.739-62.63 63.477-95.517 94.277 4.048-19.731-3.166-30.462-22.079-34.436-11.364-2.388-22.431-6.401-33.866-8.2-45.115-7.097-87.371 11.223-106.921 45.699-22.276 39.283-18.799 104.031 7.602 135.059 2.749 3.23 7.425 4.821 11.207 7.172-18.934 19.42-37.868 38.839-58.357 57.246-21.783-25.020-39.474-50.197-39.474-83.5 0.001-36.655-0.838-73.334 0.222-109.958 1.634-56.438 47.027-102.894 103.262-103.688 77.956-1.101 155.944 0.109 233.919 0.329z","M287.624 789.775c18.081 0.427 35.098 3.759 52.124 3.812 138.55 0.427 277.103 0.289 415.655 0.22 74.029-0.037 118.112-44.131 118.264-118.112 0.064-31.311 0.485-62.632-0.12-93.931-1.164-60.285-47.368-106.376-107.85-107.17-52.437-0.689-104.888-0.278-158.913-1.557 3.386-5.947 8.354-10.689 14.359-16.801 47.132-1.122 93.226-0.884 139.32-0.626 77.153 0.432 131.437 54.090 131.996 130.73 0.245 33.602 0.492 67.216-0.085 100.81-1.198 69.762-55.663 124.978-125.408 125.287-148.48 0.659-296.967 0.447-445.447-0.224-14.358-0.065-28.691-5.839-44.090-10.257 2.342-4.461 5.739-7.628 10.196-12.179z","M649.206 380.399c-25.668-8.934-49.099-22.807-74.203-27.271-64.589-11.486-120.465 9.429-167.97 54.371-10.107 9.562-23.218 18.669-35.552 3.391-13.543-16.775-0.727-29.423 10.979-38.809 37.6-30.148 78.346-51.54 128.083-58.454 60.511-8.412 114.478 2.32 165.841 36.498-8.027 10.846-16.865 20.234-27.178 30.275z","M457.684 571.617c-41.798 42.129-83.596 84.258-127.487 125.542-26.108-30.178-27.734-63.581-17.936-98.031 8.9-31.292 33.578-51.725 65.771-48.569 26.979 2.645 53.133 13.717 79.652 21.058z","M555.087 474.696c-79.512 0.311-157.501-0.898-235.457 0.203-56.235 0.794-101.629 47.25-103.262 103.688-1.060 36.624-0.222 73.304-0.222 109.958-0.001 33.303 17.69 58.48 38.115 84.132-3.578 4.636-7.352 7.627-12.548 11.199-38.258-37.499-48.771-83.707-44.678-135.084 2.656-33.344-0.771-67.69 5.864-100.084 10.887-53.154 61.187-92.077 115.433-92.715 82.792-0.973 165.607 0.070 249.637 1.926-2.965 6.528-7.154 11.386-12.881 16.777z","M545.992 531.388c11.84-0.175 22.818 0.129 33.391 2.355 33.604 7.075 50.007 25.908 50.913 57.192 0.97 33.494-15.425 58.66-47.054 66.477-19.286 4.767-39.756 4.748-62.18 7.144 0 11.261 0 23.489 0 35.717 0 11.592 0 23.183 0 35.982-8.826 0-16.258 0-26.374 0 0-48.84 0-97.766-0.001-149.426-0.001-2.734-0.505-3.42-0.505-3.42s-0.034-0.37 1.059-0.011c1.85-1.188 2.607-2.735 3.365-4.282 6.080-5.961 12.16-11.922 20.248-17.625 2.008 25.676 2.008 51.094 2.008 77.932 17.043-0.788 31.148-0.143 44.792-2.347 26.442-4.272 39.396-20.134 38.472-44.438-0.849-22.312-15.605-35.775-42.153-38.187-12.196-1.108-24.425-1.849-36.638-2.756 6.536-6.316 13.073-12.632 20.656-20.308z","M457.516 571.765c-26.352-7.489-52.505-18.561-79.484-21.206-32.194-3.157-56.871 17.277-65.771 48.569-9.799 34.449-8.172 67.852 16.564 98.65-3.448 6.296-7.618 11.127-13.318 16.489-5.312-1.82-9.989-3.41-12.737-6.64-26.401-31.028-29.878-95.776-7.602-135.059 19.55-34.476 61.806-52.796 106.921-45.699 11.434 1.799 22.502 5.812 33.866 8.2 18.913 3.974 26.127 14.705 20.595 35.66 0.088 2.163 0.8 1.185 0.968 1.037z","M524.353 551.959c13.198 0.644 25.427 1.385 37.622 2.493 26.548 2.411 41.304 15.875 42.153 38.187 0.924 24.304-12.030 40.166-38.472 44.438-13.644 2.204-27.749 1.559-44.792 2.347 0-26.838 0-52.255-1.119-79.275 0.462-3.71 2.043-5.818 4.608-8.19z","M442.653 634.725c5.711 2.892 13.627 6.618 14.409 11.489 1.947 12.132 0.649 24.785 0.649 37.229 0.001 44.083 0.322 45.885-43.559 53.704-22.158 3.949-45.723 0.005-70.051-1.469 3.368-5.73 8.136-10.36 15.853-16.702 19.909-2.381 37.037-1.881 53.745-4.292 6.746-0.974 18.282-9.024 17.833-12.635-1.964-15.793 11.32-35.329-9.472-47.16 6.517-6.27 13.034-12.539 20.593-20.164z","M420.736 655.43c22.116 11.289 8.832 30.825 10.796 46.618 0.449 3.61-11.087 11.661-17.833 12.635-16.708 2.411-33.836 1.911-52.788 2.931 18.172-20.313 38.337-40.978 59.825-62.184z","M705.677 371.722c5.749 3.655 13.914 8.32 14.481 13.775 0.965 9.296-0.721 20.474-5.65 28.014-2.087 3.192-15.409 0.021-23.229-1.883-4.723-1.15-8.685-5.423-14.177-9.351 8.399-10.435 17.986-19.812 28.575-30.555z","M497.484 579.266c0.366 1.398-0.391 2.945-2.425 4.106-1.012-2.006-0.153-3.196 2.425-4.106z","M493.7 583.183c0.484 0.227 0.988 0.913 0.833 1.308s-1.735 1.341-2.125 0.521c-0.39-0.82 0.808-2.057 1.292-1.829z","M687.197 535.787c35.449-16.776 69.246-10.846 101.426 2.555 7.546 3.142 9.914 18.717 14.664 28.573-1.731 1.056-3.462 2.112-5.193 3.168-13.071-5.543-25.698-13.385-39.331-16.022-15.639-3.026-33.51-5.423-47.889-0.59-11.501 3.866-24.662 17.713-27.342 29.37-3.91 17.009 10.104 28.208 27.148 32.21 15.949 3.745 32.176 6.358 48.039 10.411 33.454 8.548 49.042 25.958 49.196 53.785 0.149 27.116-18.82 49.848-48.58 58.593-32.354 9.507-63.842 4.168-93.416-8.941-7.604-3.37-10.195-18.049-15.266-27.874 29.28 6.563 53.793 12.965 78.679 17.143 8.693 1.459 18.716-1.368 27.408-4.348 15.339-5.259 24.545-15.794 23.865-33.383-0.664-17.149-11.304-25.157-25.972-28.854-19.636-4.949-39.941-7.543-59.224-13.52-30.839-9.56-46.717-35.762-37.202-65.543 4.324-13.535 17.918-24.109 28.99-36.732z"],"attrs":[{"fill":"rgb(255, 253, 253)"},{"fill":"rgb(255, 37, 4)"},{"fill":"rgb(255, 253, 253)"},{"fill":"rgb(255, 252, 252)"},{"fill":"rgb(251, 251, 251)"},{"fill":"rgb(252, 250, 250)"},{"fill":"rgb(5, 5, 5)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(250, 248, 248)"},{"fill":"rgb(6, 6, 6)"},{"fill":"rgb(10, 10, 10)"},{"fill":"rgb(12, 12, 12)"},{"fill":"rgb(251, 251, 251)"},{"fill":"rgb(10, 10, 10)"},{"fill":"rgb(250, 238, 238)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(113, 106, 106)"},{"fill":"rgb(113, 106, 106)"},{"fill":"rgb(12, 12, 12)"}],"width":1071,"isMulticolor":true,"isMulticolor2":false,"grid":0,"tags":["gps_signal_low"]},"attrs":[{"fill":"rgb(255, 253, 253)"},{"fill":"rgb(255, 37, 4)"},{"fill":"rgb(255, 253, 253)"},{"fill":"rgb(255, 252, 252)"},{"fill":"rgb(251, 251, 251)"},{"fill":"rgb(252, 250, 250)"},{"fill":"rgb(5, 5, 5)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(250, 248, 248)"},{"fill":"rgb(6, 6, 6)"},{"fill":"rgb(10, 10, 10)"},{"fill":"rgb(12, 12, 12)"},{"fill":"rgb(251, 251, 251)"},{"fill":"rgb(10, 10, 10)"},{"fill":"rgb(250, 238, 238)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(113, 106, 106)"},{"fill":"rgb(113, 106, 106)"},{"fill":"rgb(12, 12, 12)"}],"properties":{"order":2,"id":2,"name":"gps_signal_low","prevSize":32,"code":59648,"codes":[59648,59649,59650,59651,59652,59653,59654,59655,59656,59657,59658,59659,59660,59661,59662,59663,59664,59665,59666]},"setIdx":0,"setId":0,"iconIdx":0},{"icon":{"paths":["M601.248 1028.697c-200.416 0-398.483 0-596.55 0-0-341.333-0-682.667-0-1024 356.991-0 713.982-0 1070.972-0 0 341.333 0 682.666 0 1024-157.358 0-314.716 0-474.422 0zM992.795 670.585c26.319-76.179 33.921-154.172 18.963-233.408-47.787-253.137-266.071-416.098-522.563-391.022-277.053 27.086-471.494 299.456-412.78 571.963 40.134 186.274 229.787 382.491 455.627 363.915 38.042-3.129 77.887 4.194 114.39-4.212 168.807-38.872 283.272-142.1 346.364-307.236z","M992.239 672.346c-62.537 163.374-177.001 266.603-345.809 305.475-36.502 8.406-76.348 1.083-114.39 4.212-225.84 18.576-415.492-177.641-455.627-363.915-58.714-272.507 135.727-544.877 412.78-571.963 256.493-25.076 474.776 137.885 522.563 391.022 14.958 79.236 7.356 157.229-19.519 235.17zM664.661 812.673c34.442-0.014 68.894 0.519 103.322-0.157 68.659-1.348 123.080-55.748 124.381-124.386 0.682-35.994 0.692-72.023-0.003-108.016-1.326-68.636-55.819-123.928-124.405-124.27-148.722-0.741-297.452-0.743-446.174 0.001-68.563 0.343-123.086 55.742-124.408 124.298-0.634 32.864-0.22 65.75-0.12 98.626 0.245 80.241 53.727 133.784 133.948 133.873 109.587 0.121 219.175 0.031 333.459 0.031zM682.018 249.234c-58.826-19.859-118.173-30.421-180.832-19.534-68.359 11.877-127.277 40.675-177.552 87.976-10.499 9.878-22.386 19.995-6.634 34.441 13.496 12.377 25.599 7.537 37.361-4.155 89.111-88.585 230.179-104.762 339.503-45.382 25.276 13.729 47.029 33.919 70.515 50.983 10.067 7.314 20.821 8.483 29.222-1.699 8.968-10.869 5.112-20.28-4.318-30.329-29.478-31.414-65.62-52.506-107.267-72.301zM643.5 331.137c-28.682-7.041-57.115-18.587-86.096-20.186-60.451-3.335-115.995 13.125-163.652 53.267-26.11 21.993-34.912 35.592-22.149 46.667 22.561 19.577 33.74-3.52 47.501-15.060 37.726-31.635 82.827-48.181 130.31-46.809 53.545 1.548 101.016 25.184 141.615 61.611 5.127 4.6 16.988 1.697 25.729 2.27 0.367-10.311 5.19-25.976 0.338-30.014-22.038-18.339-46.802-33.401-73.596-51.747z","M662.312 812.673c-111.936-0-221.523 0.091-331.111-0.031-80.221-0.089-133.702-53.632-133.948-133.873-0.1-32.876-0.514-65.762 0.12-98.626 1.322-68.556 55.846-123.955 124.408-124.298 148.722-0.745 297.452-0.743 446.174-0.001 68.586 0.342 123.079 55.634 124.405 124.27 0.695 35.994 0.685 72.022 0.003 108.016-1.301 68.638-55.722 123.038-124.381 124.386-34.428 0.676-68.881 0.143-105.671 0.157zM368.775 793.819c130.586-0.001 261.173 0.164 391.759-0.071 67.376-0.121 112.735-45.978 113.104-113.691 0.179-32.841 0.547-65.694-0.096-98.524-1.159-59.195-46.848-106.465-105.64-106.775-148.567-0.785-297.144-0.749-445.711-0.025-57.945 0.282-104.202 46.517-105.834 104.128-1.040 36.722-0.916 73.515-0.024 110.244 1.173 48.284 33.133 89.407 80.327 100.115 21.711 4.926 44.915 3.271 72.114 4.599z","M683.667 250.093c39.997 18.936 76.139 40.028 105.617 71.442 9.43 10.049 13.285 19.46 4.318 30.329-8.401 10.182-19.155 9.012-29.222 1.699-23.486-17.063-45.239-37.254-70.515-50.983-109.325-59.38-250.392-43.203-339.503 45.382-11.762 11.692-23.865 16.532-37.361 4.155-15.752-14.446-3.865-24.563 6.634-34.441 50.274-47.301 109.193-76.099 177.552-87.976 62.658-10.887 122.006-0.326 182.481 20.393z","M645.005 332.189c25.288 17.293 50.053 32.356 72.090 50.695 4.853 4.038 0.030 19.703-0.338 30.014-8.741-0.573-20.601 2.331-25.729-2.27-40.6-36.427-88.071-60.063-141.615-61.611-47.484-1.373-92.584 15.174-130.31 46.809-13.761 11.54-24.94 34.636-47.501 15.060-12.763-11.075-3.961-24.674 22.149-46.667 47.658-40.143 103.201-56.602 163.652-53.267 28.982 1.599 57.415 13.145 87.602 21.238z","M366.439 793.819c-24.863-1.329-48.067 0.327-69.778-4.599-47.194-10.708-79.155-51.831-80.327-100.115-0.892-36.73-1.016-73.522 0.024-110.244 1.632-57.611 47.889-103.846 105.834-104.128 148.568-0.724 297.144-0.76 445.711 0.025 58.792 0.31 104.481 47.581 105.64 106.775 0.642 32.83 0.275 65.683 0.096 98.524-0.369 67.713-45.728 113.57-113.104 113.691-130.586 0.235-261.173 0.070-394.095 0.071zM410.844 657.647c6.827 0.309 13.654 0.619 22.056 1 0 14.41 0.614 27.571-0.338 40.618-0.327 4.485-3.472 10.549-7.185 12.728-40.471 23.743-102.254 0.921-113.308-44.194-5.174-21.115-4.564-45.116-0.526-66.677 6.082-32.478 33.028-52.991 66.314-50.82 26.181 1.708 51.959 9.592 78.275 14.804 5.308-16.108-3.216-24.293-20.19-29.897-58.040-19.164-113.328-6.091-138.535 34.017-26.323 41.883-21.286 111.849 11.512 144.489 36.008 35.836 89.26 35.018 139.289 12.143 3.876-1.772 8.115-7.772 8.234-11.92 0.748-26.179 0.379-52.389 0.379-78.263-27.606 0-52.162 0-77.875 0 0.878 8.232 1.576 14.768 2.345 21.973 9.523 0 17.283 0 29.554 0zM657.369 573.207c-1.997 41.811 9.834 57.724 50.995 67.96 17.323 4.308 35.697 5.977 51.802 12.971 8.886 3.859 18.649 15.769 19.638 24.952 1.019 9.461-5.876 22.55-13.498 29.368-7.288 6.519-20.319 10.495-30.202 9.485-26.452-2.705-52.568-8.684-81.127-13.773 1.527 5.9 0.893 18.391 6.62 23.030 27.401 22.194 96.867 19.471 124.106-3.414 16.705-14.035 24.667-31.973 20.79-54.121-3.782-21.604-17.421-34.603-37.705-40.392-17.18-4.903-34.768-8.413-52.239-12.252-20.136-4.424-36.857-13.551-34.112-36.974 2.673-22.812 21.442-31.392 41.201-29.732 24.914 2.094 49.415 9.105 74.859 14.146 5.42-15.393 0.070-27.113-21.212-30.435-11.497-1.795-22.617-6.101-34.125-7.704-38.083-5.303-68.182 10.241-85.791 46.885zM621.648 561.187c-33.585-44.783-81.323-26.053-124.295-30.444 0 70.19 0 137.675 0 205.577 8.016 0 14.624 0 23.448 0 0-24.832 0-48.61 0-72.033 97.387-10.277 124.705-37.339 100.847-103.1z","M408.589 657.647c-10.016 0-17.776 0-27.299 0-0.769-7.204-1.466-13.74-2.345-21.973 25.713 0 50.269 0 77.875 0 0 25.874 0.369 52.085-0.379 78.263-0.118 4.148-4.358 10.148-8.234 11.92-50.029 22.875-103.28 23.693-139.289-12.143-32.798-32.641-37.836-102.606-11.512-144.489 25.207-40.108 80.495-53.181 138.535-34.017 16.974 5.605 25.498 13.789 20.19 29.897-26.315-5.212-52.093-13.096-78.275-14.804-33.286-2.171-60.232 18.342-66.314 50.82-4.038 21.56-4.648 45.561 0.526 66.677 11.054 45.115 72.837 67.938 113.308 44.194 3.714-2.179 6.858-8.243 7.185-12.728 0.952-13.047 0.338-26.208 0.338-40.618-8.402-0.381-15.229-0.69-24.311-1z","M657.702 571.307c17.275-34.745 47.374-50.289 85.457-44.986 11.509 1.603 22.628 5.909 34.125 7.704 21.282 3.322 26.632 15.042 21.212 30.435-25.444-5.041-49.944-12.052-74.859-14.146-19.759-1.66-38.528 6.92-41.201 29.732-2.745 23.423 13.976 32.549 34.112 36.974 17.471 3.839 35.059 7.348 52.239 12.252 20.284 5.789 33.923 18.788 37.705 40.392 3.877 22.148-4.085 40.086-20.79 54.121-27.239 22.885-96.705 25.608-124.106 3.414-5.727-4.639-5.093-17.13-6.62-23.030 28.558 5.089 54.675 11.068 81.127 13.773 9.883 1.011 22.914-2.966 30.202-9.485 7.622-6.818 14.516-19.906 13.498-29.368-0.989-9.183-10.752-21.093-19.638-24.952-16.105-6.994-34.478-8.663-51.802-12.971-41.16-10.236-52.992-26.149-50.661-69.859z","M622.778 562.655c22.728 64.292-4.59 91.354-101.976 101.631 0 23.423 0 47.201 0 72.033-8.824 0-15.432 0-23.448 0 0-67.902 0-135.386 0-205.577 42.971 4.391 90.71-14.339 125.424 31.912zM604.49 587.095c-12.204-31.237-39.004-40.681-82.929-29.447 0 26.401 0 52.698 0 78.832 58.152 8.154 79.432-3.716 82.929-49.386z","M604.637 588.908c-3.644 43.857-24.924 55.727-83.076 47.573 0-26.134 0-52.432 0-78.832 43.925-11.234 70.725-1.79 83.076 31.26z"],"attrs":[{"fill":"rgb(255, 255, 254)"},{"fill":"rgb(255, 255, 1)"},{"fill":"rgb(6, 6, 0)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(251, 251, 0)"},{"fill":"rgb(11, 11, 4)"},{"fill":"rgb(13, 13, 4)"},{"fill":"rgb(11, 11, 4)"},{"fill":"rgb(251, 251, 0)"}],"width":1071,"isMulticolor":true,"isMulticolor2":false,"grid":0,"tags":["gps_signal_middle"]},"attrs":[{"fill":"rgb(255, 255, 254)"},{"fill":"rgb(255, 255, 1)"},{"fill":"rgb(6, 6, 0)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(251, 251, 0)"},{"fill":"rgb(11, 11, 4)"},{"fill":"rgb(13, 13, 4)"},{"fill":"rgb(11, 11, 4)"},{"fill":"rgb(251, 251, 0)"}],"properties":{"order":3,"id":1,"name":"gps_signal_middle","prevSize":32,"code":59667,"codes":[59667,59668,59669,59670,59671,59672,59673,59674,59675,59676]},"setIdx":0,"setId":0,"iconIdx":1},{"icon":{"paths":["M634.128 1028.697c-211.376 0-420.404 0-629.431 0-0-341.333-0-682.667-0-1024 356.991-0 713.982-0 1070.972-0 0 341.333 0 682.666 0 1024-146.398 0-292.795 0-441.541 0zM1018.959 501.469c-2.949-21.188-6.17-42.343-8.797-63.572-7.842-63.369-29.651-122.3-64.221-175.103-52.965-80.9-122.179-144.268-212.401-183.329-93.652-40.546-190.908-50.996-289.73-30.777-107.563 22.007-197.921 76.162-268.048 162.18-91.023 111.65-127.203 239.557-104.177 380.846 18.831 115.543 74.43 213.335 166.396 288.16 104.879 85.331 223.578 123.823 359.421 107.232 127.561-15.579 230.911-75.57 312.898-171.565 75.519-88.421 105.49-194.946 108.657-314.073z","M1019.106 503.618c-3.315 116.979-33.286 223.503-108.805 311.924-81.987 95.995-185.338 155.986-312.898 171.565-135.842 16.591-254.542-21.901-359.421-107.232-91.966-74.825-147.566-172.617-166.396-288.16-23.027-141.289 13.153-269.197 104.177-380.846 70.127-86.018 160.485-140.173 268.048-162.18 98.822-20.219 196.077-9.769 289.73 30.777 90.221 39.061 159.436 102.428 212.401 183.329 34.569 52.803 56.378 111.734 64.221 175.103 2.627 21.228 5.847 42.383 8.944 65.72zM799.743 807.979c62.703-22.243 92.613-64.375 92.766-130.838 0.074-32.063 0.444-64.135-0.102-96.189-1.189-69.756-55.905-124.893-125.817-125.173-147.803-0.59-295.611-0.59-443.413-0-69.904 0.279-124.673 55.467-125.831 125.172-0.533 32.055-0.164 64.126-0.096 96.189 0.174 82.015 53.086 135.436 134.513 135.5 141.549 0.11 283.097 0.132 424.645-0.139 13.163-0.025 26.322-2.415 43.334-4.522zM845.943 290.783c19.664-10.476 16.044-24.67 4.691-38.181-6.827-8.124-16.424-13.898-24.667-20.858-102.658-86.687-220.311-118.998-352.495-95.284-83.844 15.042-156.91 52.061-217.902 112.387-11.64 11.513-17.353 25.465-7.901 36.833 10.802 12.99 25.375 4.53 36.919-4.494 1.834-1.434 3.342-3.283 5.018-4.923 60.514-59.235 134.53-92.844 217.163-101.47 109.754-11.457 209.174 18.078 293.803 91.941 11.899 10.386 27.653 16.356 45.371 24.049zM775.039 307.636c-78.335-64.668-166.752-92.678-268.706-78.131-71.31 10.174-131.594 40.482-183.638 89.055-10.127 9.451-20.415 19.409-6.217 33.024 13.592 13.034 25.731 7.147 37.973-3.565 13.894-12.158 27.903-24.545 43.376-34.475 61.476-39.454 128.107-52.37 200.921-41.953 56.244 8.047 104.338 30.4 146.366 67.287 13.962 12.254 29.686 30.189 46.735 14.515 19.369-17.807-2.919-30.777-16.81-45.756zM399.999 413.2c4.109-3.525 8.218-7.050 12.327-10.576 55.468-47.602 118.517-67.82 188.788-42.195 32.254 11.762 60.401 34.606 90.859 51.624 5.929 3.313 16.294 6.3 20.4 3.423 5.732-4.015 9.759-13.546 10.223-20.983 0.345-5.545-4.97-12.755-9.708-17.108-71.283-65.488-154.002-84.582-245.127-52.948-31.757 11.024-60.431 32.244-88.491 51.721-7.438 5.163-7.376 21.133-10.757 32.141 9.337 1.91 18.674 3.821 31.487 4.902z","M797.817 808.386c-15.087 1.699-28.245 4.090-41.408 4.115-141.548 0.271-283.097 0.249-424.645 0.139-81.427-0.063-134.339-53.485-134.513-135.5-0.068-32.063-0.437-64.134 0.096-96.189 1.159-69.705 55.927-124.893 125.831-125.172 147.803-0.59 295.611-0.59 443.413 0 69.912 0.279 124.629 55.417 125.817 125.173 0.546 32.054 0.176 64.126 0.102 96.189-0.153 66.463-30.063 108.596-94.692 131.246zM462.679 793.82c100.986-0.005 201.975 0.427 302.958-0.18 60.49-0.363 106.842-46.898 107.908-107.238 0.608-34.434 0.52-68.894 0.025-103.332-0.887-61.658-47.25-108.284-109.092-108.475-146.39-0.453-292.783-0.452-439.173-0.001-61.838 0.191-108.207 46.807-109.117 108.458-0.496 33.655-0.422 67.327-0.021 100.984 0.751 63.060 47.040 109.14 110.297 109.701 43.836 0.389 87.678 0.079 136.214 0.082z","M844.068 290.944c-15.844-7.853-31.597-13.823-43.497-24.209-84.628-73.863-184.049-103.398-293.803-91.941-82.633 8.626-156.65 42.235-217.163 101.47-1.675 1.64-3.184 3.489-5.018 4.923-11.544 9.024-26.117 17.485-36.919 4.494-9.453-11.368-3.739-25.32 7.901-36.833 60.992-60.327 134.058-97.345 217.902-112.387 132.184-23.715 249.838 8.597 352.495 95.284 8.242 6.96 17.84 12.734 24.667 20.858 11.353 13.51 14.973 27.705-6.565 38.341z","M776.248 308.845c12.682 13.769 34.97 26.74 15.6 44.547-17.049 15.674-32.774-2.26-46.735-14.515-42.028-36.887-90.122-59.24-146.366-67.287-72.815-10.418-139.446 2.499-200.921 41.953-15.472 9.93-29.482 22.317-43.376 34.475-12.242 10.712-24.38 16.599-37.973 3.565-14.197-13.615-3.909-23.572 6.217-33.024 52.044-48.573 112.328-78.88 183.638-89.055 101.954-14.546 190.371 13.463 269.916 79.34z","M398.261 413.615c-11.075-1.496-20.412-3.406-29.749-5.317 3.381-11.008 3.319-26.978 10.757-32.141 28.060-19.476 56.734-40.696 88.491-51.721 91.125-31.634 173.844-12.54 245.127 52.948 4.738 4.353 10.054 11.563 9.708 17.108-0.463 7.438-4.491 16.968-10.223 20.983-4.106 2.876-14.471-0.111-20.4-3.423-30.457-17.017-58.605-39.862-90.859-51.624-70.271-25.626-133.319-5.407-188.788 42.195-4.109 3.526-8.218 7.051-14.065 10.991z","M460.33 793.82c-46.188-0.003-90.030 0.307-133.865-0.082-63.258-0.561-109.546-46.641-110.297-109.701-0.401-33.657-0.475-67.329 0.021-100.984 0.909-61.651 47.278-108.267 109.117-108.458 146.39-0.451 292.783-0.452 439.173 0.001 61.841 0.191 108.205 46.817 109.092 108.475 0.495 34.438 0.584 68.898-0.025 103.332-1.066 60.34-47.418 106.875-107.908 107.238-100.983 0.607-201.972 0.174-305.307 0.18zM340.633 734.174c35.799 14.349 70.394 7.23 104.144-6.899 5.113-2.141 11.149-9.453 11.386-14.62 1.171-25.462 0.532-51.008 0.532-76.842-27.198 0-51.657 0-75.875 0 0 7.697 0 13.674 0 22.485 17.769 0 34.459 0 52.007 0 0 14.544 1.262 26.438-0.504 37.863-0.961 6.217-5.759 14.609-11.061 16.943-38.518 16.957-84.093 3.123-102.989-30.044-21.968-38.559-13.994-97.141 18.296-119.752 10.837-7.589 26.101-13.677 38.802-12.783 26.576 1.871 52.794 8.84 80.39 13.943 4.744-12.517 0.763-23.549-16.791-27.935-10.55-2.636-21.136-5.176-31.792-7.328-55.339-11.175-102.859 12.441-119.261 60.624-15.491 45.51-9.434 119.357 52.716 144.345zM661.318 562.453c-12.57 40.998 1.507 66.808 42.796 77.559 18.777 4.889 38.636 6.857 56.313 14.213 8.794 3.66 18.328 16.050 19.211 25.287 0.905 9.46-6.123 22.435-13.802 29.166-7.362 6.452-20.389 10.329-30.272 9.265-26.437-2.848-52.522-8.954-81.688-14.305 2.368 7.142 2.569 19.328 8.532 23.757 27.572 20.48 90.308 19.552 118.496-0.039 18.721-13.012 28.751-30.903 26.118-54.048-2.676-23.529-16.679-38.243-39.23-44.283-19.539-5.233-39.435-9.122-59.019-14.2-15.932-4.132-26.398-14.267-26.752-31.326-0.367-17.68 11.667-26.503 26.824-31.211 6.559-2.037 14.138-3.582 20.689-2.35 22.963 4.317 45.681 9.94 71.626 15.771-2.892-8.827-3.314-22.748-8.993-25.396-36.951-17.233-99.421-26.774-130.849 22.141zM623.879 624.249c14.226-41.402 3.962-76.291-30.808-85.39-30.58-8.002-63.426-7.345-95.358-10.52 0 73.371 0 140.308 0 207.64 8.016 0 14.555 0 23.827 0 0-25.128 0-48.859 0-72.994 39.077-1.297 77.422 2.354 102.339-38.736z","M338.867 733.605c-60.385-24.419-66.442-98.266-50.95-143.776 16.401-48.183 63.922-71.799 119.261-60.624 10.657 2.152 21.243 4.692 31.792 7.328 17.554 4.386 21.535 15.418 16.791 27.935-27.596-5.103-53.814-12.071-80.39-13.943-12.701-0.894-27.964 5.194-38.802 12.783-32.29 22.611-40.264 81.194-18.296 119.752 18.897 33.168 64.471 47.001 102.989 30.044 5.302-2.334 10.1-10.726 11.061-16.943 1.766-11.425 0.504-23.319 0.504-37.863-17.548 0-34.239 0-52.007 0 0-8.811 0-14.788 0-22.485 24.218 0 48.676 0 75.875 0 0 25.834 0.639 51.38-0.532 76.842-0.238 5.167-6.273 12.479-11.386 14.62-33.75 14.129-68.344 21.248-105.909 6.329z","M662.091 560.759c30.656-47.221 93.125-37.68 130.076-20.448 5.679 2.648 6.101 16.569 8.993 25.396-25.945-5.831-48.663-11.454-71.626-15.771-6.551-1.232-14.13 0.312-20.689 2.35-15.157 4.708-27.191 13.531-26.824 31.211 0.354 17.060 10.82 27.195 26.752 31.326 19.584 5.079 39.48 8.967 59.019 14.2 22.551 6.040 36.554 20.754 39.23 44.283 2.633 23.145-7.397 41.036-26.118 54.048-28.188 19.592-90.924 20.519-118.496 0.039-5.963-4.429-6.164-16.614-8.532-23.757 29.166 5.351 55.252 11.457 81.688 14.305 9.883 1.065 22.911-2.812 30.272-9.265 7.68-6.731 14.707-19.706 13.802-29.166-0.883-9.236-10.418-21.627-19.211-25.287-17.677-7.356-37.536-9.324-56.313-14.213-41.289-10.751-55.366-36.561-42.024-79.253z","M622.875 625.792c-23.913 39.547-62.258 35.895-101.335 37.193 0 24.135 0 47.866 0 72.994-9.272 0-15.811 0-23.827 0 0-67.332 0-134.269 0-207.64 31.933 3.176 64.779 2.518 95.358 10.52 34.77 9.099 45.034 43.987 29.804 86.933zM571.737 555.427c-16.744 0-33.488 0-49.651 0 0 28.983 0 55.24 0 81.277 50.406 6.214 72.301-1.84 80.559-28.949 7.077-23.233-1.362-39.302-30.908-52.328z","M573.663 555.832c27.62 12.621 36.059 28.689 28.982 51.922-8.258 27.108-30.152 35.162-80.559 28.949 0-26.037 0-52.294 0-81.277 16.163 0 32.907 0 51.576 0.406z"],"attrs":[{"fill":"rgb(254, 255, 255)"},{"fill":"rgb(70, 248, 5)"},{"fill":"rgb(2, 5, 0)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(69, 244, 5)"},{"fill":"rgb(6, 11, 4)"},{"fill":"rgb(6, 12, 4)"},{"fill":"rgb(5, 11, 4)"},{"fill":"rgb(69, 244, 5)"}],"width":1071,"isMulticolor":true,"isMulticolor2":false,"grid":0,"tags":["gps_signal_high"]},"attrs":[{"fill":"rgb(254, 255, 255)"},{"fill":"rgb(70, 248, 5)"},{"fill":"rgb(2, 5, 0)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(2, 2, 2)"},{"fill":"rgb(1, 1, 1)"},{"fill":"rgb(69, 244, 5)"},{"fill":"rgb(6, 11, 4)"},{"fill":"rgb(6, 12, 4)"},{"fill":"rgb(5, 11, 4)"},{"fill":"rgb(69, 244, 5)"}],"properties":{"order":4,"id":0,"name":"gps_signal_high","prevSize":32,"code":59677,"codes":[59677,59678,59679,59680,59681,59682,59683,59684,59685,59686,59687]},"setIdx":0,"setId":0,"iconIdx":2}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showCodes":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon"},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215},"historySize":50}} \ No newline at end of file diff --git a/assets/customIcon/icomoon/style.css b/assets/customIcon/icomoon/style.css new file mode 100644 index 0000000..181312b --- /dev/null +++ b/assets/customIcon/icomoon/style.css @@ -0,0 +1,224 @@ +@font-face { + font-family: 'icomoon'; + src: url('fonts/icomoon.eot?eq8f84'); + src: url('fonts/icomoon.eot?eq8f84#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?eq8f84') format('truetype'), + url('fonts/icomoon.woff?eq8f84') format('woff'), + url('fonts/icomoon.svg?eq8f84#icomoon') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +[class^="icon-"], [class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'icomoon' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-gps_signal_low .path1:before { + content: "\e900"; + color: rgb(255, 253, 253); +} +.icon-gps_signal_low .path2:before { + content: "\e901"; + margin-left: -1.0458984375em; + color: rgb(255, 37, 4); +} +.icon-gps_signal_low .path3:before { + content: "\e902"; + margin-left: -1.0458984375em; + color: rgb(255, 253, 253); +} +.icon-gps_signal_low .path4:before { + content: "\e903"; + margin-left: -1.0458984375em; + color: rgb(255, 252, 252); +} +.icon-gps_signal_low .path5:before { + content: "\e904"; + margin-left: -1.0458984375em; + color: rgb(251, 251, 251); +} +.icon-gps_signal_low .path6:before { + content: "\e905"; + margin-left: -1.0458984375em; + color: rgb(252, 250, 250); +} +.icon-gps_signal_low .path7:before { + content: "\e906"; + margin-left: -1.0458984375em; + color: rgb(5, 5, 5); +} +.icon-gps_signal_low .path8:before { + content: "\e907"; + margin-left: -1.0458984375em; + color: rgb(1, 1, 1); +} +.icon-gps_signal_low .path9:before { + content: "\e908"; + margin-left: -1.0458984375em; + color: rgb(250, 248, 248); +} +.icon-gps_signal_low .path10:before { + content: "\e909"; + margin-left: -1.0458984375em; + color: rgb(6, 6, 6); +} +.icon-gps_signal_low .path11:before { + content: "\e90a"; + margin-left: -1.0458984375em; + color: rgb(10, 10, 10); +} +.icon-gps_signal_low .path12:before { + content: "\e90b"; + margin-left: -1.0458984375em; + color: rgb(12, 12, 12); +} +.icon-gps_signal_low .path13:before { + content: "\e90c"; + margin-left: -1.0458984375em; + color: rgb(251, 251, 251); +} +.icon-gps_signal_low .path14:before { + content: "\e90d"; + margin-left: -1.0458984375em; + color: rgb(10, 10, 10); +} +.icon-gps_signal_low .path15:before { + content: "\e90e"; + margin-left: -1.0458984375em; + color: rgb(250, 238, 238); +} +.icon-gps_signal_low .path16:before { + content: "\e90f"; + margin-left: -1.0458984375em; + color: rgb(1, 1, 1); +} +.icon-gps_signal_low .path17:before { + content: "\e910"; + margin-left: -1.0458984375em; + color: rgb(113, 106, 106); +} +.icon-gps_signal_low .path18:before { + content: "\e911"; + margin-left: -1.0458984375em; + color: rgb(113, 106, 106); +} +.icon-gps_signal_low .path19:before { + content: "\e912"; + margin-left: -1.0458984375em; + color: rgb(12, 12, 12); +} +.icon-gps_signal_middle .path1:before { + content: "\e913"; + color: rgb(255, 255, 254); +} +.icon-gps_signal_middle .path2:before { + content: "\e914"; + margin-left: -1.0458984375em; + color: rgb(255, 255, 1); +} +.icon-gps_signal_middle .path3:before { + content: "\e915"; + margin-left: -1.0458984375em; + color: rgb(6, 6, 0); +} +.icon-gps_signal_middle .path4:before { + content: "\e916"; + margin-left: -1.0458984375em; + color: rgb(2, 2, 2); +} +.icon-gps_signal_middle .path5:before { + content: "\e917"; + margin-left: -1.0458984375em; + color: rgb(1, 1, 1); +} +.icon-gps_signal_middle .path6:before { + content: "\e918"; + margin-left: -1.0458984375em; + color: rgb(251, 251, 0); +} +.icon-gps_signal_middle .path7:before { + content: "\e919"; + margin-left: -1.0458984375em; + color: rgb(11, 11, 4); +} +.icon-gps_signal_middle .path8:before { + content: "\e91a"; + margin-left: -1.0458984375em; + color: rgb(13, 13, 4); +} +.icon-gps_signal_middle .path9:before { + content: "\e91b"; + margin-left: -1.0458984375em; + color: rgb(11, 11, 4); +} +.icon-gps_signal_middle .path10:before { + content: "\e91c"; + margin-left: -1.0458984375em; + color: rgb(251, 251, 0); +} +.icon-gps_signal_high .path1:before { + content: "\e91d"; + color: rgb(254, 255, 255); +} +.icon-gps_signal_high .path2:before { + content: "\e91e"; + margin-left: -1.0458984375em; + color: rgb(70, 248, 5); +} +.icon-gps_signal_high .path3:before { + content: "\e91f"; + margin-left: -1.0458984375em; + color: rgb(2, 5, 0); +} +.icon-gps_signal_high .path4:before { + content: "\e920"; + margin-left: -1.0458984375em; + color: rgb(1, 1, 1); +} +.icon-gps_signal_high .path5:before { + content: "\e921"; + margin-left: -1.0458984375em; + color: rgb(2, 2, 2); +} +.icon-gps_signal_high .path6:before { + content: "\e922"; + margin-left: -1.0458984375em; + color: rgb(1, 1, 1); +} +.icon-gps_signal_high .path7:before { + content: "\e923"; + margin-left: -1.0458984375em; + color: rgb(69, 244, 5); +} +.icon-gps_signal_high .path8:before { + content: "\e924"; + margin-left: -1.0458984375em; + color: rgb(6, 11, 4); +} +.icon-gps_signal_high .path9:before { + content: "\e925"; + margin-left: -1.0458984375em; + color: rgb(6, 12, 4); +} +.icon-gps_signal_high .path10:before { + content: "\e926"; + margin-left: -1.0458984375em; + color: rgb(5, 11, 4); +} +.icon-gps_signal_high .path11:before { + content: "\e927"; + margin-left: -1.0458984375em; + color: rgb(69, 244, 5); +} diff --git a/assets/fonts/icomoon.ttf b/assets/fonts/icomoon.ttf new file mode 100644 index 0000000..f2e8bb4 Binary files /dev/null and b/assets/fonts/icomoon.ttf differ diff --git a/assets/images/QR_certificate.png b/assets/images/QR_certificate.png new file mode 100644 index 0000000..2097f17 Binary files /dev/null and b/assets/images/QR_certificate.png differ diff --git a/assets/images/QR_gifuroge_stage1.png b/assets/images/QR_gifuroge_stage1.png new file mode 100644 index 0000000..91dcdcc Binary files /dev/null and b/assets/images/QR_gifuroge_stage1.png differ diff --git a/assets/images/money.png b/assets/images/money.png new file mode 100644 index 0000000..9f2ff56 Binary files /dev/null and b/assets/images/money.png differ diff --git a/dependencies.txt b/dependencies.txt new file mode 100644 index 0000000..94e2545 --- /dev/null +++ b/dependencies.txt @@ -0,0 +1,535 @@ +Dart SDK 3.5.0 +Flutter SDK 3.24.0 +gifunavi 4.8.19+499 +├── async 2.11.0 +│ ├── collection... +│ └── meta... +├── camera 0.10.6 +│ ├── camera_android 0.10.9+11 +│ │ ├── camera_platform_interface... +│ │ ├── flutter... +│ │ ├── flutter_plugin_android_lifecycle... +│ │ └── stream_transform 2.1.0 +│ ├── camera_avfoundation 0.9.17+3 +│ │ ├── camera_platform_interface... +│ │ ├── flutter... +│ │ └── stream_transform... +│ ├── camera_platform_interface 2.8.0 +│ │ ├── cross_file... +│ │ ├── flutter... +│ │ ├── plugin_platform_interface... +│ │ └── stream_transform... +│ ├── camera_web 0.3.5 +│ │ ├── camera_platform_interface... +│ │ ├── flutter... +│ │ ├── flutter_web_plugins... +│ │ ├── stream_transform... +│ │ └── web... +│ ├── flutter... +│ └── flutter_plugin_android_lifecycle 2.0.21 +│ └── flutter... +├── camera_camera 3.0.0 +│ ├── camera... +│ ├── flutter... +│ └── font_awesome_flutter 10.7.0 +│ └── flutter... +├── circular_menu 4.0.0 +│ └── flutter... +├── collection 1.18.0 +├── connectivity_plus 6.0.5 +│ ├── collection... +│ ├── connectivity_plus_platform_interface 2.0.1 +│ │ ├── flutter... +│ │ ├── meta... +│ │ └── plugin_platform_interface... +│ ├── flutter... +│ ├── flutter_web_plugins... +│ ├── meta... +│ ├── nm 0.5.0 +│ │ └── dbus 0.7.10 +│ │ ├── args... +│ │ ├── ffi... +│ │ ├── meta... +│ │ └── xml... +│ └── web... +├── cupertino_icons 1.0.8 +├── flutter 0.0.0 +│ ├── characters 1.3.0 +│ ├── collection... +│ ├── material_color_utilities 0.11.1 +│ │ └── collection... +│ ├── meta... +│ ├── sky_engine 0.0.99 +│ └── vector_math... +├── flutter_breadcrumb 1.0.1 +│ ├── flutter... +│ └── pedantic 1.11.1 +├── flutter_compass 0.8.0 +│ └── flutter... +├── flutter_image 4.1.11 +│ └── flutter... +├── flutter_keyboard_visibility 6.0.0 +│ ├── flutter... +│ ├── flutter_keyboard_visibility_linux 1.0.0 +│ │ ├── flutter... +│ │ └── flutter_keyboard_visibility_platform_interface... +│ ├── flutter_keyboard_visibility_macos 1.0.0 +│ │ ├── flutter... +│ │ └── flutter_keyboard_visibility_platform_interface... +│ ├── flutter_keyboard_visibility_platform_interface 2.0.0 +│ │ ├── flutter... +│ │ ├── meta... +│ │ └── plugin_platform_interface... +│ ├── flutter_keyboard_visibility_web 2.0.0 +│ │ ├── flutter... +│ │ ├── flutter_keyboard_visibility_platform_interface... +│ │ └── flutter_web_plugins... +│ ├── flutter_keyboard_visibility_windows 1.0.0 +│ │ ├── flutter... +│ │ └── flutter_keyboard_visibility_platform_interface... +│ └── meta... +├── flutter_launcher_icons 0.13.1 +│ ├── args 2.5.0 +│ ├── checked_yaml 2.0.3 +│ │ ├── json_annotation... +│ │ ├── source_span... +│ │ └── yaml... +│ ├── cli_util 0.4.1 +│ │ ├── meta... +│ │ └── path... +│ ├── image 4.2.0 +│ │ ├── archive 3.6.1 +│ │ │ ├── crypto... +│ │ │ └── path... +│ │ ├── meta... +│ │ └── xml 6.5.0 +│ │ ├── collection... +│ │ ├── meta... +│ │ └── petitparser 6.0.2 +│ │ └── meta... +│ ├── json_annotation 4.9.0 +│ │ └── meta... +│ ├── path... +│ └── yaml 3.1.2 +│ ├── collection... +│ ├── source_span... +│ └── string_scanner... +├── flutter_lints 4.0.0 +│ └── lints 4.0.0 +├── flutter_map 6.2.1 +│ ├── async... +│ ├── collection... +│ ├── flutter... +│ ├── http... +│ ├── latlong2... +│ ├── logger 2.4.0 +│ ├── meta... +│ ├── polylabel 1.0.1 +│ │ └── collection... +│ ├── proj4dart... +│ └── vector_math... +├── flutter_map_location_marker 8.0.5 +│ ├── flutter... +│ ├── flutter_compass... +│ ├── flutter_map... +│ ├── geolocator... +│ └── latlong2... +├── flutter_map_marker_cluster 1.3.6 +│ ├── flutter... +│ ├── flutter_map... +│ ├── flutter_map_marker_popup 6.1.2 +│ │ ├── animated_stack_widget 0.0.4 +│ │ │ └── flutter... +│ │ ├── flutter... +│ │ ├── flutter_map... +│ │ ├── latlong2... +│ │ └── provider 6.1.2 +│ │ ├── collection... +│ │ ├── flutter... +│ │ └── nested 1.0.0 +│ │ └── flutter... +│ └── latlong2... +├── flutter_polyline_points 2.1.0 +│ ├── flutter... +│ └── http... +├── flutter_riverpod 2.5.1 +│ ├── collection... +│ ├── flutter... +│ ├── meta... +│ ├── riverpod 2.5.1 +│ │ ├── collection... +│ │ ├── meta... +│ │ ├── stack_trace... +│ │ └── state_notifier... +│ └── state_notifier 1.0.0 +│ └── meta... +├── flutter_test 0.0.0 +│ ├── async... +│ ├── boolean_selector 2.1.1 +│ │ ├── source_span... +│ │ └── string_scanner... +│ ├── characters... +│ ├── clock... +│ ├── collection... +│ ├── fake_async 1.3.1 +│ │ ├── clock... +│ │ └── collection... +│ ├── flutter... +│ ├── leak_tracker 10.0.5 +│ │ ├── clock... +│ │ ├── collection... +│ │ ├── meta... +│ │ ├── path... +│ │ └── vm_service... +│ ├── leak_tracker_flutter_testing 3.0.5 +│ │ ├── flutter... +│ │ ├── leak_tracker... +│ │ ├── leak_tracker_testing... +│ │ ├── matcher... +│ │ └── meta... +│ ├── leak_tracker_testing 3.0.1 +│ │ ├── leak_tracker... +│ │ ├── matcher... +│ │ └── meta... +│ ├── matcher 0.12.16+1 +│ │ ├── async... +│ │ ├── meta... +│ │ ├── stack_trace... +│ │ ├── term_glyph... +│ │ └── test_api... +│ ├── material_color_utilities... +│ ├── meta... +│ ├── path... +│ ├── source_span 1.10.0 +│ │ ├── collection... +│ │ ├── path... +│ │ └── term_glyph... +│ ├── stack_trace 1.11.1 +│ │ └── path... +│ ├── stream_channel 2.1.2 +│ │ └── async... +│ ├── string_scanner 1.2.0 +│ │ └── source_span... +│ ├── term_glyph 1.2.1 +│ ├── test_api 0.7.2 +│ │ ├── async... +│ │ ├── boolean_selector... +│ │ ├── collection... +│ │ ├── meta... +│ │ ├── source_span... +│ │ ├── stack_trace... +│ │ ├── stream_channel... +│ │ ├── string_scanner... +│ │ └── term_glyph... +│ ├── vector_math... +│ └── vm_service 14.2.4 +├── flutter_typeahead 5.2.0 +│ ├── flutter... +│ ├── flutter_keyboard_visibility... +│ └── pointer_interceptor 0.10.1+2 +│ ├── flutter... +│ ├── flutter_web_plugins... +│ ├── pointer_interceptor_ios 0.10.1 +│ │ ├── flutter... +│ │ ├── plugin_platform_interface... +│ │ └── pointer_interceptor_platform_interface... +│ ├── pointer_interceptor_platform_interface 0.10.0+1 +│ │ ├── flutter... +│ │ └── plugin_platform_interface... +│ └── pointer_interceptor_web 0.10.2+1 +│ ├── flutter... +│ ├── flutter_web_plugins... +│ ├── plugin_platform_interface... +│ ├── pointer_interceptor_platform_interface... +│ └── web... +├── geojson_vi 2.2.5 +├── geolocator 10.1.1 +│ ├── flutter... +│ ├── geolocator_android 4.6.1 +│ │ ├── flutter... +│ │ ├── geolocator_platform_interface... +│ │ ├── meta... +│ │ └── uuid 4.4.2 +│ │ ├── crypto... +│ │ ├── fixnum 1.1.0 +│ │ ├── meta... +│ │ └── sprintf 7.0.0 +│ ├── geolocator_apple 2.3.7 +│ │ ├── flutter... +│ │ └── geolocator_platform_interface... +│ ├── geolocator_platform_interface 4.2.4 +│ │ ├── flutter... +│ │ ├── meta... +│ │ ├── plugin_platform_interface 2.1.8 +│ │ │ └── meta... +│ │ └── vector_math... +│ ├── geolocator_web 2.2.1 +│ │ ├── flutter... +│ │ ├── flutter_web_plugins... +│ │ └── geolocator_platform_interface... +│ └── geolocator_windows 0.2.3 +│ ├── flutter... +│ └── geolocator_platform_interface... +├── get 4.6.6 +│ └── flutter... +├── google_api_availability 5.0.0 +│ ├── flutter... +│ ├── google_api_availability_android 1.0.1 +│ │ ├── flutter... +│ │ └── google_api_availability_platform_interface... +│ └── google_api_availability_platform_interface 1.0.1 +│ ├── flutter... +│ ├── meta... +│ └── plugin_platform_interface... +├── google_fonts 6.2.1 +│ ├── crypto 3.0.5 +│ │ └── typed_data 1.3.2 +│ │ └── collection... +│ ├── flutter... +│ ├── http... +│ └── path_provider... +├── http 1.2.2 +│ ├── async... +│ ├── http_parser 4.0.2 +│ │ ├── collection... +│ │ ├── source_span... +│ │ ├── string_scanner... +│ │ └── typed_data... +│ ├── meta... +│ └── web... +├── image_gallery_saver 2.0.3 +│ └── flutter... +├── image_picker 1.1.2 +│ ├── flutter... +│ ├── image_picker_android 0.8.12+12 +│ │ ├── flutter... +│ │ ├── flutter_plugin_android_lifecycle... +│ │ └── image_picker_platform_interface... +│ ├── image_picker_for_web 3.0.5 +│ │ ├── flutter... +│ │ ├── flutter_web_plugins... +│ │ ├── image_picker_platform_interface... +│ │ ├── mime 1.0.5 +│ │ └── web... +│ ├── image_picker_ios 0.8.12 +│ │ ├── flutter... +│ │ └── image_picker_platform_interface... +│ ├── image_picker_linux 0.2.1+1 +│ │ ├── file_selector_linux 0.9.2+1 +│ │ │ ├── cross_file... +│ │ │ ├── file_selector_platform_interface... +│ │ │ └── flutter... +│ │ ├── file_selector_platform_interface 2.6.2 +│ │ │ ├── cross_file... +│ │ │ ├── flutter... +│ │ │ ├── http... +│ │ │ └── plugin_platform_interface... +│ │ ├── flutter... +│ │ └── image_picker_platform_interface... +│ ├── image_picker_macos 0.2.1+1 +│ │ ├── file_selector_macos 0.9.4 +│ │ │ ├── cross_file... +│ │ │ ├── file_selector_platform_interface... +│ │ │ └── flutter... +│ │ ├── file_selector_platform_interface... +│ │ ├── flutter... +│ │ └── image_picker_platform_interface... +│ ├── image_picker_platform_interface 2.10.0 +│ │ ├── cross_file 0.3.4+2 +│ │ │ ├── meta... +│ │ │ └── web... +│ │ ├── flutter... +│ │ ├── http... +│ │ └── plugin_platform_interface... +│ └── image_picker_windows 0.2.1+1 +│ ├── file_selector_platform_interface... +│ ├── file_selector_windows 0.9.3+2 +│ │ ├── cross_file... +│ │ ├── file_selector_platform_interface... +│ │ └── flutter... +│ ├── flutter... +│ └── image_picker_platform_interface... +├── intl 0.19.0 +│ ├── clock... +│ ├── meta... +│ └── path... +├── keyboard_dismisser 3.0.0 +│ └── flutter... +├── latlong2 0.9.1 +│ └── intl... +├── logging 1.2.0 +├── material_design_icons_flutter 7.0.7296 +│ └── flutter... +├── meta 1.15.0 +├── modal_bottom_sheet 3.0.0 +│ └── flutter... +├── package_info_plus 8.0.2 +│ ├── clock 1.1.1 +│ ├── ffi... +│ ├── flutter... +│ ├── flutter_web_plugins 0.0.0 +│ │ ├── characters... +│ │ ├── collection... +│ │ ├── flutter... +│ │ ├── material_color_utilities... +│ │ ├── meta... +│ │ └── vector_math... +│ ├── http... +│ ├── meta... +│ ├── package_info_plus_platform_interface 3.0.1 +│ │ ├── flutter... +│ │ ├── meta... +│ │ └── plugin_platform_interface... +│ ├── path... +│ ├── web 1.0.0 +│ └── win32... +├── path_provider 2.1.4 +│ ├── flutter... +│ ├── path_provider_android 2.2.10 +│ │ ├── flutter... +│ │ └── path_provider_platform_interface... +│ ├── path_provider_foundation 2.4.0 +│ │ ├── flutter... +│ │ └── path_provider_platform_interface... +│ ├── path_provider_linux 2.2.1 +│ │ ├── ffi... +│ │ ├── flutter... +│ │ ├── path... +│ │ ├── path_provider_platform_interface... +│ │ └── xdg_directories 1.0.4 +│ │ ├── meta... +│ │ └── path... +│ ├── path_provider_platform_interface 2.1.2 +│ │ ├── flutter... +│ │ ├── platform 3.1.5 +│ │ └── plugin_platform_interface... +│ └── path_provider_windows 2.3.0 +│ ├── ffi... +│ ├── flutter... +│ ├── path... +│ └── path_provider_platform_interface... +├── permission_handler 11.3.1 +│ ├── flutter... +│ ├── meta... +│ ├── permission_handler_android 12.0.12 +│ │ ├── flutter... +│ │ └── permission_handler_platform_interface... +│ ├── permission_handler_apple 9.4.5 +│ │ ├── flutter... +│ │ └── permission_handler_platform_interface... +│ ├── permission_handler_html 0.1.3+1 +│ │ ├── flutter... +│ │ ├── flutter_web_plugins... +│ │ ├── permission_handler_platform_interface... +│ │ └── web... +│ ├── permission_handler_platform_interface 4.2.2 +│ │ ├── flutter... +│ │ ├── meta... +│ │ └── plugin_platform_interface... +│ └── permission_handler_windows 0.2.1 +│ ├── flutter... +│ └── permission_handler_platform_interface... +├── positioned_tap_detector_2 1.0.4 +│ └── flutter... +├── proj4dart 2.1.0 +│ ├── meta... +│ ├── mgrs_dart 2.0.0 +│ │ └── unicode 0.3.1 +│ │ └── lists 1.0.1 +│ │ └── meta... +│ └── wkt_parser 2.0.0 +├── qr_code_scanner 1.0.1 +│ ├── flutter... +│ ├── flutter_web_plugins... +│ └── js 0.6.7 +│ └── meta... +├── rename 3.0.2 +│ ├── args... +│ ├── logger... +│ └── path... +├── shared_preferences 2.3.2 +│ ├── flutter... +│ ├── shared_preferences_android 2.3.1 +│ │ ├── flutter... +│ │ └── shared_preferences_platform_interface... +│ ├── shared_preferences_foundation 2.5.2 +│ │ ├── flutter... +│ │ └── shared_preferences_platform_interface... +│ ├── shared_preferences_linux 2.4.1 +│ │ ├── file 7.0.0 +│ │ │ ├── meta... +│ │ │ └── path... +│ │ ├── flutter... +│ │ ├── path... +│ │ ├── path_provider_linux... +│ │ ├── path_provider_platform_interface... +│ │ └── shared_preferences_platform_interface... +│ ├── shared_preferences_platform_interface 2.4.1 +│ │ ├── flutter... +│ │ └── plugin_platform_interface... +│ ├── shared_preferences_web 2.4.2 +│ │ ├── flutter... +│ │ ├── flutter_web_plugins... +│ │ ├── shared_preferences_platform_interface... +│ │ └── web... +│ └── shared_preferences_windows 2.4.1 +│ ├── file... +│ ├── flutter... +│ ├── path... +│ ├── path_provider_platform_interface... +│ ├── path_provider_windows... +│ └── shared_preferences_platform_interface... +├── sqflite 2.3.3+1 +│ ├── flutter... +│ ├── path 1.9.0 +│ └── sqflite_common 2.5.4+2 +│ ├── meta... +│ ├── path... +│ └── synchronized 3.2.0 +├── timeline_tile 2.0.0 +│ └── flutter... +├── timezone 0.9.4 +│ └── path... +├── transparent_image 2.0.1 +├── tuple 2.0.2 +├── url_launcher 6.3.0 +│ ├── flutter... +│ ├── url_launcher_android 6.3.9 +│ │ ├── flutter... +│ │ └── url_launcher_platform_interface... +│ ├── url_launcher_ios 6.3.1 +│ │ ├── flutter... +│ │ └── url_launcher_platform_interface... +│ ├── url_launcher_linux 3.2.0 +│ │ ├── flutter... +│ │ └── url_launcher_platform_interface... +│ ├── url_launcher_macos 3.2.0 +│ │ ├── flutter... +│ │ └── url_launcher_platform_interface... +│ ├── url_launcher_platform_interface 2.3.2 +│ │ ├── flutter... +│ │ └── plugin_platform_interface... +│ ├── url_launcher_web 2.3.3 +│ │ ├── flutter... +│ │ ├── flutter_web_plugins... +│ │ ├── url_launcher_platform_interface... +│ │ └── web... +│ └── url_launcher_windows 3.1.2 +│ ├── flutter... +│ └── url_launcher_platform_interface... +├── vector_math 2.1.4 +├── webview_flutter 4.8.0 +│ ├── flutter... +│ ├── webview_flutter_android 3.16.6 +│ │ ├── flutter... +│ │ └── webview_flutter_platform_interface... +│ ├── webview_flutter_platform_interface 2.10.0 +│ │ ├── flutter... +│ │ ├── meta... +│ │ └── plugin_platform_interface... +│ └── webview_flutter_wkwebview 3.15.0 +│ ├── flutter... +│ ├── path... +│ └── webview_flutter_platform_interface... +└── win32 5.5.4 + └── ffi 2.1.3 diff --git a/flutter_launcher_icons.yaml b/flutter_launcher_icons.yaml new file mode 100644 index 0000000..fd81304 --- /dev/null +++ b/flutter_launcher_icons.yaml @@ -0,0 +1,5 @@ +flutter_launcher_icons: + android: "launcher_icon" + ios: true + image_path: "assets/images/appicon.png" + min_sdk_android: 21 # android min sdk min:16, default 21 diff --git a/gifunavi/.gitignore b/gifunavi/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/gifunavi/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/gifunavi/build.gradle.kts b/gifunavi/build.gradle.kts new file mode 100644 index 0000000..c692702 --- /dev/null +++ b/gifunavi/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + id("com.android.application") +} + +android { + namespace = "com.dvox.gifunavi" + compileSdk = 34 + + defaultConfig { + applicationId = "com.dvox.gifunavi" + minSdk = 21 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation("com.android.support:appcompat-v7:28.0.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("com.android.support.test:runner:1.0.2") + androidTestImplementation("com.android.support.test.espresso:espresso-core:3.0.2") +} \ No newline at end of file diff --git a/gifunavi/proguard-rules.pro b/gifunavi/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/gifunavi/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/gifunavi/src/androidTest/java/com/dvox/gifunavi/ExampleInstrumentedTest.java b/gifunavi/src/androidTest/java/com/dvox/gifunavi/ExampleInstrumentedTest.java new file mode 100644 index 0000000..559af56 --- /dev/null +++ b/gifunavi/src/androidTest/java/com/dvox/gifunavi/ExampleInstrumentedTest.java @@ -0,0 +1,25 @@ +package com.dvox.gifunavi; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.dvox.gifunavi", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/gifunavi/src/main/AndroidManifest.xml b/gifunavi/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3189f62 --- /dev/null +++ b/gifunavi/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/gifunavi/src/main/res/drawable-v24/ic_launcher_foreground.xml b/gifunavi/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/gifunavi/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/gifunavi/src/main/res/drawable/ic_launcher_background.xml b/gifunavi/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/gifunavi/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gifunavi/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/gifunavi/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/gifunavi/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/gifunavi/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/gifunavi/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/gifunavi/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/gifunavi/src/main/res/mipmap-hdpi/ic_launcher.webp b/gifunavi/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/gifunavi/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/gifunavi/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/gifunavi/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/gifunavi/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/gifunavi/src/main/res/mipmap-mdpi/ic_launcher.webp b/gifunavi/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/gifunavi/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/gifunavi/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/gifunavi/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/gifunavi/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/gifunavi/src/main/res/mipmap-xhdpi/ic_launcher.webp b/gifunavi/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/gifunavi/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/gifunavi/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/gifunavi/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/gifunavi/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/gifunavi/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/gifunavi/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/gifunavi/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/gifunavi/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/gifunavi/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/gifunavi/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/gifunavi/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/gifunavi/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/gifunavi/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/gifunavi/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/gifunavi/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/gifunavi/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/gifunavi/src/main/res/values-night/themes.xml b/gifunavi/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..e4b5f5c --- /dev/null +++ b/gifunavi/src/main/res/values-night/themes.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/gifunavi/src/main/res/values/colors.xml b/gifunavi/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/gifunavi/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/gifunavi/src/main/res/values/strings.xml b/gifunavi/src/main/res/values/strings.xml new file mode 100644 index 0000000..e03dff2 --- /dev/null +++ b/gifunavi/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + gifunavi + \ No newline at end of file diff --git a/gifunavi/src/main/res/values/themes.xml b/gifunavi/src/main/res/values/themes.xml new file mode 100644 index 0000000..8cd4042 --- /dev/null +++ b/gifunavi/src/main/res/values/themes.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/gifunavi/src/test/java/com/dvox/gifunavi/ExampleUnitTest.java b/gifunavi/src/test/java/com/dvox/gifunavi/ExampleUnitTest.java new file mode 100644 index 0000000..208582b --- /dev/null +++ b/gifunavi/src/test/java/com/dvox/gifunavi/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.dvox.gifunavi; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1becba2 --- /dev/null +++ b/gradlew @@ -0,0 +1 @@ +404: Not Found \ No newline at end of file diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..1becba2 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1 @@ +404: Not Found \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore index ad322bc..7a7f987 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -1,34 +1,34 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 9625e10..7c56964 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index dfd2626..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index a97381a..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/GifuNavigation.mobileprovision b/ios/GifuNavigation.mobileprovision new file mode 100644 index 0000000..c523c3f Binary files /dev/null and b/ios/GifuNavigation.mobileprovision differ diff --git a/ios/Podfile b/ios/Podfile index 6f71b24..d97f17e 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -32,75 +32,13 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end end -# post_install do |installer| -# installer.pods_project.targets.each do |target| -# flutter_additional_ios_build_settings(target) -# end -# end - post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) - - # Start of the permission_handler configuration - target.build_configurations.each do |config| - - # You can enable the permissions needed here. For example to enable camera - # permission, just remove the `#` character in front so it looks like this: - # - # ## dart: PermissionGroup.camera - # 'PERMISSION_CAMERA=1' - # - # Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h - config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ - '$(inherited)', - - ## dart: PermissionGroup.calendar - # 'PERMISSION_EVENTS=1', - - ## dart: PermissionGroup.reminders - # 'PERMISSION_REMINDERS=1', - - ## dart: PermissionGroup.contacts - # 'PERMISSION_CONTACTS=1', - - ## dart: PermissionGroup.camera - # 'PERMISSION_CAMERA=1', - - ## dart: PermissionGroup.microphone - # 'PERMISSION_MICROPHONE=1', - - ## dart: PermissionGroup.speech - # 'PERMISSION_SPEECH_RECOGNIZER=1', - - ## dart: PermissionGroup.photos - # 'PERMISSION_PHOTOS=1', - - ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] - 'PERMISSION_LOCATION=1' - - ## dart: PermissionGroup.notification - # 'PERMISSION_NOTIFICATIONS=1', - - ## dart: PermissionGroup.mediaLibrary - # 'PERMISSION_MEDIA_LIBRARY=1', - - ## dart: PermissionGroup.sensors - # 'PERMISSION_SENSORS=1', - - ## dart: PermissionGroup.bluetooth - # 'PERMISSION_BLUETOOTH=1', - - ## dart: PermissionGroup.appTrackingTransparency - # 'PERMISSION_APP_TRACKING_TRANSPARENCY=1', - - ## dart: PermissionGroup.criticalAlerts - # 'PERMISSION_CRITICAL_ALERTS=1' - ] - - end - # End of the permission_handler configuration end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 90b2601..2531a8c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,71 +3,71 @@ PODS: - Flutter - connectivity_plus (0.0.1): - Flutter - - ReachabilitySwift + - FlutterMacOS - Flutter (1.0.0) - flutter_compass (0.0.1): - Flutter - flutter_keyboard_visibility (0.0.1): - Flutter - - FMDB (2.7.5): - - FMDB/standard (= 2.7.5) - - FMDB/standard (2.7.5) - geolocator_apple (1.2.0): - Flutter - - google_maps_flutter_ios (0.0.1): + - image_gallery_saver (2.0.2): - Flutter - - GoogleMaps (< 8.0) - - GoogleMaps (6.2.1): - - GoogleMaps/Maps (= 6.2.1) - - GoogleMaps/Base (6.2.1) - - GoogleMaps/Maps (6.2.1): - - GoogleMaps/Base - image_picker_ios (0.0.1): - Flutter - - isar_flutter_libs (1.0.0): + - MTBBarcodeScanner (5.0.11) + - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.1.1): + - permission_handler_apple (9.3.0): - Flutter - - ReachabilitySwift (5.0.0) + - pointer_interceptor_ios (0.0.1): + - Flutter + - qr_code_scanner (0.2.0): + - Flutter + - MTBBarcodeScanner - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - sqflite (0.0.3): - Flutter - - FMDB (>= 2.7.5) + - FlutterMacOS - url_launcher_ios (0.0.1): - Flutter + - webview_flutter_wkwebview (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`) - - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - Flutter (from `Flutter`) - flutter_compass (from `.symlinks/plugins/flutter_compass/ios`) - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) - - google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`) + - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`) + - qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite (from `.symlinks/plugins/sqflite/ios`) + - sqflite (from `.symlinks/plugins/sqflite/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: trunk: - - FMDB - - GoogleMaps - - ReachabilitySwift + - MTBBarcodeScanner EXTERNAL SOURCES: camera_avfoundation: :path: ".symlinks/plugins/camera_avfoundation/ios" connectivity_plus: - :path: ".symlinks/plugins/connectivity_plus/ios" + :path: ".symlinks/plugins/connectivity_plus/darwin" Flutter: :path: Flutter flutter_compass: @@ -76,42 +76,49 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" geolocator_apple: :path: ".symlinks/plugins/geolocator_apple/ios" - google_maps_flutter_ios: - :path: ".symlinks/plugins/google_maps_flutter_ios/ios" + image_gallery_saver: + :path: ".symlinks/plugins/image_gallery_saver/ios" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" - isar_flutter_libs: - :path: ".symlinks/plugins/isar_flutter_libs/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" + pointer_interceptor_ios: + :path: ".symlinks/plugins/pointer_interceptor_ios/ios" + qr_code_scanner: + :path: ".symlinks/plugins/qr_code_scanner/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" sqflite: - :path: ".symlinks/plugins/sqflite/ios" + :path: ".symlinks/plugins/sqflite/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: - camera_avfoundation: 3125e8cd1a4387f6f31c6c63abb8a55892a9eeeb - connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4 + connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_compass: cbbd285cea1584c7ac9c4e0c3e1f17cbea55e855 flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 - FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401 - google_maps_flutter_ios: abdac20d6ce8931f6ebc5f46616df241bfaa2cfd - GoogleMaps: 20d7b12be49a14287f797e88e0e31bc4156aaeb4 - image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 - isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 - permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 - ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 - sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a - url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 + geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450 + image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb + image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb + package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + pointer_interceptor_ios: 508241697ff0947f853c061945a8b822463947c1 + qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4 -PODFILE CHECKSUM: 7a34d5e980f9e05ecf4e24c79da64ca020615638 +PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index f3465f5..986ec01 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,14 +8,26 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 2EAA11F0595B6B1A6B6ADED8 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53C8E795FF5969F8C7B6A237 /* Pods_RunnerTests.framework */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - EAFC0FFB27C8DC9071E67D5D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 853FC5087A11FEE54BE443C8 /* Pods_Runner.framework */; }; + B6A4962F862B554C98AD7E0A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F31FDD2E828C96A459E4AA85 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -30,14 +42,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 13E5CD9612AF4CC8B90A74C3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 53C8E795FF5969F8C7B6A237 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 853FC5087A11FEE54BE443C8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 84738EC4DA98A3780DDAB81F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 89205C63F97F88BDB459168E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 89EEC485D24E5B1BC0DD7B8E /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -45,8 +61,10 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - BEC4F75C6B655CA90D5B5E18 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - F1D3FD8F51C2EAF6C3A7EE25 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + AC42218FD0A0DB137D157E93 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + D773E9161AA2211A3E2B94DD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + DA00EBAA91366A0B5BFDAE96 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + F31FDD2E828C96A459E4AA85 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,21 +72,36 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - EAFC0FFB27C8DC9071E67D5D /* Pods_Runner.framework in Frameworks */, + B6A4962F862B554C98AD7E0A /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CEDA9491D04A70CE2A58DF32 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2EAA11F0595B6B1A6B6ADED8 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 6C3C11D7BE49E5BED7989712 /* Pods */ = { + 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( - BEC4F75C6B655CA90D5B5E18 /* Pods-Runner.debug.xcconfig */, - 13E5CD9612AF4CC8B90A74C3 /* Pods-Runner.release.xcconfig */, - F1D3FD8F51C2EAF6C3A7EE25 /* Pods-Runner.profile.xcconfig */, + 331C807B294A618700263BE5 /* RunnerTests.swift */, ); - path = Pods; + path = RunnerTests; + sourceTree = ""; + }; + 3806CD679A8B2BB869428B93 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F31FDD2E828C96A459E4AA85 /* Pods_Runner.framework */, + 53C8E795FF5969F8C7B6A237 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { @@ -88,8 +121,9 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 6C3C11D7BE49E5BED7989712 /* Pods */, - E6F2437A5B5AAF08D2998197 /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, + B9F40AE3939A8E59418B2C25 /* Pods */, + 3806CD679A8B2BB869428B93 /* Frameworks */, ); sourceTree = ""; }; @@ -97,6 +131,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -116,30 +151,54 @@ path = Runner; sourceTree = ""; }; - E6F2437A5B5AAF08D2998197 /* Frameworks */ = { + B9F40AE3939A8E59418B2C25 /* Pods */ = { isa = PBXGroup; children = ( - 853FC5087A11FEE54BE443C8 /* Pods_Runner.framework */, + DA00EBAA91366A0B5BFDAE96 /* Pods-Runner.debug.xcconfig */, + D773E9161AA2211A3E2B94DD /* Pods-Runner.release.xcconfig */, + 84738EC4DA98A3780DDAB81F /* Pods-Runner.profile.xcconfig */, + AC42218FD0A0DB137D157E93 /* Pods-RunnerTests.debug.xcconfig */, + 89205C63F97F88BDB459168E /* Pods-RunnerTests.release.xcconfig */, + 89EEC485D24E5B1BC0DD7B8E /* Pods-RunnerTests.profile.xcconfig */, ); - name = Frameworks; + path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + CD76B1A18202CBE77CBE3DCF /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + CEDA9491D04A70CE2A58DF32 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 6B25D2836FC6A82461E36025 /* [CP] Check Pods Manifest.lock */, + 7EC19810B524B833F58A16B8 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ED4E40E6FE1C0A73CAC736F8 /* [CP] Embed Pods Frameworks */, - BC610379D726847A0183FBD1 /* [CP] Copy Pods Resources */, + 3CF3DF1DB3C3E48099A705D1 /* [CP] Embed Pods Frameworks */, + 9D3C0722FFFB9D74548C2A72 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -156,9 +215,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -179,11 +243,19 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -214,7 +286,24 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 6B25D2836FC6A82461E36025 /* [CP] Check Pods Manifest.lock */ = { + 3CF3DF1DB3C3E48099A705D1 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 7EC19810B524B833F58A16B8 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -251,7 +340,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - BC610379D726847A0183FBD1 /* [CP] Copy Pods Resources */ = { + 9D3C0722FFFB9D74548C2A72 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -268,26 +357,39 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; - ED4E40E6FE1C0A73CAC736F8 /* [CP] Embed Pods Frameworks */ = { + CD76B1A18202CBE77CBE3DCF /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -299,6 +401,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -323,6 +433,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -352,6 +463,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -360,7 +472,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -376,13 +488,16 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = D5SL68ATB9; + DEVELOPMENT_TEAM = UMNEWT25JR; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 4.8.19; PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -391,10 +506,61 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AC42218FD0A0DB137D157E93 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.gifunavi.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89205C63F97F88BDB459168E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.gifunavi.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89EEC485D24E5B1BC0DD7B8E /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.gifunavi.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -424,6 +590,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -438,7 +605,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -450,6 +617,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -479,6 +647,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -487,7 +656,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -505,13 +674,16 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = D5SL68ATB9; + DEVELOPMENT_TEAM = UMNEWT25JR; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 4.8.19; PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -528,13 +700,16 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = D5SL68ATB9; + DEVELOPMENT_TEAM = UMNEWT25JR; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 4.8.19; PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -546,6 +721,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index c4b79bd..919434a 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index fc6bf80..18d9810 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index af0309c..f9b0d7c 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -1,8 +1,8 @@ - - - - - PreviewsEnabled - - - + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a6b826d..8e3ca5d 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + + + - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index af0309c..f9b0d7c 100644 --- a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -1,8 +1,8 @@ - - - - - PreviewsEnabled - - - + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 143ef75..6266644 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,15 +1,13 @@ -import UIKit -import Flutter -import GoogleMaps - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GMSServices.provideAPIKey("AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE") - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index 1950fd8..d36b1fa 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1,122 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json index d08a4de..0bedcf2 100644 --- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -1,23 +1,23 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md index 65a94b5..89c2725 100644 --- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -1,5 +1,5 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard index 497371e..f2e259c 100644 --- a/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,37 +1,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index bbb83ca..f3c2851 100644 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -1,26 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index de6d341..5bf9375 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -24,28 +24,32 @@ ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) + LSApplicationCategoryType + LSRequiresIPhoneOS NSCameraUsageDescription - 写真撮影のためカメラにアクセスします + 岐阜ナビはチェックポイントで撮影した写真を写真ライブラリに保存し、通過記録を保持し、競技結果として提出することができます。 NSLocationAlwaysAndWhenInUseUsageDescription - このアプリでは、位置情報の収集を行います。 + 岐阜ナビはアプリが閉じられているときでも位置情報へのアクセスが必要です。これにより、走行履歴の記録ができ、レビュー時の参考にすることができます。 NSLocationAlwaysUsageDescription - このアプリでは、バックグラウンドで位置情報を収集します。 + このアプリではバックグラウンドで位置情報にアクセスします。 NSLocationWhenInUseUsageDescription - このアプリでは、開始時点で位置情報を収集します。 + このアプリはチェックポイントへのチェックインや走行履歴を記録するために、位置情報にアクセスします。 NSMicrophoneUsageDescription - プロフィールに動画を投稿してください。 + このアプリではカメラは使用しますが、マイクの使用は当面行いません。 NSPhotoLibraryUsageDescription - 写真ライブラリへのアクセス警告 + 撮影した写真はデバイスのアルバムに保存されます。これにより、不具合時の通過記録を安全に担保することができます。 + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + location + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main - NSCameraUsageDescription - ロゲイニングのゴールでは写真撮影が必要です。 - NSMicrophoneUsageDescription - 収録音声の保存が必要です。 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -63,7 +67,5 @@ io.flutter.embedded_views_preview - UIApplicationSupportsIndirectInputEvents - diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h index fae207f..308a2a5 100644 --- a/ios/Runner/Runner-Bridging-Header.h +++ b/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" +#import "GeneratedPluginRegistrant.h" diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/data/location_data.dart b/lib/data/location_data.dart index 608819d..a7e8d6f 100644 --- a/lib/data/location_data.dart +++ b/lib/data/location_data.dart @@ -1,4 +1,5 @@ // ignore: non_constant_identifier_names +// 不要 String location_line_date = """ { "type": "FeatureCollection", diff --git a/lib/main.dart b/lib/main.dart index 3b090fc..f1a4da6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,44 +1,600 @@ - +import 'dart:async'; +import 'dart:io'; +//import 'dart:convert'; +//import 'dart:developer'; +import 'package:gifunavi/model/gps_data.dart'; +//import 'package:gifunavi/pages/home/home_page.dart'; +import 'package:gifunavi/utils/database_gps.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart'; +//import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart'; +import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_binding.dart'; -import 'package:rogapp/routes/app_pages.dart'; -import 'package:rogapp/utils/string_values.dart'; +//import 'package:vm_service/vm_service.dart'; +//import 'package:dart_vm_info/dart_vm_info.dart'; +import 'package:timezone/data/latest.dart' as tz; + +import 'package:gifunavi/pages/settings/settings_controller.dart'; + +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_binding.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/utils/location_controller.dart'; +import 'package:gifunavi/utils/string_values.dart'; +import 'package:gifunavi/widgets/debug_widget.dart'; import 'package:shared_preferences/shared_preferences.dart'; +// import 'package:is_lock_screen/is_lock_screen.dart'; +//import 'package:gifunavi/services/device_info_service.dart'; +import 'package:gifunavi/services/error_service.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +//import 'dart:async'; +//import 'package:get/get.dart'; +import 'package:flutter/services.dart'; -String? userToken; +import 'package:permission_handler/permission_handler.dart'; + +import 'pages/permission/permission.dart'; +import 'package:gifunavi/services/api_service.dart'; + +import 'package:gifunavi/provider/cached_tile_provider.dart'; +import 'package:gifunavi/pages/entry/entry_controller.dart'; +import 'package:gifunavi/pages/team/team_controller.dart'; + +Map deviceInfo = {}; + +/* +void saveGameState() async { + DestinationController destinationController = + Get.find(); + SharedPreferences pref = await SharedPreferences.getInstance(); + pref.setBool("is_in_rog", destinationController.isInRog.value); + pref.setBool( + "rogaining_counted", destinationController.rogainingCounted.value); + pref.setBool("ready_for_goal", DestinationController.ready_for_goal); +} + */ + +// 現在のユーザーのIDも一緒に保存するようにします。 +void saveGameState() async { + DestinationController destinationController = + Get.find(); + IndexController indexController = Get.find(); + SharedPreferences pref = await SharedPreferences.getInstance(); + debugPrint("indexController.currentUser = ${indexController.currentUser}"); + if(indexController.currentUser.isNotEmpty) { + pref.setInt("user_id", indexController.currentUser[0]["user"]["id"]); + }else{ + debugPrint("User is empty...."); + } + pref.setBool("is_in_rog", destinationController.isInRog.value); + pref.setBool( + "rogaining_counted", destinationController.rogainingCounted.value); + pref.setBool("ready_for_goal", DestinationController.ready_for_goal); +} + +/* +void restoreGame() async { + SharedPreferences pref = await SharedPreferences.getInstance(); + DestinationController destinationController = + Get.find(); + destinationController.skipGps = false; + destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false; + destinationController.rogainingCounted.value = + pref.getBool("rogaining_counted") ?? false; + DestinationController.ready_for_goal = + pref.getBool("ready_for_goal") ?? false; + //print( + // "--restored -- destinationController.isInRog.value ${pref.getBool("is_in_rog")} -- ${pref.getBool("rogaining_counted")}"); +} + */ + +void restoreGame() async { + SharedPreferences pref = await SharedPreferences.getInstance(); + IndexController indexController = Get.find(); + int? savedUserId = pref.getInt("user_id"); + //int? currUserId = indexController.currentUser[0]['user']['id']; + //debugPrint("savedUserId=${savedUserId}, currentUser=${currUserId}"); + if (indexController.currentUser.isNotEmpty && + indexController.currentUser[0]["user"]["id"] == savedUserId) { + DestinationController destinationController = + Get.find(); + destinationController.skipGps = false; + destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false; + destinationController.rogainingCounted.value = + pref.getBool("rogaining_counted") ?? false; + DestinationController.ready_for_goal = + pref.getBool("ready_for_goal") ?? false; + await Get.putAsync(() => ApiService().init()); + } +} void main() async { - WidgetsFlutterBinding.ensureInitialized(); - - await FlutterMapTileCaching.initialise(); - final StoreDirectory instanceA = FMTC.instance('OpenStreetMap (A)'); - await instanceA.manage.createAsync(); - await instanceA.metadata.addAsync( - key: 'sourceURL', - value: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', - ); - await instanceA.metadata.addAsync( - key: 'validDuration', - value: '14', - ); - await instanceA.metadata.addAsync( - key: 'behaviour', - value: 'cacheFirst', - ); - runApp(const MyApp()); + Get.put(LocationController()); + /* + Get.put(ApiService()); + Get.put(TeamController()); + Get.put(EntryController()); + Get.put(IndexController()); + */ + /* + await FlutterMapTileCaching.initialise(); + + final StoreDirectory instanceA = FMTC.instance('OpenStreetMap (A)'); + await instanceA.manage.createAsync(); + await instanceA.metadata.addAsync( + key: 'sourceURL', + value: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', + ); + await instanceA.metadata.addAsync( + key: 'validDuration', + value: '14', + ); + await instanceA.metadata.addAsync( + key: 'behaviour', + value: 'cacheFirst', + ); +*/ +// 新しいキャッシュプロバイダーの初期化 + await CacheProvider.initialize(); + + // 使用不可 + //deviceInfo = await DeviceInfoService.getDeviceInfo(); + + FlutterError.onError = (FlutterErrorDetails details) { + FlutterError.presentError(details); + Get.log('Flutter error: ${details.exception}'); + Get.log('Stack trace: ${details.stack}'); + ErrorService.reportError(details.exception, details.stack ?? StackTrace.current, deviceInfo, LogManager().operationLogs); + }; + + //Get.put(LocationController()); + + //await PermissionController.checkAndRequestPermissions(); + //requestLocationPermission(); + + + + // startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810 + Get.put(SettingsController()); // これを追加 + + + /* + runZonedGuarded(() { + runApp(const ProviderScope(child: MyApp())); + }, (error, stackTrace) { + ErrorService.reportError(error, stackTrace, deviceInfo); + }); + */ + + FlutterError.onError = (FlutterErrorDetails details) { + FlutterError.presentError(details); + debugPrint('Flutter error: ${details.exception}'); + debugPrint('Stack trace: ${details.stack}'); + }; + + try { + tz.initializeTimeZones(); + + // ApiServiceを初期化 + //await Get.putAsync(() => ApiService().init()); + await initServices(); + + runApp(const ProviderScope(child: MyApp())); + //runApp(HomePage()); // MyApp()からHomePage()に変更 + //runApp(const MyApp()); + }catch(e, stackTrace){ + print('Error during initialization: $e'); + print('Stack trace: $stackTrace'); + } +} + +Future initServices() async { + print('Starting services ...'); + try { + await Get.putAsync(() => ApiService().init()); + print('All services started...'); + }catch(e){ + print('Error initializing ApiService: $e'); + } + + try { + Get.put(SettingsController()); + print('SettingsController initialized successfully'); + } catch (e) { + print('Error initializing SettingsController: $e'); + } + + print('All services started...'); + +} + +Future requestLocationPermission() async { + try { + final status = await Permission.locationAlways.request(); + if (status == PermissionStatus.granted) { + print('Location permission granted'); + } else { + print('Location permission denied'); + //await showLocationPermissionDeniedDialog(); // 追加 + } + } catch (e) { + print('Error requesting location permission: $e'); + } } -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); + +// メモリ使用量の解説:https://qiita.com/hukusuke1007/items/e4e987836412e9bc73b9 + +/* +// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810 +// startMemoryMonitoring関数が5分ごとに呼び出され、メモリ使用量をチェックします。 +// メモリ使用量が閾値(ここでは500MB)を超えた場合、エラーメッセージとスタックトレースをレポートします。 +// +void startMemoryMonitoring() { + const threshold = 500 * 1024 * 1024; // 500MB + + // メモリ使用量情報を取得 + final memoryUsage = MemoryUsage.fromJson(DartVMInfo.getAllocationProfile()); + + if (memoryUsage.heapUsage > threshold) { + final now = DateTime.now().toIso8601String(); + final message = 'High memory usage detected at $now: ${memoryUsage.heapUsage} bytes'; + print(message); + reportError(message, StackTrace.current); + showMemoryWarningDialog(); + } + + Timer(const Duration(minutes: 5), startMemoryMonitoring); +} + +class MemoryUsage { + final int heapUsage; + + MemoryUsage({required this.heapUsage}); + + factory MemoryUsage.fromJson(Map json) { + return MemoryUsage( + heapUsage: json['heapUsage'] as int, + ); + } +} +*/ + +// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810 +// reportError関数でエラーレポートを送信します。具体的な実装は、利用するエラー報告サービスによって異なります。 +void reportError(String message, StackTrace stackTrace) async { + // エラーレポートの送信処理を実装 + // 例: SentryやFirebase Crashlyticsなどのエラー報告サービスを利用 + print("ReportError : $message . stacktrace : $stackTrace"); +} + +// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810 +// showMemoryWarningDialog関数で、メモリ使用量が高い場合にユーザーに警告ダイアログを表示します。 +// +void showMemoryWarningDialog() { + if (Get.context != null) { + showDialog( + context: Get.context!, + builder: (context) => AlertDialog( + title: const Text('メモリ使用量の警告'), + content: const Text('アプリのメモリ使用量が高くなっています。アプリを再起動することをお勧めします。'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), + ); + } +} + +StreamSubscription? positionStream; +bool background=false; +DateTime lastGPSCollectedTime=DateTime.now(); +String team_name=""; +String event_code=""; + +Future startBackgroundTracking() async { + if (Platform.isIOS && background==false) { + + final IndexController indexController = Get.find(); + if(indexController.currentUser.isNotEmpty) { + team_name = indexController.currentUser[0]["user"]['team_name']; + event_code = indexController.currentUser[0]["user"]["event_code"]; + } + background = true; + debugPrint("バックグラウンド処理を開始しました。"); + const LocationSettings locationSettings = LocationSettings( + accuracy: LocationAccuracy.high, + distanceFilter: 100, + ); + + try { + positionStream = Geolocator.getPositionStream(locationSettings: locationSettings) + .listen((Position? position) async { + if (position != null) { + final lat = position.latitude; + final lng = position.longitude; + //final timestamp = DateTime.now(); + final accuracy = position.accuracy; + + // GPS信号強度がlowの場合はスキップ + if (accuracy > 100) { + debugPrint("GPS signal strength is low. Skipping data saving."); + return; + } + + Duration difference = lastGPSCollectedTime.difference(DateTime.now()) + .abs(); + // 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過(普通に歩くスピード) + //debugPrint("時間差:${difference}"); + if (difference.inSeconds >= 10 ) { + debugPrint("バックグラウンドでのGPS取得時の処理(10secおき) count=${difference.inSeconds}, time=${DateTime.now()}"); + + // DBにGPSデータを保存 pages/destination/destination_controller.dart + await addGPStoDB(lat, lng); + + lastGPSCollectedTime = DateTime.now(); + } + } + }, onError: (error) { + if (error is LocationServiceDisabledException) { + print('Location services are disabled'); + } else if (error is PermissionDeniedException) { + print('Location permissions are denied'); + } else { + print('Location Error: $error'); + } + }); + } catch (e) { + print('Error starting background tracking: $e'); + } + }else if (Platform.isAndroid && background == false) { + background = true; + debugPrint("バックグラウンド処理を開始しました。"); + + try { + // 位置情報の権限が許可されているかを確認 + await PermissionController.checkAndRequestPermissions(); + }catch(e){ + print('Error starting background tracking: $e'); + } + } +} + +Future addGPStoDB(double la, double ln) async { + //debugPrint("in addGPStoDB ${indexController.currentUser}"); + GpsDatabaseHelper db = GpsDatabaseHelper.instance; + try { + GpsData gpsData = GpsData( + id: 0, + team_name: team_name, + event_code: event_code, + lat: la, + lon: ln, + is_checkin: 0, + created_at: DateTime.now().millisecondsSinceEpoch); + var res = await db.insertGps(gpsData); + debugPrint("バックグラウンドでのGPS保存:"); + } catch (err) { + print("errr ready gps $err"); + return; + } +} + +Future stopBackgroundTracking() async { + if (Platform.isIOS && background==true) { + background=false; + debugPrint("バックグラウンド処理:停止しました。"); + await positionStream?.cancel(); + positionStream = null; + }else if(Platform.isAndroid && background==true){ + background=false; + debugPrint("バックグラウンド処理:停止しました。"); + const platform = MethodChannel('location'); + try { + await platform.invokeMethod('stopLocationService'); + } on PlatformException catch (e) { + print("Failed to stop location service: '${e.message}'."); + } + } +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State with WidgetsBindingObserver { // This widget is the root of your application. + + @override + void initState() { + super.initState(); + if (!Get.isRegistered()) { + Get.put(LocationController()); + } + + if (context.mounted) { + restoreGame(); + } + WidgetsBinding.instance.addObserver(this); + + // ウィジェットが構築された後に権限をチェック + WidgetsBinding.instance.addPostFrameCallback((_) { + PermissionController.checkAndRequestPermissions(); + }); + + debugPrint("Start MyAppState..."); + } + +/* + void showPermissionRequiredDialog() { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text('権限が必要です'), + content: Text('このアプリは機能するために位置情報の権限が必要です。設定で権限を許可してください。'), + actions: [ + TextButton( + child: Text('設定を開く'), + onPressed: () { + openAppSettings(); + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('アプリを終了'), + onPressed: () { + // アプリを終了 + Navigator.of(context).pop(); + // よりクリーンな終了のために 'flutter_exit_app' のようなプラグインを使用することをお勧めします + // 今回は単にすべてのルートをポップします + Navigator.of(context).popUntil((route) => false); + }, + ), + ], + ); + }, + ); + } + + */ + + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + // void saveGameState() async { + // DestinationController destinationController = Get.find(); + // SharedPreferences pref = await SharedPreferences.getInstance(); + // pref.setBool("is_in_rog", destinationController.is_in_rog.value); + // pref.setBool("rogaining_counted", destinationController.rogaining_counted.value); + // } + + @override + Future didChangeAppLifecycleState(AppLifecycleState state) async { + try { + LocationController locationController = Get.find(); + + DestinationController destinationController = Get.find< + DestinationController>(); + + //DestinationController destinationController = + // Get.find(); + switch (state) { + case AppLifecycleState.resumed: + // 追加 2024.8.13. + await stopBackgroundTracking(); + destinationController.restartGPS(); + // 追加 2024.8.13. + + // バックグラウンド処理を停止 + if (Platform.isIOS && destinationController.isRunningBackgroundGPS) { + // Foreground に戻った時の処理 + debugPrint( + " ==(Status Changed)==> RESUMED. フォアグラウンドに戻りました"); + locationController.resumePositionStream(); + //print("RESUMED"); + restoreGame(); + + stopBackgroundTracking(); + destinationController.isRunningBackgroundGPS = false; + destinationController.restartGPS(); + } else if (Platform.isAndroid) { + if (destinationController.isRunningBackgroundGPS) { + const platform = MethodChannel('location'); + platform.invokeMethod('stopLocationService'); + destinationController.isRunningBackgroundGPS = false; + destinationController.restartGPS(); + debugPrint("stopped android location service.."); + } + + debugPrint( + "==(Status Changed)==> RESUMED. android フォアグラウンドに戻りました"); + locationController.resumePositionStream(); + //print("RESUMED"); + restoreGame(); + } else { + debugPrint("==(Status Changed)==> RESUMED 不明状態"); + } + break; + case AppLifecycleState.inactive: + // アプリが非アクティブになったときに発生します。 + + if (Platform.isIOS && !destinationController + .isRunningBackgroundGPS) { // iOSはバックグラウンドでもフロントの処理が生きている。 + // これは、別のアプリやシステムのオーバーレイ(着信通話やアラームなど)によって一時的に中断された状態です。 + debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。"); + //locationController.resumePositionStream(); + + // 追加: フロントエンドのGPS信号のlistenを停止 + locationController.stopPositionStream(); + + destinationController.isRunningBackgroundGPS = true; + startBackgroundTracking(); + } else if (Platform.isAndroid && + !destinationController.isRunningBackgroundGPS) { + debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。"); + } else { + debugPrint("==(Status Changed)==> INACTIVE 不明状態"); + } + saveGameState(); + break; + case AppLifecycleState.paused: + // バックグラウンドに移行したときの処理 + //locationController.resumePositionStream(); + debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。"); + if (Platform.isIOS && !destinationController.isRunningBackgroundGPS) { + debugPrint( + "iOS already running background GPS processing when it's inactive"); + } else if (Platform.isAndroid && + !destinationController.isRunningBackgroundGPS) { + debugPrint( + " ==(Status Changed)==> PAUSED. Android バックグラウンド処理。"); + locationController.stopPositionStream(); + const platform = MethodChannel('location'); + platform.invokeMethod('startLocationService'); + //platform.invokeMethod('stopLocationService'); + destinationController.isRunningBackgroundGPS = true; + //startBackgroundTracking(); + } + saveGameState(); + break; + case AppLifecycleState.detached: + // アプリが終了する直前に発生します。この状態では、アプリはメモリから解放される予定です。 + //locationController.resumePositionStream(); + debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。"); + saveGameState(); + break; + case AppLifecycleState.hidden: + // Web用の特殊な状態で、モバイルアプリでは発生しません。 + //locationController.resumePositionStream(); + debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた"); + saveGameState(); + break; + } + }catch(e){ + print('Error finding LocationController: $e'); + } + } + + @override Widget build(BuildContext context) { + return GetMaterialApp( translations: StringValues(), locale: const Locale('ja', 'JP'), @@ -47,7 +603,7 @@ class MyApp extends StatelessWidget { title: 'ROGAINING', theme: ThemeData( colorScheme: ColorScheme.fromSeed( - seedColor: const Color.fromARGB(255, 4, 88, 161)), + seedColor: const Color.fromARGB(255, 36, 135, 221)), useMaterial3: true, ), debugShowCheckedModeBanner: false, @@ -55,58 +611,13 @@ class MyApp extends StatelessWidget { opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: const Duration(milliseconds: 230), - initialBinding: IndexBinding(userToken), //HomeBinding(), + initialBinding: IndexBinding(), //HomeBinding(), initialRoute: AppPages.PERMISSION, getPages: AppPages.routes, enableLog: true, ); } + + + } - - - -// class MyApp extends StatelessWidget { -// MyApp({Key? key}) : super(key: key); - -// // This widget is the root of your application. -// @override -// Widget build(BuildContext context) { -// return MaterialApp( -// title: 'Flutter Demo', -// theme: ThemeData( -// primaryColor: Color(0xfff00B074), -// textTheme: const TextTheme( -// bodyText1: TextStyle( -// fontSize: 18.0, -// fontFamily: 'Barlow-Medium', -// color: Color(0xff464255)), -// ), -// ), -// home: PermissionHandlerScreen(), -// ); -// } -// } - - - - -// class SplashScreen extends StatelessWidget { -// const SplashScreen({Key? key}) : super(key: key); - -// @override -// Widget build(BuildContext context) { -// return WillPopScope( -// onWillPop: () async { -// SystemNavigator.pop(); -// return true; -// }, -// child: Scaffold( -// body: Center( -// child: Text( -// "Splash Screen", -// ), -// ), -// ), -// ); -// } -// } diff --git a/lib/model/auth_user.dart b/lib/model/auth_user.dart new file mode 100644 index 0000000..c77922e --- /dev/null +++ b/lib/model/auth_user.dart @@ -0,0 +1,27 @@ +// プロパティの型がString?やint?などのオプショナル型になっています。 +// これらのプロパティが常に値を持つことが保証されている場合は、非オプショナル型を使用することで、不要なnullチェックを回避できます。 +// +class AuthUser { + AuthUser(); + + //AuthUser.from({required this.id, required this.email, required this.is_rogaining, required this.group, required this.zekken_number, required this.event_code, required this.team_name}); + + AuthUser.fromMap(Map map) + : id = int.parse(map["id"].toString()), + email = map["email"].toString(), + is_rogaining = bool.parse(map["is_rogaining"].toString()), + group = map["group"].toString(), + zekken_number = map["zekken_number"].toString(), + event_code = map["event_code"].toString(), + team_name = map["team_name"].toString(), + auth_token = map["token"]; + + int? id; + String? email; + bool? is_rogaining; + String? group; + String? zekken_number; + String? event_code; + String? team_name; + String? auth_token; +} diff --git a/lib/model/category.dart b/lib/model/category.dart new file mode 100644 index 0000000..07e4bd3 --- /dev/null +++ b/lib/model/category.dart @@ -0,0 +1,80 @@ +// lib/models/category.dart + +class NewCategory { + final int id; + final String categoryName; + final int categoryNumber; + final Duration duration; + final int numOfMember; + final bool family; + final bool female; + final String? time; + + NewCategory({ + required this.id, + required this.categoryName, + this.time, + required this.categoryNumber, + required this.duration, + required this.numOfMember, + required this.family, + required this.female, + }); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is NewCategory && + runtimeType == other.runtimeType && + id == other.id; + + @override + int get hashCode => id.hashCode; + + factory NewCategory.fromJson(Map json) { + final fullCategoryName = json['category_name'] as String; + final parts = fullCategoryName.split('-'); + final baseName = parts[0].trim(); + final time = parts.length > 1 ? parts[1].trim() : null; + + return NewCategory( + id: json['id'] ?? 0, + categoryName: json['category_name'] ?? 'Unknown Category', + time: time, + categoryNumber: json['category_number'] ?? 0, + duration: parseDuration(json['duration']), + numOfMember: json['num_of_member'] ?? 1, + family: json['family'] ?? false, + female: json['female'] ?? false, + ); + } + + String get baseCategory => categoryName.split('-')[0].trim(); + + static Duration parseDuration(String s) { + int hours = 0; + int minutes = 0; + int micros; + List parts = s.split(':'); + if (parts.length > 2) { + hours = int.parse(parts[parts.length - 3]); + } + if (parts.length > 1) { + minutes = int.parse(parts[parts.length - 2]); + } + micros = (double.parse(parts[parts.length - 1]) * 1000000).round(); + return Duration(hours: hours, minutes: minutes, microseconds: micros); + } + + Map toJson() { + return { + 'id': id, + 'category_name': categoryName, + 'category_number': categoryNumber, + 'duration': duration.inSeconds, + 'num_of_member': numOfMember, + 'family': family, + 'female': female, + }; + } +} \ No newline at end of file diff --git a/lib/model/destination.dart b/lib/model/destination.dart index 7730066..39b7eeb 100644 --- a/lib/model/destination.dart +++ b/lib/model/destination.dart @@ -1,113 +1,138 @@ - - +// プロパティの型がString?やint?などのオプショナル型になっています。 +// これらのプロパティが常に値を持つことが保証されている場合は、非オプショナル型を使用することで、不要なnullチェックを回避できます。 +// class Destination { - String? name; - String? address; - String? phone; - String? email; - String? webcontents; - String? videos; - String? category; - int? series; - double? lat; - double? lon; - String? sub_loc_id; - int? location_id; - int? list_order; - String? photos; - double? checkin_radious; - int? auto_checkin; - bool? selected = false; - bool? checkedin = false; - double? cp; - double? checkin_point; - double? buy_point; - int? hidden_location; + String? name; + String? address; + String? phone; + String? email; + String? webcontents; + String? videos; + String? category; + int? series; + double? lat; + double? lon; + String? sub_loc_id; + int? location_id; + int? list_order; + String? photos; + double? checkin_radious; + int? auto_checkin; + bool? selected = false; + bool? checkedin = false; + double? cp; + double? checkin_point; + double? buy_point; + int? hidden_location; + String? checkin_image; + String? buypoint_image; + bool forced_checkin = false; + int recipt_times = 0; + String? tags; - Destination({ - this.name, - this.address, - this.phone, - this.email, - this.webcontents, - this.videos, - this.category, - this.series, - this.lat, - this.lon, - this.sub_loc_id, - this.location_id, - this.list_order, - this.photos, - this.checkin_radious, - this.auto_checkin, - this.selected, - this.checkedin, - this.cp, - this.checkin_point, - this.buy_point, - this.hidden_location - }); + bool use_qr_code = false; // QR code を使用するかどうか。default=false - factory Destination.fromMap(Map json) { + Destination( + {this.name, + this.address, + this.phone, + this.email, + this.webcontents, + this.videos, + this.category, + this.series, + this.lat, + this.lon, + this.sub_loc_id, + this.location_id, + this.list_order, + this.photos, + this.checkin_radious, + this.auto_checkin, + this.selected, + this.checkedin, + this.cp, + this.checkin_point, + this.buy_point, + this.hidden_location, + this.checkin_image, + this.buypoint_image, + this.forced_checkin = false, + this.recipt_times = 0, + this.tags}); //, ... use_qr_code をDBに追加したらオープン +// this.use_qr_code = false}); - bool selec = json['selected'] == 0 ? false : true; - bool checkin = json['checkedin'] == 0 ? false : true; + factory Destination.fromMap(Map json) { + bool selec = json['selected'] == 0 ? false : true; + bool checkin = json['checkedin'] == 0 ? false : true; + bool forcedCheckin = json['forced_checkin'] == 0 ? false : true; + bool useQrCode = json['use_qr_code'] == 1 ? true : false; + //print("-----tags model----- ${json}"); - return Destination( - name: json['name'], - address: json['address'], - phone: json['phone'], - email: json['email'], - webcontents: json['webcontents'], - videos: json['videos'], - category: json['category'], - series: json['series'], - lat: json['lat'], - lon: json['lon'], - sub_loc_id : json['sub_loc_id'], - location_id: json['location_id'], - list_order: json['list_order'], - photos: json['photos'], - checkin_radious: json['checkin_radious'], - auto_checkin:json['auto_checkin'], - selected: selec, - checkedin: checkin, - cp: json['cp'], - checkin_point: json['checkin_point'], - buy_point: json['buy_point'], - hidden_location: json['hidden_location'] - ); - } + return Destination( + name: json['name'], + address: json['address'], + phone: json['phone'], + email: json['email'], + webcontents: json['webcontents'], + videos: json['videos'], + category: json['category'], + series: json['series'], + lat: json['lat'], + lon: json['lon'], + sub_loc_id: json['sub_loc_id'], + location_id: json['location_id'], + list_order: json['list_order'], + photos: json['photos'], + checkin_radious: json['checkin_radious'], + auto_checkin: json['auto_checkin'], + selected: selec, + checkedin: checkin, + cp: json['cp'], + checkin_point: json['checkin_point'], + buy_point: json['buy_point'], + hidden_location: json['hidden_location'], + checkin_image: json['checkin_image'], + buypoint_image: json["buypoint_image"], + forced_checkin: forcedCheckin, + recipt_times: json["recipt_times"], + tags: json["tags"] ); //, +// use_qr_code: useQrCode); + } - Map toMap(){ - int sel = selected == false ? 0 : 1; - int check = checkedin == false ? 0 : 1; - return { - 'name':name, - 'address': address, - 'phone': phone, - 'email': email, - 'webcontents': webcontents, - 'videos': videos, - 'category':category, - 'series':series, - 'lat':lat, - 'lon':lon, - 'sub_loc_id': sub_loc_id, - 'location_id':location_id, - 'list_order':list_order, - 'photos':photos, - 'checkin_radious': checkin_radious, - 'auto_checkin': auto_checkin, - 'selected': sel, - 'checkedin': check, - 'cp' : cp, - 'checkin_point' : checkin_point, - 'buy_point' : buy_point, - 'hidden_location' : hidden_location - }; - } - - -} \ No newline at end of file + Map toMap() { + int sel = selected == false ? 0 : 1; + int check = checkedin == false ? 0 : 1; + int forcedCheckin = forced_checkin == false ? 0 : 1; + return { + 'name': name, + 'address': address, + 'phone': phone, + 'email': email, + 'webcontents': webcontents, + 'videos': videos, + 'category': category, + 'series': series, + 'lat': lat, + 'lon': lon, + 'sub_loc_id': sub_loc_id, + 'location_id': location_id, + 'list_order': list_order, + 'photos': photos, + 'checkin_radious': checkin_radious, + 'auto_checkin': auto_checkin, + 'selected': sel, + 'checkedin': check, + 'cp': cp, + 'checkin_point': checkin_point, + 'buy_point': buy_point, + 'hidden_location': hidden_location, + 'checkin_image': checkin_image, + 'buypoint_image': buypoint_image, + 'forced_checkin': forcedCheckin, + 'recipt_times': recipt_times, + 'tags': tags //, + //'use_qr_code': use_qr_code + }; + } +} diff --git a/lib/model/entry.dart b/lib/model/entry.dart new file mode 100644 index 0000000..6e84524 --- /dev/null +++ b/lib/model/entry.dart @@ -0,0 +1,50 @@ +// lib/models/entry.dart +import 'event.dart'; +import 'team.dart'; +import 'category.dart'; + +class Entry { + final int id; + final Team team; + final Event event; + final NewCategory category; + final DateTime? date; + final int zekkenNumber; // 新しく追加 + final String owner; + + Entry({ + required this.id, + required this.team, + required this.event, + required this.category, + required this.date, + required this.zekkenNumber, + required this.owner, + }); + + factory Entry.fromJson(Map json) { + return Entry( + id: json['id'], + team: Team.fromJson(json['team']), + event: Event.fromJson(json['event']), + category: NewCategory.fromJson(json['category']), + date: json['date'] != null + ? DateTime.tryParse(json['date']) + : null, + zekkenNumber: json['zekken_number'], // 新しく追加 + owner: json['owner'] is Map ? json['owner']['name'] ?? '' : json['owner'] ?? '', + ); + } + + Map toJson() { + return { + 'id': id, + 'team': team.toJson(), + 'event': event.toJson(), + 'category': category.toJson(), + 'date': date?.toIso8601String(), + 'zekken_number': zekkenNumber, // 新しく追加 + 'owner': owner, + }; + } +} \ No newline at end of file diff --git a/lib/model/event.dart b/lib/model/event.dart new file mode 100644 index 0000000..2b4746c --- /dev/null +++ b/lib/model/event.dart @@ -0,0 +1,40 @@ +// lib/models/event.dart + +class Event { + final int id; + final String eventName; + final DateTime startDatetime; + final DateTime endDatetime; + final DateTime deadlineDateTime; // 新しく追加 + + Event({ + required this.id, + required this.eventName, + required this.startDatetime, + required this.endDatetime, + required this.deadlineDateTime, + }); + + factory Event.fromJson(Map json) { + final endDatetime = DateTime.parse(json['end_datetime']); + return Event( + id: json['id'], + eventName: json['event_name'], + startDatetime: DateTime.parse(json['start_datetime']), + endDatetime: DateTime.parse(json['end_datetime']), + deadlineDateTime: json['deadline_datetime'] != null + ? DateTime.parse(json['deadline_datetime']) + : endDatetime.subtract(const Duration(days: 7)), // 仮の実装 + // deadlineDateTime: DateTime.parse(json['deadline_datetime']), + ); + } + + Map toJson() { + return { + 'id': id, + 'event_name': eventName, + 'start_datetime': startDatetime.toIso8601String(), + 'end_datetime': endDatetime.toIso8601String(), + }; + } +} \ No newline at end of file diff --git a/lib/model/game_state_instance.dart b/lib/model/game_state_instance.dart new file mode 100644 index 0000000..12f88c9 --- /dev/null +++ b/lib/model/game_state_instance.dart @@ -0,0 +1,8 @@ +enum LocationState { noGps, notInCheckin, withinCheckin } + +enum GameState { notStarted, startedNotCounted, startedCounted, nodeGoal } + +class GameInsStatetance { + LocationState locationState = LocationState.noGps; + GameState gameState = GameState.notStarted; +} diff --git a/lib/model/gps_data.dart b/lib/model/gps_data.dart new file mode 100644 index 0000000..404a900 --- /dev/null +++ b/lib/model/gps_data.dart @@ -0,0 +1,47 @@ +class GpsData { + int id; + String team_name; + String event_code; + double lat; + double lon; + int is_checkin; + int created_at; + int is_synced; + + GpsData({ + required this.id, + required this.team_name, + required this.event_code, + required this.lat, + required this.lon, + required this.created_at, + this.is_checkin = 0, + this.is_synced = 0, + }); + + factory GpsData.fromMap(Map json) { + return GpsData( + id: json["id"], + team_name: json["team_name"], + event_code: json["event_code"], + lat: json["lat"], + lon: json["lon"], + is_checkin: json["is_checkin"], + created_at: json["created_at"], + is_synced: json["is_synced"] ?? 0, + ); + } + + Map toMap() { + return { + 'id': id, + 'team_name': team_name, + 'event_code': event_code, + 'lat': lat, + 'lon': lon, + 'is_checkin': is_checkin, + 'created_at': created_at, + 'is_synced': is_synced, + }; + } +} diff --git a/lib/model/map_state_instance.dart b/lib/model/map_state_instance.dart new file mode 100644 index 0000000..933e2b3 --- /dev/null +++ b/lib/model/map_state_instance.dart @@ -0,0 +1,6 @@ +import 'package:flutter_map/flutter_map.dart'; + +class MapStateInstance { + MapController? mapController; + LatLngBounds? currentBounds; +} diff --git a/lib/model/team.dart b/lib/model/team.dart new file mode 100644 index 0000000..c906a87 --- /dev/null +++ b/lib/model/team.dart @@ -0,0 +1,51 @@ +// lib/models/team.dart + +import 'category.dart'; +import 'user.dart'; + +class Team { + final int id; +// final String zekkenNumber; + final String teamName; + final NewCategory category; + final User owner; + List members; // membersフィールドを追加 + + Team({ + required this.id, +// required this.zekkenNumber, + required this.teamName, + required this.category, + required this.owner, + this.members = const [], // デフォルト値を空のリストに設定 + }); + + factory Team.fromJson(Map json) { + return Team( + id: json['id'] ?? 0, + //zekkenNumber: json['zekken_number'] ?? 'Unknown', + teamName: json['team_name'] ?? 'Unknown Team', + category: json['category'] != null + ? NewCategory.fromJson(json['category']) + : NewCategory(id: 0, categoryName: 'Unknown', categoryNumber: 0, duration: Duration.zero, numOfMember: 1, family: false, female: false), + owner: json['owner'] != null + ? User.fromJson(json['owner']) + : User(id: 0, email: 'unknown@example.com', firstname: 'Unknown', lastname: 'User', dateOfBirth: null, female: false, isActive: false), + members: json['members'] != null // membersフィールドを解析 + ? List.from(json['members'].map((x) => User.fromJson(x))) + : [], + ); + } + + + Map toJson() { + return { + 'id': id, + //'zekken_number': zekkenNumber, + 'team_name': teamName, + 'category': category.toJson(), + 'owner': owner.toJson(), + 'members': members.map((member) => member.toJson()).toList(), // membersフィールドをJSONに変換 + }; + } +} \ No newline at end of file diff --git a/lib/model/user.dart b/lib/model/user.dart new file mode 100644 index 0000000..9ccf674 --- /dev/null +++ b/lib/model/user.dart @@ -0,0 +1,47 @@ +// lib/models/user.dart + +class User { + final int? id; + final String? email; + final String firstname; + final String lastname; + final DateTime? dateOfBirth; + late final bool female; + final bool isActive; + + User({ + this.id, + this.email, + required this.firstname, + required this.lastname, + this.dateOfBirth, + required this.female, + required this.isActive, + }); + + factory User.fromJson(Map json) { + return User( + id: json['id'], + email: json['email'], + firstname: json['firstname'] ?? 'Unknown', + lastname: json['lastname'] ?? 'Unknown', + dateOfBirth: json['date_of_birth'] != null + ? DateTime.tryParse(json['date_of_birth']) + : null, + female: json['female'] ?? false, + isActive: json['is_active'] ?? false, + ); + } + + Map toJson() { + return { + 'id': id, + 'email': email, + 'firstname': firstname, + 'lastname': lastname, + 'date_of_birth': dateOfBirth?.toIso8601String(), + 'female': female, + 'is_active': isActive, + }; + } +} \ No newline at end of file diff --git a/lib/nrog/pages/auth_page.dart b/lib/nrog/pages/auth_page.dart new file mode 100644 index 0000000..978cc60 --- /dev/null +++ b/lib/nrog/pages/auth_page.dart @@ -0,0 +1,227 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import 'package:keyboard_dismisser/keyboard_dismisser.dart'; +import 'package:gifunavi/model/auth_user.dart'; +import 'package:gifunavi/nrog/pages/home_page.dart'; +import 'package:gifunavi/provider/auth_provider.dart'; +import 'package:gifunavi/services/auth_service.dart'; +import 'package:gifunavi/widgets/c_form_text_field.dart'; +import 'package:gifunavi/widgets/c_password_text_filed.dart'; + +class AuthPage extends ConsumerStatefulWidget { + const AuthPage({super.key}); + + @override + ConsumerState createState() => _AuthPageState(); +} + +class _AuthPageState extends ConsumerState { + final _formkey = GlobalKey(); + final FocusNode focusEmail = FocusNode(); + final FocusNode focusPwd = FocusNode(); + var _authMode = 'login'; + bool _isLoginProgress = false; + + final TextEditingController _emailTextEditingController = + TextEditingController(); + final TextEditingController _passwordTextEditingController = + TextEditingController(); + + @override + void initState() { + super.initState(); + _emailTextEditingController.addListener(() => setState(() {})); + WidgetsBinding.instance.addPostFrameCallback((_) { + checkUser(); + }); + } + + void _submit() async { + setState(() { + _isLoginProgress = true; + }); + if (_formkey.currentState!.validate()) { + AuthService authService = AuthService(); + AuthUser? user = await authService.userLogin( + _emailTextEditingController.text, + _passwordTextEditingController.text); + if (user != null) { + setState(() { + ref.read(authUserStateProvider.notifier).addLogin(user); + }); + } + } + setState(() { + _isLoginProgress = false; + }); + } + + Future _submitToken(String token) async { + setState(() { + _isLoginProgress = true; + }); + AuthService authService = AuthService(); + AuthUser? user = await authService.userFromToken(token); + //////////////print("---user is ${user} ---"); + if (user != null) { + setState(() { + _isLoginProgress = false; + ref.read(authUserStateProvider.notifier).addLogin(user); + }); + } else {} + } + + void checkUser() async { + String? token = + await ref.read(authUserStateProvider.notifier).tokenFromDevice(); + //////////////print("--- red token is ${token} ---"); + await _submitToken(token!); + final id = ref.read(authUserStateProvider).id; + if (id != null) { + if (context.mounted) { + Navigator.of(context) + .push(MaterialPageRoute(builder: (ctx) => const HomePage())); + } + } + return; + } + + @override + Widget build(BuildContext context) { + if (ref.read(authUserStateProvider).id != null) { + Navigator.of(context) + .push(MaterialPageRoute(builder: (ctx) => const HomePage())); + } + + return Scaffold( + resizeToAvoidBottomInset: true, + body: KeyboardDismisser( + gestures: const [ + GestureType.onTap, + //GestureType.onVerticalDragDown + ], + child: Center( + child: SizedBox( + child: Stack( + clipBehavior: Clip.none, + children: [ + buildAuthCard(), + buildLogo(), + ], + ), + )), + ), + ); + } + + Positioned buildLogo() { + return Positioned( + top: -170, + left: MediaQuery.of(context).size.width / 2 - 100, + child: Center( + child: Container( + alignment: Alignment.center, + width: 200, + height: 200, + decoration: const BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + image: AssetImage('assets/images/appicon.png'), + fit: BoxFit.fill), + ), + ), + ), + ); + } + + Widget buildAuthCard() { + return Card( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Form( + key: _formkey, + child: Padding( + padding: const EdgeInsets.only( + top: 40, bottom: 10, left: 12, right: 12), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: CFormTextField( + cFocus: focusEmail, + cController: _emailTextEditingController), + ), + Padding( + padding: const EdgeInsets.all(10), + child: CPasswordTextField( + cController: _passwordTextEditingController, + cFocusNode: focusPwd, + ), + ), + const SizedBox( + height: 12, + ), + buildControlls(), + // SizedBox(height: MediaQuery.of(context).viewInsets.bottom,) + ], + ), + ), + ), + ], + ), + ); + } + + Widget buildControlls() { + if (_isLoginProgress) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + final usr = ref.read(authUserStateProvider); + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only(right: 12.0), + child: ElevatedButton( + onPressed: _submit, + child: Text(_authMode == "login" ? "Submit" : "Register", + style: GoogleFonts.lato( + color: Theme.of(context).colorScheme.secondary))), + ) + ], + ), + TextButton( + onPressed: () { + setState(() { + if (_authMode == 'login') { + _authMode = 'register'; + } else { + _authMode = 'login'; + } + }); + }, + child: Text( + _authMode == "login" + ? "${usr.id} Dont have account, please Register" + : "Already Registered, Login", + style: GoogleFonts.lato( + color: Theme.of(context).colorScheme.primary, + decoration: TextDecoration.underline, + fontSize: 16, + fontWeight: FontWeight.bold), + ), + ), + ], + ); + } +} diff --git a/lib/nrog/pages/home_page.dart b/lib/nrog/pages/home_page.dart new file mode 100644 index 0000000..d96e570 --- /dev/null +++ b/lib/nrog/pages/home_page.dart @@ -0,0 +1,119 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:gifunavi/provider/map_state_provider.dart'; +import 'package:gifunavi/widgets/base_layer_widget.dart'; + +class HomePage extends ConsumerStatefulWidget { + const HomePage({super.key}); + + @override + ConsumerState createState() => _HomePageState(); +} + +class _HomePageState extends ConsumerState { + StreamSubscription? subscription; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final mapStateInstance = ref.watch(mapStateNotifierProvider); + return Scaffold( + //drawer: DrawerPage(), + appBar: AppBar( + title: const Text("Rogaining"), + actions: [ + IconButton( + onPressed: () { + //Get.toNamed(AppPages.HISTORY); + }, + icon: const Icon(Icons.history)), + IconButton( + onPressed: () { + // final tk = indexController.currentUser[0]["token"]; + // if (tk != null) { + // destinationController.fixMapBound(tk); + // } + }, + icon: const Icon(Icons.refresh)), + InkWell( + onTap: () { + //Get.toNamed(AppPages.SEARCH); + }, + child: Container( + height: 32, + width: 75, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(25), + ), + child: const Center( + child: Icon(Icons.search), + ), + ), + ), + //CatWidget(indexController: indexController,), + ], + ), + body: Center( + child: FlutterMap( + mapController: mapStateInstance.mapController, + options: MapOptions( + maxZoom: 18.4, + onMapReady: () { + // indexController.is_mapController_loaded.value = true; + subscription = mapStateInstance.mapController?.mapEventStream + .listen((MapEvent mapEvent) { + if (mapEvent is MapEventMoveStart) { + //print(DateTime.now().toString() + ' [MapEventMoveStart] START'); + // do something + } + // if (mapEvent is MapEventMoveEnd && + // indexController.currentUser.isEmpty) { + //print(DateTime.now().toString() + ' [MapEventMoveStart] END'); + // indexController.loadLocationsBound(); + //indexController.rogMapController!.move(c.center, c.zoom); + // } + }); + }, + center: const LatLng(37.15319600454702, 139.58765950528198), + //bounds: + zoom: 18, + interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag, + + onPositionChanged: (MapPosition pos, isvalue) { + //indexController.currentBound = [pos.bounds!]; + }, + // onTap: (_, __) => popupController + // .hideAllPopups(), // Hide popup when the map is tapped. + ), + children: [ + const BaseLayer(), + CurrentLocationLayer( + followOnLocationUpdate: FollowOnLocationUpdate.once, + turnOnHeadingUpdate: TurnOnHeadingUpdate.never, + style: const LocationMarkerStyle( + marker: DefaultLocationMarker( + child: Icon( + Icons.navigation, + color: Colors.yellowAccent, + ), + ), + markerSize: const Size(27, 27), + markerDirection: MarkerDirection.heading, + ), + ), + const MarkerLayer(markers: []) + ], + ), + ), + ); + } +} diff --git a/lib/nrog/pages/permission_page.dart b/lib/nrog/pages/permission_page.dart new file mode 100644 index 0000000..5403060 --- /dev/null +++ b/lib/nrog/pages/permission_page.dart @@ -0,0 +1,126 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:gifunavi/nrog/pages/auth_page.dart'; + +class PermissionPage extends StatefulWidget { + const PermissionPage({super.key}); + + @override + State createState() => _PermissionPageState(); +} + +class _PermissionPageState extends State { + bool hasNavigated = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _checkPermissionStatus(); + }); + } + + _checkPermissionStatus() async { + PermissionStatus status = await Permission.location.status; + + if (status.isGranted == false) { + if (context.mounted) { + showAlert(context); + } + } else if (status.isPermanentlyDenied) { + await requestPermission(); + } else { + if (mounted) { + Navigator.of(context) + .push(MaterialPageRoute(builder: (_) => const AuthPage())); + } + } + } + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Text(""), + ); + } + + void showAlert(BuildContext context) { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('ロケーション許可'), + content: const SingleChildScrollView( + child: ListBody( + children: [ + Text('このアプリでは、位置情報の収集を行います。'), + Text( + '岐阜ナビアプリではチェックポイントの自動チェックインの機能を可能にするために、現在地のデータが収集されます。アプリを閉じている時や、使用していないときにも収集されます。位置情報は、個人を特定できない統計的な情報として、ユーザーの個人情報とは一切結びつかない形で送信されます。お知らせの配信、位置情報の利用を許可しない場合は、この後表示されるダイアログで「許可しない」を選択してください。'), + ], + ), + ), + actions: [ + ElevatedButton( + child: const Text('OK'), + onPressed: () { + requestPermission(); + }, + ), + ], + )); + } + + Future requestPermission() async { + PermissionStatus permission = await Permission.location.status; + if (permission == PermissionStatus.permanentlyDenied) { + showPermanentAlert(); + } else { + PermissionStatus newPermission = await Permission.location.request(); + if (newPermission != PermissionStatus.granted) { + exit(0); + } else { + if (context.mounted) { + Navigator.of(context) + .push(MaterialPageRoute(builder: (_) => const AuthPage())); + } + } + } + } + + void showPermanentAlert() { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('無効'), + content: const SingleChildScrollView( + child: ListBody( + children: [ + Text('位置情報が無効になっています'), + Text( + 'このアプリケーションへの位置情報アクセスが無効になっています。続行するには設定>プライバシーとセキュリティ>位置情報サービス>岐阜ナビ で有効にしてください。'), + ], + ), + ), + actions: [ + ElevatedButton( + child: const Text('OK'), + onPressed: () async { + await openAppSettings().then( + (value) async { + if (value) { + if (await Permission + .location.status.isPermanentlyDenied == + true && + await Permission.location.status.isGranted == + false) { + requestPermission(); /* opens app settings until permission is granted */ + } + } + }, + ); + }, + ), + ], + )); + } +} diff --git a/lib/pages/WebView/WebView_page.dart b/lib/pages/WebView/WebView_page.dart new file mode 100644 index 0000000..3186594 --- /dev/null +++ b/lib/pages/WebView/WebView_page.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class WebViewPage extends StatelessWidget { + final String url; + + const WebViewPage({super.key, required this.url}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('WebView'), + ), + body: WebViewWidget( + controller: WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..loadRequest(Uri.parse(url)), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/camera/camera_page.dart b/lib/pages/camera/camera_page.dart index f48fd30..0046d21 100644 --- a/lib/pages/camera/camera_page.dart +++ b/lib/pages/camera/camera_page.dart @@ -1,126 +1,492 @@ import 'dart:async'; +import 'dart:convert'; // この行を追加または確認 +import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; -import 'package:rogapp/model/destination.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/services/external_service.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/services/external_service.dart'; +import 'package:gifunavi/utils/const.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; +import 'package:http/http.dart' as http; // この行を追加 + + +// 関数 getTagText は、特定の条件に基づいて文字列から特定の部分を抽出し、返却するためのものです。 +// 関数は2つのパラメータを受け取り、条件分岐を通じて結果を返します。 +// +// この関数は、タグのリスト(空白を含む文字列)を適切に解析し、条件に応じて特定のタグを抽出するために設計されています。 +// 異なる種類の空白文字(半角、全角)で異なる分割を行い、与えられた条件(isRecept)に応じて適切なタグを選択して返却します。 +// +String getTagText(bool isRecept, String? tags) { + // bool isRecept: 真偽値を受け取り、この値によって処理の分岐が行われます。 + // String? tags: オプショナルな文字列(null が許容される)。空白文字を含む可能性のあるタグのリストを表します。 + + // 空のチェック: + // tags が null または空文字列 ("") の場合、何も含まれていないことを意味し、関数はただちに空文字列を返します。 + // + if (tags == null || tags.isEmpty) { + return ""; + } + + // タグの分割: + // tags が空ではない場合、文字列を空白文字で分割します。 + // ここで2種類の空白文字(半角 " " と全角 " ")に対応するため、2回分割を行っています。 + // tts: 半角スペース " " で分割した結果のリスト。 + // ttt: 全角スペース " " で分割した結果のリスト。 + // + List tts = tags.split(" "); + List ttt = tags.split(" "); + + // 条件分岐: + // isRecept の値によって、処理が分岐します。 + // + if (isRecept) { + // isRecept が true の場合: + // 全角スペースで分割した結果 (ttt) の長さが半角スペースで分割した結果 (tts) の長さより大きく、 + // かつ ttt が1つ以上の要素を持つ場合、ttt[1] (全角スペースで分割後の2番目の要素)を返します。 + if (ttt.length > tts.length && ttt.length > 1) { + return ttt[1]; + } + } + if (!isRecept) { + // isRecept が false の場合: + // 全角スペースで分割したリストが半角スペースで分割したリストよりも長い場合、ttt[0] (全角スペースで分割後の最初の要素)を返します。 + // 上記の条件に当てはまらない場合、半角スペースで分割したリストの最初の要素 tts[0] を返します。 + // + if (ttt.length > tts.length && ttt.length > 1) { + return ttt[0]; + } + } + if (!isRecept) { + // 最終的な返却: + // 上記の条件に何も該当しない場合(主に isRecept が true であり、全角スペースの条件に該当しない場合)、空文字列 "" を返します。 + return tts[0]; + } + return ""; +} + +// 要修正:画像の読み込みエラーが発生した場合のエラーハンドリングが不十分です。エラーメッセージを表示するなどの処理を追加してください。 +// getDisplayImage は、Destination オブジェクトを受け取り、特定の条件に基づいて表示する画像を返す機能を持っています。 +// Flutterの Image ウィジェットを使用して、適切な画像を表示します。 +// +// この関数は、提供された Destination オブジェクトに基づいて適切な画像を動的に選択し、 +// その画像を表示するための Image ウィジェットを生成します。 +// デフォルトの画像、完全なURL、またはサーバーURLと組み合わされた画像パスを使用して、条件に応じた画像の取得を試みます。 +// また、エラー発生時にはデフォルト画像にフォールバックすることでユーザー体験を向上させます。 +// +Image getDisplayImage(Destination destination) { + // Destination destination: これは Destination クラスのインスタンスで、 + // CheckPointのデータを持っているオブジェクトです。 + // このクラスには少なくとも phone と photos というプロパティが含まれている + // + + // サーバーURLの取得: + // serverUrl 変数には ConstValues.currentServer() メソッドから現在のサーバーのURLが取得されます。 + // これは画像を取得する際の基本URLとして使用される可能性があります。 + // + String serverUrl = ConstValues.currentServer(); + + // デフォルト画像の設定: + // img 変数にはデフォルトの画像が設定されます。 + // これは、アセットから "assets/images/empty_image.png" をロードするための Image.asset コンストラクタを使用しています。 + // + Image img = Image.asset("assets/images/empty_image.png"); + + // 電話番号のチェック: + // destination.phone が null の場合、関数は img(デフォルト画像)を返します。 + // これは、phone プロパティが画像URLの代用として何らかの形で使用されることを示唆していますが、 + // それが null であればデフォルト画像を使用するという意味です。 + // + if (destination.phone == null) { + return img; + } + + // 画像URLの構築と画像の返却: + // destination.photos が http を含む場合、これはすでに完全なURLとして提供されていることを意味します。 + // このURLを NetworkImage コンストラクタに渡し、Image ウィジェットを生成して返します。 + // そうでない場合は、serverUrl と destination.photos を組み合わせたURLを生成して NetworkImage に渡し、画像を取得します。 + // + if (destination.photos!.contains('http')) { + return Image( + image: NetworkImage( + destination.phone!, + ), + errorBuilder: + (BuildContext context, Object exception, StackTrace? stackTrace) { + return Image.asset("assets/images/empty_image.png"); + }, + ); + } else { + return Image( + image: NetworkImage( + '$serverUrl/media/compressed/${destination.photos}', + ), + errorBuilder: + (BuildContext context, Object exception, StackTrace? stackTrace) { + return Image.asset("assets/images/empty_image.png"); + }, + ); + } +} + +// getFinishImage は、ImageProvider 型のオブジェクトを返す関数で、Flutterアプリケーションで使用される画像を提供します。 +// この関数は、DestinationController というクラスのインスタンスに依存しており、特定の状態に基づいて適切な画像を返します。 +// +// この関数は、アプリケーションの現在の状態に依存して動的に画像を提供します。 +// DestinationController の photos リストに基づいて画像を選択し、リストが空の場合はデフォルトの画像を提供します。 +// これにより、画像の動的な管理が可能になり、ユーザーインターフェースの柔軟性が向上します。 +// また、ImageProvider クラスを使用することで、 +// 画像の具体的な取得方法(ファイルからの読み込みやアセットからのロードなど)を抽象化し、 +// Flutterの Image ウィジェットで直接使用できる形式で画像を返します。 +// +ImageProvider getFinishImage() { + + // DestinationControllerの取得: + // destinationController は Get.find() を使用して取得されます。 + // これは、GetXというFlutterの状態管理ライブラリの機能を使用して、 + // 現在のアプリケーションコンテキストから DestinationController タイプのインスタンスを取得するものです。 + // これにより、アプリケーションの他の部分で共有されている DestinationController の現在のインスタンスにアクセスします。 + // + DestinationController destinationController = + Get.find(); + + // 画像の決定: + // destinationController.photos リストが空でないかどうかをチェックします。 + // このリストは、ファイルパスまたは画像リソースへの参照を含む可能性があります。 + // + if (destinationController.photos.isNotEmpty) { + // リストが空でない場合、最初の要素 (destinationController.photos[0]) が使用されます。 + // FileImage コンストラクタを使用して、このパスから ImageProvider を生成します。 + // これは、ローカルファイルシステム上の画像ファイルを参照するためのものです。 + // + return FileImage(destinationController.photos[0]); + + } else { + // destinationController.photos が空の場合、 + // AssetImage を使用してアプリケーションのアセットからデフォルトの画像('assets/images/empty_image.png')を + // ロードします。これはビルド時にアプリケーションに組み込まれる静的なリソースです。 + // + return const AssetImage('assets/images/empty_image.png'); + } +} + +// getReceiptImage は、ImageProvider 型を返す関数です。 +// この関数は DestinationController オブジェクトに依存しており、条件に応じて特定の画像を返します。 +// この関数は getFinishImage 関数と非常に似ており、ほぼ同じロジックを使用していますが、返されるデフォルトの画像が異なります。 +// +ImageProvider getReceiptImage() { + DestinationController destinationController = + Get.find(); + if (destinationController.photos.isNotEmpty) { + return FileImage(destinationController.photos[0]); + } else { + return const AssetImage('assets/images/money.png'); + } +} + +// CameraPageクラスは、目的地に応じて適切なカメラ機能とアクションボタンを提供します。 +// 手動チェックイン、ゴール撮影、購入ポイント撮影など、様々なシナリオに対応しています。 +// また、ロゲイニングが開始されていない場合は、StartRogainingウィジェットを表示して、ユーザーにロゲイニングの開始を促します。 +// CameraPageクラスは、IndexControllerとDestinationControllerを使用して、 +// 現在の状態や目的地の情報を取得し、適切なUIを構築します。 +// また、写真の撮影や購入ポイントの処理など、様々な機能を提供しています。 +// class CameraPage extends StatelessWidget { - Destination? destination; - CameraPage({Key? key, this.destination}) : super(key: key); - DestinationController destinationController = Get.find(); + bool? manulaCheckin = false; // 手動チェックインを示すブール値(デフォルトはfalse) + bool? buyPointPhoto = false; // 購入ポイントの写真を示すブール値(デフォルトはfalse) + Destination destination; // 目的地オブジェクト + Destination? dbDest; // データベースから取得した目的地オブジェクト(オプショナル) + String? initImage; // 初期画像のパス(オプショナル) + bool? buyQrCode = false; + + CameraPage( + {super.key, + required this.destination, + this.dbDest, + this.manulaCheckin, + this.buyPointPhoto, + this.initImage}); + DestinationController destinationController = + Get.find(); IndexController indexController = Get.find(); + var settingGoal = false.obs; + Timer? timer; - ImageProvider getFinishImage(){ - if(destinationController.photos.isNotEmpty){ - return FileImage(destinationController.photos[0]); - } - else{ - return const AssetImage('assets/images/empty_image.png'); - } - } + // 現在の状態に基づいて、適切なアクションボタンを返します。 + // 要修正:エラーハンドリングが不十分です。例外が発生した場合の処理を追加することをお勧めします。 + // + Widget getAction(BuildContext context) { + if (manulaCheckin == true) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Wrap( + spacing: 16.0, + runSpacing: 8.0, + children: [ + Obx(() => ElevatedButton( + onPressed: () { + destinationController.openCamera(context, destination); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(20), + foregroundColor: Colors.white, + backgroundColor: destinationController.photos.isEmpty + ? Colors.red + : Colors.grey[300], + ), + child: destinationController.photos.isEmpty + ? const Text("撮影", style: TextStyle(color: Colors.white)) + : const Text("再撮影", style: TextStyle(color: Colors.black)), + )), + Obx(() => destinationController.photos.isNotEmpty + ? ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: Colors.red), + onPressed: () async { + await destinationController.makeCheckin(destination, true, + destinationController.photos[0].path); + destinationController.rogainingCounted.value = true; + destinationController.skipGps = false; + destinationController.isPhotoShoot.value = false; - Widget getAction(BuildContext context){ - if(destinationController.is_at_goal.value && destinationController.is_in_rog.value){ - return Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ElevatedButton( - onPressed: (){ - destinationController.openCamera(context); - }, - child: Text("take_photo of the clock".tr) - ), - Obx(() => - destinationController.photos.isNotEmpty ? - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red - ), - onPressed: (){ - int userId = indexController.currentUser[0]["user"]["id"]; - //print("--- Pressed -----"); - String team = indexController.currentUser[0]["user"]['team_name']; - //print("--- _team : ${_team}-----"); - String eventCode = indexController.currentUser[0]["user"]["event_code"]; - //print("--- _event_code : ${_event_code}-----"); - String token = indexController.currentUser[0]["token"]; - //print("--- _token : ${_token}-----"); - DateTime now = DateTime.now(); - String formattedDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(now); - - ExternalService().makeGoal(userId, token, team, destinationController.photos[0].path, formattedDate, eventCode).then((value){ - print("---called ext api ${value['status']} ------"); - if(value['status'] == 'OK'){ - Get.back(); - destinationController.skip_gps = false; - Get.snackbar("目標が保存されました", "目標が正常に追加されました"); - destinationController.resetRogaining(); - } - else{ - print("---- status ${value['status']} ---- "); - Get.snackbar("目標が追加されていません", "please_try_again"); - } - }); - }, - child: Text("finish_goal".tr) - ): - Container() - ) - ], - ); + Get.snackbar("チェックインしました。", + "${destination.sub_loc_id} : ${destination.name}", + backgroundColor: Colors.green, + colorText: Colors.white, + duration: const Duration(seconds: 2), + ); + await Future.delayed(const Duration(seconds: 2)); + + Navigator.of(context).pop(true); + }, + child: const Text("チェックイン", style: TextStyle(color: Colors.white)), + ) + : Container()) + ], + ), + ); } - else{ + + if (destinationController.isAtGoal.value && + destinationController.isInRog.value && + destination.cp == -1) { + // isAtGoalがtrueで、isInRogがtrue、destination.cpが-1の場合は、ゴール用の撮影ボタンとゴール完了ボタンを表示します。 + //goal return Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Obx(() => - ElevatedButton( - onPressed: (){ - destinationController.openCamera(context); - }, - child: destinationController.photos.isNotEmpty ? const Text("再撮影") : const Text("撮影") - ) - ), - Obx(() => - destinationController.photos.isNotEmpty ? - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red - ), - onPressed: (){ - print("##### current destination ${indexController.currentDestinationFeature[0].sub_loc_id} #######"); - destinationController.makeCheckin(indexController.currentDestinationFeature[0], true, destinationController.photos[0].path); - Get.back(); - destinationController.rogaining_counted.value = true; - destinationController.skip_gps = false; - destinationController.is_photo_shoot.value = false; - Get.snackbar("チェックインした", "正常にチェックインしました"); - - // ExternalService().makeGoal(user_id, _token, _team, destinationController.photos[0].path, formattedDate, _event_code).then((value){ - // print("---called ext api ${value['status']} ------"); - // if(value['status'] == 'OK'){ - // Get.back(); - // destinationController.skip_gps = false; - // Get.snackbar("Checked in", "Checked in successfuly"); - // } - // else{ - // print("---- status ${value['status']} ---- "); - // Get.snackbar("Checkin not added", "please_try_again"); - // } - // }); - }, - child: const Text("チェックイン") - ): - Container() - ) - ], - ); + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () { + if (settingGoal.value == false) { + destinationController.openCamera(context, destination); + } + }, + child: Text("take_photo of the clock".tr)), + Obx(() => destinationController.photos.isNotEmpty + ? settingGoal.value == false + ? ElevatedButton( + style: + ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.red + ), + onPressed: () async { + // print( + // "----- user isss ${indexController.currentUser[0]} -----"); + + settingGoal.value = true; + try { + int userId = + indexController.currentUser[0]["user"]["id"]; + //print("--- Pressed -----"); + String team = indexController.currentUser[0]["user"] + ['team_name']; + //print("--- _team : ${_team}-----"); + String eventCode = indexController.currentUser[0] + ["user"]["event_code"]; + //print("--- _event_code : ${_event_code}-----"); + String token = + indexController.currentUser[0]["token"]; + //print("--- _token : ${_token}-----"); + DateTime now = DateTime.now(); + String formattedDate = + DateFormat('yyyy-MM-dd HH:mm:ss').format(now); + + await ExternalService() + .makeGoal( + userId, + token, + team, + destinationController.photos[0].path, + formattedDate, + eventCode) + .then((value) { + // print( + // "---called ext api ${value['status']} ------"); + if (value['status'] == 'OK') { + Get.back(); + destinationController.skipGps = false; + Get.snackbar("目標が保存されました", "目標が正常に追加されました", + backgroundColor: Colors.green, + colorText: Colors.white + ); + destinationController.resetRogaining( + isgoal: true); + } else { + //print("---- status ${value['status']} ---- "); + Get.snackbar(value["detail"], "ERROR", + backgroundColor: Colors.green, + colorText: Colors.white + ); + } + }); + } on Exception catch (_) { + settingGoal.value = false; + } finally { + settingGoal.value = false; + } + }, + child: Text("finish_goal".tr)) + : const Center( + child: CircularProgressIndicator(), + ) + : Container()) + ], + ); + + } else if ((destinationController.isInRog.value || (destination.buy_point != null && destination.buy_point! > 0)) && + dbDest?.checkedin != null && + destination.cp != -1 && + dbDest?.checkedin == true) { + // isInRogがtrueで、dbDest?.checkedinがtrue、destination.cpが-1以外の場合は、購入ポイントの撮影ボタンと完了ボタンを表示します。 + //make buypoint image + return Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Obx(() => ElevatedButton( + onPressed: () { + destinationController.openCamera(context, destination); + }, + child: destinationController.photos.isNotEmpty + ? const Text("再撮影") + : const Text("撮影"))), + Obx(() => destinationController.photos.isNotEmpty + ? ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: Colors.red), + onPressed: () async { + // print( + // "##### current destination ${indexController.currentDestinationFeature[0].sub_loc_id} #######"); + await destinationController.makeBuyPoint( + destination, destinationController.photos[0].path); + Get.back(); + destinationController.rogainingCounted.value = true; + destinationController.skipGps = false; + destinationController.isPhotoShoot.value = false; + Get.snackbar("お買い物加点を行いました。", + "${destination.sub_loc_id} : ${destination.name}", + backgroundColor: Colors.green, + colorText: Colors.white + ); + Navigator.of(context).pop(true); // ここを修正 + }, + child: const Text("レシートの写真を撮ってください")) + : Container()) + ], + ); + + } else if ((destinationController.isInRog.value || (destination.buy_point != null && destination.buy_point! > 0)) && + dbDest?.checkedin != null && + destination.cp != -1 && + destination.use_qr_code == true && + dbDest?.checkedin == true) { + // isInRogがtrueで、dbDest?.checkedinがtrue、destination.cpが-1以外、qrCode == true の場合は、 + // QRCode 撮影ボタンを表示 + return Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Obx(() => ElevatedButton( + onPressed: () { + destinationController.openCamera(context, destination); + }, + child: destinationController.photos.isNotEmpty + ? const Text("再QR読込") + : const Text("QR読込"))), + Obx(() => destinationController.photos.isNotEmpty + ? ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: Colors.red), + onPressed: () async { + // print( + // "##### current destination ${indexController.currentDestinationFeature[0].sub_loc_id} #######"); + await destinationController.makeBuyPoint( + destination, destinationController.photos[0].path); + Get.back(); + destinationController.rogainingCounted.value = true; + destinationController.skipGps = false; + destinationController.isPhotoShoot.value = false; + Get.snackbar("お買い物加点を行いました。", + "${destination.sub_loc_id} : ${destination.name}", + backgroundColor: Colors.green, + colorText: Colors.white + ); + }, + child: const Text("QRコードを読み取ってください")) + : Container()) + ], + ); + } else { + // それ以外の場合は、撮影ボタンとチェックインボタンを表示します。 + return Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Obx(() => ElevatedButton( + onPressed: () { + destinationController.openCamera(context, destination); + }, + child: destinationController.photos.isNotEmpty + ? const Text("再撮影") + : const Text("撮影"))), + Obx(() => destinationController.photos.isNotEmpty + ? ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: Colors.red), + onPressed: () async { + // print( + + // "##### current destination ${indexController.currentDestinationFeature[0].sub_loc_id} #######"); + await destinationController.makeCheckin( + indexController.currentDestinationFeature[0], + true, + destinationController.photos[0].path); + //Get.back(); + destinationController.rogainingCounted.value = true; + destinationController.skipGps = false; + destinationController.isPhotoShoot.value = false; + + + Get.snackbar( + "チェックインしました", + indexController.currentDestinationFeature[0].name ?? + "", + backgroundColor: Colors.green, + colorText: Colors.white, + duration: const Duration(seconds: 2), // 表示時間を1秒に設定 + ); + // SnackBarの表示が終了するのを待ってからCameraPageを閉じる + await Future.delayed(const Duration(seconds: 2)); + + Navigator.of(context).pop(true); // ここを修正 + }, + child: const Text("チェックイン")) + : Container()) + ], + ); } } @@ -130,127 +496,477 @@ class CameraPage extends StatelessWidget { @override Widget build(BuildContext context) { - if(destinationController.is_in_rog.value){ + //print("---- photos ${destination.photos} ----"); + if (buyPointPhoto == true) { + // buyPointPhotoがtrueの場合は、BuyPointCameraウィジェットを返します。 + //print("--- buy point camera ${destination.toString()}"); + //return BuyPointCamera(destination: destination); + + return SwitchableBuyPointCamera(destination: destination); + + //}else if(destination.use_qr_code){ + // return QRCodeScannerPage(destination: destination); + } else if (destinationController.isInRog.value || (destination.buy_point != null && destination.buy_point! > 0)) { + // isInRogがtrueの場合は、カメラページのUIを構築します。 + // AppBarには、目的地の情報を表示します。 + // ボディには、目的地の画像、タグ、アクションボタンを表示します。 + //print("-----tags camera page----- ${destination.tags}"); + //print("--- in normal camera ${destination.toString()}"); return Scaffold( - appBar: - destinationController.is_in_rog.value && destinationController.rogaining_counted.value == true ? - AppBar( - title: destination!.cp == -1 ? - Text("finishing_rogaining".tr) - : - Text("cp_pls_take_photo".tr) - , - leading: IconButton( - icon: Text("cancel".tr), - onPressed: (){ - Navigator.of(context).pop(); - destinationController.skip_10s = true; - timer = Timer.periodic(const Duration(seconds: 10), (Timer t){ - destinationController.skip_10s = false; - }); - }, - ), - centerTitle: true, - ) - : - AppBar( - title: const Text("チェックポイント"), + appBar: destinationController.isInRog.value && + destinationController.rogainingCounted.value == true + ? AppBar( + automaticallyImplyLeading: false, + title: destination.cp == -1 + ? Text("finishing_rogaining".tr) + : Text("${destination.sub_loc_id} : ${destination.name}"), + leading: IconButton( + icon: Text("cancel".tr), + onPressed: () { + Navigator.of(context).pop(); + destinationController.skip_10s = true; + timer = + Timer.periodic(const Duration(seconds: 10), (Timer t) { + destinationController.skip_10s = false; + }); + }, + ), + centerTitle: true, + ) + : AppBar( + automaticallyImplyLeading: false, + title: Text("${destination.sub_loc_id} : ${destination.name}"), + ), + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Obx( + () => Container( + width: MediaQuery.of(context).size.width, + height: 370, + decoration: BoxDecoration( + image: DecorationImage( + image: destinationController.photos.isEmpty + ? getDisplayImage(destination).image + : getFinishImage(), + fit: BoxFit.cover)), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text(getTagText( + false, + destination.tags, + )) + // child: Obx(() => destinationController.photos.isEmpty == true + // ? const Text("撮影してチェックインしてください。") + // : const Text("チェックインをタップしてください。")), + ), + getAction(context), + ], + ), + ), + ); + } else { + // isInRogがfalseの場合は、StartRogainingウィジェットを返します。 + return StartRogaining(); + } + } +} + +// ロゲイニングが開始されていない場合に表示されるウィジェットです。 +// "You have not started rogaining yet."というメッセージと、戻るボタンを表示します。 +// +class StartRogaining extends StatelessWidget { + StartRogaining({super.key}); + + DestinationController destinationController = + Get.find(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text( + "Not started yet".tr, + ), ), - body: Column( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("You have not started rogaining yet.".tr, + style: const TextStyle(fontSize: 24)), + const SizedBox( + height: 40.0, + ), + ElevatedButton( + onPressed: () { + Get.back(); + destinationController.skipGps = false; + }, + child: const Text("Back"), + ), + ], + ), + ), + ); + } +} + +// 購入ポイントの写真撮影用のウィジェットです。 +// 目的地の画像、タグ、撮影ボタン、完了ボタン、購入なしボタンを表示します。 +// 撮影ボタンをタップすると、カメラが起動します。 +// 完了ボタンをタップすると、購入ポイントの処理が行われます。 +// 購入なしボタンをタップすると、購入ポイントがキャンセルされます。 +// +class SwitchableBuyPointCamera extends StatefulWidget { + final Destination destination; + + const SwitchableBuyPointCamera({super.key, required this.destination}); + + @override + _SwitchableBuyPointCameraState createState() => _SwitchableBuyPointCameraState(); +} + +class _SwitchableBuyPointCameraState extends State { + bool isQRMode = true; + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + final screenHeight = MediaQuery.of(context).size.height; + final qrViewWidth = screenWidth * 2 / 3; + + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text("${widget.destination.sub_loc_id} : ${widget.destination.name}"), + ), + body: SafeArea( + child: Stack( + children: [ + if (isQRMode) + Column( + children: [ + SizedBox(height: screenHeight * 0.1), + Center( + child: SizedBox( + width: qrViewWidth, + height: qrViewWidth, + child: BuyPointCamera_QR(destination: widget.destination), + ), + ), + const Expanded( + child: Align( + alignment: Alignment.topCenter, + child: Padding( + padding: EdgeInsets.only(top: 16.0), + child: Text( + "岐阜ロゲQRコードにかざしてください。", + style: TextStyle(fontSize: 16), + ), + ), + ), + ), + ], + ) + else + Positioned.fill( + child: BuyPointCamera(destination: widget.destination), + ), + Positioned( + right: 16, + bottom: 16, + child: Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.7), + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(isQRMode ? "カメラへ" : "QRへ"), + Switch( + value: isQRMode, + onChanged: (value) { + setState(() { + isQRMode = value; + }); + }, + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} + + +class BuyPointCamera extends StatelessWidget { + BuyPointCamera({super.key, required this.destination}); + + Destination destination; + DestinationController destinationController = + Get.find(); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Center( - child: Obx(() => - Container( - width: MediaQuery.of(context).size.width, - height: 370, - decoration: BoxDecoration( - image: - DecorationImage( - image: getFinishImage(), - fit: BoxFit.cover - ) - ), - ), + child: Obx( + () => + Container( + width: MediaQuery + .of(context) + .size + .width, + height: 370, + decoration: BoxDecoration( + image: DecorationImage( + // 要修正:getReceiptImage関数の戻り値がnullの場合のエラーハンドリングが不十分です。適切なデフォルト画像を表示するなどの処理を追加してください。 + // + image: getReceiptImage(), fit: BoxFit.cover)), + ), ), ), ), - getAction(context), - ], - ), - ); - } - else { - return StartRogaining(); - } - } -} - -class StartRogaining extends StatelessWidget { - StartRogaining({Key? key}) : super(key: key); - - DestinationController destinationController = Get.find(); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Not started yet".tr,), - ), - body: Container( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text("You have not started rogaining yet.".tr, style: const TextStyle(fontSize: 24)), - const SizedBox(height: 40.0,), - ElevatedButton( - onPressed: (){ - Get.back(); - destinationController.skip_gps = false; - }, - child: const Text("Back"), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text(getTagText(true, destination.tags)), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Wrap( + spacing: 16.0, + runSpacing: 8.0, + children: [ + Obx(() => + ElevatedButton( + onPressed: () { + destinationController.openCamera(context, destination); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(20), + backgroundColor: destinationController.photos.isEmpty + ? Colors.red + : Colors.grey[300], + ), + child: destinationController.photos.isEmpty + ? const Text("撮影", + style: TextStyle(color: Colors.white)) + : const Text("再撮影", + style: TextStyle(color: Colors.black)), + )), + ElevatedButton( + onPressed: () async { + await destinationController.cancelBuyPoint(destination); + Navigator.of(Get.context!).pop(); + destinationController.rogainingCounted.value = true; + destinationController.skipGps = false; + destinationController.isPhotoShoot.value = false; + }, + child: const Text("買い物なし")), + Obx(() => + destinationController.photos.isNotEmpty + ? ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red), + onPressed: () async { + await destinationController.makeBuyPoint( + destination, + destinationController.photos[0].path); + Get.back(); + destinationController.rogainingCounted.value = true; + destinationController.skipGps = false; + destinationController.isPhotoShoot.value = false; + Get.snackbar("お買い物加点を行いました", + "${destination.sub_loc_id} : ${destination.name}", + backgroundColor: Colors.green, + colorText: Colors.white); + }, + child: const Text("完了", + style: TextStyle(color: Colors.white))) + : Container()) + ], ), - ], - ), + ), + ], ), - ), ); } } -class NotAtGoal extends StatelessWidget { - NotAtGoal({Key? key}) : super(key: key); - DestinationController destinationController = Get.find(); + +class BuyPointCamera_QR extends StatefulWidget { + final Destination destination; + + const BuyPointCamera_QR({super.key, required this.destination}); + + @override + _BuyPointCamera_QRState createState() => _BuyPointCamera_QRState(); +} + + + +class _BuyPointCamera_QRState extends State { + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + QRViewController? controller; + bool isQRScanned = false; + + final DestinationController destinationController = Get.find(); + + @override + Widget build(BuildContext context) { + return QRView( + key: qrKey, + onQRViewCreated: _onQRViewCreated, + ); + } + + void _onQRViewCreated(QRViewController controller) { + this.controller = controller; + controller.scannedDataStream.listen((scanData) { + if (!isQRScanned && scanData.code != null && scanData.code!.startsWith('https://rogaining.sumasen.net/api/activate_buy_point/')) { + isQRScanned = true; + _processBuyPoint(); + //_activateBuyPoint(scanData.code!); + } + }); + } + + Future getImageFilePathFromAssets(String assetPath) async { + final byteData = await rootBundle.load(assetPath); + final buffer = byteData.buffer; + Directory tempDir = await getTemporaryDirectory(); + String tempPath = tempDir.path; + var filePath = '$tempPath/temp_qr_receipt.png'; + return (await File(filePath).writeAsBytes( + buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes) + )).path; + } + + + void _processBuyPoint() async { + // アセットの画像をテンポラリファイルにコピー + String predefinedImagePath = await getImageFilePathFromAssets('assets/images/QR_certificate.png'); + + try { + await destinationController.makeBuyPoint(widget.destination, predefinedImagePath); + Get.snackbar('成功', 'お買い物ポイントが有効化されました'); + Navigator.of(context).pop(); + } catch (e) { + Get.snackbar('エラー', 'お買い物ポイントの有効化に失敗しました'); + } finally { + isQRScanned = false; + } + } + + void _activateBuyPoint(String qrCode) async { + final IndexController indexController = Get.find(); + + final userId = indexController.currentUser[0]["user"]["id"]; + final token = indexController.currentUser[0]["token"]; + final teamName = indexController.currentUser[0]["user"]['team_name']; + final eventCode = indexController.currentUser[0]["user"]["event_code"]; + //final cpNumber = destinationController.currentDestinationFeature[0].cp; + final cpNumber = widget.destination.cp; + + try { + final response = await http.post( + Uri.parse('https://rogaining.sumasen.net/api/activate_buy_point/'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Token $token', + }, + body: jsonEncode({ + 'user_id': userId, + 'team_name': teamName, + 'event_code': eventCode, + 'cp_number': cpNumber, + 'qr_code': qrCode, + }), + ); + + if (response.statusCode == 200) { + Get.snackbar('成功', 'お買い物ポイントが有効化されました'); + Navigator.of(context).pop(); + } else { + Get.snackbar('エラー', 'お買い物ポイントの有効化に失敗しました'); + } + } catch (e) { + Get.snackbar('エラー', 'ネットワークエラーが発生しました'); + } finally { + isQRScanned = false; + } + } + + @override + void dispose() { + controller?.dispose(); + super.dispose(); + } +} + + + +class QRCodeScannerPage extends StatefulWidget { + + QRCodeScannerPage({super.key, required this.destination}); + + Destination destination; + + @override + _QRCodeScannerPageState createState() => _QRCodeScannerPageState(); +} + +class _QRCodeScannerPageState extends State { + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + QRViewController? controller; + + @override + void dispose() { + controller?.dispose(); + super.dispose(); + } + + void _onQRViewCreated(QRViewController controller) { + this.controller = controller; + controller.scannedDataStream.listen((scanData) { + // QRコードのデータを処理する + debugPrint("scan data = $scanData"); + String? qrCodeData = scanData.code; + // qrCodeDataを使用してチェックポイントの処理を行う + // 例えば、qrCodeDataからCPのIDと店名を取得し、加点処理を行う + }); + } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Not reached the goal yet".tr,), + body: QRView( + key: qrKey, + onQRViewCreated: _onQRViewCreated, ), - body: Container( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text("You have not reached the goal yet.".tr, style: const TextStyle(fontSize: 24)), - const SizedBox(height: 40.0,), - ElevatedButton( - onPressed: (){ - Get.back(); - destinationController.skip_gps = false; - }, - child: const Text("Back"), - ), - ], - ), - ), - ), ); } } \ No newline at end of file diff --git a/lib/pages/camera/custom_camera_view.dart b/lib/pages/camera/custom_camera_view.dart new file mode 100644 index 0000000..7684b43 --- /dev/null +++ b/lib/pages/camera/custom_camera_view.dart @@ -0,0 +1,185 @@ +import 'dart:io'; +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; +import 'package:gifunavi/model/destination.dart'; + +class CustomCameraView extends StatefulWidget { + final Function(String) onImageCaptured; + final Destination? destination; + + const CustomCameraView({super.key, required this.onImageCaptured, required this.destination}); + + @override + _CustomCameraViewState createState() => _CustomCameraViewState(); +} + +class _CustomCameraViewState extends State { + CameraController? _controller; + late List _cameras; + int _selectedCameraIndex = 0; + double _currentScale = 1.0; + FlashMode _currentFlashMode = FlashMode.off; + Destination? destination; + + @override + void initState() { + super.initState(); + _initializeCamera(); + destination = widget.destination; + } + + Future _initializeCamera() async { + _cameras = await availableCameras(); + _controller = CameraController(_cameras[_selectedCameraIndex], ResolutionPreset.medium); + await _controller!.initialize(); + setState(() {}); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } + + Future _toggleCameraLens() async { + final newIndex = (_selectedCameraIndex + 1) % _cameras.length; + await _controller!.dispose(); + + setState(() { + _controller = null; + _selectedCameraIndex = newIndex; + }); + + _controller = CameraController(_cameras[_selectedCameraIndex], ResolutionPreset.medium); + await _controller!.initialize(); + + setState(() {}); + } + + void _toggleFlashMode() { + setState(() { + _currentFlashMode = (_currentFlashMode == FlashMode.off) ? FlashMode.torch : FlashMode.off; + }); + _controller!.setFlashMode(_currentFlashMode); + } + + void _zoomIn() { + setState(() { + _currentScale += 0.1; + if (_currentScale > 5.0) _currentScale = 5.0; + }); + _controller!.setZoomLevel(_currentScale); + } + + void _zoomOut() { + setState(() { + _currentScale -= 0.1; + if (_currentScale < 1.0) _currentScale = 1.0; + }); + _controller!.setZoomLevel(_currentScale); + } + + void _captureImage() async { + if (_controller!.value.isInitialized) { + final Directory appDirectory = await getApplicationDocumentsDirectory(); + final String imagePath = path.join(appDirectory.path, '${DateTime.now()}.jpg'); + + final XFile imageFile = await _controller!.takePicture(); + await imageFile.saveTo(imagePath); + + widget.onImageCaptured(imagePath); + Navigator.pop(context); + } + } + + @override + Widget build(BuildContext context) { + if (_controller == null || !_controller!.value.isInitialized) { + return Container(); + } + + return Stack( + children: [ + Padding( + padding: const EdgeInsets.only(top: 60.0), // 上部に60ピクセルのパディングを追加 + child: CameraPreview(_controller!), + ), + Positioned( + bottom: 120.0, + left: 16.0, + right: 16.0, + child: Center( + child: Text( + destination?.tags ?? '', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Positioned( + bottom: 16.0, + left: 16.0, + right: 16.0, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + onPressed: _toggleFlashMode, + icon: Icon( + (_currentFlashMode == FlashMode.off) ? Icons.flash_off : Icons.flash_on, + color: Colors.white, + ), + iconSize: 32, + color: Colors.orange, + ), + GestureDetector( + onTap: _captureImage, + child: Container( + height: 80, + width: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + border: Border.all(color: Colors.red, width: 4), + ), + child: const Icon(Icons.camera_alt, color: Colors.red, size: 40), + ), + ), + IconButton( + onPressed: _toggleCameraLens, + icon: const Icon(Icons.flip_camera_ios, color: Colors.white), + iconSize: 32, + color: Colors.blue, + ), + ], + ), + ), + Positioned( + top: 16.0, + right: 16.0, + child: Column( + children: [ + IconButton( + onPressed: _zoomIn, + icon: const Icon(Icons.zoom_in, color: Colors.white), + iconSize: 32, + color: Colors.green, + ), + IconButton( + onPressed: _zoomOut, + icon: const Icon(Icons.zoom_out, color: Colors.white), + iconSize: 32, + color: Colors.green, + ), + ], + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/pages/category/category_page.dart b/lib/pages/category/category_page.dart index 12d7cb9..fee3118 100644 --- a/lib/pages/category/category_page.dart +++ b/lib/pages/category/category_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; class CategoryPage extends StatelessWidget { - const CategoryPage({Key? key}) : super(key: key); + const CategoryPage({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/changepassword/change_password_page.dart b/lib/pages/changepassword/change_password_page.dart index 3c6798c..6de73ae 100644 --- a/lib/pages/changepassword/change_password_page.dart +++ b/lib/pages/changepassword/change_password_page.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/widgets/debug_widget.dart'; class ChangePasswordPage extends StatelessWidget { - ChangePasswordPage({Key? key}) : super(key: key); + ChangePasswordPage({super.key}); + + LogManager logManager = LogManager(); IndexController indexController = Get.find(); @@ -13,150 +16,174 @@ class ChangePasswordPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - resizeToAvoidBottomInset: false, + resizeToAvoidBottomInset: false, backgroundColor: Colors.white, appBar: AppBar( elevation: 0, - backgroundColor: Colors.white, - leading: - IconButton( onPressed: (){ - Navigator.pop(context); - },icon:const Icon(Icons.arrow_back_ios,size: 20,color: Colors.black,)), + leading: IconButton( + onPressed: () { + logManager.addOperationLog('User clicked cancel button on the drawer'); + Navigator.pop(context); + }, + icon: const Icon( + Icons.arrow_back_ios, + size: 20, + color: Colors.black, + )), ), - body: - SizedBox( - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Column( - children: [ - Column( - children: [ - Container( - child: Text("change_password".tr, style: const TextStyle(fontSize: 24.0),), - ), - const SizedBox(height: 30,), - ], - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 40 - ), - child: Column( + body: SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + children: [ + Column( children: [ - makeInput(label: "old_password".tr, controller: oldPasswordController), - makeInput(label: "new_password".tr, controller: newPasswordController, obsureText: true), + Text( + "change_password".tr, + style: const TextStyle(fontSize: 24.0), + ), + const SizedBox( + height: 30, + ), ], ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 40), - child: Container( - padding: const EdgeInsets.only(top: 3,left: 3), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(40), - ), - child: Obx((() => - indexController.is_loading == true ? MaterialButton( - minWidth: double.infinity, - height:60, - onPressed: (){ - - }, - color: Colors.grey[400], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: const CircularProgressIndicator(), - ) : - Column( - children: [ - MaterialButton( - minWidth: double.infinity, - height:60, - onPressed: (){ - if(oldPasswordController.text.isEmpty || newPasswordController.text.isEmpty){ - Get.snackbar( - "no_values".tr, - "values_required".tr, - icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), - snackPosition: SnackPosition.TOP, - duration: const Duration(milliseconds: 800), - backgroundColor: Colors.yellow, - //icon:Image(image:AssetImage("assets/images/dora.png")) - ); - return; - } - indexController.is_loading.value = true; - indexController.changePassword(oldPasswordController.text, newPasswordController.text, context); - //indexController.login(oldPasswordController.text, newPasswordController.text, context); - }, - color: Colors.indigoAccent[400], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: const Text("ログイン",style: TextStyle( - fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70 - ), - ), - ), - const SizedBox(height: 10.0,), - - ], - ) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Column( + children: [ + makeInput( + label: "old_password".tr, + controller: oldPasswordController), + makeInput( + label: "new_password".tr, + controller: newPasswordController, + obsureText: true), + ], ), ), - ) - ), - const SizedBox(height: 20,), - const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - - ], - ) - ], - - ), - ], - ), - ) - ); + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Container( + padding: const EdgeInsets.only(top: 3, left: 3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + ), + child: Obx( + (() => indexController.isLoading.value == true + ? MaterialButton( + minWidth: double.infinity, + height: 60, + onPressed: () {}, + color: Colors.grey[400], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40)), + child: const CircularProgressIndicator(), + ) + : Column( + children: [ + MaterialButton( + minWidth: double.infinity, + height: 60, + onPressed: () { + if (oldPasswordController + .text.isEmpty || + newPasswordController + .text.isEmpty) { + logManager.addOperationLog('User tried to login with blank old password ${oldPasswordController.text} or new password ${newPasswordController.text}.'); + Get.snackbar( + "no_values".tr, + "values_required".tr, + backgroundColor: Colors.red, + colorText: Colors.white, + icon: const Icon( + Icons.assistant_photo_outlined, + size: 40.0, + color: Colors.blue), + snackPosition: SnackPosition.TOP, + duration: const Duration( + milliseconds: 800), + //backgroundColor: Colors.yellow, + //icon:Image(image:AssetImage("assets/images/dora.png")) + ); + return; + } + indexController.isLoading.value = true; + indexController.changePassword( + oldPasswordController.text, + newPasswordController.text, + context); + //indexController.login(oldPasswordController.text, newPasswordController.text, context); + }, + color: Colors.indigoAccent[400], + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: Text( + "login".tr, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white70), + ), + ), + const SizedBox( + height: 10.0, + ), + ], + )), + ), + )), + const SizedBox( + height: 20, + ), + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [], + ) + ], + ), + ], + ), + )); } - Widget makeInput({label, required TextEditingController controller, obsureText = false}){ - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(label,style:const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - color: Colors.black87 - ),), - const SizedBox(height: 5,), - TextField( - controller: controller, - obscureText: obsureText, - decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 0,horizontal: 10), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: (Colors.grey[400])!, + Widget makeInput( + {label, required TextEditingController controller, obsureText = false}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87), + ), + const SizedBox( + height: 5, + ), + TextField( + controller: controller, + obscureText: obsureText, + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: (Colors.grey[400])!, + ), + ), + border: OutlineInputBorder( + borderSide: BorderSide(color: (Colors.grey[400])!), ), ), - border: OutlineInputBorder( - borderSide: BorderSide(color: (Colors.grey[400])! - ), ), - ), - ), - const SizedBox(height: 30.0,) - ], - ); + const SizedBox( + height: 30.0, + ) + ], + ); + } } - - -} \ No newline at end of file diff --git a/lib/pages/city/city_page.dart b/lib/pages/city/city_page.dart index 1682025..67efe54 100644 --- a/lib/pages/city/city_page.dart +++ b/lib/pages/city/city_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; class CityPage extends StatelessWidget { - const CityPage({Key? key}) : super(key: key); + const CityPage({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/debug/debug_binding.dart b/lib/pages/debug/debug_binding.dart new file mode 100644 index 0000000..7160fde --- /dev/null +++ b/lib/pages/debug/debug_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:gifunavi/pages/debug/debug_controller.dart'; + +class DebugBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => DebugController()); + } +} \ No newline at end of file diff --git a/lib/pages/debug/debug_controller.dart b/lib/pages/debug/debug_controller.dart new file mode 100644 index 0000000..c85693e --- /dev/null +++ b/lib/pages/debug/debug_controller.dart @@ -0,0 +1,47 @@ +import 'package:geolocator/geolocator.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/utils/location_controller.dart'; + +class DebugController extends GetxController { + final LocationController locationController = Get.find(); + final gpsSignalStrength = 'high'.obs; + final currentPosition = Rx(null); + final isSimulationMode = false.obs; + + @override + void onInit() { + super.onInit(); + // 位置情報の更新を監視 + locationController.locationMarkerPositionStream.listen((position) { + if (position != null) { + currentPosition.value = Position( + latitude: position.latitude, + longitude: position.longitude, + accuracy: position.accuracy, + altitudeAccuracy: 30, + headingAccuracy: 30, + heading: 0, + altitude: 0, + speed: 0, + speedAccuracy: 0, + timestamp: DateTime.now(), + ); + } + }); + } + + void setGpsSignalStrength(String value) { + gpsSignalStrength.value = value; + locationController.setSimulatedSignalStrength(value); + } + + void toggleSimulationMode() { + isSimulationMode.value = !isSimulationMode.value; + locationController.setSimulationMode(isSimulationMode.value); + if (!isSimulationMode.value) { + // 標準モードに切り替えた場合は、シミュレートされた信号強度をリセット + locationController.setSimulatedSignalStrength('low'); + gpsSignalStrength.value = 'low'; + } + } +} \ No newline at end of file diff --git a/lib/pages/debug/debug_page.dart b/lib/pages/debug/debug_page.dart new file mode 100644 index 0000000..19fbc93 --- /dev/null +++ b/lib/pages/debug/debug_page.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/pages/debug/debug_controller.dart'; + +class DebugPage extends GetView { + const DebugPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('デバッグモード'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('GPS信号強度'), + const SizedBox(height: 20), + Obx( + () => DropdownButton( + value: controller.gpsSignalStrength.value, + onChanged: (value) { + controller.setGpsSignalStrength(value!); + }, + items: ['high', 'medium', 'low'] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + ), + const SizedBox(height: 20), + const Text('現在のGPS座標'), + const SizedBox(height: 10), + Obx( + () => Text( + '緯度: ${controller.currentPosition.value?.latitude ?? '-'}, 経度: ${controller.currentPosition.value?.longitude ?? '-'}', + ), + ), + const SizedBox(height: 20), + const Text('現在のGPS精度'), + const SizedBox(height: 10), + Obx( + () => Text( + '精度: ${controller.currentPosition.value?.accuracy.toStringAsFixed(2) ?? '-'} m', + ), + ), + const SizedBox(height: 20), + Obx( + () => ElevatedButton( + onPressed: controller.toggleSimulationMode, + child: Text(controller.isSimulationMode.value + ? 'シミュレーションモード' + : '標準モード'), + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/destination/destination_binding.dart b/lib/pages/destination/destination_binding.dart index 8fab747..ac36fa5 100644 --- a/lib/pages/destination/destination_binding.dart +++ b/lib/pages/destination/destination_binding.dart @@ -1,9 +1,11 @@ import 'package:get/get.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; +import 'package:gifunavi/main.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; class DestinationBinding extends Bindings { @override void dependencies() { Get.put(DestinationController()); + restoreGame(); } } diff --git a/lib/pages/destination/destination_controller.dart b/lib/pages/destination/destination_controller.dart index 0416732..931657f 100644 --- a/lib/pages/destination/destination_controller.dart +++ b/lib/pages/destination/destination_controller.dart @@ -1,84 +1,250 @@ import 'dart:io'; - -import 'package:camera_camera/camera_camera.dart'; -import 'package:flutter/foundation.dart'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:geojson/geojson.dart'; +import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; +import 'package:geojson_vi/geojson_vi.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; -import 'package:rogapp/model/destination.dart'; -import 'package:rogapp/pages/camera/camera_page.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/routes/app_pages.dart'; -import 'package:rogapp/services/destination_service.dart'; -import 'package:rogapp/services/external_service.dart'; -import 'package:rogapp/services/location_service.dart'; -import 'package:rogapp/services/maxtrix_service.dart'; -import 'package:rogapp/services/perfecture_service.dart'; -import 'package:rogapp/utils/database_helper.dart'; -import 'package:rogapp/widgets/bottom_sheet_new.dart'; +import 'package:gifunavi/main.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/model/gps_data.dart'; +import 'package:gifunavi/pages/camera/camera_page.dart'; +import 'package:gifunavi/pages/camera/custom_camera_view.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/services/DatabaseService.dart'; +import 'package:gifunavi/services/destination_service.dart'; +import 'package:gifunavi/services/external_service.dart'; +import 'package:gifunavi/services/location_service.dart'; +import 'package:gifunavi/services/maxtrix_service.dart'; +import 'package:gifunavi/services/perfecture_service.dart'; +import 'package:gifunavi/utils/database_gps.dart'; +import 'package:gifunavi/utils/database_helper.dart'; +import 'package:gifunavi/utils/location_controller.dart'; +import 'package:gifunavi/widgets/bottom_sheet_new.dart'; import 'dart:async'; +import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; +import 'package:gifunavi/widgets/debug_widget.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; + +import 'package:gifunavi/pages/permission/permission.dart' ; + +// 目的地に関連する状態管理とロジックを担当するクラスです。 +// class DestinationController extends GetxController { - late LocationSettings locationSettings; + late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。 - var destinationCount = 0.obs; - List destinations = [].obs; - double current_lat = 0.0; - double current_lon = 0.0; + //late TeamController teamController = TeamController(); + //Timer? _GPStimer; // GPSタイマーを保持する変数です。 - bool skip_10s = false; + var destinationCount = 0.obs; // 目的地の数を保持するReactive変数です。 + List destinations = [].obs; // 目的地のリストを保持するObservable変数です。 + double currentLat = 0.0; // 現在の緯度と経度を保持する変数です。 + double currentLon = 0.0; + double lastValidLat = 0.0; // 最後に中・強信号で拾ったGPS位置。 + // ロゲ開始を屋内でやったら 0 のままなので、屋外で行うこと。 + double lastValidLon = 0.0; + DateTime lastGPSCollectedTime = DateTime.now(); // 最後にGPSデータが収集された時刻を保持する変数です。 - List currentSelectedDestinations = [].obs; + bool shouldShowBottomSheet = true; // ボトムシートを表示すべきかどうかを示すフラグです。 - var is_in_checkin = false.obs; - var is_in_rog = false.obs; - var is_at_start = false.obs; - var is_at_goal = false.obs; - var is_photo_shoot = false.obs; - DateTime last_goal_at = DateTime.now().subtract(const Duration(days: 1)); + static bool gps_push_started = false; // ゲームの状態を示す静的変数です。 + static bool game_started = false; + static bool ready_for_goal = false; + + bool skip_10s = false; // 10秒間のスキップフラグを示す変数です。 + + List currentSelectedDestinations = [].obs; // 現在選択されている目的地のリストを保持するObservable変数です。 + + var isInCheckin = false.obs; // ゲームの状態を示すReactive変数です。 + var isInRog = false.obs; + var isAtStart = false.obs; + var isAtGoal = false.obs; + var isPhotoShoot = false.obs; + + DateTime lastGoalAt = DateTime.now().subtract(const Duration(days: 1)); // 最後にゴールした時刻を保持する変数です。 //List rogainings = [].obs; - bool checking_in = false; - var is_gps_selected = true.obs; - BuildContext? context; + bool checkingIn = false; // チェックイン中かどうかを示すフラグです。 + var isGpsSelected = true.obs; // GPSが選択されているかどうかを示すReactive変数です。 + BuildContext? context; // ビルドコンテキストを保持する変数です。 - List gps = ["-- stating --"].obs; + List gps = ["-- stating --"].obs; // GPSと位置情報の許可に関する情報を保持するObservable変数です。 List locationPermission = [" -- starting -- "].obs; - var travelMode = 0.obs; + var travelMode = 0.obs; // 移動モードを保持するReactive変数です。 - bool skip_gps = false; + bool skipGps = false; // GPSをスキップするかどうかを示すフラグです。 + bool okToUseGPS = false; // 最新のGPS情報を使用して良いかを示すフラグ。 - Map matrix = {}; + Map matrix = {}; // 行列データを保持する変数です。 - final photos = [].obs; + final photos = [].obs; // 写真のリストを保持するReactive変数です。 - final IndexController indexController = Get.find(); + final IndexController indexController = Get.find(); // IndexControllerのインスタンスを保持する変数です。 + final LocationController locationController = Get.put(LocationController()); // LocationControllerのインスタンスを保持する変数です。 + final DatabaseService dbService = DatabaseService(); // DatabaseServiceのインスタンスを保持する変数です。 - Timer? _timer; - int _start = 0; - int chekcs = 0; - var rogaining_counted = false.obs; + int _start = 0; // 開始時刻を保持する変数です。 + int chekcs = 0; // チェックポイントの数を保持する変数です。 + var rogainingCounted = false.obs; // ロゲイニングがカウントされたかどうかを示すReactive変数です。 + // destinationController.rogainingCountedは、現在のロゲイニングセッションでポイントがカウントされたかどうかを管理するフラグです。 + // + // このフラグは以下のような状況で使用されます: + // + // ロゲイニングを開始したとき、rogainingCountedはfalseに初期化されます。これは、まだポイントがカウントされていないことを示します。 + // チェックポイントに到着し、チェックインが成功したとき、rogainingCountedはtrueに設定されます。これは、そのセッションでポイントがカウントされたことを示します。 + // ロゲイニングを終了したとき、rogainingCountedは再びfalseに設定されます。これは、次のセッションに備えてフラグをリセットするためです。 + // このフラグは、主に以下の目的で使用されます: + // + // ゴール地点でのロジックの制御:rogainingCountedがtrueの場合、つまりポイントがカウントされている場合にのみ、ゴール処理を実行できます。 + // UI の更新:rogainingCountedの状態に基づいて、適切なメッセージやボタンを表示することができます。 + + bool isMapControllerReady = false; + + LatLng lastValidGPSLocation = const LatLng(0, 0); + DateTime lastGPSDataReceivedTime = DateTime.now(); + DateTime lastPopupShownTime = DateTime.now().subtract(const Duration(minutes: 10)); + bool isPopupShown = false; + bool hasReceivedGPSData = true; + + var isCheckingIn = false.obs; // チェックイン操作中はtrueになり、重複してポップアップが出ないようにするもの。 + + var isRouteShowing = false.obs; // ルートが表示されているかどうかを示すReactive変数 + /* + //==== Akira .. GPS信号シミュレーション用 ===== ここから、2024-4-5 + // + + bool kDebugMode = true; + + // シミュレーションモードのフラグ + RxBool isSimulationMode = RxBool(true); + + // シミュレーションモードを切り替えるための関数 + void toggleSimulationMode(bool value) { + isSimulationMode.value = value; + } + + // 現在位置の取得メソッドを追加 + LatLng getCurrentLocation() { + return LatLng(lastValidLat, lastValidLon); + } + + // + // GPS信号の強弱を判断するメソッドを追加します。 + // + String getGpsSignalStrength() { + // デバッグモードかつシミュレーションモードの場合は、シミュレートされた信号強度を返す + print("kDebugMode : ${kDebugMode}, isSimulationMode : ${isSimulationMode.value}"); + if (kDebugMode && isSimulationMode.value) { + return locationController.getSimulatedSignalStrength(); + } + + // 通常モードの場合は、実際の信号強度を返す + final accuracy = locationController.currentPosition.value?.accuracy ?? double.infinity; + if (accuracy <= 10) { + return 'high'; + } else if (accuracy <= 30) { + return 'medium'; + } else { + return 'low'; + } + } + + // + //==== Akira .. GPS信号シミュレーション用 ======= ここまで + */ + + + // ルートをクリアする関数です。 + void clearRoute() { + indexController.routePoints.clear(); + indexController.routePointLenght.value = 0; + isRouteShowing.value = false; + } + + void showGPSDataNotReceivedPopup() { + if (Get.context != null) { + Get.dialog( + AlertDialog( + title: const Text('GPS信号が受信できません'), + content: const Text('GPS信号が受信できる場所に移動してください。'), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('OK'), + ), + ], + ), + ); + } else { + // Get.contextがnullの場合の処理を追加 + print('GPS signal not received, but context is null'); + } + } + + // 最後に有効なGPSデータを受け取ってから10分以上経過している場合にのみメッセージを表示するようにします。 + // + void checkGPSDataReceived() { + if (!hasReceivedGPSData) { + //debugPrint("GPS信号を全く受信していない。"); + if (!isPopupShown) { + // ポップアップしていない。 + showGPSDataNotReceivedPopup(); + lastPopupShownTime = DateTime.now(); + isPopupShown = true; + } + } else { + if (DateTime.now().difference(lastGPSDataReceivedTime).inSeconds >= 600) { + // 前回GPS信号を受信してから10分経過。 + if (!isPopupShown && DateTime.now().difference(lastPopupShownTime).inMinutes >= 3) { + // 前回ポップアップしてから3分経過してなければ + showGPSDataNotReceivedPopup(); + lastPopupShownTime = DateTime.now(); + isPopupShown = true; + } + } else { + isPopupShown = false; + } + } + + } + + // 日時をフォーマットされた文字列に変換する関数です。 + // String getFormatedTime(DateTime datetime) { return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime); } - Destination festuretoDestination(GeoJsonFeature fs) { - GeoJsonMultiPoint mp = fs.geometry as GeoJsonMultiPoint; - LatLng pt = LatLng(mp.geoSerie!.geoPoints[0].latitude, - mp.geoSerie!.geoPoints[0].longitude); + // 追加:Akira 2024-4-5 + // GPS信号の精度が一定値以上の場合、GPS信号が弱いと判断する + // + bool isGpsSignalWeak() { + final accuracy = locationController.currentPosition.value?.accuracy; + if (accuracy == null) { + return true; // 位置情報が取得できていない場合、GPS信号が弱いと見なす + } + return accuracy > 60; + //return locationController.currentPosition.value?.accuracy ?? double.infinity > 50; + } + + // + Destination festuretoDestination(GeoJSONFeature fs) { + GeoJSONMultiPoint mp = fs.geometry as GeoJSONMultiPoint; + LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]); //print("----- ${indexController.currentFeature[0].properties} -----"); return Destination( name: fs.properties!["location_name"], + sub_loc_id: fs.properties!["sub_loc_id"], address: fs.properties!["address"], phone: fs.properties!["phone"], email: fs.properties!["email"], @@ -98,263 +264,520 @@ class DestinationController extends GetxController { buy_point: fs.properties!["buy_point"], selected: false, checkedin: false, - hidden_location: fs.properties!["hidden_location"] == true ? 1 : 0); + hidden_location: fs.properties!["hidden_location"] == true ? 1 : 0, + tags: fs.properties!["tags"]); } - void startTimerLocation(GeoJsonFeature fs, double distance) { - print("---- in startTimer ----"); - //skip_gps = true; + // 指定された目的地の位置情報に基づいてタイマーを開始する関数です。 + // CP情報(fs)と現在位置からCPまでの距離distance を引数として渡します。 + // + Future startTimerLocation(GeoJSONFeature fs, double distance) async { + //print("---- in startTimer ----"); + // print("---- is in rog is $is_in_rog ----"); + double checkinRadious = fs.properties!['checkin_radius'] ?? double.infinity; + // CPのcheckin_radiusを取得し、checkinRadius に代入。値がなければinfinityとする。 + if (checkinRadious >= distance) { + // checkinRadious以内に入ったら、 + indexController.currentFeature.clear(); + // indexController.currentFeatureを空にします。 + Destination d = festuretoDestination(fs); - for (Destination de in destinations) { - if (de.location_id == d.location_id) { - d = de; - break; - } - } + // festuretoDestination(fs)を呼び出し、GeoJSONFeatureオブジェクトfsからDestinationオブジェクトdを作成します。 + + // print("----- destination lenght is ${destinations.length} -----"); + indexController.currentFeature.add(fs); - print("---- before calling startTimer ----"); - startTimer(d, distance); + // indexController.currentFeatureにfsを追加します。 + + //print("---- before calling startTimer ----"); + await startTimer(d, distance); + // startTimer(d, distance)を非同期で呼び出し、その完了を待機します。 + + return; } } - void CallforCheckin(Destination d) { - bool autoCheckin = d.auto_checkin == 0 ? false : true; - if (autoCheckin) { - if (!checking_in) { - print( - "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ make checkin ${d.sub_loc_id}@@@@@@@@@@@"); - makeCheckin(d, true, ""); - if (d.cp != -1) { - rogaining_counted.value = true; - } - skip_gps = false; - } - } else { - print("--- hidden loc ${d.hidden_location} ----"); - // ask for checkin - if (d.hidden_location != null && - d.hidden_location == 0 && - is_in_rog.value == true && - d.cp != -1) { - chekcs = 3; - is_in_checkin.value = true; - photos.clear(); - showModalBottomSheet( - context: Get.context!, - isScrollControlled: true, - builder: ((context) => CameraPage( - destination: d, - ))).whenComplete(() { - skip_gps = false; - rogaining_counted.value = true; - chekcs = 0; - is_in_checkin.value = false; - }); - } else { - Get.snackbar("始まっていない", "ロゲイニングを始める必要があります"); - } - //else if(is_in_rog.value == true && d.cp != -1){ - // chekcs = 4; - // is_in_checkin.value = true; - // showMaterialModalBottomSheet( - // expand: true, - // context: Get.context!, - // backgroundColor: Colors.transparent, - // builder: (context) => BottomSheetNew() - // ).whenComplete(() { - // skip_gps = false; - // chekcs = 0; - // is_in_checkin.value = false; - // }); - // showModalBottomSheet(context: Get.context!, isScrollControlled: true, - // builder:((context) => BottomSheetNew()) - // ).whenComplete((){ - // skip_gps = false; - // chekcs = 0; - // is_in_checkin.value = false; - // }); - //} - } - } - - void startTimer(Destination d, double distance) async { - print("=== passed dest is ${d.location_id} ${d.checkedin} ===="); - skip_gps = true; - print("---- in startTimer ----"); - double checkinRadious = d.checkin_radious ?? double.infinity; - bool autoCheckin = d.auto_checkin == 0 ? false : true; - bool locationAlreadyCheckedIn = d.checkedin ?? false; - bool isuserLoggedIn = indexController.currentUser.isNotEmpty ? true : false; - //make current destination - print("---- checkin_radious $checkinRadious ----"); - print("---- distance $distance ----"); - if (checkinRadious >= distance) { - //currentSelectedDestinations.add(d); - indexController.currentDestinationFeature.clear(); - indexController.currentDestinationFeature.add(d); - - print( - "---- checked in as ${indexController.currentDestinationFeature[0].checkedin.toString()} ----"); - } else { - skip_gps = false; - return; - } - - if (is_photo_shoot.value == true) { - photos.clear(); - showModalBottomSheet( - context: Get.context!, - isScrollControlled: true, - builder: ((context) => CameraPage())).whenComplete(() { - skip_gps = false; - chekcs = 0; - is_in_checkin.value = false; - }); - return; - } + // 指定された目的地に対してタイマーを開始する関数です。 + // 目的地の位置情報を取得し、チェックイン半径内にいるかどうかを確認します。 + // 写真撮影モードの場合は、ボトムシートを表示して写真撮影を行います。 + // 目的地がデータベースに存在しない場合は、新しい目的地としてデータベースに挿入します。 + // 目的地に応じて、チェックイン、ゴール、買い物ポイントの処理を行います。 + // + // 2024-4-8 akira: GPS信号が弱い場合でも、最後に取得した位置情報を使用してチェックインやゴールの処理を続行できるようになります。また、チェックインやゴールの処理では、GPS信号の精度チェックを緩和することで、GPS信号が弱い場合でもボタンを押せるようになります。 + // + // 要検討:エラーが発生した場合のエラーハンドリングを追加し、適切なメッセージを表示することを検討してください。 + // + // 引数:CPオブジェクトと現在地からCPまでの距離を渡す。 + // + Future startTimer(Destination d, double distance) async { + //print("=== passed dest is ${d.location_id} ${d.checkedin} ===="); + skipGps = true; + //debugPrint("---- in startTimer ----"); DatabaseHelper db = DatabaseHelper.instance; List ds = await db.getDestinationByLatLon(d.lat!, d.lon!); - if (ds.isEmpty) { - print("----- in location popup cp - ${d.cp}----"); - if (d.cp == -1 && DateTime.now().difference(last_goal_at).inHours >= 24) { - chekcs = 1; - //start - print("---- in start -----"); - chekcs = 1; - is_in_checkin.value = true; - is_at_start.value = true; - showModalBottomSheet( - context: Get.context!, - isScrollControlled: true, - builder: ((context) => BottomSheetNew())).whenComplete(() { - skip_gps = false; - chekcs = 0; - is_at_start.value = false; - is_in_checkin.value = false; - }); - } else if (is_in_rog.value == true && indexController.rog_mode == 1) { - print("----- in location popup checkin cp - ${d.cp}----"); - chekcs = 2; - is_in_checkin.value = true; - showModalBottomSheet( - context: Get.context!, - isScrollControlled: true, - builder: ((context) => BottomSheetNew())).whenComplete(() { - skip_gps = false; - chekcs = 0; - is_in_checkin.value = false; - }); + //指定位置のオブジェクトのリストを取得。 + Destination? dss; + if (ds.isNotEmpty) { + dss = ds.first; // 取得したリストが空でない場合、dss変数に最初の要素を代入します。 + } + + // 変数を計算 + double checkinRadious = d.checkin_radious ?? double.infinity; // 反応半径 + bool autoCheckin = d.auto_checkin == 0 ? false : true; // 自動チェックイン + bool buyPoint = dss != null && dss.buy_point != null && dss.buy_point! > 0 // 買い物ポイント + ? true + : false; + bool buyPointImageAdded = // 買い物画像 + dss != null && dss.buypoint_image != null ? true : false; + bool buyPointCanceled = // 買い物キャンセル + dss != null && dss.buy_point != null && dss.buy_point == 0 + ? true + : false; + bool locationAlreadyCheckedIn = // チェックイン済みか + ds.isNotEmpty && ds[0].checkedin == true ? true : false; + bool isuserLoggedIn = indexController.currentUser.isNotEmpty ? true : false; // ログイン済みか + + /* + // スタートとゴールは除外 + debugPrint("startTimer CP=${d.cp}"); + if (d.cp == -1 || d.cp == 0 || d.cp == -2) { + skipGps = false; + return; + } + + */ + + // 初期化。GPS信号が強くても弱くても + if (checkinRadious >= distance || checkinRadious == -1) { + //currentSelectedDestinations.add(d); + // 目的地として登録する。 + //debugPrint("目的地の初期化"); + indexController.currentDestinationFeature.clear(); + indexController.currentDestinationFeature.add(d); + + // print( + // "---- checked in as ${indexController.currentDestinationFeature[0].checkedin.toString()} ----"); + } else { + // ここには来ないのでは? + debugPrint("検出範囲外..."); + + // GPS信号が弱い場合でも、チェックインやゴールの処理を続行する + // comment out by Akira, 2024-4-5 + // skipGps = false; + // return; + // GPS信号が弱い場合、最後に取得した高いまたは中程度の位置情報を使用 + if (okToUseGPS) { + double lastValidDistance = Geolocator.distanceBetween( + lastValidLat, lastValidLon, + d.lat!, d.lon! + ); + /* + double lastValidDistance = distance.as( + LengthUnit.Meter, + LatLng(lastValidLat, lastValidLon), + LatLng(d.lat!, d.lon!), + ); + */ + + if (checkinRadious >= lastValidDistance || checkinRadious == -1) { // 反応半径内か、距離無視CPなら + indexController.currentDestinationFeature.clear(); + indexController.currentDestinationFeature.add(d); + } else { + skipGps = false; + return; + } + } else { + skipGps = false; + return; } } - print("---- location checkin radious ${d.checkin_radious} ----"); - print("---- already checked in $locationAlreadyCheckedIn ----"); - if (checkinRadious >= distance && - locationAlreadyCheckedIn == false && - is_in_rog.value == true) { - CallforCheckin(d); + if (isPhotoShoot.value == true) { // 写真撮影するなら ... isPhotoShoot=True にしてる場所がない。 + debugPrint("isPhotoShoot.value == true ... will camera popup"); + photos.clear(); // まず既存の写真をクリア + if (shouldShowBottomSheet) { // ボトムシートを使うべきなら + shouldShowBottomSheet = false; + if (d.cp == -1) return; // CPは開始点なら戻る。 + + // カメラページをポップアップ + await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => CameraPage(destination: d))) + .whenComplete(() { + shouldShowBottomSheet = true; + skipGps = false; + chekcs = 0; + isInCheckin.value = false; + }); + } + return; } - print("---- cp --- ${d.cp} -----"); - print("--- at goal $is_at_goal ---"); - print("--- rog counted $rogaining_counted ---"); - print("--- loc already checked in $locationAlreadyCheckedIn ---"); - print( - "==== date diff is ${DateTime.now().difference(last_goal_at).inHours} ===="); - if (isuserLoggedIn && - d.cp == -1 && - locationAlreadyCheckedIn && - skip_10s == false) { - //check for rogaining - if (is_at_goal.value == false && rogaining_counted.value) { - //goal - print("---- in goal -----"); - chekcs = 5; - is_at_goal.value = true; - photos.clear(); - showModalBottomSheet( + + // 写真撮影モードでない場合 + + if (ds.isEmpty) { + debugPrint("* 目的地がない場合 ==> 検知半径=-1の場合"); + + // print("----- in location popup cp - ${d.cp}----"); + if ((d.cp == -1 || d.cp==0 ) && DateTime.now().difference(lastGoalAt).inHours >= 10) { + debugPrint("**1: 開始CPで、最後にゴールしてから24時間経過していれば、"); + + chekcs = 1; + //start + // print("~~~~ calling start ~~~~"); + print("---- in start -----"); + + chekcs = 1; // スタート地点で前のゴールから24時間経過 + + isInCheckin.value = true; + isAtStart.value = true; + if (shouldShowBottomSheet) { + shouldShowBottomSheet = false; // bottom_sheet を起動させない。 + + Widget bottomSheet = BottomSheetNew(destination: d); + + await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.85)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => bottomSheet) + ).whenComplete(() { + shouldShowBottomSheet = true; // bottom_sheet 起動許可 + skipGps = false; + chekcs = 0; // ボトムシートモード=1, + isAtStart.value = false; + isInCheckin.value = false; + }); + } + return; + // 以下の条件分岐を追加 + } else if (ds.isNotEmpty && ds[0].checkedin == true) { + // 目的地がDBに存在し、すでにチェックインしている場合は自動ポップアップを表示しない + debugPrint("チェックイン済み"); + return; + + } else if (isInRog.value == true && + indexController.rogMode.value == 1 && + (locationAlreadyCheckedIn==false) && + d.cp != -1 && d.cp != 0 && d.cp != -2) { + + debugPrint("**2: 標準CP まだチェックインしていない。"); + + // print("----- in location popup checkin cp - ${d.cp}----"); + chekcs = 2; // 標準CP + + isInCheckin.value = true; + if (shouldShowBottomSheet) { + shouldShowBottomSheet = false; + + Widget bottomSheet = BottomSheetNew(destination: d); + + await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => bottomSheet) + ).whenComplete(() { + shouldShowBottomSheet = true; + skipGps = false; + chekcs = 0; + isInCheckin.value = false; + }); + } + return; + } + } + + // 以降、検知範囲にある場合。 + //debugPrint("検知範囲にある場合"); + + // print("---- location checkin radious ${d.checkin_radious} ----"); + // print("---- already checked in $locationAlreadyCheckedIn ----"); + if ((checkinRadious >= distance || checkinRadious == -1) && + locationAlreadyCheckedIn == false && + isInRog.value == true && + !isCheckingIn.value) { + + debugPrint("* 検知範囲または距離無視CPで、ゲーム中でまだチェックインしていない。"); + + if (autoCheckin) { // 自動チェックインなら + if (!checkingIn) { + debugPrint("** 自動チェックインの場合"); + //print( + // "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ make checkin ${d.sub_loc_id}@@@@@@@@@@@"); + makeCheckin(d, true, ""); // チェックインして + if (d.cp != -1 && d.cp != -2 && d.cp != 0 ) { + rogainingCounted.value = true; // ゴール用チェックイン済み + } + skipGps = false; + } + return; // 戻る + + } else { + // それ以外 + debugPrint("* 自動チェックイン以外の場合"); + + // print("--- hidden loc ${d.hidden_location} ----"); + // ask for checkin + if (d.hidden_location != null && + d.hidden_location == 0 && // 隠しCPフラグ==0 ... 通常CP + isInRog.value == true && + d.cp != -1 && d.cp != -2 && d.cp != 0) { + // 隠しCPの場合、 + debugPrint("**3 通常CPの場合"); + + chekcs = 3; + isInCheckin.value = true; + isCheckingIn.value = true; + photos.clear(); + // print("--- calling checkin ---"); + if (shouldShowBottomSheet) { + shouldShowBottomSheet = false; + await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => CameraPage( + destination: d, + ))).whenComplete(() { + shouldShowBottomSheet = true; + skipGps = false; + rogainingCounted.value = true; + chekcs = 0; + isInCheckin.value = false; + isCheckingIn.value = false; + }); + } + return; + + } else if (isInRog.value == true && + (locationAlreadyCheckedIn==false) && + d.cp != -1 && d.cp != -2 && d.cp != 0) { + // 通常CP + + debugPrint("**4 通常CP以外の場合....どんな場合?"); + + chekcs = 4; + isInCheckin.value = true; + if (shouldShowBottomSheet) { + shouldShowBottomSheet = false; + + Widget bottomSheet = BottomSheetNew(destination: d); + + await showMaterialModalBottomSheet( + expand: true, + context: Get.context!, + backgroundColor: Colors.transparent, + builder: (context) => bottomSheet + ).whenComplete(() { + shouldShowBottomSheet = true; + skipGps = false; + chekcs = 0; + isInCheckin.value = false; + }); + } + return; + } + } + } else if ((checkinRadious >= distance || checkinRadious == -1) && + locationAlreadyCheckedIn == true && + buyPointImageAdded == false && + ds.isNotEmpty && + buyPoint == true && + buyPointCanceled == false && + isInRog.value == true) { + // チェックイン後で買い物ポイントの場合。 + + debugPrint("**5 チェックイン後で買い物ポイントの場合"); + + + chekcs = 5; + isInCheckin.value = true; + photos.clear(); + //print("--- open buy point $buyPointImageAdded ${d.buypoint_image} ----"); + if (shouldShowBottomSheet) { + shouldShowBottomSheet = false; + if (d.cp == -1 && d.cp != -2 && d.cp != 0) return; + await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => CameraPage( - destination: d, - ))).whenComplete(() { - skip_gps = false; + destination: d, + buyPointPhoto: true, + dbDest: ds.first, + ))).whenComplete(() { + shouldShowBottomSheet = true; + skipGps = false; + rogainingCounted.value = true; chekcs = 0; - is_at_goal.value = false; - }); - } else if (is_in_rog.value == false && - indexController.rog_mode == 1 && - DateTime.now().difference(last_goal_at).inHours >= 24) { - //start - print("---- in start -----"); - chekcs = 6; - is_at_start.value = true; - showModalBottomSheet( - context: Get.context!, - isScrollControlled: true, - builder: ((context) => BottomSheetNew())).whenComplete(() { - print("----- finished start -------"); - skip_gps = false; - chekcs = 0; - is_at_start.value = false; + isInCheckin.value = false; }); } + return; } - print("==== _chekcs $chekcs ===="); + // print("---- cp --- ${d.cp} -----"); + // print("--- at goal $is_at_goal ---"); + // print("--- rog counted $rogaining_counted ---"); + // print("--- loc already checked in $locationAlreadyCheckedIn ---"); + // print( + // "==== date diff is ${DateTime.now().difference(last_goal_at).inHours} ===="); + if (isuserLoggedIn && + (d.cp == -2 || d.cp == 0 || d.cp == -1 ) && // Goal CP + locationAlreadyCheckedIn && + skip_10s == false) { + //check for rogaining + if (isAtGoal.value == false && rogainingCounted.value) { + //goal + //print("---- in goal -----"); + + debugPrint("**5 ゴールで時計撮影の場合"); + + chekcs = 5; // Goal 時計撮影 + isAtGoal.value = true; + photos.clear(); + if (shouldShowBottomSheet) { + shouldShowBottomSheet = false; + if (d.cp == -1) return; + await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => CameraPage( + destination: d, + ))).whenComplete(() { + shouldShowBottomSheet = true; + skipGps = false; + chekcs = 0; + isAtGoal.value = false; + }); + } + return; + + } else if (isInRog.value == false && + indexController.rogMode.value == 1 && + DateTime.now().difference(lastGoalAt).inHours >= 10) { + //start + //print("---- in start -----"); + + debugPrint("**5 スタートの場合で最後のゴールから10時間経過している場合"); + + + chekcs = 6; // start point + isAtStart.value = true; + if (shouldShowBottomSheet) { + shouldShowBottomSheet = false; + + if (d.cp != -1 && d.cp != 0) return; + Widget bottomSheet = BottomSheetNew(destination: d); + + await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => bottomSheet) + ).whenComplete(() { + shouldShowBottomSheet = true; + //print("----- finished start -------"); + skipGps = false; + chekcs = 0; + isAtStart.value = false; + }); + } + return; + } + } + //print("==== _chekcs $chekcs ===="); if (chekcs == 0) { - skip_gps = false; + //debugPrint("いずれにも当てはまらないので、処理スキップ"); + skipGps = false; } + return; } - void resetRogaining() async { - print("----- resetting --------"); + // ロゲイニングをリセットする関数です。 + // ゲームの状態をリセットし、データベースからデータを削除します。 + // + Future resetRogaining({bool isgoal = false}) async { + //print("----- resetting --------"); - is_in_checkin.value = false; - is_in_rog.value = false; - is_at_start.value = false; - is_at_goal.value = false; - is_gps_selected.value = true; - skip_gps = false; + isInCheckin.value = false; + isInRog.value = false; + isAtStart.value = false; + isAtGoal.value = false; + isGpsSelected.value = true; + skipGps = false; + ready_for_goal = false; _start = 0; chekcs = 0; - rogaining_counted.value = false; + rogainingCounted.value = false; DatabaseHelper db = DatabaseHelper.instance; - int? latgoal = await db.latestGoal(); - if (latgoal != null) { - last_goal_at = DateTime.fromMicrosecondsSinceEpoch(latgoal); - print("===== last goal : $last_goal_at ====="); + + if (isgoal == false) { + await db.deleteAllDestinations(); + await db.deleteAllRogaining(); } - db.deleteAllDestinations().then((value) { - PopulateDestinations(); - initGPS(); - }); - - // currentSelectedDestinations.forEach((element) { - // deleteDestination(element); - // }); + int? latgoal = await db.latestGoal(); + lastGoalAt = DateTime.fromMicrosecondsSinceEpoch(latgoal!); + //print("===== last goal : $last_goal_at ====="); + dbService.updateDatabase(); } - void openCamera(BuildContext context) { + // すべての目的地を削除する関数です。 + // + void deleteAllDestinations() { + DatabaseHelper db = DatabaseHelper.instance; + db.deleteAllDestinations().then((value) { + populateDestinations(); + }); + } + + // カメラを開いて写真を撮影する関数です。 + // + void openCamera(BuildContext context, Destination? destination) { photos.clear(); Navigator.push( context, MaterialPageRoute( + builder: (_) => CustomCameraView( + onImageCaptured: (imagePath) { + photos.add(File(imagePath)); + }, + destination: destination, + ), + /* builder: (_) => CameraCamera( + resolutionPreset: ResolutionPreset.medium, onFile: (file) { photos.add(file); Navigator.pop(context); - print("----image file is : $file----"); + //print("----image file is : $file----"); //setState(() {}); }, - ))); + ) + */ + ), + ); } + // ルートポイントを取得する関数です。 + // void getRoutePoints() { indexController.routePoints = []; indexController.routePointLenght.value = 0; @@ -365,6 +788,8 @@ class DestinationController extends GetxController { }); } + // 指定された緯度と経度に対応する目的地を取得する関数です。 + // Future getDestinationForLatLong(double lat, double long) async { for (final d in destinations) { if (lat == d.lat && long == d.lon) { @@ -374,89 +799,365 @@ class DestinationController extends GetxController { return null; } - void checkForCheckin(double la, double ln) { - print("--- skip_gps ---- $skip_gps----"); - - for (final d in destinations) { - print("--- check checkin for--loc_id- ${d.sub_loc_id}----"); - - double lat = d.lat!; - double lon = d.lon!; - LatLng p = LatLng(lat, lon); - getDestinationForLatLong(lat, lon).then((value) { - var distance = const Distance(); - double dist = - distance.as(LengthUnit.Meter, LatLng(lat, lon), LatLng(la, ln)); - //double checkin_radious = value!.checkin_radious ?? double.infinity; - //bool auto_checkin = value.auto_checkin == 0 ? false : true; - //bool location_already_checked_id = d.checkedin ?? false; - - // print("-----rogaining_counted---${rogaining_counted.value}-----"); - // print("-----is_in_rog---${is_in_rog}-----"); - // print("-----dist is ---${dist}-----"); - //print("----- ${indexController.currentUser} ----"); - - if (dist <= 250 && skip_gps == false) { - //near a destination - print("---- time with ${d.location_id} ----"); - startTimer(d, dist); + // チェックインの呼び出しを行う関数です。 + // 指定された目的地に対してチェックインの処理を行います。 + // + Future callforCheckin(Destination d) async { + bool autoCheckin = d.auto_checkin == 0 ? false : true; + print("---- f- checkin ${d.sub_loc_id} ----"); + if (autoCheckin) { + if (!checkingIn) { + makeCheckin(d, true, ""); + if (d.cp != -1 && d.cp != 0 && d.cp != -2) { + rogainingCounted.value = true; } - }); - } - - if (indexController.locations.isEmpty) return; - - //check for location in bounds - for (GeoJsonFeature fs in indexController.locations[0].collection) { - GeoJsonMultiPoint mp = fs.geometry as GeoJsonMultiPoint; - LatLng pt = LatLng(mp.geoSerie!.geoPoints[0].latitude, - mp.geoSerie!.geoPoints[0].longitude); - - double latFs = pt.latitude; - double lonFs = pt.longitude; - LatLng pFs = LatLng(latFs, lonFs); - var distanceFs = const Distance(); - double distFs = - distanceFs.as(LengthUnit.Meter, LatLng(latFs, lonFs), LatLng(la, ln)); - - if (distFs <= 250 && skip_gps == false) { - //near a location - print("---- before call startTimerLocation ----"); - startTimerLocation(fs, distFs); } + } else { + //print("--- hidden loc ${d.hidden_location} ----"); + // ask for checkin + //print("is rog ---- ${is_in_rog.value} ----"); + if (d.hidden_location != null && + d.hidden_location == 0 && + (isInRog.value == true || (d.buy_point != null && d.buy_point! > 0)) && + d.cp != -1 && d.cp != 0 && d.cp != -2) { + chekcs = 3; + photos.clear(); + isInCheckin.value = true; + + final result = await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => CameraPage( + manulaCheckin: true, + destination: d, + ))); + + if (result ?? false) { + debugPrint("==> Checkin complete...."); + if (d.buy_point != null && d.buy_point! > 0) { + skipGps = true; + photos.clear(); + DatabaseHelper db = DatabaseHelper.instance; + List ds = + await db.getDestinationByLatLon(d.lat!, d.lon!); + Destination? dss; + if (ds.isNotEmpty) { + dss = ds.first; + } + + await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => + CameraPage( + buyPointPhoto: true, + destination: d, + dbDest: dss, + ))).whenComplete(() { + skipGps = false; + rogainingCounted.value = true; + chekcs = 0; + isInCheckin.value = false; + //Get.back(); + }); + } + } else { + debugPrint("キャンセルされました"); + Get.snackbar( + "キャンセルされました", + "チェックインしていません。必要ならもう一度チェックポイントをタップして下さい。", + backgroundColor: Colors.yellow, + colorText: Colors.black, + icon: const Icon( + Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + ); + } + } else { + Get.snackbar( + "ロゲが始まっていません", + "ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります", + backgroundColor: Colors.yellow, + colorText: Colors.black, + icon: const Icon( + Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + ); + } + /* + await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => CameraPage( + manulaCheckin: true, + destination: d, + ))).whenComplete(() async { + if (d.buy_point != null && d.buy_point! > 0) { + skipGps = true; + photos.clear(); + DatabaseHelper db = DatabaseHelper.instance; + List ds = + await db.getDestinationByLatLon(d.lat!, d.lon!); + Destination? dss; + if (ds.isNotEmpty) { + dss = ds.first; + } + + await showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => CameraPage( + buyPointPhoto: true, + destination: d, + dbDest: dss, + ))).whenComplete(() { + skipGps = false; + rogainingCounted.value = true; + chekcs = 0; + isInCheckin.value = false; + //Get.back(); + }); + } else { + skipGps = false; + chekcs = 0; + isInCheckin.value = false; + } + }); + } else { + Get.snackbar( + "ロゲが始まっていません", + "ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります", + backgroundColor: Colors.yellow, + colorText: Colors.white, + icon: const Icon( + Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3) +// backgroundColor: Colors.yellow, + ); + } + */ + } } + // GPSデータをデータベースに追加する関数です。 + // + Future addGPStoDB(double la, double ln, {isCheckin = 0}) async { + //debugPrint("in addGPStoDB ${indexController.currentUser}"); + try { + GpsDatabaseHelper db = GpsDatabaseHelper.instance; + if(indexController.currentUser.isNotEmpty){ + final teamName = indexController.currentUser[0]["user"]['team_name']; + final eventCode = indexController.currentUser[0]["user"]["event_code"]; + GpsData gpsData = GpsData( + id: 0, + team_name: teamName, + event_code: eventCode, + lat: la, + lon: ln, + is_checkin: isCheckin, + created_at: DateTime.now().millisecondsSinceEpoch); + var res = await db.insertGps(gpsData); + //debugPrint("Saved GPS data into DB...:${gps_data}"); + } + } catch (err) { + print("errr ready gps $err"); + return; + } + } + + // チェックインを確認する関数です。 + // ゲームが開始されていない場合は、ゲームを開始します。 + // 目的地のリストを走査し、現在位置がチェックイン半径内にある場合は、チェックインの処理を行います。 + // GPSデータの送信を開始します。 + // + // 2024-4-8 Akira : See 2809 + // checkForCheckinメソッドの再帰呼び出しをunawaitedで囲んで、非同期処理の結果を待たずに先に進むようにしました。また、再帰呼び出しの前に一定時間待機するようにしました。 + // + Future checkForCheckin() async { + //print("--- Start of checkForCheckin function ---"); + dbService.updateDatabase(); + await Future.delayed(const Duration(milliseconds: 3000)); + game_started = true; + + try { + // ここで、エラー + if( indexController.locations.isNotEmpty ) { + indexController.locations[0].features.forEach((fs) async { + GeoJSONMultiPoint mp = fs!.geometry as GeoJSONMultiPoint; + LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]); + + double latFs = pt.latitude; + double lonFs = pt.longitude; + var distanceFs = const Distance(); + double distFs = distanceFs.as(LengthUnit.Meter, LatLng(latFs, lonFs), + LatLng(currentLat, currentLon)); + Destination des = festuretoDestination(fs); + + if (distFs <= des.checkin_radious! + && skipGps == false + //&& des.isCheckedIn == false + && des.cp!=0 && des.cp!=-1 && des.cp!=-2) { + await startTimerLocation(fs, distFs); + // Note: You cannot break out of forEach. If you need to stop processing, you might have to reconsider using forEach. + } + }); + + if (gps_push_started == false) { + unawaited(pushGPStoServer()); + } + } + //print("--- 123 ---- $skip_gps----"); + } catch (e) { + print("An error occurred: $e"); + // await checkForCheckin(); + } finally { + await Future.delayed(const Duration(seconds: 1)); // 一定時間待機してから再帰呼び出し + //print("--- End of checkForCheckin function, calling recursively ---"); + unawaited( checkForCheckin() ); + } + } + + // GPSデータをサーバーにプッシュする関数です。 + // + Future pushGPStoServer() async { + // print( + // "^^^^^^^^ ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}"); + try { + gps_push_started = true; + ExternalService().pushGPS(); + } catch (e) { + //print("An error occurred: $e"); + //await pushGPStoServer(); + } finally { + //print("--- End of pushGPStoServer function, calling recursively ---"); + await Future.delayed(const Duration(seconds: 5 * 60)); + await pushGPStoServer(); + } + } + + + + // ロゲイニングにデータを追加する関数です。 + // void addToRogaining(double lat, double lon, int destinationId) async { DatabaseHelper db = DatabaseHelper.instance; List d = await db.getDestinationById(destinationId); if (d.isEmpty) { Destination df = festuretoDestination(indexController.currentFeature[0]); - print("--- made checkin ${df.location_id} ----"); + //print("--- made checkin ${df.location_id} ----"); makeCheckin(df, true, ""); } - is_in_rog.value = true; + isInRog.value = true; + + saveGameState(); } - void makeCheckin( - Destination destination, bool action, String imageurl) async { - print( - "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ressssss ${destination.sub_loc_id}@@@@@@@@@@@"); + // 買い物ポイントをキャンセルする関数です。 + // + Future cancelBuyPoint(Destination destination) async { DatabaseHelper db = DatabaseHelper.instance; - List ddd = - await db.getDestinationByLatLon(destination.lat!, destination.lon!); + await db.updateCancelBuyPoint(destination); + populateDestinations(); + } - if (ddd.isEmpty) { - destination.checkedin = true; - await db.insertDestination(destination); + // 指定されたパスの画像をギャラリーに保存する関数です。 + // + _saveImageFromPath(String imagePath) async { + try { + // Read the image file from the given path + File imageFile = File(imagePath); + Uint8List imageBytes = await imageFile.readAsBytes(); + + // Save the image to the gallery + final result = await ImageGallerySaver.saveImage(imageBytes); + //print("--- save result --- ${result}"); + } catch(e, stackTrace){ + print('エラーが発生しました: $e'); + print('スタックトレース: $stackTrace'); + } + } + + Future _saveImageToGallery(String imagePath) async { + final status = await PermissionController.checkStoragePermission(); + if(!status){ + await PermissionController.requestStoragePermission(); } - PopulateDestinations(); + /* + final status = await Permission.storage.status; + if (!status.isGranted) { + final result = await Permission.storage.request(); + if (!result.isGranted) { + // ユーザーがストレージの権限を拒否した場合の処理 + showDialog( + context: Get.context!, + builder: (BuildContext context) { + return AlertDialog( + title: Text('ストレージの権限が必要です'), + content: Text( + '画像をギャラリーに保存するには、ストレージの権限が必要です。アプリの設定画面で権限を許可してください。'), + actions: [ + TextButton( + child: Text('キャンセル'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('設定'), + onPressed: () { + Navigator.of(context).pop(); + openAppSettings(); // アプリの設定画面を開く + }, + ), + ], + ); + } + ); + + return; + } + } + */ + + try { + final result = await ImageGallerySaver.saveFile(imagePath); + print('Image saved to gallery: $result'); + } catch (e) { + print('Failed to save image to gallery: $e'); + } + } + + // 買い物ポイントを作成する関数です。 指定された目的地に対して買い物ポイントの処理を行います。 + // + // 買い物ポイントの作成に失敗した場合のエラーハンドリングを追加することを検討してください。 + // + Future makeBuyPoint(Destination destination, String imageurl) async { + DatabaseHelper db = DatabaseHelper.instance; + await db.updateBuyPoint(destination, imageurl); + populateDestinations(); + //await _saveImageFromPath(imageurl); + await _saveImageToGallery(imageurl); + + - /// post to NATNAT if (indexController.currentUser.isNotEmpty) { double cpNum = destination.cp!; + //int teamId = indexController.teamId.value; // teamIdを使用 + int userId = indexController.currentUser[0]["user"]["id"]; //print("--- Pressed -----"); String team = indexController.currentUser[0]["user"]['team_name']; @@ -468,160 +1169,571 @@ class DestinationController extends GetxController { DateTime now = DateTime.now(); String formattedDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(now); - print("------ checkin event $eventCode ------"); + //print("------ checkin event $eventCode ------"); ExternalService() .makeCheckpoint(userId, token, formattedDate, team, cpNum.round(), eventCode, imageurl) .then((value) { - print("------Ext service check point $value ------"); + //print("------Ext service check point $value ------"); }); } } - void initGPS() { - checkPermission(); - PopulateDestinations(); - //print("------ in iniit"); - - if (defaultTargetPlatform == TargetPlatform.android) { - print("---- GPS android -----"); - locationSettings = AndroidSettings( - accuracy: LocationAccuracy.best, - distanceFilter: 3, - forceLocationManager: true, - intervalDuration: const Duration(seconds: 1), - //(Optional) Set foreground notification config to keep the app alive - //when going to the background - foregroundNotificationConfig: const ForegroundNotificationConfig( - notificationText: - " App will continue to receive your location even when you aren't using it", - notificationTitle: "Running in Background", - enableWakeLock: true, - )); - } else if (defaultTargetPlatform == TargetPlatform.iOS || - defaultTargetPlatform == TargetPlatform.macOS) { - locationSettings = AppleSettings( - accuracy: LocationAccuracy.bestForNavigation, - activityType: ActivityType.fitness, - distanceFilter: 0, - pauseLocationUpdatesAutomatically: false, - // Only set to true if our app will be started up in the background. - showBackgroundLocationIndicator: true); - } else { - locationSettings = const LocationSettings( - accuracy: LocationAccuracy.high, - distanceFilter: 0, - ); - } + // チェックインを行う関数です。 指定された目的地に対してチェックインの処理を行います。 + // + // 要検討:チェックインのリクエストが失敗した場合のエラーハンドリングを追加することをお勧めします。 + // + Future makeCheckin( + Destination destination, bool action, String imageurl) async { try { - StreamSubscription positionStream = - Geolocator.getPositionStream(locationSettings: locationSettings) - .listen((Position? position) { - current_lat = position != null ? position.latitude : 0; - current_lon = position != null ? position.longitude : 0; + // print("~~~~ calling checkin function ~~~~"); + // print( + // "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ressssss ${destination.sub_loc_id}@@@@@@@@@@@"); + DatabaseHelper db = DatabaseHelper.instance; + List ddd = + await db.getDestinationByLatLon(destination.lat!, destination.lon!); - print( - "==== gps skip is : ${skip_gps.toString()}, selected is $is_gps_selected , $current_lat"); - - if (is_gps_selected.value) { - double czoom = indexController.rogMapController.zoom; - indexController.rogMapController - .move(LatLng(position!.latitude, position.longitude), czoom); - //String user_id = indexController.currentUser[0]["user"]["id"].toString(); - //TrackingService.addTrack(user_id, position!.latitude, position.longitude).then((val){ - //print("---- postion is ${position.latitude}, ${position.longitude}"); - gps.clear(); - gps.add( - "-- lat : ${position.latitude}, lon : ${position.longitude} --"); - checkForCheckin(position.latitude, position.longitude); - print("--- call check checkin"); - print("---- skip gps is ${skip_gps.toString()} ----"); - //}); - } - //print(position == null ? 'Unknown' : 'current position is ${position.latitude.toString()}, ${position.longitude.toString()}'); - }); - } catch (err) { - locationPermission.clear(); - locationPermission.add(err.toString()); - } - - ever(indexController.connectionStatusName, connectionChanged); - } - - @override - void onInit() async { - initGPS(); - - super.onInit(); - } - - void loadInitPoints(String token) async { - await indexController.loadUserDetailsForToken(token); - LocationService.getLocationsExt(token).then((value) { - if (value != null) { - print("--- loc ext is - $value ----"); - LatLngBounds bnds = LatLngBounds( - LatLng(value[1], value[0]), LatLng(value[3], value[2])); - print("--- bnds is - $bnds ----"); - indexController.mapController.fitBounds( - bnds, - ); - indexController.currentBound.clear(); - indexController.currentBound.add(bnds); - indexController.loadLocationsBound(); + if (ddd.isEmpty) { + destination.checkedin = true; + destination.checkin_image = imageurl; + await db.insertDestination(destination); + // print("~~~~ inserted into db ~~~~"); } + + if (imageurl.isEmpty) { + if (photos.isNotEmpty) { + // imageurlが空の場合は、destinationのcheckin_imageプロパティを使用する + debugPrint("photos = $photos"); + imageurl = photos[0].path; + } + debugPrint("imageurl = $imageurl"); + //await _saveImageFromPath(imageurl!); + } + if (imageurl.isNotEmpty) { + await _saveImageToGallery(imageurl); + } + + populateDestinations(); + + /// post to NATNAT + if (indexController.currentUser.isNotEmpty) { + double cpNum = destination.cp!; + + //int teamId = indexController.teamId.value; // teamIdを使用 + //Team team0 = teamController.teams[0]; + //print("team={team0}"); + + + int userId = indexController.currentUser[0]["user"]["id"]; + //print("--- Pressed -----"); + String team = indexController.currentUser[0]["user"]['team_name']; + //print("--- _team : ${_team}-----"); + String eventCode = indexController.currentUser[0]["user"]["event_code"]; + //print("--- _event_code : ${_event_code}-----"); + String token = indexController.currentUser[0]["token"]; + //print("--- _token : ${_token}-----"); + DateTime now = DateTime.now(); + String formattedDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(now); + + await addGPStoDB(currentLat, currentLon, isCheckin: 1); + + // print("------ checkin event $eventCode ------"); + ExternalService() + .makeCheckpoint( + userId, // teamIdを使用 + token, + formattedDate, + team, + cpNum.round(), + eventCode, + imageurl) + .then((value) { + // print("------Ext service check point $value ------"); + }); + } + // dbService.updateDatabase(); + + }catch(e){ + print("エラー:$e"); + //print("stack : ${stacktrace}"); + }finally{ + dbService.updateDatabase(); + } + + + } + + // チェックインを削除する関数です。 + // + Future removeCheckin(int cp) { + dbService.updateDatabase(); + return ExternalService().removeCheckin(cp); + } + + // ゲームを開始する関数です。 + // + Future startGame() async { + debugPrint("------ starting game ------"); + if (game_started == false) { + await checkForCheckin(); + } + } + + Timer? gpsCheckTimer; // 一定間隔でGPSデータの受信状態をチェックするタイマー + + void startGPSCheckTimer() { + gpsCheckTimer = Timer.periodic(const Duration(seconds: 5), (timer) { + checkGPSDataReceived(); }); } - readUserToken() async{ + // コントローラーの初期化時に呼び出されるライフサイクルメソッドです。 + // + bool inError=false; + bool isRunningBackgroundGPS=false; + int activeEngineCount = 0; + + @override + void onInit() async { + super.onInit(); + + /* + WidgetsBinding.instance.addPostFrameCallback((_) async { + await PermissionController.checkAndRequestPermissions(); + }); + */ + + startGPSCheckTimer(); + + // MapControllerの初期化完了を待機するフラグを設定 + WidgetsBinding.instance.addPostFrameCallback((_) { + //checkGPSDataReceived(); removed 2024-5-4 + + isMapControllerReady = true; + }); + + // 要検討:エラーメッセージを表示するなどの適切な処理を追加することを検討してください。 + // + // locationController からデバイスの受け取るGPS情報を取得し、 + // handleLocationUpdate を呼び出している。 + // + locationController.locationMarkerPositionStream.listen( + (locationMarkerPosition) { + //if (locationMarkerPosition != null) { + handleLocationUpdate(locationMarkerPosition); + //} + }, onError: (err) { + if(inError==false){ + inError = true; + debugPrint("Location Error: $err"); + // エラーが発生した場合、locationMarkerPositionStreamControllerにエラーを追加します。 + locationController.locationMarkerPositionStreamController.addError(err); + + // ここにエラー発生時の処理を追加します。 + if (err is LocationServiceDisabledException) { + // 位置情報サービスが無効になっている場合の処理 + print('Location services are disabled'); + Get.snackbar( + 'エラー', + '位置情報サービスが無効になっています。設定画面から位置情報サービスを有効にして下さい。不明な場合にはエンジニアスタッフにお問い合わせください。', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + inError = false; + /* + } else if (err is PermissionDeniedException) { + // 位置情報の権限がない場合の処理 + print('Location permissions are denied'); + Get.snackbar( + 'エラー', + '位置情報サービスが許可されていません。設定画面から岐阜ナビの位置情報サービスを許可して下さい。不明な場合にはエンジニアスタッフにお問い合わせください。', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + inError = false; + */ + } else { + // その他のエラーの場合の処理 + print('Location Error: $err'); + Get.snackbar( + 'エラー', + '位置情報サービスに問題が発生しました。位置情報サービスを再起動していますので少しお待ちください。', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + + // GPSデータのListenを再開する処理を追加 + if( isRunningBackgroundGPS==false && inError ) { + restartGPS(); + } + } + } + //print("Location Error: $err"); + }); + + startGame(); + + //checkGPSDataReceived(); + } + + void restartGPS(){ + // GPSデータのListenを再開する処理を追加 + Future.delayed(const Duration(seconds: 5), () { + locationController.startPositionStream(); + inError=false; + }); + } + + // コントローラーのクローズ時に呼び出されるライフサイクルメソッドです。 + // + @override + void onClose() { + gpsCheckTimer?.cancel(); + locationController.stopPositionStream(); + super.onClose(); + } + + // 位置情報の更新を処理する関数です。 + // 現在位置とスタート地点との距離を計算します。 + // 現在位置と前回の位置情報との距離と時間差を確認し、一定の条件を満たす場合はGPSデータをデータベースに追加します。 + // + // 要検討:GPSデータの追加に失敗した場合のエラーハンドリングを追加することをお勧めします。 + // + double prevLat = 0.0; // 直前の位置 + double prevLon = 0.0; + bool gpsDebugMode=false; + + void handleLocationUpdate(LocationMarkerPosition? position) async { + //debugPrint("DestinationController.handleLocationUpdate"); + + try { + //final DestinationController destinationController = Get.find(); + //final signalStrength = locationController.getGpsSignalStrength(); + + okToUseGPS = false; + + if (position != null) { + currentLat = position.latitude; + currentLon = position.longitude; + if( prevLat==0.0 ){ + prevLat = currentLat; + prevLon = currentLon; + } + lastValidGPSLocation = LatLng(currentLat, currentLon); + lastValidLat = currentLat; + lastValidLon = currentLon; + okToUseGPS = true; + lastGPSDataReceivedTime = DateTime.now(); + hasReceivedGPSData = true; + + } else { + debugPrint("....position is null...."); + checkGPSDataReceived(); + + // 信号強度が低い場合、最後に取得した高いまたは中程度の位置情報を使用 + // 但し、最初から高精度のものがない場合、どうするか? + // + // GPSデータが受信できない場合、最後に有効なGPSデータを使用 + position = LocationMarkerPosition( + latitude: lastValidGPSLocation.latitude, + longitude: lastValidGPSLocation.longitude, + accuracy: 0, + ); + currentLat = position.latitude; + currentLon = position.longitude; + okToUseGPS = false; + + /* + if (lastValidLat != 0.0 && lastValidLon != 0.0) { + currentLat = lastValidLat; + currentLon = lastValidLon; + okToUseGPS = true; + } else { + // GPSの届く場所に行って、信号を拾ってください。とメッセージを出す。 + position = null; + print("GPSの届く場所に行って、信号を拾ってください。"); + Get.snackbar( + "GPS信号を正確に拾えていません", + "空が大きく見えるところへ行ってGPS信号を拾ってください。", + icon: const Icon( + Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + backgroundColor: Colors.yellow, + ); + } + */ + } + + if (okToUseGPS) { + // スタート位置から150m離れたら、ready_for_goal + if (distanceToStart() >= 150) { + ready_for_goal = true; + } + + var distance = const Distance(); + double distanceToDest = distance.as( + LengthUnit.Meter, + LatLng(position.latitude, position.longitude), + LatLng(prevLat, prevLon) + ); + + Duration difference = lastGPSCollectedTime.difference(DateTime.now()) + .abs(); + // 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過(普通に歩くスピード) + //debugPrint("時間差:${difference.inSeconds}, 距離差:${distanceToDest}"); + if (difference.inSeconds >= 10 || distanceToDest >= 30) { + // print( + // "^^^^^^^^ GPS data collected ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}, ^^^ ${position.latitude}, ${position.longitude}"); + + LogManager().addLog( + "GPS : $currentLat, $currentLon - ${DateTime + .now() + .hour}:${DateTime + .now() + .minute}:${DateTime + .now() + .second}:${DateTime + .now() + .microsecond}"); + if (isInRog.value) { + await addGPStoDB(position.latitude, position.longitude); + lastGPSCollectedTime = DateTime.now(); + prevLat = position.latitude; + prevLon = position.longitude; + gpsDebugMode ? debugPrint("フロントエンドでのGPS保存(時間差:${difference.inSeconds}, 距離差:$distanceToDest) : Time=$lastGPSCollectedTime"):null; + } + } + } + } catch(e) { + debugPrint("handleLocationUpdate Error: $e"); + } finally { + /* Akira , 2024-4-5 + if (position != null && + (position.latitude != 0 && position.longitude != 0)) { + currentLat = position.latitude; + currentLon = position.longitude; + } + */ + if (okToUseGPS) { + // 位置情報が取得できた場合、精度に関わらず最後の位置情報を更新 + //currentLat = position.latitude; + //currentLon = position.longitude; + } + } + } + + // スタート地点までの距離を計算する関数です。 + // + double distanceToStart() { + if (indexController.locations.isEmpty) { + return 1000000000; + } + //print("=== gfs len == ${indexController.locations[0].collection.length}"); + double distanceToDest = double.infinity; + if (indexController.locations[0].features.isEmpty) { + return distanceToDest; + } + GeoJSONFeature? gfs = indexController.locations[0].features.firstWhere( + (element) => festuretoDestination(element!).cp == -1, + orElse: () => null, // Provide a null value if no element is found + ); + + //print("gfs : ${gfs}"); + + if (gfs == null) { + return distanceToDest; + } + + //final currentLocation = getCurrentLocation(); // GPS信号中以上での現在位置 + + Destination des = festuretoDestination(gfs); + + //print("=== gfs == ${des.toMap()}"); + + var distance = const Distance(); + distanceToDest = distance.as(LengthUnit.Meter, + LatLng(currentLat,currentLon), LatLng(des.lat!, des.lon!)); +// LatLng(currentLat, currentLon), LatLng(des.lat!, des.lon!)); + //print("==== dist==${distanceToDest}"); + return distanceToDest; + } + + // 強制チェックイン距離を取得する関数です。 + // + int getForcedChckinDistance(Destination dest) { + if (dest.checkin_radious == -1) { + return 10000000000000000; + } + + int retValue = 100; + if (dest.cp == -1) { + return 500; + } + Destination? ds; + GeoJSONFeature? gfs = indexController.locations[0].features.firstWhere( + (element) => festuretoDestination(element!).cp == -1, + orElse: () => null, // Provide a null value if no element is found + ); + + if (gfs == null) { + return retValue; + } + + ds = festuretoDestination(gfs); + var distance = const Distance(); + double distanceToDest = distance.as(LengthUnit.Meter, + LatLng(dest.lat!, dest.lon!), LatLng(ds.lat!, ds.lon!)); + if (distanceToDest <= 500) { + return 500; + } + //print("==== forced dist ==${distanceToDest}"); + return retValue; + } + + // ユーザートークンを読み取る関数です。 + // + readUserToken() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); indexController.userToken = prefs.getString("user_token"); } + // コントローラーの準備完了時に呼び出されるライフサイクルメソッドです。 + // @override void onReady() async { await readUserToken(); - if(indexController.userToken != null && indexController.userToken!.isNotEmpty){ - loadInitPoints(indexController.userToken!); - indexController.switchPage(AppPages.INITIAL); - return; + final token = indexController.userToken; + if (token != null && token.isNotEmpty) { + await indexController.loadUserDetailsForToken(token); + fixMapBound(token); + }else { + Get.toNamed(AppPages.LOGIN)!.then((value) { + if (indexController.currentUser.isNotEmpty) { + final tk = indexController.currentUser[0]["token"]; + fixMapBound(tk); + } else { + Get.toNamed(AppPages.TRAVEL); + PerfectureService.getSubExt("9").then((value) { + if (value != null) { + LatLngBounds bnds = LatLngBounds( + LatLng(value[1], value[0]), LatLng(value[3], value[2])); + indexController.mapController + .fitBounds(bnds); //.centerZoomFitBounds(bnds); + } + }); + } + }); } - Get.toNamed(AppPages.LOGIN)!.then((value) { - if (indexController.currentUser.isNotEmpty) { - String token = indexController.currentUser[0]["token"]; - indexController.switchPage(AppPages.INITIAL); - loadInitPoints(token); - } else { - Get.toNamed(AppPages.TRAVEL); - PerfectureService.getSubExt("9").then((value) { - if (value != null) { - LatLngBounds bnds = LatLngBounds( - LatLng(value[1], value[0]), LatLng(value[3], value[2])); - indexController.mapController - .fitBounds(bnds); //.centerZoomFitBounds(bnds); - } - }); + + // 地図のイベントリスナーを設定 + indexController.mapController.mapEventStream.listen((MapEvent mapEvent) { + if (mapEvent is MapEventMoveEnd) { + indexController.loadLocationsBound(indexController.currentUser[0]["user"]["event_code"]); } }); + super.onReady(); } + // 地図の境界を修正する関数です。 + // + void fixMapBound(String token) { + //String _token = indexController.currentUser[0]["token"]; + indexController.switchPage(AppPages.INDEX); + + if (isMapControllerReady) { + LocationService.getLocationsExt(token).then((value) { + if (value != null) { + //print("--- loc ext is - $value ----"); + LatLngBounds bnds = LatLngBounds( + LatLng(value[1], value[0]), LatLng(value[3], value[2])); + //print("--- bnds is - $bnds ----"); + indexController.mapController.fitBounds( + bnds, + ); + indexController.currentBound.clear(); + indexController.currentBound.add(bnds); + indexController.loadLocationsBound(indexController.currentUser[0]["user"]["event_code"]); + centerMapToCurrentLocation(); + } + }); + } else { + // MapControllerの初期化が完了していない場合は、遅延して再試行 + Future.delayed(const Duration(milliseconds: 100), () { + fixMapBound(token); + }); + } + } + + +/* + void fixMapBound(String token) { + indexController.switchPage(AppPages.INDEX); + LocationService.getLocationsExt(token).then((value) { + if (value != null) { + LatLngBounds bnds = LatLngBounds( + LatLng(value[1], value[0]), + LatLng(value[3], value[2]), + ); + if (indexController.isMapControllerReady.value) { + indexController.mapController.fitBounds( + bnds, + ); + indexController.currentBound.clear(); + indexController.currentBound.add(bnds); + indexController.loadLocationsBound(); + centerMapToCurrentLocation(); + } else { + // MapControllerが初期化されるまで待機し、その後fitBoundsを実行 + WidgetsBinding.instance.addPostFrameCallback((_) { + indexController.mapController.fitBounds( + bnds, + ); + indexController.currentBound.clear(); + indexController.currentBound.add(bnds); + indexController.loadLocationsBound(); + centerMapToCurrentLocation(); + }); + } + } + }); + } +*/ + + // 地図を現在位置に中央揃えする関数です。 + // + void centerMapToCurrentLocation() { + //print("center is ${currentLat}, ${currentLon}"); + // Akira ... 状況によって呼ぶか呼ばないか + if (currentLat != 0 || currentLon != 0) { + indexController.mapController.move(LatLng(currentLat, currentLon), 17.0); + } + } + + // 接続状態が変更されたときに呼び出される関数です。 + // void connectionChanged(String val) { - print('----- %%%%%%%%%%%%%%%%%%%%% ----- $val'); + //print('----- %%%%%%%%%%%%%%%%%%%%% ----- $val'); Map res = {}; if (val == "wifi" || val == "mobile") { + //int teamId = indexController.teamId.value; // teamIdを使用 + String token = indexController.currentUser[0]["token"]; DatabaseHelper db = DatabaseHelper.instance; db.allRogianing().then((value) { value.forEach((e) async { if (e.rog_action_type == 0) { - res = await ExternalService().StartRogaining(); + res = await ExternalService().startRogaining(); } else if (e.rog_action_type == 1) { var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!); res = await ExternalService().makeCheckpoint( - e.user_id!, + e.user_id!, // teamId??? token, getFormatedTime(datetime), e.team_name!, @@ -631,7 +1743,7 @@ class DestinationController extends GetxController { } else if (e.rog_action_type == 2) { var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!); res = await ExternalService().makeGoal( - e.user_id!, + e.user_id!, // // teamId??? token, e.team_name!, e.image!, @@ -647,6 +1759,9 @@ class DestinationController extends GetxController { } } + /* + // 位置情報の許可を確認する関数です。 + // void checkPermission() async { LocationPermission permission = await Geolocator.checkPermission(); if (permission != LocationPermission.whileInUse || @@ -656,12 +1771,15 @@ class DestinationController extends GetxController { permission = await Geolocator.requestPermission(); } } + */ + // IDに基づいて目的地を取得する関数です。 + // Destination? destinationById(int id) { Destination? d; - print("--- target des - $id ----"); + //print("--- target des - $id ----"); for (Destination ss in destinations) { - print("--- des - ${ss.location_id} ----"); + //print("--- des - ${ss.location_id} ----"); if (ss.location_id == id) { d = ss; break; @@ -670,6 +1788,8 @@ class DestinationController extends GetxController { return d; } + // 目的地を削除する関数です。 + // void deleteDestination(Destination d) { //int id = destinations[index].location_id!; //print("---- index ${destinations[index].location_id!}-----"); @@ -681,61 +1801,85 @@ class DestinationController extends GetxController { } DatabaseHelper db = DatabaseHelper.instance; db.deleteDestination(d.location_id!).then((value) { - PopulateDestinations(); + populateDestinations(); }); + dbService.updateDatabase(); } - void deleteAllDestinations() { + // データベースからすべての目的地を削除する関数です。 + // + void deleteDBDestinations() { DatabaseHelper db = DatabaseHelper.instance; db.deleteAllDestinations().then((value) { - PopulateDestinations(); + populateDestinations(); }); + dbService.updateDatabase(); } // ---------- database ------------------/// + // 目的地を追加する関数です。 + // void addDestinations(Destination dest) { - print( - '------ destination controller in add destination ${dest.checkin_radious} ---- :::::'); - DatabaseHelper db = DatabaseHelper.instance; db.getDestinationByLatLon(dest.lat!, dest.lon!).then((value) { if (value.isNotEmpty) { db.deleteDestination(value[0].location_id!).then((value) { db.insertDestination(dest).then((value) { - print( - "----- destination controller deleted and inserted destination id $value ---- :::::"); - PopulateDestinations(); + //print( + // "----- destination controller deleted and inserted destination id $value ---- :::::"); + populateDestinations(); }); }); } else { db.insertDestination(dest).then((value) { - print("----- destination controller added as new $value--- :::::"); - PopulateDestinations(); + //print("----- destination controller added as new $value--- :::::"); + populateDestinations(); }); } }); + dbService.updateDatabase(); } + // 目的地の選択状態を切り替える関数です。 + // void toggleSelection(Destination dest) async { - DatabaseHelper db = DatabaseHelper.instance; - await db.toggleSelecttion(dest); - destinations.clear(); - db.getDestinations().then((value) { - destinationCount.value = 0; - currentSelectedDestinations.clear(); - for (Destination d in value) { - //print("------ destination controller populating destination-------- ${d.checkedin}-------- :::::"); - //print("-----populated----- ${d.toMap()}"); - if (d.selected!) { - currentSelectedDestinations.add(d); + try { + DatabaseHelper db = DatabaseHelper.instance; + await db.toggleSelecttion(dest); + destinations.clear(); + db.getDestinations().then((value) { + destinationCount.value = 0; + currentSelectedDestinations.clear(); + for (Destination d in value) { + //print("------ destination controller populating destination-------- ${d.checkedin}-------- :::::"); + //print("-----populated----- ${d.toMap()}"); + if (d.selected!) { + currentSelectedDestinations.add(d); + } + destinations.add(d); } - destinations.add(d); - } - destinationCount.value = destinations.length; - }); + destinationCount.value = destinations.length; + }); + } catch( e ){ + print('Error in toggleSelection: $e'); + Get.snackbar( + "画面切り替えでエラー", + "画面の切り替えができませんでした", + backgroundColor: Colors.red, + colorText: Colors.white, + icon: const Icon( + Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + //backgroundColor: Colors.yellow, + ); + + } } + // ダイアログを表示する関数です。 + // buildShowDialog(BuildContext context) { return showDialog( context: context, @@ -747,10 +1891,12 @@ class DestinationController extends GetxController { }); } + // 現在地点からの目的地の行列を計算する関数です。 + // void destinationMatrixFromCurrentPoint(List points) { - buildShowDialog(Get.context!); + //buildShowDialog(Get.context!); MatrixService.getDestinations(points).then((mat) { - print(" matrix is ------- $mat"); + //print(" matrix is ------- $mat"); matrix = mat; try { @@ -760,94 +1906,42 @@ class DestinationController extends GetxController { indexController.routePoints = value; indexController.routePointLenght.value = indexController.routePoints.length; - Get.toNamed(AppPages.TRAVEL); + //Get.toNamed(AppPages.TRAVEL); }); destinationCount.value = destinations.length; + } catch (_) { - skip_gps = false; + skipGps = false; return; } finally { - Get.back(); + //Get.back(); + isRouteShowing.value = true; } }); } - void PopulateDestinations() { - print( - "--------- destination controller populsting destinations ----------- ::::::"); - + // 目的地のリストを取得してObservable変数を更新する関数です。 + // + void populateDestinations() { DatabaseHelper db = DatabaseHelper.instance; destinations.clear(); db.getDestinations().then((value) { destinationCount.value = 0; for (Destination d in value) { - print( - "------ destination controller populating destination-------- ${d.checkedin}-------- :::::"); - print("-----populated----- ${d.toMap()}"); destinations.add(d); } - // destinationCount.value = 0; - print( - "------ destination controller destinationcount-------- $destinationCount-------- :::::"); - - MatrixService.getDestinations(value).then((mat) { - print(" matrix is ------- $mat"); - matrix = mat; - - try { - getRoutePoints(); - destinationCount.value = destinations.length; - } catch (_) { - skip_gps = false; - return; - } - }); + if (destinations.isEmpty) { + rogainingCounted.value = false; + } }); } + // 目的地の順序を変更する関数です。 + // void makeOrder(Destination d, int dir) { DatabaseHelper db = DatabaseHelper.instance; db.updateOrder(d, dir).then((value) { - PopulateDestinations(); + populateDestinations(); }); } - - void makeNext(Destination pt) { - for (int i = 0; i <= destinations.length - 1; i++) { - Destination p = destinations[i]; - - if (p.lat == pt.lat && p.lon == pt.lon) { - if (indexController.currentDestinationFeature.isNotEmpty) { - indexController.currentDestinationFeature.clear(); - } - if (i >= destinations.length - 1) { - indexController.currentDestinationFeature.add(destinations[0]); - //getAction(); - } else { - indexController.currentDestinationFeature.add(destinations[i + 1]); - //getAction(); - } - } - } - } - - void makePrevious(Destination pt) { - for (int i = 0; i <= destinations.length - 1; i++) { - Destination p = destinations[i]; - - if (p.lat == pt.lat && p.lon == pt.lon) { - if (indexController.currentDestinationFeature.isNotEmpty) { - indexController.currentDestinationFeature.clear(); - } - if (i <= 0) { - indexController.currentDestinationFeature - .add(destinations[destinations.length - 1]); - //getAction(); - } else { - indexController.currentDestinationFeature.add(destinations[i - 1]); - //getAction(); - } - } - } - } } diff --git a/lib/pages/destination/destination_page.dart b/lib/pages/destination/destination_page.dart deleted file mode 100644 index 49a7ebc..0000000 --- a/lib/pages/destination/destination_page.dart +++ /dev/null @@ -1,212 +0,0 @@ - -import 'package:flutter/material.dart'; -import 'package:geolocator/geolocator.dart'; -import 'package:get/get.dart'; -import 'package:latlong2/latlong.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; -import 'package:rogapp/pages/destination_map/destination_map_page.dart'; -import 'package:rogapp/pages/drawer/drawer_page.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/routes/app_pages.dart'; -import 'package:rogapp/widgets/destination_widget.dart'; - -class DestnationPage extends StatelessWidget { - DestnationPage({Key? key}) : super(key: key); - - final DestinationController destinationController = Get.find(); - - final IndexController indexController = Get.find(); - - final List _items = List.generate(50, (int index) => index); - - Future showCurrentPosition() async { - LocationPermission permission = await Geolocator.checkPermission(); - if (permission != LocationPermission.whileInUse || - permission != LocationPermission.always) { - permission = await Geolocator.requestPermission(); - } - Position position = await Geolocator.getCurrentPosition( - desiredAccuracy: LocationAccuracy.high); - indexController.rogMapController.move(LatLng(position.latitude, position.longitude), 14); - } - - Image getImage(int index){ - if(destinationController.destinations[index].photos == null || destinationController.destinations[index].photos == ""){ - return const Image(image: AssetImage('assets/images/empty_image.png')); - } - else{ - return Image(image: NetworkImage(destinationController.destinations[index].photos!)); - } - } - - Widget getRoutingImage(int route){ - switch (route) { - case 0: - return const Image(image: AssetImage('assets/images/p4_9_man.png'), width: 35.0,); - case 1: - return const Image(image: AssetImage('assets/images/p4_8_car.png'), width: 35.0,); - case 2: - return const Image(image: AssetImage('assets/images/p4_10_train.png'), width: 35.0,); - default: - return const Image(image: AssetImage('assets/images/p4_9_man.png'), width: 35.0,); - } - } - -@override - Widget build(BuildContext context) { - final ColorScheme colorScheme = Theme.of(context).colorScheme; - final Color oddItemColor = colorScheme.primary.withOpacity(0.05); - final Color evenItemColor = colorScheme.primary.withOpacity(0.15); - return WillPopScope( - onWillPop: () async { - indexController.switchPage(AppPages.INITIAL); - return false; - }, - child: Scaffold( - - drawer: DrawerPage(), - bottomNavigationBar: BottomAppBar( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(left:13.0), - child: InkWell( - child: Obx((() => getRoutingImage(destinationController.travelMode.value))), - onTap: (){ - Get.bottomSheet( - Obx(() => - ListView( - children: [ - Padding( - padding: const EdgeInsets.only(top:30.0, bottom: 30), - child: Center(child: Text("select_travel_mode".tr, style: const TextStyle(fontSize: 22.0, color:Colors.red, fontWeight:FontWeight.bold),),), - ), - ListTile( - selected: destinationController.travelMode == 0 ? true : false, - selectedTileColor: Colors.amber.shade200, - leading: const Image(image: AssetImage('assets/images/p4_9_man.png'),), - title: Text("walking".tr), - onTap:(){ - destinationController.travelMode.value = 0; - destinationController.PopulateDestinations(); - Get.back(); - }, - ), - ListTile( - selected: destinationController.travelMode == 1 ? true : false, - selectedTileColor: Colors.amber.shade200, - leading: const Image(image: AssetImage('assets/images/p4_8_car.png'),), - title: Text("driving".tr), - onTap:(){ - destinationController.travelMode.value = 1; - destinationController.PopulateDestinations(); - Get.back(); - }, - ), - // ListTile( - // selected: destinationController.travelMode == 2 ? true : false, - // selectedTileColor: Colors.amber.shade200, - // leading: Image(image: AssetImage('assets/images/p4_10_train.png'),), - // title: Text("transit".tr), - // onTap:(){ - // destinationController.travelMode.value = 2; - // destinationController.PopulateDestinations(); - // Get.back(); - // }, - // ), - ], - - ), - ), - isScrollControlled:false, - backgroundColor: Colors.white, - ); - //destinationController.PopulateDestinations(); - } - ), - ) - , - IconButton( - icon: const Icon(Icons.travel_explore, size: 35,), - onPressed: (){ - indexController.switchPage(AppPages.INITIAL); - } - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: (){ - //print("######"); - indexController.toggleDestinationMode(); - }, - tooltip: 'Increment', - elevation: 4.0, - child: Obx(() => - indexController.desination_mode == 1 ? - const Image(image: AssetImage('assets/images/list2.png')) - : - const Image(image: AssetImage('assets/images/map.png')) - ), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, - appBar:AppBar( - automaticallyImplyLeading: true, - title: Text("app_title".tr), - actions: [ - InkWell( - onTap: (){ - Get.toNamed(AppPages.CAMERA_PAGE); - }, - child: destinationController.is_in_rog == true ? - Image.asset("assets/images/basic-walking.gif",height: 10.0,) - : - destinationController.is_at_goal == true ? - IconButton( - onPressed:(){Get.toNamed(AppPages.CAMERA_PAGE);}, - icon: const Icon(Icons.assistant_photo), - ) - : - IconButton( - onPressed:(){Get.toNamed(AppPages.CAMERA_PAGE);}, - icon: const Icon(Icons.accessibility), - ), - ), - // Obx(() => - // Text(indexController.connectionStatusName.value) - // ), - Obx(() => - ToggleButtons( - disabledColor: Colors.grey.shade200, - selectedColor: Colors.red, - onPressed: (int index) { - destinationController.is_gps_selected.value = !destinationController.is_gps_selected.value; - if(destinationController.is_gps_selected.value){ - destinationController.chekcs = 0; - destinationController.skip_gps = false; - //destinationController.resetRogaining(); - } - }, - isSelected: [destinationController.is_gps_selected.value], - children: const [ - Icon(Icons.explore, size: 35.0, - )], - ), - ), - // IconButton(onPressed: (){ - // showCurrentPosition(); - // }, - // icon: Icon(Icons.location_on_outlined)) - ], - ), - body: Obx(() => - indexController.desination_mode.value == 0 ? - DestinationWidget(): - DestinationMapPage() - ) - ), - ); - - } -} diff --git a/lib/pages/destination_map/destination_map_controller.dart b/lib/pages/destination_map/destination_map_controller.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/pages/destination_map/destination_map_page.dart b/lib/pages/destination_map/destination_map_page.dart index 63bd1ab..1310c9c 100644 --- a/lib/pages/destination_map/destination_map_page.dart +++ b/lib/pages/destination_map/destination_map_page.dart @@ -1,429 +1,207 @@ - import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_map/plugin_api.dart'; +import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; -import 'package:flutter_map_marker_popup/flutter_map_marker_popup.dart'; +//import 'package:flutter_map_marker_popup/flutter_map_marker_popup.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:get/get.dart'; import 'package:latlong2/latlong.dart'; -import 'package:rogapp/model/destination.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/utils/text_util.dart'; -import 'package:rogapp/widgets/base_layer_widget.dart'; -import 'package:rogapp/widgets/bottom_sheet_new.dart'; - +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/utils/text_util.dart'; +import 'package:gifunavi/widgets/base_layer_widget.dart'; +import 'package:gifunavi/widgets/bottom_sheet_new.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_start.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_goal.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_normal_point.dart'; +// FlutterMapウィジェットを使用して、地図を表示します。 +// IndexControllerから目的地のリストを取得し、マーカーとしてマップ上に表示します。 +// マーカーがタップされると、BottomSheetウィジェットを表示します。 +// 現在地の表示、ルートの表示、ベースレイヤーの表示などの機能を提供します。 +// 主なロジック: +// FlutterMapウィジェットを使用して、地図を表示します。 +// IndexControllerから目的地のリストを取得し、MarkerLayerを使用してマーカーを表示します。 +// getMarkerShapeメソッドを使用して、マーカーの見た目をカスタマイズします。目的地の種類に応じて、異なるマーカーを表示します。 +// マーカーがタップされると、festuretoDestinationメソッドを使用してGeoJSONFeatureをDestinationオブジェクトに変換し、showModalBottomSheetを使用してBottomSheetウィジェットを表示します。 +// CurrentLocationLayerを使用して、現在地をマップ上に表示します。 +// PolylineLayerを使用して、ルートをマップ上に表示します。getPointsメソッドを使用して、ルートの座標を取得します。 +// BaseLayerを使用して、マップのベースレイヤーを表示します。 +// class DestinationMapPage extends StatelessWidget { - DestinationMapPage({Key? key}) : super(key: key); + DestinationMapPage({super.key}); final IndexController indexController = Get.find(); - final DestinationController destinationController = Get.find(); + final DestinationController destinationController = + Get.find(); StreamSubscription? subscription; - final PopupController _popupLayerController = PopupController(); + //final PopupController _popupLayerController = PopupController(); - List? getPoints(){ - print("##### --- route point ${indexController.routePoints.length}"); - List pts = []; - for(PointLatLng p in indexController.routePoints){ - LatLng l = LatLng(p.latitude, p.longitude); - pts.add(l); - } - return pts; + List? getPoints() { + //print("##### --- route point ${indexController.routePoints.length}"); + List pts = []; + for (PointLatLng p in indexController.routePoints) { + LatLng l = LatLng(p.latitude, p.longitude); + pts.add(l); } + return pts; + } - List? getMarkers() { - List pts = []; - int index = -1; - for (int i = 0; i < destinationController.destinations.length; i++) { - Destination d = destinationController.destinations[i]; - print("^^^^ $d ^^^^"); - Marker m = Marker( + // 要検討:マーカーのタップイベントを処理する際に、エラーハンドリングが不十分です。例外が発生した場合の処理を追加することをお勧めします。 + // + List? getMarkers() { + List pts = []; + //int index = -1; + for (int i = 0; i < destinationController.destinations.length; i++) { + Destination d = destinationController.destinations[i]; + //print("^^^^ $d ^^^^"); + Marker m = Marker( point: LatLng(d.lat!, d.lon!), - anchorPos: AnchorPos.align(AnchorAlign.center), - builder:(cts){ + alignment: Alignment.center, + child: InkWell( + onTap: () { + //print("-- Destination is --- ${d.name} ------"); + if (indexController.currentDestinationFeature.isNotEmpty) { + indexController.currentDestinationFeature.clear(); + } + indexController.currentDestinationFeature.add(d); + //indexController.getAction(); - return InkWell( - onTap: (){ - print("-- Destination is --- ${d.name} ------"); - if(indexController.currentDestinationFeature.isNotEmpty) { - indexController.currentDestinationFeature.clear(); - } - indexController.currentDestinationFeature.add(d); - //indexController.getAction(); - - showModalBottomSheet(context: Get.context!, isScrollControlled: true, - builder:((context) => BottomSheetNew()) - ).whenComplete((){ - print("---- set skip gps to false -----"); - destinationController.skip_gps = false; - }); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width:20, - height:20, - decoration: BoxDecoration( - color: Colors.red, - shape: BoxShape.circle, - border: Border.all( - color: Colors.white, - width: d.checkin_radious != null ? d.checkin_radious! : 1, - ), - ), - child: Center( - child: Text( - (i + 1).toString(), - style: const TextStyle(color: Colors.white), - ), + Widget bottomSheet = BottomSheetNew(destination: d); + /* + if (d.cp == -1 || d.cp == 0) { + bottomSheet = BottomSheetStart(destination: d); + } else if (d.cp == -2 || d.cp == 0) { + bottomSheet = BottomSheetGoal(destination: d); + } else { + bottomSheet = BottomSheetNormalPoint(destination: d); + } + */ + + showModalBottomSheet( + context: Get.context!, + isScrollControlled: true, + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.85)), + builder: ((context) => bottomSheet ), + + ).whenComplete(() { + //print("---- set skip gps to false -----"); + destinationController.skipGps = false; + }); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, + width: d.checkin_radious != null ? d.checkin_radious! : 1, ), ), - Container( color: Colors.yellow, child: Text(TextUtils.getDisplayText(d), style: const TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold, overflow: TextOverflow.visible),)), - ], - ), - ); - - }); - - pts.add(m); - } - return pts; + child: Center( + child: Text( + (i + 1).toString(), + style: const TextStyle(color: Colors.white), + ), + ), + ), + Container( + color: Colors.yellow, + child: Text( + TextUtils.getDisplayText(d), + style: const TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + overflow: TextOverflow.visible), + )), + ], + ), + )); + + pts.add(m); } + return pts; + } @override Widget build(BuildContext context) { - return Obx((() => - Stack( - children: [ - // indexController.is_rog_mapcontroller_loaded.value == false ? - // Center(child: CircularProgressIndicator()) - // : - // Padding( - // padding: const EdgeInsets.only(left:8.0), - // child: BreadCrumbWidget(mapController:indexController.rogMapController), - // ), - Padding( - padding: const EdgeInsets.only(top:0.0), - //child: TravelMap(), - child: - TravelMap(), - ), - ], - ) - )); + return Obx((() => Stack( + children: [ + // indexController.is_rog_mapcontroller_loaded.value == false ? + // Center(child: CircularProgressIndicator()) + // : + // Padding( + // padding: const EdgeInsets.only(left:8.0), + // child: BreadCrumbWidget(mapController:indexController.rogMapController), + // ), + Padding( + padding: const EdgeInsets.only(top: 0.0), + //child: TravelMap(), + child: travelMap(), + ), + ], + ))); } - FlutterMap TravelMap() { + // 要検討:MapOptionsのboundsプロパティにハードコードされた座標が使用されています。これを動的に設定できるようにすることを検討してください。 + // + FlutterMap travelMap() { return FlutterMap( mapController: indexController.rogMapController, - options: MapOptions( - onMapReady: (){ - indexController.is_rog_mapcontroller_loaded.value = true; - subscription = indexController.rogMapController.mapEventStream.listen((MapEvent mapEvent) { - if (mapEvent is MapEventMoveStart) { - } + options: MapOptions( + onMapReady: () { + indexController.isRogMapcontrollerLoaded.value = true; + subscription = indexController.rogMapController.mapEventStream + .listen((MapEvent mapEvent) { + if (mapEvent is MapEventMoveStart) {} if (mapEvent is MapEventMoveEnd) { //destinationController.is_gps_selected.value = true; //indexController.mapController!.move(c.center, c.zoom); LatLngBounds bounds = indexController.rogMapController.bounds!; indexController.currentBound.clear(); indexController.currentBound.add(bounds); - if(indexController.currentUser.isEmpty){ - indexController.loadLocationsBound(); + if (indexController.currentUser.isEmpty) { + indexController.loadLocationsBound(indexController.currentUser[0]["user"]["event_code"]); } } }); - } , - bounds: indexController.currentBound.isNotEmpty ? indexController.currentBound[0]: LatLngBounds.fromPoints([LatLng(35.03999881162295, 136.40587119778962), LatLng(36.642756778706904, 137.95226720406063)]), - zoom: 1, - maxZoom: 42, - interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag, - ), - children: [ - const BaseLayer(), - Obx(() => - indexController.routePointLenght > 0 ? - PolylineLayer( - polylines: [ - Polyline( - points: getPoints()!, - strokeWidth: 6.0, - color: Colors.indigo - ), - ], - ) - - : - Container(), + }, + bounds: indexController.currentBound.isNotEmpty + ? indexController.currentBound[0] + : LatLngBounds.fromPoints([ + const LatLng(35.03999881162295, 136.40587119778962), + const LatLng(36.642756778706904, 137.95226720406063) + ]), + initialZoom: 1, + maxZoom: 42, + interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag, ), - CurrentLocationLayer(), - MarkerLayer( - markers: getMarkers()! - ), - ], - - ); + children: [ + const BaseLayer(), + Obx( + () => indexController.routePointLenght > 0 + ? PolylineLayer( + polylines: [ + Polyline( + points: getPoints()!, + strokeWidth: 6.0, + color: Colors.indigo), + ], + ) + : Container(), + ), + CurrentLocationLayer(), + MarkerLayer(markers: getMarkers()!), + ], + ); } } - - - - -// class DestinationMapPage extends StatefulWidget { -// DestinationMapPage({ Key? key }) : super(key: key); - - -// @override -// State createState() => _DestinationMapPageState(); -// } - -//class _DestinationMapPageState extends State { - // final IndexController indexController = Get.find(); - - // final DestinationController destinationController = Get.find(); - // StreamSubscription? subscription; - // final PopupController _popupLayerController = PopupController(); - - // List? getPoints(List ptts){ - // //print("##### --- route point ${indexController.routePoints.length}"); - // List pts = []; - // for(PointLatLng p in ptts){ - // LatLng l = LatLng(p.latitude, p.longitude); - // pts.add(l); - // } - // return pts; - // } - - // String getDisplaytext(Destination dp){ - // String txt = ""; - // if(dp.cp! > 0){ - // txt = "${dp.cp}"; - // if(dp.checkin_point != null && dp.checkin_point! > 0){ - // txt = txt + "{${dp.checkin_point}}"; - // } - // if(dp.buy_point != null && dp.buy_point! > 0){ - // txt = txt + "[${dp.buy_point}]"; - // } - // } - // return txt; - // } - - // List? getMarkers() { - // List pts = []; - // int index = -1; - // for (int i = 0; i < destinationController.destinations.length; i++) { - // Destination d = destinationController.destinations[i]; - // //for(Destination d in destinationController.destinations){ - // //print("-----lat ${lat}, ----- lon ${lan}"); - // Marker m = Marker( - // point: LatLng(d.lat!, d.lon!), - // anchorPos: AnchorPos.align(AnchorAlign.center), - // builder:(cts){ - - // return InkWell( - // onTap: (){ - // print("-- Destination is --- ${d.name} ------"); - // if(d != null){ - // if(indexController.currentDestinationFeature.length > 0) { - // indexController.currentDestinationFeature.clear(); - // } - // indexController.currentDestinationFeature.add(d); - // //indexController.getAction(); - - // showModalBottomSheet(context: context, isScrollControlled: true, - // //builder:((context) => BottomSheetWidget()) - // builder:((context) => BottomSheetNew()) - // ); - // } - // }, - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Container( - // width:20, - // height:20, - // decoration: BoxDecoration( - // color: Colors.red, - // shape: BoxShape.circle, - // border: new Border.all( - // color: Colors.white, - // width: d.checkin_radious != null ? d.checkin_radious! : 1, - // ), - // ), - // child: new Center( - // child: new Text( - // (i + 1).toString(), - // style: TextStyle(color: Colors.white), - // ), - // ), - // ), - // Container( color: Colors.yellow, child: Text(getDisplaytext(d), style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold, overflow: TextOverflow.visible),)), - // ], - // ), - // ); - - // //return Icon(Icons.pin_drop); - // // return IconButton( - // // onPressed: ()async { - // // Destination? fs = await destinationController.getDEstinationForLatLong(d.lat!, d.lon!); - // // print("-- Destination is --- ${fs!.name} ------"); - // // if(fs != null){ - // // if(indexController.currentDestinationFeature.length > 0) { - // // indexController.currentDestinationFeature.clear(); - // // } - // // indexController.currentDestinationFeature.add(fs); - // // //indexController.getAction(); - - // // showModalBottomSheet(context: context, isScrollControlled: true, - // // //builder:((context) => BottomSheetWidget()) - // // builder:((context) => BottomSheetNew()) - // // ); - // // } - // // }, - // // icon: Container( - // // width: 60, - // // height: 60, - // // decoration: BoxDecoration( - // // borderRadius: BorderRadius.circular(d.checkin_radious ?? 0), - // // color: Colors.transparent, - // // border: BoxBorder() - // // ), - // // child: Icon(Icons.pin_drop) - // // ) - // // ); - - // }); - - // pts.add(m); - // } - // return pts; - // } - - // @override - // void initState() { - - // //indexController.routePoints.clear(); - // DestinationService.getDestinationLine(destinationController.destinations)?.then((value){ - // //print("---- loading destination points ------ ${value}"); - // setState(() { - // indexController.routePoints = value; - // }); - // }); - // super.initState(); - // } - - // void reload(){ - // setState(() { - - // }); - // } - - // @override - // Widget build(BuildContext context) { - // return Obx((() => - // Stack( - // children: [ - // indexController.is_rog_mapcontroller_loaded.value == false ? - // Center(child: CircularProgressIndicator()) - // : - // BreadCrumbWidget(mapController:indexController.rogMapController), - // Padding( - // padding: const EdgeInsets.only(top:50.0), - // //child: TravelMap(), - // child: - // TravelMap(indexController.routePoints), - // ), - // // Positioned( - // // bottom: 200, - // // left: 10, - // // child: Container( - // // color: Colors.white, - // // child: Row( - // // children: [ - // // Text(destinationController.gps[0]), - // // Text(destinationController.locationPermission[0]) - // // ], - // // ), - // // ) - // // ), - // ], - // ) - // )); - // } - - // FlutterMap TravelMap(List ptts) { - // return FlutterMap( - // options: MapOptions( - // onMapCreated: (c){ - // indexController.rogMapController = c; - // indexController.rogMapController!.onReady.then((_) { - // indexController.is_rog_mapcontroller_loaded.value = true; - // subscription = indexController.rogMapController!.mapEventStream.listen((MapEvent mapEvent) { - // if (mapEvent is MapEventMoveStart) { - // //print(DateTime.now().toString() + ' [MapEventMoveStart] START'); - // // do something - // } - // if (mapEvent is MapEventMoveEnd) { - // destinationController.isSelected.value = false; - // //print(DateTime.now().toString() + ' [MapEventMoveStart] END'); - // //indexController.loadLocationsBound(); - // } - // }); - // }); - // } , - // bounds: indexController.currentBound.length > 0 ? indexController.currentBound[0]: LatLngBounds.fromPoints([LatLng(35.03999881162295, 136.40587119778962), LatLng(36.642756778706904, 137.95226720406063)]), - // zoom: 1, - // maxZoom: 42, - // interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag, - // //plugins: [LocationMarkerPlugin(),] - // ), - // children: [ - // TileLayerWidget( - // options: TileLayerOptions( - // urlTemplate: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', - // subdomains: ['a', 'b', 'c'], - // ), - // ), - // //Obx(() => - // indexController.routePoints.length > 0 ? - // PolylineLayerWidget( - // options: PolylineLayerOptions( - // polylines: [ - // Polyline( - // points: getPoints(ptts)!, - // strokeWidth: 6.0, - // color: Colors.indigo - // ), - // ], - // ), - // ) - // : - // Container(), - // //), - // // PopupMarkerLayerWidget( - // // options: PopupMarkerLayerOptions( - // // popupController: _popupLayerController, - // // markers: _markers, - // // markerRotateAlignment: - // // PopupMarkerLayerOptions.rotationAlignmentFor(AnchorAlign.top), - // // popupBuilder: (BuildContext context, Marker marker) => - - // // examplePopup(marker), - // // ), - // // ), - // LocationMarkerLayerWidget(), - // MarkerLayerWidget( - // options: MarkerLayerOptions( - // markers: getMarkers()! - // ), - // ), - // ], - - // ); - // } -//} \ No newline at end of file diff --git a/lib/pages/drawer/drawer_binding.dart b/lib/pages/drawer/drawer_binding.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/pages/drawer/drawer_page.dart b/lib/pages/drawer/drawer_page.dart index 169cbad..fd1e806 100644 --- a/lib/pages/drawer/drawer_page.dart +++ b/lib/pages/drawer/drawer_page.dart @@ -1,128 +1,274 @@ - import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/routes/app_pages.dart'; -import 'package:rogapp/services/auth_service.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/services/auth_service.dart'; +import 'package:gifunavi/utils/database_helper.dart'; +import 'package:gifunavi/widgets/debug_widget.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:gifunavi/pages/WebView/WebView_page.dart'; +// SafeAreaウィジェットを使用して、画面の安全領域内にメニューを表示しています。 +// Columnウィジェットを使用して、メニューアイテムを縦に並べています。 +// class DrawerPage extends StatelessWidget { - DrawerPage({ Key? key }) : super(key: key); + DrawerPage({super.key}); final IndexController indexController = Get.find(); + LogManager logManager = LogManager(); + + // 要検討:URLの起動に失敗した場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。 + // + /* void _launchURL(url) async { - if (!await launch(url)) throw 'Could not launch $url'; + if (!await launchUrl(url)) throw 'Could not launch $url'; } + */ + + void _launchURL(BuildContext context,String urlString) async { + try { + logManager.addOperationLog('User clicked $urlString on the drawer'); + Uri url = Uri.parse(urlString); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } else { + // URLを開けない場合のフォールバック動作 + // 例えば、WebViewを使用してアプリ内でURLを開く + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => WebViewPage(url: urlString), + ), + ); + } + }catch(e){ + // エラーメッセージを表示する + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('URLを開けませんでした: $e')), + ); + } + } + @override Widget build(BuildContext context) { return SafeArea( child: Drawer( - // Add a ListView to the drawer. This ensures the user can scroll - // through the options in the drawer if there isn't enough vertical - // space to fit everything. child: Column( children: [ Container( height: 100, color: Colors.amber, - child: Obx(() => - Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: - indexController.currentUser.isEmpty ? - Flexible(child: Text("drawer_title".tr, style: const TextStyle(color: Colors.black, fontSize: 20),)) - : - Text(indexController.currentUser[0]['user']['email'], style: const TextStyle(color: Colors.black, fontSize: 20),), - ), - ) - ), + child: Obx(() => Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: indexController.currentUser.isEmpty + ? Flexible( + child: Text( + "drawer_title".tr, + style: const TextStyle( + color: Colors.black, fontSize: 20), + )) + : Text( + indexController.currentUser[0]['user']['email'], + style: const TextStyle( + color: Colors.black, fontSize: 20), + ), + ), + )), ), - Obx(() => - indexController.currentUser.isEmpty ? - ListTile( - leading: const Icon(Icons.login), - title: Text("login".tr), - onTap: (){ - Get.toNamed(AppPages.LOGIN); - }, - ) : - ListTile( - leading: const Icon(Icons.login), - title: Text("logout".tr), - onTap: (){ - indexController.logout(); - Get.toNamed(AppPages.TRAVEL); - }, - ) - ), - indexController.currentUser.isNotEmpty ? + ListTile( - leading: const Icon(Icons.password), - title: Text("change_password".tr), - onTap: (){ - Get.toNamed(AppPages.CHANGE_PASSWORD); + leading: const Icon(Icons.group), + title: const Text('チーム管理'), + onTap: () async{ + //Get.back(); + // スナックバーを安全に閉じる + await _safelyCloseSnackbar(); + Get.toNamed(AppPages.TEAM_LIST); }, - ) : - const SizedBox(width: 0, height: 0,), - indexController.currentUser.isEmpty ? + ), + ListTile( + leading: const Icon(Icons.event), + title: const Text('エントリー管理'), + onTap: () { + Get.back(); + Get.toNamed(AppPages.ENTRY_LIST); + }, + ), + ListTile( + leading: const Icon(Icons.event), + title: const Text('イベント参加'), + onTap: () { + Get.back(); // ドロワーを閉じる + Get.toNamed(AppPages.EVENT_ENTRY); + }, + ), ListTile( leading: const Icon(Icons.person), - title: Text("sign_up".tr), - onTap: (){ - Get.toNamed(AppPages.REGISTER); + title: const Text("個人情報の修正"), + onTap: () { + Get.back(); // Close the drawer + Get.toNamed(AppPages.USER_DETAILS_EDIT); }, - ) : - const SizedBox(width: 0, height: 0,), - indexController.currentUser.isNotEmpty ? - ListTile( - leading: const Icon(Icons.delete_forever), - title: Text("delete_account".tr), - onTap: (){ - String token = indexController.currentUser[0]['token']; - AuthService.deleteUser(token).then((value){ - if(value.isNotEmpty){ - indexController.logout(); - Get.toNamed(AppPages.TRAVEL); - Get.snackbar("accounted_deleted".tr, "account_deleted_message".tr); - } - }); - }, - ) : - const SizedBox(width: 0, height: 0,), - // ListTile( - // leading: const Icon(Icons.person), - // title: Text("profile".tr), - // onTap: (){}, - // ), - // ListTile( - // leading: const Icon(Icons.route), - // title: Text("recommended_route".tr), - // onTap: (){}, - // ), - // ListTile( - // leading: const Icon(Icons.favorite_rounded), - // title: Text("point_rank".tr), - // onTap: (){}, - // ), - indexController.currentUser.isNotEmpty ? - ListTile( - leading: const Icon(Icons.featured_video), - title: Text("rog_web".tr), - onTap: (){ - _launchURL("https://www.gifuai.net/?page_id=17397"); - }, - ) : - const SizedBox(width: 0, height: 0,), + ), + + Obx(() => indexController.currentUser.isEmpty + ? ListTile( + leading: const Icon(Icons.login), + title: Text("login".tr), + onTap: () { + Get.toNamed(AppPages.LOGIN); + }, + ) + : ListTile( + leading: const Icon(Icons.login), + title: Text("logout".tr), + onTap: () { + indexController.logout(); + Get.toNamed(AppPages.LOGIN); + }, + )), + indexController.currentUser.isNotEmpty + ? ListTile( + leading: const Icon(Icons.password), + title: Text("change_password".tr), + onTap: () { + Get.toNamed(AppPages.CHANGE_PASSWORD); + }, + ) + : const SizedBox( + width: 0, + height: 0, + ), + indexController.currentUser.isEmpty + ? ListTile( + leading: const Icon(Icons.person), + title: Text("sign_up".tr), + onTap: () { + Get.toNamed(AppPages.REGISTER); + }, + ) + : const SizedBox( + width: 0, + height: 0, + ), + indexController.currentUser.isNotEmpty + ? ListTile( + leading: const Icon(Icons.password), + title: Text('reset_button'.tr), + onTap: () { + logManager.addOperationLog('User clicked RESET button on the drawer'); + // 要検討:リセット操作の確認メッセージをローカライズすることを検討してください。 + // + Get.defaultDialog( + title: "reset_title".tr, + middleText: "reset_message".tr, + textConfirm: "confirm".tr, + textCancel: "cancel".tr, + onCancel: () => Get.back(), + onConfirm: () async { + DestinationController destinationController = + Get.find(); + DatabaseHelper databaseHelper = DatabaseHelper.instance; + + // ゲーム中のデータを削除 + await databaseHelper.deleteAllRogaining(); + await databaseHelper.deleteAllDestinations(); + destinationController.resetRogaining(); + + //destinationController.resetRogaining(); + //destinationController.deleteDBDestinations(); + Get.back(); + Get.snackbar( + "reset_done".tr, + "reset_explain".tr, + backgroundColor: Colors.green, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + }, + ); + }, + ) + : const SizedBox( + width: 0, + height: 0, + ), + indexController.currentUser.isNotEmpty + ? ListTile( + leading: const Icon(Icons.delete_forever), + title: Text("delete_account".tr), + onTap: () { + Get.defaultDialog( + title: "delete_account_title".tr, + middleText: "delete_account_middle".tr, + textConfirm: "confirm".tr, + textCancel: "cancel".tr, + onCancel: () => Get.back(), + onConfirm: () { + logManager.addOperationLog('User clicked Confirm button on the account delete dialog'); + String token = indexController.currentUser[0]['token']; + AuthService.deleteUser(token).then((value) { + if (value.isNotEmpty) { + indexController.logout(); + Get.toNamed(AppPages.TRAVEL); + Get.snackbar("accounted_deleted".tr, + "account_deleted_message".tr, + backgroundColor: Colors.green, + colorText: Colors.white + ); + } + }); + }, + ); + }, + ) + : const SizedBox( + width: 0, + height: 0, + ), + indexController.currentUser.isNotEmpty + ? ListTile( + leading: const Icon(Icons.featured_video), + title: Text("rog_web".tr), + onTap: () { + _launchURL(context, "https://www.gifuai.net/?page_id=60043"); + }, + ) + : const SizedBox( + width: 0, + height: 0, + ), + ListTile( leading: const Icon(Icons.privacy_tip), title: Text("privacy".tr), - onTap: (){ - _launchURL("https://rogaining.sumasen.net/api/privacy/"); + onTap: () { + _launchURL(context, "https://rogaining.sumasen.net/api/privacy/"); }, - ) + ), + ListTile( + leading: const Icon(Icons.settings), + title: Text('open_settings'.tr), + onTap: () { + Get.back(); // ドロワーを閉じる + Get.toNamed(Routes.SETTINGS); + }, + ), + + //ListTile( + // leading: const Icon(Icons.developer_mode), + // title: const Text('open_settings'), + // onTap: () { + // Get.back(); // ドロワーを閉じる + // Get.toNamed('/debug'); // デバッグ画面に遷移 + // }, + //), + + // ListTile( // leading: const Icon(Icons.router), // title: Text("my_route".tr), @@ -138,4 +284,14 @@ class DrawerPage extends StatelessWidget { ), ); } -} \ No newline at end of file + + Future _safelyCloseSnackbar() async { + if (Get.isSnackbarOpen) { + try { + await Get.closeCurrentSnackbar(); + } catch (e) { + print('Error closing snackbar: $e'); + } + } + } +} diff --git a/lib/pages/entry/entry_binding.dart b/lib/pages/entry/entry_binding.dart new file mode 100644 index 0000000..b29ec11 --- /dev/null +++ b/lib/pages/entry/entry_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:gifunavi/pages/entry/entry_controller.dart'; +import 'package:gifunavi/services/api_service.dart'; + +class EntryBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => ApiService()); + Get.lazyPut(() => EntryController()); + } +} \ No newline at end of file diff --git a/lib/pages/entry/entry_controller.dart b/lib/pages/entry/entry_controller.dart new file mode 100644 index 0000000..4e71d86 --- /dev/null +++ b/lib/pages/entry/entry_controller.dart @@ -0,0 +1,310 @@ +// lib/entry/entry_controller.dart + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/model/entry.dart'; +import 'package:gifunavi/model/event.dart'; +import 'package:gifunavi/model/team.dart'; +import 'package:gifunavi/model/category.dart'; +import 'package:gifunavi/services/api_service.dart'; + +import '../index/index_controller.dart'; +import 'package:timezone/timezone.dart' as tz; + +class EntryController extends GetxController { + late ApiService _apiService; + + final entries = [].obs; + final events = [].obs; + final teams = [].obs; + final categories = [].obs; + + final selectedEvent = Rx(null); + final selectedTeam = Rx(null); + final selectedCategory = Rx(null); + final selectedDate = Rx(null); + + final currentEntry = Rx(null); + final isLoading = true.obs; + + @override + void onInit() async { + super.onInit(); + await initializeApiService(); + await loadInitialData(); + } + + Future initializeApiService() async { + try { + _apiService = await Get.putAsync(() => ApiService().init()); + } catch (e) { + print('Error initializing ApiService: $e'); + Get.snackbar('Error', 'Failed to initialize API service'); + } + } + + Future loadInitialData() async { + try { + isLoading.value = true; + await Future.wait([ + fetchEntries(), + fetchEvents(), + fetchTeams(), + fetchCategories(), + ]); + if (Get.arguments != null && Get.arguments['entry'] != null) { + currentEntry.value = Get.arguments['entry']; + initializeEditMode(currentEntry.value!); + } else { + // 新規作成モードの場合、最初のイベントを選択 + if (events.isNotEmpty) { + selectedEvent.value = events.first; + selectedDate.value = events.first.startDatetime; + } + } + } catch(e) { + print('Error initializing data: $e'); + Get.snackbar('Error', 'Failed to load initial data'); + } finally { + isLoading.value = false; + } + } + + + void initializeEditMode(Entry entry) { + currentEntry.value = entry; + selectedEvent.value = entry.event; + selectedTeam.value = entry.team; + selectedCategory.value = entry.category; + selectedDate.value = entry.date; + } + + void updateEvent(Event? value) { + selectedEvent.value = value; + if (value != null) { + // イベント変更時に日付を調整 + if (selectedDate.value == null || + selectedDate.value!.isBefore(value.startDatetime) || + selectedDate.value!.isAfter(value.endDatetime)) { + selectedDate.value = value.startDatetime; + } + } + } + + void updateTeam(Team? value) { + selectedTeam.value = value; + if (value != null) { + selectedCategory.value = value.category; + } + } + //void updateTeam(Team? value) => selectedTeam.value = value; + void updateCategory(NewCategory? value) => selectedCategory.value = value; + //void updateDate(DateTime value) => selectedDate.value = value; + void updateDate(DateTime value) { + selectedDate.value = tz.TZDateTime.from(value, tz.getLocation('Asia/Tokyo')); + } + /* + void updateDate(DateTime value){ + selectedDate.value = DateFormat('yyyy-MM-dd').format(value!) as DateTime?; + } + + */ + + void _initializeEntryData() { + if (currentEntry.value != null) { + selectedEvent.value = currentEntry.value!.event; + selectedTeam.value = currentEntry.value!.team; + selectedCategory.value = currentEntry.value!.category; + selectedDate.value = currentEntry.value!.date; + } + } + + Future fetchEntries() async { + try { + final fetchedEntries = await _apiService.getEntries(); + entries.assignAll(fetchedEntries); + } catch (e) { + print('Error fetching entries: $e'); + Get.snackbar('Error', 'Failed to fetch entries'); + } + } + + Future fetchEvents() async { + try { + final fetchedEvents = await _apiService.getEvents(); + events.assignAll(fetchedEvents.map((event) { + // end_dateの7日前を締め切りとして設定 + final deadlineDateTime = event.endDatetime.subtract(const Duration(days: 7)); + return Event( + id: event.id, + eventName: event.eventName, + startDatetime: event.startDatetime, + endDatetime: event.endDatetime, + deadlineDateTime: deadlineDateTime, + ); + }).toList()); + } catch (e) { + print('Error fetching events: $e'); + Get.snackbar('Error', 'Failed to fetch events'); + } + } + + Future fetchEvents_old() async { + try { + final fetchedEvents = await _apiService.getEvents(); + events.assignAll(fetchedEvents); + } catch (e) { + print('Error fetching events: $e'); + Get.snackbar('Error', 'Failed to fetch events'); + } + } + + Future fetchTeams() async { + try { + final fetchedTeams = await _apiService.getTeams(); + teams.assignAll(fetchedTeams); + } catch (e) { + print('Error fetching teams: $e'); + Get.snackbar('Error', 'Failed to fetch team'); + } + } + + Future fetchCategories() async { + try { + final fetchedCategories = await _apiService.getCategories(); + categories.assignAll(fetchedCategories); + } catch (e) { + print('Error fetching categories: $e'); + Get.snackbar('Error', 'Failed to fetch categories'); + } + } + + Future createEntry() async { + if (selectedEvent.value == null || selectedTeam.value == null || + selectedCategory.value == null || selectedDate.value == null) { + Get.snackbar('Error', 'Please fill all fields'); + return; + } + try { + isLoading.value = true; + // Get zekken number + final updatedCategory = await _apiService.getZekkenNumber(selectedCategory.value!.id); + final zekkenNumber = updatedCategory.categoryNumber.toString(); + + final newEntry = await _apiService.createEntry( + selectedTeam.value!.id, + selectedEvent.value!.id, + selectedCategory.value!.id, + selectedDate.value!, + zekkenNumber, + ); + entries.add(newEntry); + Get.back(); + } catch (e) { + print('Error creating entry: $e'); + Get.snackbar('Error', 'Failed to create entry'); + } finally { + isLoading.value = false; + } + } + + Future updateEntryAndRefreshMap() async { + await updateEntry(); + + // エントリーが正常に更新された後、マップをリフレッシュ + final indexController = Get.find(); + final eventCode = currentEntry.value?.event.eventName ?? ''; + indexController.reloadMap(eventCode); + } + + bool isEntryEditable(Event event) { + return DateTime.now().isBefore(event.deadlineDateTime); + } + + Future updateEntry() async { + if (currentEntry.value == null) { + Get.snackbar('Error', 'No entry selected for update'); + return; + } + + if (!isEntryEditable(currentEntry.value!.event)) { + Get.dialog( + AlertDialog( + title: Text('エントリー変更不可'), + content: Text('締め切りを過ぎているため、エントリーの変更はできません。変更が必要な場合は事務局にお問い合わせください。'), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () => Get.back(), + ), + ], + ), + ); + return; + } + + try { + isLoading.value = true; + final updatedEntry = await _apiService.updateEntry( + currentEntry.value!.id, + currentEntry.value!.team.id, + selectedEvent.value!.id, + selectedCategory.value!.id, + selectedDate.value!, + currentEntry.value!.zekkenNumber, + ); + final index = entries.indexWhere((entry) => entry.id == updatedEntry.id); + if (index != -1) { + entries[index] = updatedEntry; + } + Get.back(); + } catch (e) { + print('Error updating entry: $e'); + Get.snackbar('Error', 'Failed to update entry'); + } finally { + isLoading.value = false; + } + } + + Future updateEntryCategory(int entryId, int newCategoryId) async { + try { + //await _apiService.updateEntryCategory(entryId, newCategoryId); + final updatedEntry = await _apiService.updateEntry( + currentEntry.value!.id, + currentEntry.value!.team.id, + selectedEvent.value!.id, + newCategoryId, + currentEntry.value!.date!, + currentEntry.value!.zekkenNumber, + ); + await fetchEntries(); + } catch (e) { + print('Error updating entry category: $e'); + Get.snackbar('エラー', 'エントリーのカテゴリ更新に失敗しました'); + } + } + + Future deleteEntry() async { + if (currentEntry.value == null) { + Get.snackbar('Error', 'No entry selected for deletion'); + return; + } + try { + isLoading.value = true; + await _apiService.deleteEntry(currentEntry.value!.id); + entries.removeWhere((entry) => entry.id == currentEntry.value!.id); + Get.back(); + } catch (e) { + print('Error deleting entry: $e'); + Get.snackbar('Error', 'Failed to delete entry'); + } finally { + isLoading.value = false; + } + } + + + bool isOwner() { + // Implement logic to check if the current user is the owner of the entry + return true; // Placeholder + } +} \ No newline at end of file diff --git a/lib/pages/entry/entry_detail_page.dart b/lib/pages/entry/entry_detail_page.dart new file mode 100644 index 0000000..f16e050 --- /dev/null +++ b/lib/pages/entry/entry_detail_page.dart @@ -0,0 +1,193 @@ +// lib/pages/entry/entry_detail_page.dart + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/pages/entry/entry_controller.dart'; +import 'package:gifunavi/model/event.dart'; +import 'package:gifunavi/model/category.dart'; +import 'package:gifunavi/model/team.dart'; +import 'package:intl/intl.dart'; + +import 'package:timezone/timezone.dart' as tz; + +class EntryDetailPage extends GetView { + const EntryDetailPage({super.key}); + + @override + Widget build(BuildContext context) { + final Map arguments = Get.arguments ?? {}; + final mode = Get.arguments['mode'] as String? ?? 'new'; + final entry = Get.arguments['entry']; + + if (mode == 'edit' && entry != null) { + controller.initializeEditMode(entry); + } + + return Scaffold( + appBar: AppBar( + title: Text(mode == 'new' ? 'エントリー登録' : 'エントリー詳細'), + ), + body: Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + return Padding( + padding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDropdown( + label: 'イベント', + items: controller.events, + selectedId: controller.selectedEvent.value?.id, + onChanged: (eventId) => controller.updateEvent( + controller.events.firstWhere((e) => e.id == eventId) + ), + getDisplayName: (event) => event.eventName, + getId: (event) => event.id, + ), + const SizedBox(height: 16), + _buildDropdown( + label: 'チーム', + items: controller.teams, + selectedId: controller.selectedTeam.value?.id, + onChanged: (teamId) => controller.updateTeam( + controller.teams.firstWhere((t) => t.id == teamId) + ), + getDisplayName: (team) => team.teamName, + getId: (team) => team.id, + ), + const SizedBox(height: 16), + _buildCategoryDropdown(), + /* + _buildDropdown() + label: 'カテゴリ', + items: controller.categories, + selectedId: controller.selectedCategory.value?.id, + onChanged: (categoryId) => controller.updateCategory( + controller.categories.firstWhere((c) => c.id == categoryId) + ), + getDisplayName: (category) => category.categoryName, + getId: (category) => category.id, + ), + + */ + const SizedBox(height: 16), + ListTile( + title: const Text('日付'), + subtitle: Text( + controller.selectedDate.value != null + ? DateFormat('yyyy-MM-dd').format(tz.TZDateTime.from(controller.selectedDate.value!, tz.getLocation('Asia/Tokyo'))) + : '日付を選択してください', + ), + onTap: () async { + if (controller.selectedEvent.value == null) { + Get.snackbar('Error', 'Please select an event first'); + return; + } + final tz.TZDateTime now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo')); + final tz.TZDateTime eventStart = tz.TZDateTime.from(controller.selectedEvent.value!.startDatetime, tz.getLocation('Asia/Tokyo')); + final tz.TZDateTime eventEnd = tz.TZDateTime.from(controller.selectedEvent.value!.endDatetime, tz.getLocation('Asia/Tokyo')); + + final tz.TZDateTime initialDate = controller.selectedDate.value != null + ? tz.TZDateTime.from(controller.selectedDate.value!, tz.getLocation('Asia/Tokyo')) + : (now.isAfter(eventStart) ? now : eventStart); + + // 選択可能な最初の日付を設定(今日かイベント開始日のうち、より後の日付) + final tz.TZDateTime firstDate = now.isAfter(eventStart) ? now : eventStart; + + final DateTime? picked = await showDatePicker( + context: context, + initialDate: initialDate.isAfter(firstDate) ? initialDate : firstDate, + firstDate: firstDate, + lastDate: eventEnd, + ); + if (picked != null) { + controller.updateDate(tz.TZDateTime.from(picked, tz.getLocation('Asia/Tokyo'))); + } + }, + ), + const SizedBox(height: 32), + if (mode == 'new') + ElevatedButton( + onPressed: () => controller.createEntry(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + minimumSize: const Size(double.infinity, 50), + ), + child: const Text('エントリーを作成'), + ) + else + Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () => controller.deleteEntry(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + minimumSize: const Size(0, 50), + ), + child: const Text('エントリーを削除'), + ), + ), + const SizedBox(width: 16), + Expanded( + child: ElevatedButton( + onPressed: () => controller.updateEntry(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.lightBlue, + foregroundColor: Colors.white, + minimumSize: const Size(0, 50), + ), + child: const Text('エントリーを更新'), + ), + ), + ], + ), + ], + ), + ), + ); + }), + ); + } + + Widget _buildDropdown({ + required String label, + required List items, + required int? selectedId, + required void Function(int?) onChanged, + required String Function(T) getDisplayName, + required int Function(T) getId, + }) { + return DropdownButtonFormField( + decoration: InputDecoration(labelText: label), + value: selectedId, + items: items.map((item) => DropdownMenuItem( + value: getId(item), + child: Text(getDisplayName(item)), + )).toList(), + onChanged: onChanged, + ); + } + + Widget _buildCategoryDropdown() { + final eligibleCategories = controller.categories.where((c) => + c.baseCategory == controller.selectedCategory.value?.baseCategory + ).toList(); + + return DropdownButtonFormField( + decoration: InputDecoration(labelText: 'カテゴリ'), + value: controller.selectedCategory.value, + items: eligibleCategories.map((category) => DropdownMenuItem( + value: category, + child: Text(category.categoryName), + )).toList(), + onChanged: (value) => controller.updateCategory(value), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/entry/entry_list_page.dart b/lib/pages/entry/entry_list_page.dart new file mode 100644 index 0000000..1a87226 --- /dev/null +++ b/lib/pages/entry/entry_list_page.dart @@ -0,0 +1,116 @@ +// lib/pages/entry/entry_list_page.dart + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:gifunavi/pages/entry/entry_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:timezone/timezone.dart' as tz; + +class EntryListPage extends GetView { + const EntryListPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('エントリー管理'), + actions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () => Get.toNamed(AppPages.ENTRY_DETAIL, arguments: {'mode': 'new'}), + ), + ], + ), + body: Obx(() { + if (controller.entries.isEmpty) { + return const Center( + child: Text('表示するエントリーがありません。'), + ); + } + return ListView.builder( + itemCount: controller.entries.length, + itemBuilder: (context, index) { + final entry = controller.entries[index]; + return ListTile( + title: Row( + children: [ + Expanded( + child: Text('${_formatDate(entry.date)}: ${entry.event.eventName}'), + ), + Text(entry.team.teamName, style: const TextStyle(fontWeight: FontWeight.bold)), + ], + ), + subtitle: Row( + children: [ + Expanded( + child: Text('カテゴリー: ${entry.category.categoryName}'), + ), + Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'), + ], + ), + onTap: () => + Get.toNamed(AppPages.ENTRY_DETAIL, + arguments: {'mode': 'edit', 'entry': entry}), + ); + }, + ); + }), + ); + } + + String _formatDate(DateTime? date) { + if (date == null) { + return '日時未設定'; + } + final jstDate = tz.TZDateTime.from(date, tz.getLocation('Asia/Tokyo')); + return DateFormat('yyyy-MM-dd').format(jstDate); + } +} + +class EntryListPage_old extends GetView { + const EntryListPage_old({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('エントリー管理'), + actions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () => Get.toNamed(AppPages.ENTRY_DETAIL, arguments: {'mode': 'new'}), + ), + ], + ), + body: Obx((){ + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + + // エントリーを日付昇順にソート + final sortedEntries = controller.entries.toList() + ..sort((a, b) => (a.date ?? DateTime(0)).compareTo(b.date ?? DateTime(0))); + + return ListView.builder( + itemCount: sortedEntries.length, + itemBuilder: (context, index) { + final entry = sortedEntries[index]; + return ListTile( + title: Text(entry.event.eventName ?? 'イベント未設定'), + subtitle: Text( + '${entry.team.teamName ?? 'チーム未設定'} - ${entry.category + .categoryName ?? 'カテゴリ未設定'}'), + trailing: Text( + entry.date?.toString().substring(0, 10) ?? '日付未設定'), + onTap: () => + Get.toNamed(AppPages.ENTRY_DETAIL, + arguments: {'mode': 'edit', 'entry': entry}), + ); + }, + ); + }), + ); + + } +} \ No newline at end of file diff --git a/lib/pages/entry/event_entries_binding.dart b/lib/pages/entry/event_entries_binding.dart new file mode 100644 index 0000000..e736b76 --- /dev/null +++ b/lib/pages/entry/event_entries_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:gifunavi/pages/entry/event_entries_controller.dart'; + +class EventEntriesBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => EventEntriesController()); + } +} diff --git a/lib/pages/entry/event_entries_controller.dart b/lib/pages/entry/event_entries_controller.dart new file mode 100644 index 0000000..60d7970 --- /dev/null +++ b/lib/pages/entry/event_entries_controller.dart @@ -0,0 +1,129 @@ +import 'package:get/get.dart'; +import 'package:gifunavi/model/entry.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/services/api_service.dart'; +import 'package:flutter/material.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:timezone/data/latest.dart' as tz; + +class EventEntriesController extends GetxController { + final ApiService _apiService = Get.find(); + final IndexController _indexController = Get.find(); + late final DestinationController _destinationController; + + final entries = [].obs; + final filteredEntries = [].obs; + final showTodayEntries = true.obs; + + static bool _timezoneInitialized = false; + + @override + void onInit() { + super.onInit(); + _initializeTimezone(); + // DestinationControllerが登録されていない場合に備えて、lazyPutを使用 + Get.lazyPut(() => DestinationController(), fenix: true); + _destinationController = Get.find(); + + fetchEntries(); + } + + void _initializeTimezone() { + if (!_timezoneInitialized) { + tz.initializeTimeZones(); + _timezoneInitialized = true; + } + } + + Future fetchEntries() async { + try { + final fetchedEntries = await _apiService.getEntries(); + entries.assignAll(fetchedEntries); + filterEntries(); + } catch (e) { + print('Error fetching entries: $e'); + // エラー処理を追加 + } + } + + void filterEntries() { + if (showTodayEntries.value) { + filterEntriesForToday(); + } else { + filteredEntries.assignAll(entries); + } + } + + void filterEntriesForToday() { + final now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo')); + filteredEntries.assignAll(entries.where((entry) { + final entryDate = tz.TZDateTime.from(entry.date!, tz.getLocation('Asia/Tokyo')); + return entryDate.year == now.year && + entryDate.month == now.month && + entryDate.day == now.day; + })); + } + + void filterEntriesForToday_old() { + final now = DateTime.now(); + filteredEntries.assignAll(entries.where((entry) => + entry.date?.year == now.year && + entry.date?.month == now.month && + entry.date?.day == now.day + )); + } + + void toggleShowTodayEntries() { + showTodayEntries.toggle(); + filterEntries(); + } + + void refreshMap() { + final tk = _indexController.currentUser[0]["token"]; + if (tk != null) { + + _destinationController.fixMapBound(tk); + } + } + + Future joinEvent(Entry entry) async { + //final now = DateTime.now(); + final now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo')); + final entryDate = tz.TZDateTime.from(entry.date!, tz.getLocation('Asia/Tokyo')); + bool isToday = entryDate.year == now.year && + entryDate.month == now.month && + entryDate.day == now.day; + + _indexController.setReferenceMode(!isToday); + _indexController.setSelectedEventName(entry.event.eventName); + + final userid = _indexController.currentUser[0]["user"]["id"]; + + await _apiService.updateUserInfo(userid,entry); + + _indexController.currentUser[0]["user"]["event_date"] = entryDate; // 追加2024-8-9 + _indexController.currentUser[0]["user"]["event_code"] = entry.event.eventName; + _indexController.currentUser[0]["user"]["team_name"] = entry.team.teamName; + _indexController.currentUser[0]["user"]["group"] = entry.team.category.categoryName; + _indexController.currentUser[0]["user"]["zekken_number"] = entry.zekkenNumber; + + Get.back(); // エントリー一覧ページを閉じる + //_indexController.isLoading.value = true; + _indexController.reloadMap(entry.event.eventName); + + refreshMap(); + + if (isToday) { + Get.snackbar('成功', 'イベントに参加しました。', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white); + } else { + Get.snackbar('参照モード', '過去または未来のイベントを参照しています。ロゲの開始やチェックインはできません。', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.orange, + colorText: Colors.white); + } + } +} \ No newline at end of file diff --git a/lib/pages/entry/event_entries_page.dart b/lib/pages/entry/event_entries_page.dart new file mode 100644 index 0000000..75da284 --- /dev/null +++ b/lib/pages/entry/event_entries_page.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:gifunavi/pages/entry/event_entries_controller.dart'; +import 'package:timezone/timezone.dart' as tz; + +class EventEntriesPage_old extends GetView { + const EventEntriesPage_old({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('イベント参加')), + body: Obx(() => ListView.builder( + itemCount: controller.entries.length, + itemBuilder: (context, index) { + final entry = controller.entries[index]; + return ListTile( + title: Text(entry.event.eventName), + subtitle: Text('${entry.category.categoryName} - ${entry.date}'), + onTap: () => controller.joinEvent(entry), + ); + }, + )), + ); + } +} + +class EventEntriesPage extends GetView { + const EventEntriesPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Obx(() => Text(controller.showTodayEntries.value ? 'イベント参加' : 'イベント参照')), + ), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Obx(() => Text( + controller.showTodayEntries.value ? '本日のエントリー' : 'すべてのエントリー', + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + )), + Obx(() => Switch( + value: !controller.showTodayEntries.value, + onChanged: (value) { + controller.toggleShowTodayEntries(); + }, + activeColor: Colors.blue, + )), + ], + ), + ), + Expanded( + child: Obx(() { + if (controller.filteredEntries.isEmpty) { + return const Center( + child: Text('表示するエントリーがありません。'), + ); + } + return ListView.builder( + itemCount: controller.filteredEntries.length, + itemBuilder: (context, index) { + final entry = controller.filteredEntries[index]; + return ListTile( + title: Row( + children: [ + Expanded( + child: Text('${_formatDate(entry.date)}: ${entry.event.eventName}'), + ), + Text(entry.team.teamName, style: const TextStyle(fontWeight: FontWeight.bold)), + ], + ), + subtitle: Row( + children: [ + Expanded( + child: Text('カテゴリー: ${entry.category.categoryName}'), + ), + Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'), + ], + ), + onTap: () async { + await controller.joinEvent(entry); + }, + ); + }, + ); + }), + ), + ], + ), + ); + } + + String _formatDate(DateTime? date) { + if (date == null) { + return '日時未設定'; + } + final jstDate = tz.TZDateTime.from(date, tz.getLocation('Asia/Tokyo')); + return DateFormat('yyyy-MM-dd').format(jstDate); + } +} + diff --git a/lib/pages/gps/gps_page.dart b/lib/pages/gps/gps_page.dart new file mode 100644 index 0000000..897215e --- /dev/null +++ b/lib/pages/gps/gps_page.dart @@ -0,0 +1,159 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:get/get.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:gifunavi/model/gps_data.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/utils/database_gps.dart'; +import 'package:gifunavi/widgets/base_layer_widget.dart'; + +class GpsPage extends StatefulWidget { + const GpsPage({super.key}); + + @override + State createState() => _GpsPageState(); +} + +class _GpsPageState extends State { + var gpsData = [].obs; + MapController? mapController; + StreamSubscription? subscription; + final IndexController indexController = Get.find(); + final DestinationController destinationController = + Get.find(); + + @override + void initState() { + super.initState(); + loadGpsData(); + } + + // 要検討:GPSデータの読み込みに失敗した場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。 + // + void loadGpsData() async { + final teamName = indexController.currentUser[0]["user"]['team_name']; + final eventCode = indexController.currentUser[0]["user"]["event_code"]; + GpsDatabaseHelper db = GpsDatabaseHelper.instance; + var data = await db.getGPSData(teamName, eventCode); + gpsData.value = data; + + //print("--- gps data ${data} ----"); + } + + // 要検討:マーカーの形状を決定する際に、マジックナンバーが使用されています。定数を使用するなどして、コードの可読性を向上させることを検討してください。 + // + Widget getMarkerShape(GpsData i) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + InkWell( + onTap: () {}, + child: Container( + height: 22, + width: 22, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.transparent, + border: Border.all( + color: + i.is_checkin == 0 ? Colors.blueAccent : Colors.green, + width: i.is_checkin == 0 ? 0.4 : 2, + style: BorderStyle.solid)), + child: const Stack( + alignment: Alignment.center, + children: [ + Icon( + Icons.circle, + size: 6.0, + ), + ], + )), + ), + /* + Container( + color: Colors.transparent, + child: i.is_checkin == 1 + ? Text( + DateTime.fromMicrosecondsSinceEpoch(i.created_at) + .hour + .toString() + + ":" + + DateTime.fromMicrosecondsSinceEpoch(i.created_at) + .minute + .toString(), + // ":" + + // DateTime.fromMicrosecondsSinceEpoch(i.created_at) + // .second + // .toString(), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + )) + : Container()), + + */ + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("movement_history".tr), + ), + body: Container( + child: Obx( + () => FlutterMap( + mapController: mapController, + options: MapOptions( + maxZoom: 18.4, + onMapReady: () {}, + //center: LatLng(37.15319600454702, 139.58765950528198), + bounds: indexController.currentBound.isNotEmpty + ? indexController.currentBound[0] + : LatLngBounds.fromPoints([ + const LatLng(35.03999881162295, 136.40587119778962), + const LatLng(36.642756778706904, 137.95226720406063) + ]), + zoom: 1, + interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag, + onPositionChanged: (MapPosition pos, bool hasGesture) { + if (hasGesture) { + indexController.currentBound = [pos.bounds!]; + } + }, + onTap: (tapPos, cord) {}, // Hide popup when the map is tapped. + ), + children: [ + const BaseLayer(), + MarkerLayer( + markers: gpsData.map((i) { + return Marker( + width: 30.0, // Fixed width + height: 30.0, // Fixed height + point: LatLng(i.lat, i.lon), + child: getMarkerShape(i), + alignment: Alignment.center); + }).toList(), + ), + // MarkerLayer( + // markers: gpsData.map((i) { + // return Marker( + // alignment: Alignment.center, + // height: 32.0, + // width: 120.0, + // point: LatLng(i.lat, i.lon), + // child: getMarkerShape(i)); + // }).toList(), + // ) + ], + ), + )), + ); + } +} diff --git a/lib/pages/history/history_page.dart b/lib/pages/history/history_page.dart index 11e6781..acc462c 100644 --- a/lib/pages/history/history_page.dart +++ b/lib/pages/history/history_page.dart @@ -1,8 +1,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:rogapp/model/destination.dart'; -import 'package:rogapp/utils/database_helper.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/utils/database_helper.dart'; +import 'package:get/get.dart'; class HistoryPage extends StatefulWidget { const HistoryPage({super.key}); @@ -18,12 +19,14 @@ class _HistoryPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text("History"), + title: Text("pass_history".tr), ), body: SingleChildScrollView( child: Column( children: [ FutureBuilder( + // 要検討:スナップショットのエラーハンドリングが行われていますが、具体的なエラーメッセージを表示するようにすることをお勧めします。 + // future: db.getDestinations(), builder: (BuildContext context, AsyncSnapshot> snapshot) { @@ -32,28 +35,48 @@ class _HistoryPageState extends State { return Center( child: Text( '${snapshot.error} occurred', - style: TextStyle(fontSize: 18), + style: const TextStyle(fontSize: 18), ), ); } else if (snapshot.hasData) { final dests = snapshot.data; - if (dests!.length > 0) { - return Center( - child: ListView.builder(itemBuilder:(ctx, index){ - return ListTile( - title: Text(dests[index].name?? ""), - subtitle: Text(dests[index].address ?? ""), - leading: dests[0].photos != null ? Image.file(File(dests[0].photos!)) : Container(), - ); - }), - ); + if (dests!.isNotEmpty) { + debugPrint("----- 通過履歴表示 -----"); + return SizedBox( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + child: ListView.builder( + itemCount: dests.length, + itemBuilder: (ctx, index) { + //print("--- photo ${dests[index].checkin_image!} ----"); + return Padding( + padding: const EdgeInsets.all(8.0), + child: CustomWidget( + // 要検討:画像のサイズがハードコードされています。画像のサイズを動的に設定できるようにすることを検討してください。 + title: dests[index].name!, + subtitle: + "${dests[index].sub_loc_id} : ${dests[index].name}", + image1: dests[index].checkin_image != null + ? Image.file( + File(dests[index].checkin_image!)) + : null, + image2: + dests[index].buypoint_image != null + ? Image.file(File( + dests[index].buypoint_image!)) + : null, + ), + ); + })); } else { - return Center(child: Text("No checkin yet")); + return Center(child: Text("no_checkin_yet".tr)); } } - } - else if(snapshot.connectionState == ConnectionState.waiting){ - return Center(child: CircularProgressIndicator(),); + } else if (snapshot.connectionState == + ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator(), + ); } return Container(); }), @@ -63,3 +86,69 @@ class _HistoryPageState extends State { ); } } + +class CustomWidget extends StatelessWidget { + final Image? image1; + final Image? image2; + final String title; + final String subtitle; + + const CustomWidget({ + super.key, + this.image1, + this.image2, + required this.title, + required this.subtitle, + }); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: + 104, // 50 (width of each image) + 2 (space between images) + 2*1 (padding on both sides) + child: Row( + children: [ + if (image1 != null) + SizedBox( + width: 50, + height: 100, + child: image1, + ), + if (image1 != null && image2 != null) const SizedBox(width: 2), + if (image2 != null) + SizedBox( + width: 50, + height: 100, + child: image2, + ), + ], + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: + const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + maxLines: + null, // Allows the text to wrap onto an unlimited number of lines + ), + Text( + subtitle, + style: const TextStyle(fontSize: 16), + maxLines: + null, // Allows the text to wrap onto an unlimited number of lines + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages/home/home_binding.dart b/lib/pages/home/home_binding.dart index 9e044a2..f32609e 100644 --- a/lib/pages/home/home_binding.dart +++ b/lib/pages/home/home_binding.dart @@ -1,7 +1,7 @@ import 'package:get/get.dart'; -import 'package:rogapp/pages/home/home_controller.dart'; +import 'package:gifunavi/pages/home/home_controller.dart'; class HomeBinding extends Bindings{ @override diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index 779b2ee..a3e2e10 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -1,44 +1,82 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/search/search_page.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:gifunavi/routes/app_pages.dart'; -class HomePage extends GetView{ - const HomePage({Key? key}) : super(key: key); +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + bool _isLocationServiceEnabled = true; + + @override + void initState() { + super.initState(); + /* + WidgetsBinding.instance.addPostFrameCallback((_) { + _checkLocationService(); // 非同期的に呼び出す + }); + */ + _checkLocationService(); + } + + Future _checkLocationService() async { + final serviceEnabled = await Permission.location.serviceStatus.isEnabled; + setState(() { + _isLocationServiceEnabled = serviceEnabled; + }); + } + + void _showLocationDisabledDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('location_disabled_title'.tr), + content: Text('location_disabled_message'.tr), + actions: [ + TextButton( + child: Text('ok'.tr), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: Text('open_settings'.tr), + onPressed: () { + Navigator.of(context).pop(); + openAppSettings(); + }, + ), + ], + ); + }, + ); + } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - elevation: 0, - backgroundColor: Colors.white, - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("app_title".tr, - style: const TextStyle( - color: Colors.blue - ), - ), - InkWell( - onTap: (){ - Navigator.push(context, MaterialPageRoute(builder: (context) => SearchPage())); - }, - child: Container( - height: 32, - width: 75, - decoration: BoxDecoration( - color: Colors.blue, - borderRadius: BorderRadius.circular(25), - - ), - child: const Center(child: Icon(Icons.search),), - ), - ), - ], - ), + title: Text('home'.tr), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('welcome'.tr), + const SizedBox(height: 20), + ElevatedButton( + onPressed: _isLocationServiceEnabled + ? () => Get.offNamed(AppPages.INDEX) + : () => _showLocationDisabledDialog(), + child: Text('start_app'.tr), + ), + ], ), - body: Container(), + ), ); } - } \ No newline at end of file diff --git a/lib/pages/index/index_binding.dart b/lib/pages/index/index_binding.dart index 298fc61..3d4d69c 100644 --- a/lib/pages/index/index_binding.dart +++ b/lib/pages/index/index_binding.dart @@ -1,17 +1,14 @@ - import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/utils/location_controller.dart'; class IndexBinding extends Bindings { - - IndexBinding(this.token); - - String? token; - @override void dependencies() { - final IndexController indexController = IndexController(); - indexController.userToken = token; - Get.put(indexController); + Get.lazyPut(() => IndexController()); + //Get.put(IndexController()); + Get.put(LocationController()); + Get.put(DestinationController()); } } diff --git a/lib/pages/index/index_controller.dart b/lib/pages/index/index_controller.dart index e19b84f..aa43b9b 100644 --- a/lib/pages/index/index_controller.dart +++ b/lib/pages/index/index_controller.dart @@ -4,27 +4,36 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/plugin_api.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; -import 'package:geojson/geojson.dart'; +import 'package:geojson_vi/geojson_vi.dart'; +import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:latlong2/latlong.dart'; -import 'package:rogapp/model/destination.dart'; -import 'package:rogapp/pages/destination/destination_binding.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; -import 'package:rogapp/pages/destination/destination_page.dart'; -import 'package:rogapp/routes/app_pages.dart'; -import 'package:rogapp/services/action_service.dart'; -import 'package:rogapp/services/auth_service.dart'; -import 'package:rogapp/services/cat_service.dart'; -import 'package:rogapp/services/location_service.dart'; -import 'package:rogapp/services/perfecture_service.dart'; -import 'package:rogapp/utils/database_helper.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/model/entry.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/team/team_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/services/auth_service.dart'; +import 'package:gifunavi/services/location_service.dart'; +import 'package:gifunavi/utils/database_helper.dart'; +import 'package:gifunavi/widgets/debug_widget.dart'; import 'package:shared_preferences/shared_preferences.dart'; -class IndexController extends GetxController { - List locations = [].obs; - List currentFeature = [].obs; +import 'package:gifunavi/services/api_service.dart'; +import 'package:gifunavi/model/user.dart'; + + + +import 'package:gifunavi/main.dart'; + +import 'package:gifunavi/widgets/helper_dialog.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:timezone/data/latest.dart' as tz; + +class IndexController extends GetxController with WidgetsBindingObserver { + List locations = [].obs; + List currentFeature = [].obs; List currentDestinationFeature = [].obs; List perfectures = [].obs; List currentBound = [].obs; @@ -35,29 +44,40 @@ class IndexController extends GetxController { List currentCat = [].obs; - String? userToken; - List> currentUser = >[].obs; List currentAction = [].obs; List routePoints = [].obs; var routePointLenght = 0.obs; - var is_loading = false.obs; + double currentLat = 0.0, currentLon = 0.0; - var is_mapController_loaded = false.obs; - var is_rog_mapcontroller_loaded = false.obs; + var isLoading = false.obs; - var is_custom_area_selected = false.obs; + var isRogMapcontrollerLoaded = false.obs; + + var isCustomAreaSelected = false.obs; + + RxBool isMapControllerReady = RxBool(false); // MapControllerの初期化状態を管理するフラグ + //final mapControllerReadyStream = Stream.value(false); // MapControllerの初期化状態を通知するためのストリーム MapController mapController = MapController(); MapController rogMapController = MapController(); + LogManager logManager = LogManager(); + + String? userToken; + + //late final ApiService _apiService; + final ApiService _apiService = Get.find(); + final DatabaseHelper _dbHelper = DatabaseHelper.instance; + + // mode = 0 is map mode, mode = 1 list mode var mode = 0.obs; // master mode, rog or selection - var rog_mode = 1.obs; + var rogMode = 1.obs; - var desination_mode = 1.obs; + var desinationMode = 1.obs; bool showPopup = true; @@ -66,12 +86,43 @@ class IndexController extends GetxController { String areaDropdownValue = "-1"; String cateogory = "-all-"; - late Worker _ever; + final selectedEventName = 'add_location'.tr.obs; - ConnectivityResult connectionStatus = ConnectivityResult.none; + void setSelectedEventName(String eventName) { + selectedEventName.value = eventName; + } + + final connectionStatus = Rx(ConnectivityResult.none); var connectionStatusName = "".obs; final Connectivity _connectivity = Connectivity(); - late StreamSubscription _connectivitySubscription; + late StreamSubscription> _connectivitySubscription; + + final Rx lastUserUpdateTime = DateTime.now().obs; + + RxInt teamId = RxInt(-1); // チームIDを保存するための変数 + + //late TeamController teamController = TeamController(); + /* + void updateUserInfo(Map newUserInfo) { + currentUser.clear(); + currentUser.add(newUserInfo); + lastUserUpdateTime.value = DateTime.now(); + } + */ + + final isReferenceMode = false.obs; + + void setReferenceMode(bool value) { + isReferenceMode.value = value; + } + + bool canStartRoge() { + return !isReferenceMode.value; + } + + bool canCheckin() { + return !isReferenceMode.value; + } void toggleMode() { if (mode.value == 0) { @@ -82,87 +133,285 @@ class IndexController extends GetxController { } void toggleDestinationMode() { - if (desination_mode.value == 0) { - desination_mode.value += 1; + if (desinationMode.value == 0) { + desinationMode.value += 1; } else { - desination_mode.value -= 1; + desinationMode.value -= 1; } } void switchPage(String page) { - //print("######## ${currentUser[0]["user"]["id"]}"); + ////print("######## ${currentUser[0]["user"]["id"]}"); switch (page) { - case AppPages.INITIAL: + case AppPages.INDEX: { - rog_mode.value = 0; - print("-- rog mode is ctrl is ${rog_mode.value}"); + rogMode.value = 0; + //print("-- rog mode is ctrl is ${rog_mode.value}"); Get.toNamed(page); } break; case AppPages.TRAVEL: { - rog_mode.value = 1; + rogMode.value = 1; //Get.back(); - Get.off(DestnationPage(), binding: DestinationBinding()); + //Get.off(DestnationPage(), binding: DestinationBinding()); } break; case AppPages.LOGIN: { - rog_mode.value = 2; + rogMode.value = 2; Get.toNamed(page); } break; default: { - rog_mode.value = 0; - Get.toNamed(AppPages.INITIAL); + rogMode.value = 1; + Get.toNamed(AppPages.INDEX); } } } + Future _checkLocationPermission() async { + if (Get.context == null) { + debugPrint('Get.context is null in _checkLocationPermission'); + return; + } + LocationPermission permission = await Geolocator.checkPermission(); + //permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + debugPrint('GPS : Denied'); + await showLocationPermissionDeniedDialog(); + } else if (permission == LocationPermission.deniedForever) { + debugPrint('GPS : Denied forever'); + await showLocationPermissionDeniedDialog(); + }else if (permission == LocationPermission.whileInUse){ + debugPrint('GPS : While-In-Use'); + await showLocationPermissionDeniedDialog(); + + }else{ + debugPrint("Permission is no problem...."); + } + } + + + // 追加 + Future showLocationPermissionDeniedDialog() async { + if (Get.context != null) { + print('Showing location permission denied dialog'); + await showDialog( + context: Get.context!, + barrierDismissible: false, + builder: (BuildContext context) { + return WillPopScope( + onWillPop: () async => false, + child: AlertDialog( + title: Text('location_permission_needed_title'.tr), + content: Text('location_permission_needed_main'.tr), + actions: [ + TextButton( + onPressed: () { + logManager.addOperationLog("User tapped confirm button for location permission required."); + Navigator.of(context).pop(); + }, + child: Text('confirm'.tr), + ), + ], + ), + ); + }, + ); + } else { + print('Get.context is null in showLocationPermissionDeniedDialog'); + // Get.contextがnullの場合の処理 + print('Location permission denied, but context is null'); + } + } + + @override void onInit() { - _ever = ever(rog_mode, (_) => print("$_ has been changed (ever)")); + try { + super.onInit(); + initConnectivity(); + _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus); - if (perfectures.isEmpty) { - PerfectureService.loadPerfectures().then((value) { - perfectures.add(value); - loadAreaFor("9"); + WidgetsBinding.instance.addObserver(this); + _startLocationService(); // アプリ起動時にLocationServiceを開始する - //loadSubPerfFor("9"); - }); + initializeApiService(); + + print('IndexController onInit called'); // デバッグ用の出力を追加 + + tz.initializeTimeZones(); + //teamController = Get.find(); + }catch(e,stacktrace){ + print('Error in IndexController.onInit: $e'); + print('Stack trace: $stacktrace'); + // エラーレポートサービスにエラーを送信 + //ErrorService.reportError(e, stackTrace, deviceInfo, LogManager().operationLogs); + + } + } + + void _updateConnectionStatus(List results) { + + final result = results.isNotEmpty ? results.first : ConnectivityResult.none; + connectionStatus.value = result; + + switch (result) { + case ConnectivityResult.wifi: + connectionStatusName.value = "WiFi"; + break; + case ConnectivityResult.mobile: + connectionStatusName.value = "モバイルデータ"; + break; + case ConnectivityResult.none: + connectionStatusName.value = "オフライン"; + break; + default: + connectionStatusName.value = "不明"; + break; } - _connectivitySubscription = - _connectivity.onConnectivityChanged.listen(_updateConnectionStatus); + logManager.addOperationLog("Connection status changed to: ${connectionStatusName.value}"); - super.onInit(); + if (result != ConnectivityResult.none) { + _handleConnectionRestored(); + } } + Future _handleConnectionRestored() async { + await _syncPendingData(); + } + + Future initConnectivity() async { + try { + final result = await _connectivity.checkConnectivity(); + _updateConnectionStatus(result); // リストとして渡す + } on PlatformException catch (e) { + print('Couldn\'t check connectivity status: ${e.message}'); + } + } + +// 保留中のデータを同期する例のメソッド + Future _syncPendingData() async { + // ここに保留中のデータを同期するロジックを実装 + // 例: ローカルDBに保存された未送信のデータをサーバーに送信する + try { + // 同期処理 + logManager.addOperationLog("Syncing pending data"); + // 同期ロジックをここに実装 + } catch (e) { + logManager.addOperationLog("Error syncing pending data: $e"); + // エラーハンドリング + } + } + + Future initializeApiService() async { + if (currentUser.isNotEmpty) { + // 既にログインしている場合 + await Get.putAsync(() => ApiService().init()); + //await Get.putAsync(() => ApiService().init()); + // 必要に応じて追加の初期化処理 + } + } + +/* + void checkPermission() + { + debugPrint("MapControllerの初期化が完了したら、位置情報の許可をチェックする"); + _checkLocationPermission(); + } +*/ + @override void onClose() { _connectivitySubscription.cancel(); + WidgetsBinding.instance.removeObserver(this); + _stopLocationService(); // アプリ終了時にLocationServiceを停止する super.onClose(); } - Future _updateConnectionStatus(ConnectivityResult result) async { - connectionStatus = result; - connectionStatusName.value = result.name; + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + + if (state == AppLifecycleState.resumed) { + if (!_isLocationServiceRunning()) { + _startLocationService(); + } + } else if (state == AppLifecycleState.paused) { + _stopLocationService(); + } } + bool _isLocationServiceRunning() { + // LocationServiceが実行中かどうかを確認する処理を実装する + // 例えば、SharedPreferencesにサービスの状態を保存するなど + // ここでは簡単のために、常にfalseを返すようにしています + return false; + } + + void _startLocationService() async { + const platform = MethodChannel('location'); + try { + logManager.addOperationLog("Called start location service."); + await platform.invokeMethod('startLocationService'); + } on PlatformException catch (e) { + print("Failed to start location service: '${e.message}'."); + } + } + + void _stopLocationService() async { + const platform = MethodChannel('location'); + try { + logManager.addOperationLog("Called stop location service."); + await platform.invokeMethod('stopLocationService'); + } on PlatformException catch (e) { + print("Failed to stop location service: '${e.message}'."); + } + } + + /* + @override + void onReady() async { + await readUserToken(); + final token = userToken; + if (token != null && token.isNotEmpty) { + await loadUserDetailsForToken(token); + fixMapBound(token); + } else { + // ユーザートークンが存在しない場合はログイン画面にリダイレクト + Get.offAllNamed(AppPages.LOGIN); + } + + // 地図のイベントリスナーを設定 + indexController.mapController.mapEventStream.listen((MapEvent mapEvent) { + if (mapEvent is MapEventMoveEnd) { + indexController.loadLocationsBound(); + } + }); + + super.onReady(); + } + */ + + /* Future initConnectivity() async { late ConnectivityResult result; // Platform messages may fail, so we use a try/catch PlatformException. try { - result = await _connectivity.checkConnectivity(); - } on PlatformException catch (e) { - print('Couldn\'t check connectivity status --- $e'); + connectionStatus = (await _connectivity.checkConnectivity()) as ConnectivityResult; + await _updateConnectionStatus(connectionStatus); + } on PlatformException catch (_) { + //print('Couldn\'t check connectivity status --- $e'); return; } - return _updateConnectionStatus(result); + //return _updateConnectionStatus(result); } + */ + LatLngBounds boundsFromLatLngList(List list) { double? x0, x1, y0, y1; for (LatLng latLng in list) { @@ -177,345 +426,305 @@ class IndexController extends GetxController { } } - return LatLngBounds(LatLng(x1!, y1!), LatLng(x0!, y0!)); + logManager.addOperationLog("Called boundsFromLatLngList (${x1!},${y1!})-(${x0!},${y0!})."); + + return LatLngBounds(LatLng(x1, y1), LatLng(x0, y0)); } - List getLocationsList() { - List locs = []; - for (int i = 0; i <= locations[0].collection.length - 1; i++) { - GeoJsonMultiPoint p = - locations[0].collection[i].geometry as GeoJsonMultiPoint; + // 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。 + // + void login(String email, String password, BuildContext context) async{ - LatLng latLng = LatLng(p.geoSerie!.geoPoints[0].latitude, - p.geoSerie!.geoPoints[0].longitude); - locs.add(latLng); - } - return locs; - } - - void saveToDevice(String val) async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - await prefs.setString("user_token", val); - } - - void changeUser(Map value, {bool replace = true}) { - print("---- change user to ${value} -----"); - currentUser.clear(); - currentUser.add(value); - if(replace){ - saveToDevice(currentUser[0]["token"]); - } - is_loading.value = false; - loadUserDetails(); - loadLocationsBound(); - cats.clear(); - if (currentFeature.isNotEmpty) { - getAction(); - } - - if (currentUser.isNotEmpty) { - rog_mode.value = 0; - } else { - rog_mode.value = 1; - } - - if (rog_mode.value == 1) { - switchPage(AppPages.TRAVEL); - } else { - switchPage(AppPages.INITIAL); - } - //Get.toNamed(AppPages.INITIAL); - } - - void login(String email, String password, BuildContext context) { - AuthService.login(email, password).then((value) { + AuthService.login(email, password).then((value) async { print("------- logged in user details ######## $value ###### --------"); if (value.isNotEmpty) { - Navigator.pop(context); + logManager.addOperationLog("User logged in : $value."); + + // Navigator.pop(context); print("--------- user details login ----- $value"); changeUser(value); + + // ログイン成功後、api_serviceを初期化 + await Get.putAsync(() => ApiService().init()); + + // ユーザー情報の完全性をチェック + if (await checkUserInfoComplete()) { + Get.offAllNamed(AppPages.INDEX); + } else { + Get.offAllNamed(AppPages.USER_DETAILS_EDIT); + } + } else { - is_loading.value = false; + logManager.addOperationLog("User failed login : $email , $password."); + isLoading.value = false; Get.snackbar( - "Failed", - "User login failed, please try again.", - icon: const Icon(Icons.error, size: 40.0, color: Colors.blue), + "login_failed".tr, + "check_login_id_or_password".tr, + backgroundColor: Colors.red, + colorText: Colors.white, + icon: const Icon(Icons.error, size: 40.0, color: Colors.blue), snackPosition: SnackPosition.TOP, - duration: const Duration(milliseconds: 800), - backgroundColor: Colors.yellow, + duration: const Duration(seconds: 3), + //backgroundColor: Colors.yellow, //icon:Image(image:AssetImage("assets/images/dora.png")) ); } }); } + Future checkUserInfoComplete() async { + final user = await ApiService.to.getCurrentUser(); + return user.firstname.isNotEmpty && + user.lastname.isNotEmpty && + user.dateOfBirth != null; + } + + // 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。 + // void changePassword( String oldpassword, String newpassword, BuildContext context) { String token = currentUser[0]['token']; - //print("------- change password ######## ${currentUser[0]['token']} ###### --------"); + ////print("------- change password ######## ${currentUser[0]['token']} ###### --------"); AuthService.changePassword(oldpassword, newpassword, token).then((value) { - print("------- change password ######## $value ###### --------"); + ////print("------- change password ######## $value ###### --------"); if (value.isNotEmpty) { - is_loading.value = false; + logManager.addOperationLog("User successed to change password : $oldpassword , $newpassword."); + isLoading.value = false; Navigator.pop(context); - if (rog_mode.value == 1) { + if (rogMode.value == 1) { switchPage(AppPages.TRAVEL); } else { - switchPage(AppPages.INITIAL); + switchPage(AppPages.INDEX); } } else { + logManager.addOperationLog("User failed to change password : $oldpassword , $newpassword."); Get.snackbar( 'failed'.tr, 'password_change_failed_please_try_again'.tr, - icon: const Icon(Icons.error, size: 40.0, color: Colors.blue), + backgroundColor: Colors.red, + colorText: Colors.white, + icon: const Icon(Icons.error, size: 40.0, color: Colors.blue), snackPosition: SnackPosition.TOP, duration: const Duration(milliseconds: 800), - backgroundColor: Colors.yellow, + //backgroundColor: Colors.yellow, //icon:Image(image:AssetImage("assets/images/dora.png")) ); } }); - is_loading.value = false; + isLoading.value = false; } + /* void logout() async { locations.clear(); DatabaseHelper db = DatabaseHelper.instance; db.deleteAllDestinations().then((value) { DestinationController destinationController = Get.find(); - destinationController.PopulateDestinations(); + destinationController.populateDestinations(); }); currentUser.clear(); cats.clear(); + + // ユーザートークンをデバイスから削除 + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.remove("user_token"); + } + */ + + void logout() async { + logManager.addOperationLog("User logout : $currentUser ."); + saveGameState(); + locations.clear(); + DatabaseHelper db = DatabaseHelper.instance; + db.deleteAllDestinations().then((value) { + DestinationController destinationController = + Get.find(); + destinationController.populateDestinations(); + }); + currentUser.clear(); + cats.clear(); + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.remove("user_token"); } - void register(String email, String password, BuildContext context) { - AuthService.register(email, password).then((value) { - if (value.isNotEmpty) { - currentUser.clear(); - currentUser.add(value); - is_loading.value = false; - Navigator.pop(context); - loadUserDetails(); - Get.toNamed(AppPages.INITIAL); - } else { - is_loading.value = false; + // 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。 + // + void register(String email, String password, String password2, BuildContext context) { + AuthService.register(email, password,password2).then((value) { + if (value.containsKey("error")) { + String errMessage = value["error"]; + debugPrint("ユーザー登録失敗:$email, $password,$errMessage"); + logManager.addOperationLog("User failed to register new account : $email , $password ,$errMessage."); + isLoading.value = false; Get.snackbar( - 'failed'.tr, - 'user_registration_failed_please_try_again'.tr, + 'user_registration_failed_please_try_again'.tr, // ユーザー登録に失敗しました。 + errMessage, + backgroundColor: Colors.red, + colorText: Colors.white, icon: const Icon(Icons.error, size: 40.0, color: Colors.blue), snackPosition: SnackPosition.TOP, - duration: const Duration(milliseconds: 800), - backgroundColor: Colors.yellow, + duration: const Duration(seconds: 3), + //backgroundColor: Colors.yellow, //icon:Image(image:AssetImage("assets/images/dora.png")) ); + }else{ + debugPrint("ユーザー登録成功:$email, $password"); + logManager.addOperationLog("User tried to register new account : $email , $password ."); + + currentUser.clear(); + //currentUser.add(value); + isLoading.value = false; + + // ユーザー登録成功メッセージを表示 + Get.snackbar( + 'success'.tr, + 'user_registration_successful'.tr, + backgroundColor: Colors.green, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + ); + + //Navigator.pop(context); + Get.toNamed(AppPages.LOGIN); } }); } - void makeAction(BuildContext context) { - int userId = currentUser[0]["user"]["id"] as int; - int locationId = currentFeature[0].properties!["location_id"] as int; - bool wanttogo = currentAction[0][0]["wanttogo"]; - bool like = currentAction[0][0]["like"]; - bool checkin = currentAction[0][0]["checkin"]; - if (userId > 0) { - ActionService.makeAction(userId, locationId, wanttogo, like, checkin) - .then((value) {}); + void saveToDevice(String val) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setString("user_token", val); + print("saveToDevice: $val"); + } + + /* + void changeUser(Map value, {bool replace = true}) { + print("---- change user to $value -----"); + currentUser.clear(); + currentUser.add(value); + if (replace) { + saveToDevice(currentUser[0]["token"]); } - } - - String getCatText() { - String cat = 'all'.tr; - if (currentUser.isNotEmpty) { - Map urs = currentUser[0]; - print('-- is_rogaining :-- ${urs['user']['is_rogaining']} ------'); - if (urs['user']['is_rogaining'] == true) { - cat = 'rogaining'.tr; - } else { - cat = 'sight_seeing'.tr; - } - } - return cat; - } - - void loadCatsv2() { - dynamic initVal = {'category': getCatText()}; - LatLngBounds bounds = mapController.bounds!; - CatService.loadCats( - bounds.southWest.latitude, - bounds.southWest.longitude, - bounds.northWest.latitude, - bounds.northWest.longitude, - bounds.northEast.latitude, - bounds.northEast.longitude, - bounds.southEast.latitude, - bounds.southEast.longitude) - .then((value) { - cats.clear(); - cats.add(initVal); - for (dynamic cat in value!) { - if (cat['category'] != null) { - cats.add(cat!); - } - } - }); - } - - void loadCatForCity(String city) { - dynamic initVal = {'category': getCatText()}; - LatLngBounds bounds = mapController.bounds!; - CatService.loadCatByCity(city).then((value) { - cats.clear(); - cats.add(initVal); - for (dynamic cat in value!) { - if (cat['category'] != null) { - cats.add(cat!); - } - } - }); - } - - void refreshLocationForCat() { + isLoading.value = false; loadLocationsBound(); - // if(subDropdownValue == "-1"){ - // LocationService.loadLocationsFor(dropdownValue, currentCat[0]).then((value){ - // locations.clear(); - // locations.add(value!); - // is_loading.value = false; - // }); - // print("loading main------"); - // } - // else{ - // LocationService.loadLocationsSubFor(subDropdownValue, currentCat[0]).then((value){ - // locations.clear(); - // locations.add(value!); - // is_loading.value = false; - // }); - // print("loading sub------"); - // } - } - - void loadAreaFor(String perf) { - areas.clear(); - dynamic initVal = {'id': '-1', 'area_nm': '----'}; - PerfectureService.loadGifuAreas(perf).then((value) { - value!.add(initVal); - areas.add(value); - }); - } - - void loadUserDetails() { if (currentUser.isNotEmpty) { - int userId = currentUser[0]["user"]["id"] as int; - AuthService.UserDetails(userId).then((value) { - print("--------- user details ----- $value"); - // if (value != null && value.isNotEmpty) { - // bool paid = value[0]["paid"] as bool; - // if (paid) { - // loadCustomAreas(); - // } - // } - }); + rogMode.value = 0; + } else { + rogMode.value = 1; + } + print('--- c rog mode --- ${rogMode.value}'); + Get.toNamed(AppPages.INDEX); + } + */ + + void changeUser(Map value, {bool replace = true}) async{ + currentUser.clear(); + currentUser.add(value); + if (replace) { + saveToDevice(currentUser[0]["token"]); + } + isLoading.value = false; + + // ユーザーのイベント情報を取得 + await fetchUserEventInfo(); + + loadLocationsBound( currentUser[0]["user"]["event_code"]); + if (currentUser.isNotEmpty) { + rogMode.value = 0; + restoreGame(); + + // チームデータを取得 + await fetchTeamData(); + } else { + rogMode.value = 1; + } + Get.toNamed(AppPages.INDEX); + } + + Future fetchUserEventInfo() async { + try { + final List entries = await _apiService.getEntries(); + + if (entries.isNotEmpty) { + final Entry latestEntry = entries.last; + + final tokyo = tz.getLocation('Asia/Tokyo'); + final eventDate = latestEntry.date!.toUtc(); + //final eventDate = tz.TZDateTime.from(utcDate, tokyo); + + final eventDateOnly = tz.TZDateTime(tokyo, eventDate.year, eventDate.month, eventDate.day); + + currentUser[0]['user']['event_date'] = eventDateOnly.toIso8601String().split('T')[0]; + currentUser[0]['user']['event_code'] = latestEntry.event.eventName; + currentUser[0]['user']['team_name'] = latestEntry.team.teamName; + currentUser[0]['user']['group'] = latestEntry.team.category.categoryName; + currentUser[0]['user']['zekken_number'] = latestEntry.zekkenNumber; + + // 最後のゴール日時を取得 + final lastGoalTime = await getLastGoalTime(); + currentUser[0]['user']['last_goal_time'] = lastGoalTime?.toIso8601String(); + + print('Updated user event info: ${currentUser[0]['user']}'); + } else { + print('No entries found for the user'); + _clearUserEventInfo(); + } + } catch (e) { + print('Error fetching user event info: $e'); + _clearUserEventInfo(); } } + Future getLastGoalTime() async { + try { + final userId = currentUser[0]['user']['id']; + return await _apiService.getLastGoalTime(userId); + } catch (e) { + print('Error getting last goal time: $e'); + } + return null; + } + + void _clearUserEventInfo() { + currentUser[0]['user']['event_date'] = null; + currentUser[0]['user']['event_code'] = null; + currentUser[0]['user']['team_name'] = null; + currentUser[0]['user']['group'] = null; + currentUser[0]['user']['zekken_number'] = null; + } + + Future fetchTeamData() async { + try { + Get.put(TeamController()); + // \"TeamController\" not found. You need to call \"Get.put(TeamController())\" or \"Get.lazyPut(()=>TeamController())\" + final teamController = Get.find(); + await teamController.fetchTeams(); + if (teamController.teams.isNotEmpty) { + teamId.value = teamController.teams.first.id; + } + } catch (e) { + print("Error fetching team data: $e"); + } + } + + + loadUserDetailsForToken(String token) async { - AuthService.userForToken(token).then((value) { - print("----token val-- $value ------"); - if(value![0]["user"].isEmpty){ - Get.toNamed(AppPages.LOGIN); - return; - } - changeUser(value![0], replace:false); - print("--------- user details ----- $value"); - // if (value != null && value.isNotEmpty) { - // bool paid = value[0]["paid"] as bool; - // if (paid) { - // loadCustomAreas(); - // } - // } - }); - } - - void loadCustomAreas() { - customAreas.clear(); - PerfectureService.loadCustomAreas().then((value) { - print("--- loading custom areas $value"); - customAreas.add(value); - }); - } - - void loadSubPerfFor(String perf) { - subPerfs.clear(); - dynamic initVal = {'id': '-1', 'adm2_ja': '----'}; - PerfectureService.loadSubPerfectures(perf).then((value) { - value!.add(initVal); - subPerfs.add(value); - subDropdownValue = getSubInitialVal(); - }); - } - - String getSubInitialVal() { - int min = 0; - if (subPerfs.isNotEmpty) { - min = int.parse(subPerfs[0][0]['id'].toString()); - for (var sub in subPerfs[0]) { - int x = int.parse(sub['id'].toString()); // as int; - if (x < min) { - min = x; - } + AuthService.userForToken(token).then((value) { + print("----token val-- $value ------"); + if (value![0]["user"].isEmpty) { + Get.toNamed(AppPages.LOGIN); + return; } - } - return min.toString(); - } - - void loadLocationforPerf(String perf, MapController mapController) async { - String cat = currentCat.isNotEmpty == true ? currentCat[0] : ""; - print(currentCat); - // LocationService.loadLocationsFor(perf, cat).then((value){ - // locations.clear(); - // locations.add(value!); - // mapController.fitBounds(currentBound[0]); - // }); - locations.clear(); - mapController.fitBounds(currentBound[0]); - } - - void loadLocationforSubPerf( - String subperf, MapController mapController) async { - String cat = currentCat.isNotEmpty == true ? currentCat[0] : ""; - if (currentCat.isNotEmpty && currentCat[0] == "-all-") { - cat = ""; - } - LocationService.loadLocationsSubFor(subperf, cat).then((value) { - locations.clear(); - locations.add(value!); - }); - } - - void loadCustomLocation(String customarea) async { - String cat = currentCat.isNotEmpty == true ? currentCat[0] : ""; - if (currentCat.isNotEmpty && currentCat[0] == "-all-") { - cat = ""; - } - print("----- $customarea"); - LocationService.loadCustomLocations(customarea, cat).then((value) { - locations.clear(); - locations.add(value!); - List locs = getLocationsList(); - LatLngBounds bounds = boundsFromLatLngList(locs); - mapController.fitBounds(bounds); - setBound(bounds); - Future.delayed(const Duration(microseconds: 400), () { - mapController.fitBounds(bounds); - }); + changeUser(value[0], replace: false); }); } +/* Old code void loadLocationsBound() { - if (is_custom_area_selected.value == true) { + if (isCustomAreaSelected.value == true) { return; } locations.clear(); @@ -526,7 +735,7 @@ class IndexController extends GetxController { LatLngBounds bounds = mapController.bounds!; currentBound.clear(); currentBound.add(bounds); - //print(currentCat); + ////print(currentCat); LocationService.loadLocationsBound( bounds.southWest.latitude, bounds.southWest.longitude, @@ -538,11 +747,11 @@ class IndexController extends GetxController { bounds.southEast.longitude, cat) .then((value) { - //print("---value length ------ ${value!.collection.length}"); + ////print("---value length ------ ${value!.collection.length}"); if (value == null) { return; } - if (value.collection.isEmpty) { + if (value.features.isEmpty) { if (showPopup == false) { return; } @@ -552,173 +761,204 @@ class IndexController extends GetxController { icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), snackPosition: SnackPosition.TOP, - duration: const Duration(milliseconds: 800), + duration: const Duration(seconds: 2), backgroundColor: Colors.yellow, //icon:Image(image:AssetImage("assets/images/dora.png")) ); showPopup = false; //Get.showSnackbar(GetSnackBar(message: "Too many points, please zoom in",)); } - if (value.collection.isNotEmpty) { - //print("---- added---"); + if (value.features.isNotEmpty) { + ////print("---- added---"); locations.add(value); - loadCatsv2(); } }); } +*/ + + + // 2024-04-03 Akira .. Update the code . See ticket 2800. + // + // 2024-4-8 Akira : See 2809 + // IndexControllerクラスでは、Future.delayedの呼び出しをunawaitedで囲んで、 + // 非同期処理の結果を待たずに先に進むようにしました。これにより、メモリリークを防ぐことができます + // + // 要検討:Future.delayedを使用して非同期処理を待たずに先に進むようにしていますが、 + // これによってメモリリークが発生する可能性があります。非同期処理の結果を適切に処理することを検討してください。 + // + void loadLocationsBound(String eventCode) async { + if (isCustomAreaSelected.value == true) { + return; + } + + // MapControllerの初期化が完了するまで待機 + await waitForMapControllerReady(); + + locations.clear(); + String cat = currentCat.isNotEmpty ? currentCat[0] : ""; + if (currentCat.isNotEmpty && currentCat[0] == "-all-") { + cat = ""; + } +/* + // Akira Add 2024-4-6 + if( mapController.controller == null ) { + print("操作が完了する前にMapControllerまたはウィジェットが破棄されました。"); + isLoading.value = true; // ローディング状態をtrueに設定 + return; + } + // +*/ + + LatLngBounds bounds = mapController.bounds!; + + currentBound.clear(); + currentBound.add(bounds); + + //isLoading.value = true; // ローディング状態をtrueに設定 + + //print("bounds --- (${bounds.southWest.latitude},${bounds.southWest.longitude}),(${bounds.northWest.latitude},${bounds.northWest.longitude}),(${bounds.northEast.latitude},${bounds.northEast.longitude}),(${bounds.southEast.latitude},${bounds.southEast.longitude})"); + + // 要検討:APIからのレスポンスがnullの場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。 + try { + final eventCode = currentUser[0]["user"]["event_code"]; + final value = await LocationService.loadLocationsBound( + bounds.southWest.latitude, + bounds.southWest.longitude, + bounds.northWest.latitude, + bounds.northWest.longitude, + bounds.northEast.latitude, + bounds.northEast.longitude, + bounds.southEast.latitude, + bounds.southEast.longitude, + cat, + eventCode + ); + /* + if (value == null) { + // APIからのレスポンスがnullの場合 + print("LocationService.loadLocationsBound からの回答がnullのため、マップをリロード"); + DestinationController destinationController = Get.find(); // 追加 + final tk = currentUser[0]["token"]; // 追加 + if (tk != null) { // 追加 + destinationController.fixMapBound(tk); // 追加 + } // 追加 + return; + } + */ + isLoading.value = false; // ローディング状態をfalseに設定 + + if (value == null) { + // APIからのレスポンスがnullの場合 + print("LocationService.loadLocationsBound からの回答がnullです"); + } else { + if (value.features.isEmpty) { + if (showPopup == false) { + return; + } + Get.snackbar( + "Too many Points", + "please zoom in", + backgroundColor: Colors.yellow, + colorText: Colors.white, + icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + ); + showPopup = false; + } + if (value.features.isNotEmpty) { + locations.add(value); + } + } + /* + if (value != null && value.features.isEmpty) { + if (showPopup == false) { + return; + } + Get.snackbar( + "Too many Points", + "please zoom in", + backgroundColor: Colors.yellow, + colorText: Colors.white, + icon: const Icon( + Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + //backgroundColor: Colors.yellow, + ); + showPopup = false; + } + if (value != null && value.features.isNotEmpty) { + locations.add(value); + } + */ + } catch ( e) { + print("Error in loadLocationsBound: $e"); + // エラーが発生した場合のリトライ処理や適切なエラーメッセージの表示を行う + // 例えば、一定時間後に再度loadLocationsBound()を呼び出すなど + } + + } + + + //===Akira 追加:2024-4-6 #2800 + // 要検討:MapControllerの初期化が完了するまで待機していますが、タイムアウトを設定することを検討してください。 + // 初期化に時間がかかりすぎる場合、ユーザーにわかりやすいメッセージを表示するようにしてください。 + // + Future waitForMapControllerReady() async { + if (!isMapControllerReady.value) { + await Future.doWhile(() async { + await Future.delayed(const Duration(milliseconds: 100)); + return !isMapControllerReady.value; + }); + } + } + //===Akira 追加:2024-4-6 #2800 void setBound(LatLngBounds bounds) { currentBound.clear(); currentBound.add(bounds); } - void zoomtoMainPerf(String id) { - PerfectureService.getMainPerfExt(id).then((value) { - LatLng lat1 = LatLng(value![1], value[0]); - LatLng lat2 = LatLng(value[3], value[2]); - LatLngBounds bound = LatLngBounds(lat1, lat2); - mapController.fitBounds(bound); - setBound(bound); - }); - } - - void zoomtoSubPerf(String id) { - print("zooooom"); - - PerfectureService.getSubExt(id).then((value) { - LatLng lat1 = LatLng(value![1], value[0]); - LatLng lat2 = LatLng(value[3], value[2]); - LatLngBounds bound = LatLngBounds(lat1, lat2); - mapController.fitBounds(bound); - setBound(bound); - }); - } - - void populateForPerf(String perf, MapController mapController) { - loadSubPerfFor(perf); - loadLocationforPerf(perf, mapController); - zoomtoMainPerf(perf); - is_loading.value = false; - } - - void populateForSubPerf(String subperf, MapController mapController) { - //subDropdownValue = subperf; - loadLocationforSubPerf(subperf, mapController); - zoomtoSubPerf(subperf); - is_loading.value = false; - } - - void populateSubPerForArea(String area, MapController mapController) { - loadSubPerfFor(area); - //loadCustomLocation("cus", mapController); - //zoomtoSubPerf(subperf); - is_loading.value = false; - } - - GeoJsonFeature? getFeatureForLatLong(double lat, double long) { + GeoJSONFeature? getFeatureForLatLong(double lat, double long) { if (locations.isNotEmpty) { - for (GeoJsonFeature i in locations[0].collection) { - GeoJsonMultiPoint p = i.geometry as GeoJsonMultiPoint; - if (p.geoSerie!.geoPoints[0].latitude == lat && - p.geoSerie!.geoPoints[0].longitude == long) { - return i; + GeoJSONFeature? foundFeature; + + for (var i in locations[0].features) { + GeoJSONMultiPoint p = i!.geometry as GeoJSONMultiPoint; + if (p.coordinates[0][1] == lat && p.coordinates[0][0] == long) { + foundFeature = i; } } + + return foundFeature; } return null; } - void getAction() { - //print(currentUser[0]["user"]["id"]); - //print(currentFeature[0].properties!["location_id"]); - if (currentUser.isEmpty) { - return; - } - int userId = currentUser[0]["user"]["id"] as int; - print("---- loc id ${currentFeature[0].properties}"); - int locationId = currentFeature[0].properties!["location_id"] as int; - ActionService.userAction(userId, locationId).then((value) { - print("------$value"); - if (value != null && value.isNotEmpty) { - currentAction.clear(); - currentAction.add(value); - print("------${currentAction[0]}"); - } else { - List initval = [ - { - "user": userId, - "location": locationId, - "wanttogo": false, - "like": false, - "checkin": false - } - ]; - currentAction.clear(); - currentAction.add(initval); - } - }); + + void reloadMap( String eventCode ) { + // マップをリロードするロジックを実装 + // 例: 現在の位置情報を再取得し、マップを更新する + loadLocationsBound( eventCode ); } - void makeNext(GeoJsonFeature fs) { - if (rog_mode == 1) { - DestinationController destinationController = - Get.find(); - } else { - GeoJsonFeature pt = - fs as GeoJsonFeature; - - for (int i = 0; i <= locations[0].collection.length - 1; i++) { - GeoJsonMultiPoint p = - locations[0].collection[i].geometry as GeoJsonMultiPoint; - - if (p.geoSerie!.geoPoints[0].latitude == - pt.geometry!.geoSerie!.geoPoints[0].latitude && - p.geoSerie!.geoPoints[0].longitude == - pt.geometry!.geoSerie!.geoPoints[0].longitude) { - if (currentFeature.isNotEmpty) { - currentFeature.clear(); - } - if (i >= locations[0].collection.length - 1) { - currentFeature.add(locations[0].collection[0]); - getAction(); - } else { - currentFeature.add(locations[0].collection[i + 1]); - getAction(); - } - } - } + Future checkEntryData() async { + // エントリーデータの有無をチェックするロジック + final teamController = TeamController(); + bool hasEntryData = teamController.checkIfUserHasEntryData(); + if (!hasEntryData) { + await showHelperDialog( + 'イベントに参加するには、チーム登録・メンバー登録及びエントリーが必要になります。', + 'entry_check' + ); + // ドロワーを表示するロジック + Get.toNamed('/drawer'); } } - void makePrevious(GeoJsonFeature fs) { - if (rog_mode == 1) { - DestinationController destinationController = - Get.find(); - } else { - GeoJsonFeature pt = - fs as GeoJsonFeature; - - for (int i = 0; i <= locations[0].collection.length - 1; i++) { - GeoJsonMultiPoint p = - locations[0].collection[i].geometry as GeoJsonMultiPoint; - - if (p.geoSerie!.geoPoints[0].latitude == - pt.geometry!.geoSerie!.geoPoints[0].latitude && - p.geoSerie!.geoPoints[0].longitude == - pt.geometry!.geoSerie!.geoPoints[0].longitude) { - if (currentFeature.isNotEmpty) { - currentFeature.clear(); - } - if (i == 0) { - currentFeature.add( - locations[0].collection[locations[0].collection.length - 1]); - getAction(); - } else { - currentFeature.add(locations[0].collection[i - 1]); - getAction(); - } - } - } - } + void updateCurrentUser(User updatedUser) { + currentUser[0]['user'] = updatedUser.toJson(); + update(); } } diff --git a/lib/pages/index/index_page.dart b/lib/pages/index/index_page.dart index 4cfef8a..883e45d 100644 --- a/lib/pages/index/index_page.dart +++ b/lib/pages/index/index_page.dart @@ -1,38 +1,147 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; -import 'package:rogapp/pages/drawer/drawer_page.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/routes/app_pages.dart'; -import 'package:rogapp/widgets/list_widget.dart'; -import 'package:rogapp/widgets/map_widget.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/drawer/drawer_page.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/widgets/list_widget.dart'; +import 'package:gifunavi/widgets/map_widget.dart'; +import 'package:gifunavi/utils/location_controller.dart'; -class IndexPage extends GetView { - IndexPage({Key? key}) : super(key: key); +// index_page.dartファイルの主な内容です。 +// このファイルは、アプリのメインページのUIを構築し、各機能へのナビゲーションを提供しています。 +// また、IndexControllerとDestinationControllerを使用して、状態管理と各種機能の実装を行っています。 +// +// MapWidgetとListWidgetは、それぞれ別のファイルで定義されているウィジェットであり、マップモードとリストモードの表示を担当しています。 +// +// 全体的に、index_page.dartはアプリのメインページの構造を定義し、他のコンポーネントやページへの橋渡しを行っているファイルです。 +// +// 要検討:GPSデータの表示アイコンをタップした際のエラーハンドリングを追加することをお勧めします。 +// MapWidgetとListWidgetの切り替えにObxを使用していますが、パフォーマンスを考慮して、必要な場合にのみウィジェットを再構築するようにしてください。 +// DestinationControllerのisSimulationModeを使用してGPS信号の強弱をシミュレーションしていますが、本番環境では適切に実際のGPS信号を使用するようにしてください。 + +// IndexPageクラスは、GetViewを継承したStatelessWidgetです。このクラスは、アプリのメインページを表すウィジェットです。 +// + +class IndexPage extends StatefulWidget { + const IndexPage({super.key}); + + @override + _IndexPageState createState() => _IndexPageState(); +} + +class _IndexPageState extends State { + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + //checkLoginAndShowDialog(); + }); + } + + void checkLoginAndShowDialog() { + if (indexController.currentUser.isEmpty) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('ログインが必要です'), + content: const Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('1) ログインされていません。ロゲに参加するにはログインが必要です。'), + SizedBox(height: 10), + Text('2) ログイン後、個人情報入力、チーム登録、エントリー登録を行なってください。'), + SizedBox(height: 10), + Text('3) エントリー登録は場所と日にちごとに行なってください。'), + ], + ), + actions: [ + TextButton( + child: const Text('キャンセル'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ElevatedButton( + child: const Text('ログイン'), + onPressed: () { + Navigator.of(context).pop(); + Get.toNamed(AppPages.LOGIN); + }, + ), + ], + ); + }, + ); + } + } + + +// class IndexPage extends GetView { +// IndexPage({Key? key}) : super(key: key); + + // IndexControllerとDestinationControllerのインスタンスを取得しています。 + // + final LocationController locationController = Get.find(); final IndexController indexController = Get.find(); final DestinationController destinationController = Get.find(); + // buildメソッドは、ウィジェットのUIを構築するメソッドです。 + // ここでは、WillPopScopeウィジェットを使用して、端末の戻るボタンが押された際の動作を制御しています。 + // @override Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { - indexController.switchPage(AppPages.INITIAL); - return false; - }, + return PopScope( + canPop: false, child: Scaffold( + // + // Scaffoldウィジェットを使用して、アプリのメインページのレイアウトを構築しています。 + // drawer: DrawerPage(), appBar: AppBar( - // leading: IconButton( - // icon: const Icon(Icons.arrow_back_ios), - // onPressed: (){ - // indexController.switchPage(AppPages.TRAVEL); - // }, - // ), - //automaticallyImplyLeading: false, - title: Text("add_location".tr), + title: Obx(() => Text(indexController.selectedEventName.value)), + //title: Text("add_location".tr), actions: [ + // IconButton( + // onPressed: () { + // DatabaseService ds = DatabaseService(); + // ds.updateDatabase(); + // }, + // icon: const Icon(Icons.ten_k_sharp)), + + // + // AppBarには、タイトルとアクションアイコンが含まれています。 + // アクションアイコンには、GPSデータの表示、履歴の表示、マップの更新、検索などの機能が含まれています。 + // + IconButton( + onPressed: () async { + // GpsDatabaseHelper db = GpsDatabaseHelper.instance; + // List data = await db.getGPSData( + // indexController.currentUser[0]["user"]['team_name'], + // indexController.currentUser[0]["user"]["event_code"]); + // print("GPS data is ${data.length}"); + Get.toNamed(AppPages.GPS); + }, + icon: const Icon(Icons.telegram)), + IconButton( + onPressed: () { + Get.toNamed(AppPages.HISTORY); + }, + icon: const Icon(Icons.history)), + IconButton( + onPressed: () { + final tk = indexController.currentUser[0]["token"]; + if (tk != null) { + destinationController.fixMapBound(tk); + } + }, + icon: const Icon(Icons.refresh)), InkWell( onTap: () { Get.toNamed(AppPages.SEARCH); @@ -41,6 +150,7 @@ class IndexPage extends GetView { height: 32, width: 75, decoration: BoxDecoration( + color: Colors.blue, borderRadius: BorderRadius.circular(25), ), child: const Center( @@ -48,125 +158,129 @@ class IndexPage extends GetView { ), ), ), - IconButton(onPressed: () { - Get.toNamed(AppPages.HISTORY); - }, icon: const Icon(Icons.history)) - //CatWidget(indexController: indexController,), + // + // デバッグ時のみリロードボタンの横にGPS信号レベルの設定ボタンを設置し、 + // タップすることでGPS信号の強弱をシミュレーションできるようにする + // Akira 2024-4-5 + // +/* + Obx(() { + if (locationController.isSimulationMode) { + return DropdownButton( + value: locationController.getSimulatedSignalStrength(), + onChanged: (value) { + //debugPrint("DropDown changed!"); + locationController.setSimulatedSignalStrength(value!); + }, + items: ['low', 'medium', 'high', 'real'] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ); + } else { + return Container(); + } + }), + */ + ], ), - bottomNavigationBar: BottomAppBar( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Padding( - padding: - const EdgeInsets.only(right: 10.0, top: 4.0, bottom: 4.0), - child: InkWell( - child: - Obx(() => destinationController.is_gps_selected == true - ? Padding( - padding: const EdgeInsets.only( - right: 10.0, top: 4.0, bottom: 4.0), - child: InkWell( - child: const Image( - image: AssetImage( - 'assets/images/route3_off.png'), - width: 35, - height: 35, - ), - onTap: () { - indexController.switchPage(AppPages.TRAVEL); - }, - ), - ) - : Padding( - padding: const EdgeInsets.only( - right: 10.0, top: 4.0, bottom: 4.0), - child: InkWell( - child: const Image( - image: AssetImage( - 'assets/images/route2_on.png'), - width: 35, - height: 35, - ), - onTap: () { - indexController.switchPage(AppPages.TRAVEL); - }, - ), - ))), - ), - ], - ), - ), + // bottomNavigationBar: BottomAppBar( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Obx( + // () => destinationController.isInRog.value == true + // ? IconButton( + // onPressed: () {}, + // icon: const Icon( + // Icons.run_circle, + // size: 44, + // color: Colors.green, + // )) + // : IconButton( + // onPressed: () {}, + // icon: const Icon( + // Icons.run_circle, + // size: 44, + // color: Colors.black12, + // )), + // ), + // Padding( + // padding: + // const EdgeInsets.only(right: 10.0, top: 4.0, bottom: 4.0), + // child: InkWell( + // child: Obx(() => destinationController + // .isGpsSelected.value == + // true + // ? Padding( + // padding: const EdgeInsets.only( + // right: 10.0, top: 4.0, bottom: 4.0), + // child: InkWell( + // child: const Image( + // image: + // AssetImage('assets/images/route3_off.png'), + // width: 35, + // height: 35, + // ), + // onTap: () { + // //indexController.switchPage(AppPages.TRAVEL); + // }, + // ), + // ) + // : Padding( + // padding: const EdgeInsets.only( + // right: 10.0, top: 4.0, bottom: 4.0), + // child: InkWell( + // child: const Image( + // image: + // AssetImage('assets/images/route2_on.png'), + // width: 35, + // height: 35, + // ), + // onTap: () { + // //indexController.switchPage(AppPages.TRAVEL); + // }, + // ), + // ))), + // ), + // ], + // ), + // ), + + // + // マップモードとリストモードを切り替えるためのボタンです。 + // floatingActionButton: FloatingActionButton( onPressed: () { indexController.toggleMode(); - if (indexController.currentCat.isNotEmpty) { - print(indexController.currentCat[0].toString()); - } }, - tooltip: 'Increment', - elevation: 4.0, + elevation: 1.0, + // + // Obxウィジェットを使用して、indexController.mode.valueの値に基づいて、MapWidgetまたはListWidgetを表示しています。 + // child: Obx( - () => indexController.mode == 0 + () => indexController.mode.value == 0 ? const Image(image: AssetImage('assets/images/list2.png')) : const Image(image: AssetImage('assets/images/map.png')), ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, + // + // bodyには、SafeAreaウィジェットを使用して、画面の安全な領域内にUIを構築しています。 + // body: SafeArea( child: Column( children: [ - // Container( - // padding: const EdgeInsets.symmetric(horizontal: 8.0), - // alignment: Alignment.centerLeft, - // height: 50.0, - // //child: SingleChildScrollView( - // // scrollDirection: Axis.horizontal, - // // child:Row( - // // mainAxisAlignment: MainAxisAlignment.start, - // // children: [ - // // TextButton(child:Text("Main Pef >", style: TextStyle(fontSize:16.0, fontWeight: FontWeight.bold),), onPressed: (){Get.toNamed(AppPages.MAINPERF);},), - // // TextButton(child:Text("Sub Pef >", style: TextStyle(fontSize:16.0, fontWeight: FontWeight.bold),), onPressed: (){Get.toNamed(AppPages.SUBPERF);},), - // // TextButton(child:Text("Cities >", style: TextStyle(fontSize:16.0, fontWeight: FontWeight.bold),), onPressed: (){Get.toNamed(AppPages.CITY);},), - // // TextButton(child:Text("Categories", style: TextStyle(fontSize:16.0, fontWeight: FontWeight.bold),), onPressed: (){Get.toNamed(AppPages.CATEGORY);},), - // // ], - // // ) - // // ), - // child: SingleChildScrollView( - // scrollDirection: Axis.horizontal, - // child: Obx(() => - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // indexController.is_mapController_loaded.value == false ? - // Center(child: CircularProgressIndicator()) - // : - // BreadCrumbWidget(mapController: indexController.mapController), - // Container(width: 24.0,), - // // Row( - // // children: [ - // // indexController.currentCat.isNotEmpty ? Text(indexController.currentCat[0].toString()): Text(""), - // // indexController.currentCat.isNotEmpty ? - // // IconButton( - // // onPressed: (){ - // // indexController.currentCat.clear(); - // // indexController.loadLocationsBound(); - // // }, - // // icon: Icon(Icons.cancel, color: Colors.red,) - // // ) : - // // Container(width: 0, height: 0,) - // // ], - // // ) - // ], - // ) - // ), - // ), - // ), Expanded( child: Obx( - () => indexController.mode == 0 ? MapWidget() : ListWidget(), + () => indexController.mode.value == 0 + ? const MapWidget() + : const ListWidget(), )) ], ), diff --git a/lib/pages/landing/landing_page.dart b/lib/pages/landing/landing_page.dart index 67127f8..7be382c 100644 --- a/lib/pages/landing/landing_page.dart +++ b/lib/pages/landing/landing_page.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/routes/app_pages.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。 +// ボタンのテキストをローカライズすることを検討してください。 +// class LandingPage extends StatefulWidget { - const LandingPage({ Key? key }) : super(key: key); + const LandingPage({ super.key }); @override State createState() => _LandingPageState(); diff --git a/lib/pages/loading/loading_page.dart b/lib/pages/loading/loading_page.dart index 5444425..4683799 100644 --- a/lib/pages/loading/loading_page.dart +++ b/lib/pages/loading/loading_page.dart @@ -1,16 +1,17 @@ import 'package:flutter/material.dart'; class LoadingPage extends StatelessWidget { - const LoadingPage({ Key? key }) : super(key: key); + const LoadingPage({super.key}); + // 要検討:ローディングインジケーターの値を固定値(0.8)にしていますが、実際のローディング進捗に合わせて動的に変更することを検討してください。 + // @override Widget build(BuildContext context) { return Container( - alignment: Alignment.topCenter, - margin: const EdgeInsets.only(top: 20), - child: const CircularProgressIndicator( - value: 0.8, - ) - ); + alignment: Alignment.center, + margin: const EdgeInsets.only(top: 20), + child: const CircularProgressIndicator( + value: 0.8, + )); } -} \ No newline at end of file +} diff --git a/lib/pages/login/login_page.dart b/lib/pages/login/login_page.dart index 31b1d57..c02aa6e 100644 --- a/lib/pages/login/login_page.dart +++ b/lib/pages/login/login_page.dart @@ -1,224 +1,398 @@ - import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/routes/app_pages.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/widgets/helper_dialog.dart'; +import 'package:gifunavi/services/api_service.dart'; -class LoginPage extends StatelessWidget { +import 'package:package_info_plus/package_info_plus.dart'; +// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。 +// エラーメッセージをローカライズすることを検討してください。 +// ログイン処理中にエラーが発生した場合のエラーハンドリングを追加することをお勧めします。 +// +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); + + @override + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { +//class LoginPage extends StatelessWidget { final IndexController indexController = Get.find(); + final ApiService apiService = Get.find(); TextEditingController emailController = TextEditingController(); TextEditingController passwordController = TextEditingController(); + bool _obscureText = true; + String _version = ''; // バージョン情報を保持する変数 - LoginPage({Key? key}) : super(key: key); + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + showHelperDialog( + '参加するにはユーザー登録が必要です。サインアップからユーザー登録してください。', + 'login_page' + ); + }); + _getVersionInfo(); // バージョン情報を取得 + } + + // バージョン情報を取得するメソッド + Future _getVersionInfo() async { + final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + setState(() { + _version = packageInfo.version; + }); + } + + void _showResetPasswordDialog() { + TextEditingController resetEmailController = TextEditingController(); + + Get.dialog( + AlertDialog( + title: const Text('パスワードのリセット'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('パスワードをリセットするメールアドレスを入力してください。'), + const SizedBox(height: 10), + TextField( + controller: resetEmailController, + decoration: const InputDecoration( + labelText: 'メールアドレス', + border: OutlineInputBorder(), + ), + ), + ], + ), + actions: [ + TextButton( + child: const Text('キャンセル'), + onPressed: () => Get.back(), + ), + ElevatedButton( + child: const Text('リセット'), + onPressed: () async { + if (resetEmailController.text.isNotEmpty) { + bool success = await apiService.resetPassword(resetEmailController.text); + Get.back(); + if (success) { + Get.dialog( + AlertDialog( + title: const Text('パスワードリセット'), + content: const Text('パスワードリセットメールを送信しました。メールのリンクからパスワードを設定してください。'), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () => Get.back(), + ), + ], + ), + ); + } else { + Get.snackbar('エラー', 'パスワードリセットに失敗しました。もう一度お試しください。', + snackPosition: SnackPosition.BOTTOM); + } + } + }, + ), + ], + ), + ); + } + + //LoginPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, + backgroundColor: Colors.white, + appBar: AppBar( + elevation: 0, backgroundColor: Colors.white, - appBar: AppBar( - elevation: 0, - backgroundColor: Colors.white, - leading: - IconButton( onPressed: (){ - Navigator.pop(context); - },icon:const Icon(Icons.arrow_back_ios,size: 20,color: Colors.black,)), - ), - body: - indexController.currentUser.isEmpty ? - SizedBox( - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Column( - children: [ - Column( - children: [ - Container( - height: MediaQuery.of(context).size.height/6, - decoration: const BoxDecoration( - image:DecorationImage(image: AssetImage('assets/images/login_image.jpg')) - ), - ), - const SizedBox(height: 5,), - - ], - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 40 - ), - child: Column( + automaticallyImplyLeading: false, + ), + body: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: indexController.currentUser.isEmpty + ? SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Column( + children: [ + Column( children: [ - makeInput(label: "email".tr, controller: emailController), - makeInput(label: "password".tr, controller: passwordController, obsureText: true), + Container( + height: MediaQuery.of(context).size.height / 6, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + 'assets/images/login_image.jpg'))), + ), + const SizedBox( + height: 5, + ), + // バージョン情報を表示 + Text( + 'Version: $_version', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), ], ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 40), - child: Container( - padding: const EdgeInsets.only(top: 3,left: 3), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(40), - ), - child: Obx((() => - indexController.is_loading == true ? MaterialButton( - minWidth: double.infinity, - height:60, - onPressed: (){ - - }, - color: Colors.grey[400], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: const CircularProgressIndicator(), - ) : - Column( - children: [ - MaterialButton( - minWidth: double.infinity, - height:40, - onPressed: (){ - if(emailController.text.isEmpty || passwordController.text.isEmpty){ - Get.snackbar( - "no_values".tr, - "email_and_password_required".tr, - icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), - snackPosition: SnackPosition.TOP, - duration: const Duration(milliseconds: 800), - backgroundColor: Colors.yellow, - //icon:Image(image:AssetImage("assets/images/dora.png")) - ); - return; - } - indexController.is_loading.value = true; - indexController.login(emailController.text, passwordController.text, context); - }, - color: Colors.indigoAccent[400], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: const Text("ログイン",style: TextStyle( - fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70 - ), - ), - ), - const SizedBox(height: 5.0,), - MaterialButton( - minWidth: double.infinity, - height:40, - onPressed: (){ - Get.toNamed(AppPages.REGISTER); - }, - color: Colors.redAccent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: Text("sign_up".tr,style: const TextStyle( - fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70 - ), - ), - ), - const SizedBox(height: 2.0,), - MaterialButton( - minWidth: double.infinity, - height:40, - onPressed: (){ - Get.back(); - }, - color: Colors.grey, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: Text("cancel".tr,style: const TextStyle( - fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70 - ), - ), - ), - ], - ) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Column( + children: [ + makeInput( + label: "email".tr, controller: emailController), + makePasswordInput( + label: "password".tr, + controller: passwordController, + obscureText: _obscureText, + onToggleVisibility: () { + setState(() { + _obscureText = !_obscureText; + }); + }), + ], ), ), - ) - ), - const SizedBox(height: 5,), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text("rogaining_user_need_tosign_up".tr, style: const TextStyle( - overflow: TextOverflow.ellipsis, - ),), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Container( + padding: const EdgeInsets.only(top: 3, left: 3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + ), + child: Obx( + (() => indexController.isLoading.value == true + ? MaterialButton( + minWidth: double.infinity, + height: 60, + onPressed: () {}, + color: Colors.grey[400], + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: const CircularProgressIndicator(), + ) + : Column( + children: [ + MaterialButton( + minWidth: double.infinity, + height: 40, + onPressed: () async { + if (emailController.text.isEmpty || + passwordController + .text.isEmpty) { + Get.snackbar( + "no_values".tr, + "email_and_password_required" + .tr, + backgroundColor: Colors.red, + colorText: Colors.white, + icon: const Icon( + Icons + .assistant_photo_outlined, + size: 40.0, + color: Colors.blue), + snackPosition: + SnackPosition.TOP, + duration: const Duration( + seconds: 3), + ); + return; + } + indexController.isLoading.value = + true; + indexController.login( + emailController.text, + passwordController.text, + context); + }, + color: Colors.indigoAccent[400], + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: Text( + "login".tr, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white70), + ), + ), + const SizedBox( + height: 5.0, + ), + MaterialButton( + minWidth: double.infinity, + height: 36, + onPressed: () { + Get.toNamed(AppPages.REGISTER); + }, + color: Colors.redAccent, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: Text( + "sign_up".tr, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white70), + ), + ), + ], + )), + ), + )), + const SizedBox( + height: 3, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: _showResetPasswordDialog, + child: Text( + "forgot_password".tr, + style: const TextStyle(color: Colors.blue), + ), ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text("app_developed_by_gifu_dx".tr, style: const TextStyle( - overflow: TextOverflow.ellipsis, fontSize: 10.0 - ),), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "app_developed_by_gifu_dx".tr, + style: const TextStyle( + overflow: TextOverflow.ellipsis, + fontSize: 10.0), + ), + ), ), - ), - ], - ) - ], - - ), - ], - ), - ): - Container( - child: TextButton( - onPressed: (){ - indexController.currentUser.clear(); + ], + ), + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "※第8回と第9回は、岐阜県の令和5年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています", + style: TextStyle( + fontSize: 10.0, + ), + ), + ), + ), + ], + ), + ], + ), + ], + ), + ) + : TextButton( + onPressed: () { + indexController.logout(); + Get.offAllNamed(AppPages.LOGIN); }, child: const Text("Already Logged in, Click to logout"), ), - ) - , + ), ); } } -Widget makeInput({label, required TextEditingController controller, obsureText = false}){ +Widget makePasswordInput({ + required String label, + required TextEditingController controller, + required bool obscureText, + required VoidCallback onToggleVisibility, +}) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label,style:const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - color: Colors.black87 - ),), - const SizedBox(height: 5,), + Text( + label, + style: const TextStyle( + fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87), + ), + const SizedBox(height: 5), + TextField( + controller: controller, + obscureText: obscureText, + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey[400]!), + ), + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey[400]!), + ), + suffixIcon: IconButton( + icon: Icon( + obscureText ? Icons.visibility : Icons.visibility_off, + color: Colors.grey, + ), + onPressed: onToggleVisibility, + ), + ), + ), + const SizedBox(height: 30.0) + ], + ); +} + +Widget makeInput( + {label, required TextEditingController controller, obsureText = false}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87), + ), + const SizedBox( + height: 5, + ), TextField( controller: controller, obscureText: obsureText, decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 0,horizontal: 10), + contentPadding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 10), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (Colors.grey[400])!, ), ), border: OutlineInputBorder( - borderSide: BorderSide(color: (Colors.grey[400])! + borderSide: BorderSide(color: (Colors.grey[400])!), ), ), ), - ), - const SizedBox(height: 30.0,) + const SizedBox( + height: 30.0, + ) ], ); } diff --git a/lib/pages/login/login_page.dart_backup b/lib/pages/login/login_page.dart_backup new file mode 100644 index 0000000..b8464ef --- /dev/null +++ b/lib/pages/login/login_page.dart_backup @@ -0,0 +1,381 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:rogapp/pages/index/index_controller.dart'; +import 'package:rogapp/routes/app_pages.dart'; +import 'package:rogapp/widgets/helper_dialog.dart'; +import 'package:rogapp/services/api_service.dart'; + +// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。 +// エラーメッセージをローカライズすることを検討してください。 +// ログイン処理中にエラーが発生した場合のエラーハンドリングを追加することをお勧めします。 +// +class LoginPage extends StatefulWidget { + @override + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { +//class LoginPage extends StatelessWidget { + final IndexController indexController = Get.find(); + final ApiService apiService = Get.find(); + + TextEditingController emailController = TextEditingController(); + TextEditingController passwordController = TextEditingController(); + bool _obscureText = true; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + showHelperDialog( + '参加するにはユーザー登録が必要です。サインアップからユーザー登録してください。', + 'login_page' + ); + }); + } + + void _showResetPasswordDialog() { + TextEditingController resetEmailController = TextEditingController(); + + Get.dialog( + AlertDialog( + title: Text('パスワードのリセット'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('パスワードをリセットするメールアドレスを入力してください。'), + SizedBox(height: 10), + TextField( + controller: resetEmailController, + decoration: InputDecoration( + labelText: 'メールアドレス', + border: OutlineInputBorder(), + ), + ), + ], + ), + actions: [ + TextButton( + child: Text('キャンセル'), + onPressed: () => Get.back(), + ), + ElevatedButton( + child: Text('リセット'), + onPressed: () async { + if (resetEmailController.text.isNotEmpty) { + bool success = await apiService.resetPassword(resetEmailController.text); + Get.back(); + if (success) { + Get.dialog( + AlertDialog( + title: Text('パスワードリセット'), + content: Text('パスワードリセットメールを送信しました。メールのリンクからパスワードを設定してください。'), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () => Get.back(), + ), + ], + ), + ); + } else { + Get.snackbar('エラー', 'パスワードリセットに失敗しました。もう一度お試しください。', + snackPosition: SnackPosition.BOTTOM); + } + } + }, + ), + ], + ), + ); + } + + //LoginPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.white, + appBar: AppBar( + elevation: 0, + backgroundColor: Colors.white, + automaticallyImplyLeading: false, + ), + body: indexController.currentUser.isEmpty + ? SizedBox( + width: double.infinity, + + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Column( + children: [ + Column( + children: [ + Container( + height: MediaQuery.of(context).size.height / 6, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + 'assets/images/login_image.jpg'))), + ), + const SizedBox( + height: 5, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Column( + children: [ + makeInput( + label: "email".tr, controller: emailController), + makePasswordInput( + label: "password".tr, + controller: passwordController, + obscureText: _obscureText, + onToggleVisibility: () { + setState(() { + _obscureText = !_obscureText; + }); + }), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Container( + padding: const EdgeInsets.only(top: 3, left: 3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + ), + child: Obx( + (() => indexController.isLoading.value == true + ? MaterialButton( + minWidth: double.infinity, + height: 60, + onPressed: () {}, + color: Colors.grey[400], + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: const CircularProgressIndicator(), + ) + : Column( + children: [ + MaterialButton( + minWidth: double.infinity, + height: 40, + onPressed: () async { + if (emailController.text.isEmpty || + passwordController + .text.isEmpty) { + Get.snackbar( + "no_values".tr, + "email_and_password_required" + .tr, + backgroundColor: Colors.red, + colorText: Colors.white, + icon: const Icon( + Icons + .assistant_photo_outlined, + size: 40.0, + color: Colors.blue), + snackPosition: + SnackPosition.TOP, + duration: const Duration( + seconds: 3), + // backgroundColor: Colors.yellow, + //icon:Image(image:AssetImage("assets/images/dora.png")) + ); + return; + } + indexController.isLoading.value = + true; + indexController.login( + emailController.text, + passwordController.text, + context); + }, + color: Colors.indigoAccent[400], + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: Text( + "login".tr, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white70), + ), + ), + const SizedBox( + height: 5.0, + ), + MaterialButton( + minWidth: double.infinity, + height: 36, + onPressed: () { + Get.toNamed(AppPages.REGISTER); + }, + color: Colors.redAccent, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: Text( + "sign_up".tr, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white70), + ), + ), + + ], + )), + ), + )), + const SizedBox( + height: 3, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: _showResetPasswordDialog, + child: Text( + "forgot_password".tr, + style: TextStyle(color: Colors.blue), + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "app_developed_by_gifu_dx".tr, + style: const TextStyle( + overflow: TextOverflow.ellipsis, + fontSize: 10.0), + ), + ), + ), + ], + ), + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "※第8回と第9回は、岐阜県の令和5年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています", + style: TextStyle( + //overflow: TextOverflow.ellipsis, + fontSize: + 10.0, // Consider adjusting the font size if the text is too small. + // Removed overflow: TextOverflow.ellipsis to allow text wrapping. + ), + ), + ), + ), + ], + ), + ], + ), + ], + ), + + ) + : TextButton( + onPressed: () { + indexController.logout(); + Get.offAllNamed(AppPages.LOGIN); + }, + child: const Text("Already Logged in, Click to logout"), + ), + ); + } +} + +Widget makePasswordInput({ + required String label, + required TextEditingController controller, + required bool obscureText, + required VoidCallback onToggleVisibility, +}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87), + ), + const SizedBox(height: 5), + TextField( + controller: controller, + obscureText: obscureText, + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey[400]!), + ), + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey[400]!), + ), + suffixIcon: IconButton( + icon: Icon( + obscureText ? Icons.visibility : Icons.visibility_off, + color: Colors.grey, + ), + onPressed: onToggleVisibility, + ), + ), + ), + const SizedBox(height: 30.0) + ], + ); +} + +Widget makeInput( + {label, required TextEditingController controller, obsureText = false}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87), + ), + const SizedBox( + height: 5, + ), + TextField( + controller: controller, + obscureText: obsureText, + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: (Colors.grey[400])!, + ), + ), + border: OutlineInputBorder( + borderSide: BorderSide(color: (Colors.grey[400])!), + ), + ), + ), + const SizedBox( + height: 30.0, + ) + ], + ); +} diff --git a/lib/pages/login_popup/login_popup_page.dart b/lib/pages/login_popup/login_popup_page.dart index 98a5277..5bba4f1 100644 --- a/lib/pages/login_popup/login_popup_page.dart +++ b/lib/pages/login_popup/login_popup_page.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/routes/app_pages.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。 +// エラーメッセージをローカライズすることを検討してください。 +// ポップアップを閉じるボタンを追加することを検討してください。 +// class LoginPopupPage extends StatelessWidget { - LoginPopupPage({Key? key}) : super(key: key); + LoginPopupPage({super.key}); final IndexController indexController = Get.find(); @@ -15,195 +19,235 @@ class LoginPopupPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, + backgroundColor: Colors.white, + appBar: AppBar( + elevation: 0, backgroundColor: Colors.white, - appBar: AppBar( - elevation: 0, - backgroundColor: Colors.white, - leading: - IconButton( onPressed: (){ - Navigator.pop(context); - },icon:const Icon(Icons.arrow_back_ios,size: 20,color: Colors.black,)), - ), - body: - indexController.currentUser.isEmpty ? - SizedBox( - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Column( - children: [ - Column( - children: [ - Container( - height: MediaQuery.of(context).size.height/5, - decoration: const BoxDecoration( - image:DecorationImage(image: AssetImage('assets/images/login_image.jpg')) - ), - ), - const SizedBox(height: 5,), - - ], - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 40 - ), - child: Column( + leading: IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon( + Icons.arrow_back_ios, + size: 20, + color: Colors.black, + )), + ), + body: indexController.currentUser.isEmpty + ? SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Column( children: [ - makeInput(label: "email".tr, controller: emailController), - makeInput(label: "password".tr, controller: passwordController, obsureText: true), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 40), - child: Container( - padding: const EdgeInsets.only(top: 3,left: 3), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(40), - ), - child: Obx((() => - indexController.is_loading == true ? MaterialButton( - minWidth: double.infinity, - height:60, - onPressed: (){ - - }, - color: Colors.grey[400], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: const CircularProgressIndicator(), - ) : Column( children: [ - MaterialButton( - minWidth: double.infinity, - height:60, - onPressed: (){ - if(emailController.text.isEmpty || passwordController.text.isEmpty){ - Get.snackbar( - "no_values".tr, - "email_and_password_required".tr, - icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), - snackPosition: SnackPosition.TOP, - duration: const Duration(milliseconds: 800), - backgroundColor: Colors.yellow, - //icon:Image(image:AssetImage("assets/images/dora.png")) - ); - return; - } - indexController.is_loading.value = true; - indexController.login(emailController.text, passwordController.text, context); - }, - color: Colors.indigoAccent[400], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: const Text("ログイン",style: TextStyle( - fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70 - ), - ), - ), - const SizedBox(height: 19.0,), - MaterialButton( - minWidth: double.infinity, - height:50, - onPressed: (){ - Get.toNamed(AppPages.REGISTER); - }, - color: Colors.redAccent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: Text("sign_up".tr,style: const TextStyle( - fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70 - ), - ), - ), - const SizedBox(height: 19.0,), - MaterialButton( - minWidth: double.infinity, - height:50, - onPressed: (){ - Get.toNamed(AppPages.TRAVEL); - }, - color: Colors.grey, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: Text("cancel".tr,style: const TextStyle( - fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70 - ), - ), - ) + Container( + height: MediaQuery.of(context).size.height / 5, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + 'assets/images/login_image.jpg'))), + ), + const SizedBox( + height: 5, + ), ], - ) - ), - ), - ) - ), - const SizedBox(height: 20,), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text("rogaining_user_need_tosign_up".tr, style: const TextStyle( - overflow: TextOverflow.ellipsis, - ),), ), - ), - ], - ) - ], - + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Column( + children: [ + makeInput( + label: "email".tr, controller: emailController), + makeInput( + label: "password".tr, + controller: passwordController, + obsureText: true), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Container( + padding: const EdgeInsets.only(top: 3, left: 3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + ), + child: Obx( + (() => indexController.isLoading.value == true + ? MaterialButton( + minWidth: double.infinity, + height: 60, + onPressed: () {}, + color: Colors.grey[400], + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: const CircularProgressIndicator(), + ) + : Column( + children: [ + MaterialButton( + minWidth: double.infinity, + height: 60, + onPressed: () { + if (emailController.text.isEmpty || + passwordController + .text.isEmpty) { + Get.snackbar( + "no_values".tr, + "email_and_password_required" + .tr, + backgroundColor: Colors.red, + colorText: Colors.white, + icon: const Icon( + Icons + .assistant_photo_outlined, + size: 40.0, + color: Colors.blue), + snackPosition: + SnackPosition.TOP, + duration: const Duration( + milliseconds: 800), + //backgroundColor: Colors.yellow, + //icon:Image(image:AssetImage("assets/images/dora.png")) + ); + return; + } + indexController.isLoading.value = + true; + indexController.login( + emailController.text, + passwordController.text, + context); + }, + color: Colors.indigoAccent[400], + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: const Text( + "ログイン", + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white70), + ), + ), + const SizedBox( + height: 19.0, + ), + MaterialButton( + minWidth: double.infinity, + height: 50, + onPressed: () { + Get.toNamed(AppPages.REGISTER); + }, + color: Colors.redAccent, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: Text( + "sign_up".tr, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white70), + ), + ), + const SizedBox( + height: 19.0, + ), + MaterialButton( + minWidth: double.infinity, + height: 50, + onPressed: () { + Get.toNamed(AppPages.TRAVEL); + }, + color: Colors.grey, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(40)), + child: Text( + "cancel".tr, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white70), + ), + ) + ], + )), + ), + )), + const SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "rogaining_user_need_tosign_up".tr, + style: const TextStyle( + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + ], + ) + ], + ), + ], + ), + ) + : TextButton( + onPressed: () { + indexController.logout(); + Get.offAllNamed(AppPages.LOGIN); + }, + child: const Text("Already Logged in, Click to logout"), ), - ], - ), - ): - Container( - child: TextButton( - onPressed: (){ - indexController.currentUser.clear(); - }, - child: const Text("Already Logged in, Click to logout"), - ), - ) - , ); } } -Widget makeInput({label, required TextEditingController controller, obsureText = false}){ +Widget makeInput( + {label, required TextEditingController controller, obsureText = false}) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label,style:const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - color: Colors.black87 - ),), - const SizedBox(height: 5,), + Text( + label, + style: const TextStyle( + fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87), + ), + const SizedBox( + height: 5, + ), TextField( controller: controller, obscureText: obsureText, decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 0,horizontal: 10), + contentPadding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 10), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (Colors.grey[400])!, ), ), border: OutlineInputBorder( - borderSide: BorderSide(color: (Colors.grey[400])! + borderSide: BorderSide(color: (Colors.grey[400])!), ), ), ), - ), - const SizedBox(height: 30.0,) + const SizedBox( + height: 30.0, + ) ], ); } diff --git a/lib/pages/mainperf/mainperf_page.dart b/lib/pages/mainperf/mainperf_page.dart index 2684d80..94ec651 100644 --- a/lib/pages/mainperf/mainperf_page.dart +++ b/lib/pages/mainperf/mainperf_page.dart @@ -1,31 +1,31 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; +// import 'package:flutter/material.dart'; +// import 'package:get/get.dart'; +// import 'package:rogapp/pages/index/index_controller.dart'; -class MainPerfPage extends StatelessWidget { - MainPerfPage({Key? key}) : super(key: key); +// class MainPerfPage extends StatelessWidget { +// MainPerfPage({Key? key}) : super(key: key); - IndexController indexController = Get.find(); +// IndexController indexController = Get.find(); - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text("Select Main Perfecture"), - ), - body: ListView.builder( - itemCount: indexController.perfectures.length, - itemBuilder: (context, index){ - return ListTile( - onTap: (){ - indexController.dropdownValue = indexController.perfectures[index][0]["id"].toString(); - indexController.populateForPerf(indexController.dropdownValue, indexController.mapController); - Get.back(); - }, - title: Text(indexController.perfectures[index][0]["adm1_ja"].toString()), - ); - }, - ), - ); - } -} \ No newline at end of file +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// appBar: AppBar( +// title: const Text("Select Main Perfecture"), +// ), +// body: ListView.builder( +// itemCount: indexController.perfectures.length, +// itemBuilder: (context, index){ +// return ListTile( +// onTap: (){ +// indexController.dropdownValue = indexController.perfectures[index][0]["id"].toString(); +// indexController.populateForPerf(indexController.dropdownValue, indexController.mapController); +// Get.back(); +// }, +// title: Text(indexController.perfectures[index][0]["adm1_ja"].toString()), +// ); +// }, +// ), +// ); +// } +// } \ No newline at end of file diff --git a/lib/pages/permission/permission.dart b/lib/pages/permission/permission.dart index b244ecf..637bc51 100644 --- a/lib/pages/permission/permission.dart +++ b/lib/pages/permission/permission.dart @@ -1,154 +1,311 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:rogapp/routes/app_pages.dart'; +import 'dart:async'; -class PermissionHandlerScreen extends StatefulWidget { - const PermissionHandlerScreen({Key? key}) : super(key: key); - @override - State createState() => _PermissionHandlerScreenState(); -} +class PermissionController { -class _PermissionHandlerScreenState extends State { - + static bool _isRequestingPermission = false; + static Completer? _permissionCompleter; - Future _showMyDialog() async { - return showDialog( - context: context, - barrierDismissible: false, // user must tap button! - builder: (BuildContext context) { - return AlertDialog( - title: const Text('ロケーション許可'), - content: const SingleChildScrollView( - child: ListBody( - children: [ - Text( 'このアプリでは、位置情報の収集を行います。'), - Text( 'このアプリでは、開始時点で位置情報を収集します。'), - ], - ), - ), - actions: [ - TextButton( - child: const Text('わかった'), - onPressed: () { - //Navigator.of(context).pop(); - Get.toNamed(AppPages.TRAVEL); - }, - ), - ], - ); - }, - ); + static Future checkLocationPermissions() async { + final locationPermission = await Permission.location.status; + final whenInUsePermission = await Permission.locationWhenInUse.status; + final alwaysPermission = await Permission.locationAlways.status; + + return locationPermission == PermissionStatus.granted && + (whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted); } - @override - void initState() { - // TODO: implement initState - super.initState(); - - //permissionServiceCall(); - } + static Future checkAndRequestPermissions() async { + if (_isRequestingPermission) { + return _permissionCompleter!.future; + } - Future checkLocationPermission() async { - return await Permission.location.status; - } + _isRequestingPermission = true; + _permissionCompleter = Completer(); - permissionServiceCall() async { - await permissionServices().then( - (value) { - if (value[Permission.location]!.isGranted ) { - /* ========= New Screen Added ============= */ - - Get.toNamed(AppPages.TRAVEL); - - // Navigator.pushReplacement( - // context, - // MaterialPageRoute(builder: (context) => SplashScreen()), - // ); + bool hasPermissions = await checkLocationPermissions(); + if (!hasPermissions) { + bool userAgreed = await showLocationDisclosure(); + if (userAgreed) { + try { + await requestAllLocationPermissions(); + hasPermissions = await checkLocationPermissions(); + } catch (e) { + print('Error requesting location permissions: $e'); + hasPermissions = false; } - else{ - _showMyDialog(); - } - }, - ); - } - - - /*Permission services*/ - Future> permissionServices() async { - // You can request multiple permissions at once. - Map statuses = await [ - Permission.location, - - //add more permission to request here. - ].request(); - - if (statuses[Permission.location]!.isPermanentlyDenied) { - await openAppSettings().then( - (value) async { - if (value) { - if (await Permission.location.status.isPermanentlyDenied == true && - await Permission.location.status.isGranted == false) { - // openAppSettings(); - permissionServiceCall(); /* opens app settings until permission is granted */ - } - } - }, - ); - } else { - if (statuses[Permission.location]!.isDenied) { - permissionServiceCall(); + } else { + print('User did not agree to location usage'); + hasPermissions = false; + // アプリを終了 + SystemNavigator.pop(); } } - - /*{Permission.camera: PermissionStatus.granted, Permission.storage: PermissionStatus.granted}*/ - return statuses; + + _isRequestingPermission = false; + _permissionCompleter!.complete(hasPermissions); + return _permissionCompleter!.future; } + static Future requestAllLocationPermissions() async { + await Permission.location.request(); + await Permission.locationWhenInUse.request(); + await Permission.locationAlways.request(); - - @override - Widget build(BuildContext context) { - var status = Permission.location.status.then((value){ - if(value.isGranted == false){ - Future.delayed(Duration.zero, () => showAlert(context)); + if (await Permission.locationAlways.isGranted) { + const platform = MethodChannel('location'); + try { + await platform.invokeMethod('startLocationService'); + } on PlatformException catch (e) { + debugPrint("Failed to start location service: '${e.message}'."); } - else { - Get.toNamed(AppPages.TRAVEL); - } - }); - return Scaffold( - body: Container( - child: const Text(""), - ), - ); + } } - void showAlert(BuildContext context) { - showDialog( - context: context, - builder: (_) => AlertDialog( - title: const Text('ロケーション許可'), - content: const SingleChildScrollView( - child: ListBody( - children: [ - Text( 'このアプリでは、位置情報の収集を行います。'), - Text('岐阜ナビアプリではチェックポイントの自動チェックインの機能を可能にするために、現在地のデータが収集されます。アプリを閉じている時や、使用していないときにも収集されます。位置情報は、個人を特定できない統計的な情報として、ユーザーの個人情報とは一切結びつかない形で送信されます。お知らせの配信、位置情報の利用を許可しない場合は、この後表示されるダイアログで「許可しない」を選択してください。'), - ], - ), + static Future showLocationDisclosure() async { + return await Get.dialog( + AlertDialog( + title: const Text('位置情報の使用について'), + content: const SingleChildScrollView( + child: ListBody( + children: [ + Text('このアプリでは、以下の目的で位置情報を使用します:'), + Text('• チェックポイントの自動チェックイン(アプリが閉じているときも含む)'), + Text('• 移動履歴の記録(バックグラウンドでも継続)'), + Text('• 現在地周辺の情報表示'), + Text('\nバックグラウンドでも位置情報を継続的に取得します。'), + Text('これにより、バッテリーの消費が増加する可能性があります。'), + Text('同意しない場合には、アプリは終了します。'), + ], ), - actions: [ - TextButton( - child: const Text('わかった'), - onPressed: () { - permissionServiceCall(); - }, - ), - ], - ) + ), + actions: [ + TextButton( + child: const Text('同意しない'), + onPressed: () => Get.back(result: false), + ), + TextButton( + child: const Text('同意する'), + onPressed: () => Get.back(result: true), + ), + ], + ), + barrierDismissible: false, + ) ?? false; + } + + static void showPermissionDeniedDialog(String title,String message) { + Get.dialog( + AlertDialog( + //title: Text('location_permission_needed_title'.tr), + title: Text(title.tr), + // 位置情報への許可が必要です + //content: Text('location_permission_needed_main'.tr), + content: Text(message.tr), + // 岐阜ロゲでは、位置情報を使用してスタート・チェックイン・ゴール等の通過照明及び移動手段の記録のために、位置情報のトラッキングを行なっています。このためバックグラウンドでもトラッキングができるように位置情報の権限が必要です。 + // 設定画面で、「岐阜ナビ」に対して、常に位置情報を許可するように設定してください。 + actions: [ + TextButton( + child: const Text('キャンセル'), + onPressed: () => Get.back(), + ), + TextButton( + child: const Text('設定'), + onPressed: () { + Get.back(); + openAppSettings(); + }, + ), + ], + ), ); } + + + +/* + static Future requestLocationPermissions(BuildContext context) async { + if (_isRequestingPermission) { + // If a request is already in progress, wait for it to complete + return _permissionCompleter!.future; + } + + _isRequestingPermission = true; + _permissionCompleter = Completer(); + + bool userAgreed = await showLocationDisclosure(context); + if (userAgreed) { + try { + final locationStatus = await Permission.location.request(); + final whenInUseStatus = await Permission.locationWhenInUse.request(); + final alwaysStatus = await Permission.locationAlways.request(); + + if (locationStatus == PermissionStatus.granted && + (whenInUseStatus == PermissionStatus.granted || alwaysStatus == PermissionStatus.granted)) { + _permissionCompleter!.complete(true); + } else { + showPermissionDeniedDialog('location_permission_needed_title', 'location_permission_needed_main'); + _permissionCompleter!.complete(false); + } + } catch (e) { + print('Error requesting location permission: $e'); + _permissionCompleter!.complete(false); + } + } else { + print('User did not agree to location usage'); + _permissionCompleter!.complete(false); + // Exit the app + SystemNavigator.pop(); + } + + _isRequestingPermission = false; + return _permissionCompleter!.future; + } +*/ + + + + static Future checkStoragePermission() async { + //debugPrint("(gifunavi)== checkStoragePermission =="); + final storagePermission = await Permission.storage.status; + return storagePermission == PermissionStatus.granted; + } + + static Future requestStoragePermission() async { + //debugPrint("(gifunavi)== requestStoragePermission =="); + final storagePermission = await Permission.storage.request(); + + if (storagePermission == PermissionStatus.granted) { + return; + } + + if (storagePermission == PermissionStatus.permanentlyDenied) { + // リクエストが完了するまで待機 + await Future.delayed(const Duration(milliseconds: 500)); + showPermissionDeniedDialog('storage_permission_needed_title','storage_permission_needed_main'); + } + + } + + /* + static Future checkLocationBasicPermission() async { + //debugPrint("(gifunavi)== checkLocationBasicPermission =="); + final locationPermission = await Permission.location.status; + return locationPermission == PermissionStatus.granted; + } + + static Future checkLocationWhenInUsePermission() async { + //debugPrint("(gifunavi)== checkLocationWhenInUsePermission =="); + final whenInUsePermission = await Permission.locationWhenInUse.status; + return whenInUsePermission == PermissionStatus.granted; + } + + static Future checkLocationAlwaysPermission() async { + //debugPrint("(gifunavi)== checkLocationAlwaysPermission =="); + final alwaysPermission = await Permission.locationAlways.status; + return alwaysPermission == PermissionStatus.granted; + } + + static bool isBasicPermission=false; + static Future requestLocationBasicPermissions() async { + //debugPrint("(gifunavi)== requestLocationBasicPermissions =="); + try{ + if(!isBasicPermission){ + isBasicPermission=true; + final locationStatus = await Permission.location.request(); + + if (locationStatus != PermissionStatus.granted) { + showPermissionDeniedDialog('location_permission_needed_title','location_permission_needed_main'); + } + } + }catch (e, stackTrace){ + print('Exception: $e'); + print('Stack trace: $stackTrace'); + debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace); + } + + } + + static bool isLocationServiceRunning = false; + static bool isRequestedWhenInUsePermission = false; + + static Future requestLocationWhenInUsePermissions() async { + //debugPrint("(gifunavi)== requestLocationWhenInUsePermissions =="); + + try{ + if(!isRequestedWhenInUsePermission){ + isRequestedWhenInUsePermission=true; + final whenInUseStatus = await Permission.locationWhenInUse.request(); + + if (whenInUseStatus != PermissionStatus.granted) { + showPermissionDeniedDialog('location_permission_needed_title','location_permission_needed_main'); + }else{ + if( !isLocationServiceRunning ){ + isLocationServiceRunning=true; + const platform = MethodChannel('location'); + try { + await platform.invokeMethod('startLocationService'); // Location Service を開始する。 + } on PlatformException catch (e) { + debugPrint("Failed to start location service: '${e.message}'."); + } + } + } + } + }catch (e, stackTrace){ + debugPrint('Exception: $e'); + debugPrint('Stack trace: $stackTrace'); + debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace); + } + + } + + static bool isRequestedAlwaysPermission = false; + + static Future requestLocationAlwaysPermissions() async { + //debugPrint("(gifunavi)== requestLocationAlwaysPermissions =="); + + try { + if( !isRequestedAlwaysPermission ){ + isRequestedAlwaysPermission=true; + final alwaysStatus = await Permission.locationAlways.request(); + + if (alwaysStatus != PermissionStatus.granted) { + showPermissionDeniedDialog('location_permission_needed_title','location_permission_needed_main'); + } + } + }catch (e, stackTrace){ + print('Exception: $e'); + print('Stack trace: $stackTrace'); + debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace); + } + + } + + static Future checkAndRequestPermissions() async { + final hasPermissions = await checkLocationBasicPermission(); + if (!hasPermissions) { + await requestLocationBasicPermissions(); + } + + final hasWIUPermissions = await checkLocationWhenInUsePermission(); + if (!hasWIUPermissions) { + await requestLocationWhenInUsePermissions(); + } + + final hasAlwaysPermissions = await checkLocationAlwaysPermission(); + if (!hasAlwaysPermissions) { + await requestLocationAlwaysPermissions(); + } + } + + +*/ + + } \ No newline at end of file diff --git a/lib/pages/progress/progress.dart b/lib/pages/progress/progress.dart index 1a85543..256d392 100644 --- a/lib/pages/progress/progress.dart +++ b/lib/pages/progress/progress.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; class ProgressPage extends StatelessWidget { - const ProgressPage({Key? key}) : super(key: key); + const ProgressPage({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/register/register_page.dart b/lib/pages/register/register_page.dart index 1a1c7a7..686d4ca 100644 --- a/lib/pages/register/register_page.dart +++ b/lib/pages/register/register_page.dart @@ -1,143 +1,114 @@ - - import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/routes/app_pages.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/widgets/helper_dialog.dart'; -class RegisterPage extends StatelessWidget { +class RegisterPage extends StatefulWidget { + const RegisterPage({super.key}); + @override + _RegisterPageState createState() => _RegisterPageState(); +} + +class _RegisterPageState extends State { final IndexController indexController = Get.find(); - TextEditingController emailController = TextEditingController(); - TextEditingController passwordController = TextEditingController(); - TextEditingController confirmPasswordController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + final TextEditingController confirmPasswordController = TextEditingController(); - RegisterPage({Key? key}) : super(key: key); + bool _obscurePassword = true; + bool _obscureConfirmPassword = true; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + showHelperDialog( + '登録メールにアクティベーションメールが送信されます。メールにあるリンクをタップすると正式登録になります。', + 'register_page' + ); + }); + } @override Widget build(BuildContext context) { return Scaffold( - resizeToAvoidBottomInset: false, + resizeToAvoidBottomInset: true, backgroundColor: Colors.white, appBar: AppBar( elevation: 0, backgroundColor: Colors.white, - leading: - IconButton( onPressed: (){ - Navigator.pop(context); - },icon:const Icon(Icons.arrow_back_ios,size: 20,color: Colors.black,)), + leading: IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.arrow_back_ios, size: 20, color: Colors.black), + ), ), body: SafeArea( child: SingleChildScrollView( - child: SizedBox( + child: Container( height: MediaQuery.of(context).size.height, width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 40), child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Column( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const Text ("サインアップ", style: TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - ),), - const SizedBox(height: 20,), - Text("アカウントを作成し、無料です",style: TextStyle( - fontSize: 15, - color: Colors.grey[700], - ),), - const SizedBox(height: 30,) - ], - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 40 - ), - child: Column( - children: [ - makeInput(label: "Eメール", controller: emailController), - makeInput(label: "パスワード", controller: passwordController,obsureText: true), - makeInput(label: "パスワードを認証する", controller: confirmPasswordController,obsureText: true) - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 40), - child: Container( - padding: const EdgeInsets.only(top: 3,left: 3), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(40), - border: const Border( - bottom: BorderSide(color: Colors.black), - top: BorderSide(color: Colors.black), - right: BorderSide(color: Colors.black), - left: BorderSide(color: Colors.black) - ) - ), - child: MaterialButton( - minWidth: double.infinity, - height:60, - onPressed: (){ - if(passwordController.text != confirmPasswordController.text){ - Get.snackbar( - "No match", - "Passwords does not match", - icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), - snackPosition: SnackPosition.TOP, - duration: const Duration(milliseconds: 800), - backgroundColor: Colors.yellow, - //icon:Image(image:AssetImage("assets/images/dora.png")) - ); - } - if(emailController.text.isEmpty || passwordController.text.isEmpty){ - Get.snackbar( - "no_values".tr, - "email_and_password_required".tr, - icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), - snackPosition: SnackPosition.TOP, - duration: const Duration(milliseconds: 800), - backgroundColor: Colors.yellow, - //icon:Image(image:AssetImage("assets/images/dora.png")) - ); - return; - } - indexController.is_loading.value = true; - indexController.register(emailController.text, passwordController.text, context); - }, - color: Colors.redAccent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40) - ), - child: Text("sign_up".tr,style: const TextStyle( - fontWeight: FontWeight.w600,fontSize: 16, - - ),), - ), - ), - ), - const SizedBox(height: 20,), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Flexible(child: Text("すでにアカウントをお持ちですか?")), - TextButton( - onPressed: (){ - Get.toNamed(AppPages.LOGIN); - }, - child: const Text("ログイン",style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 18 - ),), - ), - ], - ) - ], - + Text( + "sign_up".tr, + style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold), ), + const SizedBox(height: 20), + Text( + "create_account".tr, + style: TextStyle(fontSize: 15, color: Colors.grey[700]), + ), + const SizedBox(height: 30), + makeInput(label: "email".tr, controller: emailController), + //makeInput(label: "password".tr, controller: passwordController, obsureText: true), + //makeInput(label: "confirm_password".tr, controller: confirmPasswordController, obsureText: true), + makePasswordInput( + label: "password".tr, + controller: passwordController, + obscureText: _obscurePassword, + onToggleVisibility: () { + setState(() { + _obscurePassword = !_obscurePassword; + }); + }, + ), + makePasswordInput( + label: "confirm_password".tr, + controller: confirmPasswordController, + obscureText: _obscureConfirmPassword, + onToggleVisibility: () { + setState(() { + _obscureConfirmPassword = !_obscureConfirmPassword; + }); + }, + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: _handleRegister, + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.redAccent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)), + minimumSize: const Size(double.infinity, 60), + ), + child: Text("sign_up".tr, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16)), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible(child: Text("already_have_account".tr)), + TextButton( + onPressed: () => Get.toNamed(AppPages.LOGIN), + child: Text("login".tr, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18)), + ), + ], + ) ], ), ), @@ -145,36 +116,96 @@ class RegisterPage extends StatelessWidget { ), ); } + + Widget makePasswordInput({ + required String label, + required TextEditingController controller, + required bool obscureText, + required VoidCallback onToggleVisibility, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87)), + const SizedBox(height: 5), + TextField( + controller: controller, + obscureText: obscureText, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10), + enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)), + border: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)), + suffixIcon: IconButton( + icon: Icon( + obscureText ? Icons.visibility : Icons.visibility_off, + color: Colors.grey, + ), + onPressed: onToggleVisibility, + ), + ), + ), + const SizedBox(height: 20), + ], + ); + } + + void _handleRegister() { + if (passwordController.text != confirmPasswordController.text) { + _showErrorSnackbar("no_match".tr, "password_does_not_match".tr); + return; + } + if (emailController.text.isEmpty || passwordController.text.isEmpty) { + _showErrorSnackbar("no_values".tr, "email_and_password_required".tr); + return; + } + + indexController.isLoading.value = true; + try { + indexController.register( + emailController.text, + passwordController.text, + confirmPasswordController.text, + context + ); + // 登録が成功したと仮定し、ログインページに遷移 + //Get.offNamed(AppPages.LOGIN); + } catch (error) { + _showErrorSnackbar("registration_error".tr, error.toString()); + } finally { + indexController.isLoading.value = false; + } + } + + void _showErrorSnackbar(String title, String message) { + Get.snackbar( + title, + message, + backgroundColor: Colors.red, + colorText: Colors.white, + icon: const Icon(Icons.error_outline, size: 40.0, color: Colors.white), + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + ); + } } -Widget makeInput({label, required TextEditingController controller, obsureText = false}){ +Widget makeInput({required String label, required TextEditingController controller, bool obsureText = false}) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label,style:const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - color: Colors.black87 - ),), - const SizedBox(height: 5,), + Text(label, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87)), + const SizedBox(height: 5), TextField( controller: controller, obscureText: obsureText, decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 0,horizontal: 10), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: (Colors.grey[400])!, - ), - ), - border: OutlineInputBorder( - borderSide: BorderSide(color: (Colors.grey[400])! - ), + contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10), + enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)), + border: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)), ), ), - ), - const SizedBox(height: 30,) - + const SizedBox(height: 20), ], ); } + diff --git a/lib/pages/register/user_detail_page.dart b/lib/pages/register/user_detail_page.dart new file mode 100644 index 0000000..97d366c --- /dev/null +++ b/lib/pages/register/user_detail_page.dart @@ -0,0 +1,263 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/model/user.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/services/api_service.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:intl/intl.dart'; + +import 'package:gifunavi/widgets/custom_date_picker.dart'; // 追加: 日付フォーマット用 + +class UserDetailsEditPage extends StatefulWidget { + const UserDetailsEditPage({super.key}); + + @override + _UserDetailsEditPageState createState() => _UserDetailsEditPageState(); +} + +class _UserDetailsEditPageState extends State { + final _formKey = GlobalKey(); + final IndexController indexController = Get.find(); + late User _user; + final TextEditingController _firstnameController = TextEditingController(); + final TextEditingController _lastnameController = TextEditingController(); + final TextEditingController _dateOfBirthController = TextEditingController(); + late bool _female; + + @override + void initState() { + super.initState(); + _user = User.fromJson(indexController.currentUser[0]['user']); + _firstnameController.text = _user.firstname; + _lastnameController.text = _user.lastname; + _dateOfBirthController.text = _user.dateOfBirth != null + ? '${_user.dateOfBirth!.year}/${_user.dateOfBirth!.month.toString().padLeft(2, '0')}/${_user.dateOfBirth!.day.toString().padLeft(2, '0')}' + : ''; + _female = _user.female; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('個人情報の修正'), + automaticallyImplyLeading: false, + ), + body: Form( + key: _formKey, + child: ListView( + padding: const EdgeInsets.all(16.0), + children: [ + TextFormField( + controller: _lastnameController, + decoration: const InputDecoration( + labelText: '姓', + border: OutlineInputBorder(), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return '姓を入力してください'; + } + return null; + }, + ), + + const SizedBox(height: 16), + TextFormField( + controller: _firstnameController, + decoration: const InputDecoration( + labelText: '名', + border: OutlineInputBorder(), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return '名を入力してください'; + } + return null; + }, + ), + const SizedBox(height: 16), + TextFormField( + controller: _dateOfBirthController, + decoration: InputDecoration( + labelText: '生年月日 (YYYY/MM/DD)', + border: const OutlineInputBorder(), + hintText: 'YYYY/MM/DD', + suffixIcon: IconButton( + icon: const Icon(Icons.calendar_today), + onPressed: () => _selectDate(context), + ), + ), + keyboardType: TextInputType.number, // <=datetime, + onChanged: (value) { + // スラッシュを除去 + value = value.replaceAll('/', ''); + + if (value.length <= 8) { + String formattedValue = ''; + + // 年の処理(4桁) + if (value.length >= 4) { + formattedValue += '${value.substring(0, 4)}/'; + value = value.substring(4); + } else { + formattedValue += value; + value = ''; + } + + // 月の処理(2桁) + if (value.length >= 2) { + formattedValue += '${value.substring(0, 2)}/'; + value = value.substring(2); + } else if (value.isNotEmpty) { + formattedValue += value; + value = ''; + } + + // 残りの日付 + formattedValue += value; + + // 末尾のスラッシュを削除 + if (formattedValue.endsWith('/')) { + formattedValue = formattedValue.substring(0, formattedValue.length - 1); + } + + _dateOfBirthController.value = TextEditingValue( + text: formattedValue, + selection: TextSelection.collapsed(offset: formattedValue.length), + ); + } + }, + validator: (value) { + if (value == null || value.isEmpty) { + return '生年月日を入力してください'; + } + if (!RegExp(r'^\d{4}/\d{2}/\d{2}$').hasMatch(value)) { + return '正しい形式で入力してください (YYYY/MM/DD)'; + } + final date = DateTime.tryParse(value.replaceAll('/', '-')); + if (date == null) { + return '有効な日付を入力してください'; + } + if (date.isAfter(DateTime.now())) { + return '未来の日付は入力できません'; + } + return null; + }, + ), + const SizedBox(height: 16), + SwitchListTile( + title: const Text('性別'), + subtitle: Text(_female ? '女性' : '男性'), + value: _female, + onChanged: (bool value) { + setState(() { + _female = value; + }); + }, + ), + const SizedBox(height: 16), + TextFormField( + initialValue: _user.email, + decoration: const InputDecoration( + labelText: 'メールアドレス', + border: OutlineInputBorder(), + ), + enabled: false, + ), + const SizedBox(height: 16), + SwitchListTile( + title: const Text('アクティブ状態'), + value: _user.isActive, + onChanged: null, + ), + const SizedBox(height: 32), + ElevatedButton( + onPressed: _updateUserDetails, + child: const Text('更新'), + ), + ], + ), + ), + ); + } + + // 日付選択用の関数を追加 + Future _selectDate(BuildContext context) async { + final DateTime? picked = await showDialog( + context: context, + builder: (BuildContext context) { + return CustomDatePicker( + initialDate: _user.dateOfBirth ?? DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + currentDateText: _dateOfBirthController.text, + ); + }, + ); + if (picked != null) { + setState(() { + _dateOfBirthController.text = DateFormat('yyyy/MM/dd').format(picked); + }); + } + } + + void _updateUserDetails() async { + if (_formKey.currentState!.validate()) { + final dateOfBirth = DateTime.tryParse(_dateOfBirthController.text.replaceAll('/', '-')); + if (dateOfBirth == null || dateOfBirth.isAfter(DateTime.now())) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('生年月日が無効です', style: TextStyle(color: Colors.red))), + ); + return; + } + + // 13歳以上かどうかをチェック + final now = DateTime.now(); + final age = now.year - dateOfBirth.year - + (now.month > dateOfBirth.month || + (now.month == dateOfBirth.month && now.day >= dateOfBirth.day) ? 0 : 1); + + if (age < 13) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('13歳未満の方は登録できません', style: TextStyle(color: Colors.red))), + ); + return; + } + + User updatedUser = User( + id: _user.id, + email: _user.email, + firstname: _firstnameController.text, + lastname: _lastnameController.text, + dateOfBirth: dateOfBirth, + female: _female, + isActive: _user.isActive, + ); + + try { + bool success = await ApiService.updateUserDetail(updatedUser, indexController.currentUser[0]['token']); + if (success) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('個人情報が更新されました')), + ); + indexController.updateCurrentUser(updatedUser); + Get.offAllNamed(AppPages.INDEX); + //Get.back(); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('更新に失敗しました', style: TextStyle(color: Colors.red))), + ); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('エラーが発生しました: $e', style: const TextStyle(color: Colors.red))), + ); + } + } + } +} + + + + diff --git a/lib/pages/search/search_binding.dart b/lib/pages/search/search_binding.dart index 9b209ed..1612035 100644 --- a/lib/pages/search/search_binding.dart +++ b/lib/pages/search/search_binding.dart @@ -1,5 +1,5 @@ import 'package:get/get.dart'; -import 'package:rogapp/pages/search/search_controller.dart'; +import 'package:gifunavi/pages/search/search_controller.dart'; class SearchBinding extends Bindings { @override diff --git a/lib/pages/search/search_controller.dart b/lib/pages/search/search_controller.dart index 6102f6e..d2211e7 100644 --- a/lib/pages/search/search_controller.dart +++ b/lib/pages/search/search_controller.dart @@ -1,23 +1,21 @@ -import 'package:geojson/geojson.dart'; +import 'package:geojson_vi/geojson_vi.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; class SearchBarController extends GetxController { + List searchResults = [].obs; -List searchResults = [].obs; - - -@override + @override void onInit() { IndexController indexController = Get.find(); - if(indexController.locations.isNotEmpty){ - for(int i=0; i<= indexController.locations[0].collection.length - 1; i++){ - GeoJsonFeature p = indexController.locations[0].collection[i]; + if (indexController.locations.isNotEmpty) { + for (int i = 0; + i <= indexController.locations[0].features.length - 1; + i++) { + GeoJSONFeature p = indexController.locations[0].features[i]!; searchResults.add(p); } } super.onInit(); } - - -} \ No newline at end of file +} diff --git a/lib/pages/search/search_page.dart b/lib/pages/search/search_page.dart index b89b8fb..2199a11 100644 --- a/lib/pages/search/search_page.dart +++ b/lib/pages/search/search_page.dart @@ -1,28 +1,35 @@ import 'package:flutter/material.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; -import 'package:geojson/geojson.dart'; +import 'package:geojson_vi/geojson_vi.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/pages/search/search_controller.dart'; -import 'package:rogapp/widgets/bottom_sheet_new.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/pages/search/search_controller.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_start.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_goal.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_normal_point.dart'; +import 'package:gifunavi/widgets/bottom_sheet_new.dart'; class SearchPage extends StatelessWidget { - SearchPage({Key? key}) : super(key: key); + SearchPage({super.key}); SearchBarController searchController = Get.find(); IndexController indexController = Get.find(); - Image getImage(int index){ - if(searchController.searchResults[index].properties!["photos"] == null || searchController.searchResults[index].properties!["photos"] == ""){ + Image getImage(int index) { + if (searchController.searchResults[index].properties!["photos"] == null || + searchController.searchResults[index].properties!["photos"] == "") { return const Image(image: AssetImage('assets/images/empty_image.png')); - } - else{ + } else { return Image( - image: NetworkImage(searchController.searchResults[index].properties!["photos"]), - errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { + image: NetworkImage( + searchController.searchResults[index].properties!["photos"]), + errorBuilder: + (BuildContext context, Object exception, StackTrace? stackTrace) { return Image.asset("assets/images/empty_image.png"); }, - ); + ); } } @@ -33,64 +40,84 @@ class SearchPage extends StatelessWidget { elevation: 0, backgroundColor: Colors.white, leading: IconButton( - onPressed:(){ - Navigator.pop(context); - }, - icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black,)), - title: TypeAheadField( - textFieldConfiguration: const TextFieldConfiguration( - autofocus: true, - ), - suggestionsCallback: (pattern) async{ - return searchController.searchResults.where((GeoJsonFeature element) => element.properties!["location_name"].toString().contains(pattern)); - //return await + onPressed: () { + Get.back(); + }, + icon: const Icon( + Icons.arrow_back_ios_new, + color: Colors.black, + )), + centerTitle: true, + //title: const CupertinoSearchTextField(), + ), + body: SingleChildScrollView( + child: TypeAheadField( + // textFieldConfiguration: TextFieldConfiguration( + // autofocus: true, + // style: DefaultTextStyle.of(context).style.copyWith( + // fontStyle: FontStyle.normal, + // fontSize: 15.0, + // ), + // decoration: InputDecoration( + // border: const OutlineInputBorder(), + // hintText: "検索", + // prefixIcon: const Icon(Icons.search), + // suffixIcon: IconButton( + // icon: const Icon(Icons.clear), + // onPressed: () { + // // clear the text field + // }, + // ), + // ), + // ), + onSelected: (GeoJSONFeature suggestion) { + indexController.currentFeature.clear(); + indexController.currentFeature.add(suggestion); + DestinationController destinationController = + Get.find(); + Destination des = + destinationController.festuretoDestination(suggestion); + Get.back(); + + Widget bottomSheet = BottomSheetNew(destination: des); + /* + if (des.cp == -1 || des.cp == 0) { + bottomSheet = BottomSheetStart(destination: des); + } else if (des.cp == -2 || des.cp == 0) { + bottomSheet = BottomSheetGoal(destination: des); + } else { + bottomSheet = BottomSheetNormalPoint(destination: des); + } + */ + showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), + isScrollControlled: true, + context: context, + builder: ((context) => bottomSheet) + ); }, - itemBuilder: (context, GeoJsonFeature suggestion){ + + suggestionsCallback: (pattern) async { + return searchController.searchResults + .where((GeoJSONFeature element) => element + .properties!["location_name"] + .toString() + .contains(pattern)) + .toList(); + //return await + }, + itemBuilder: (context, GeoJSONFeature suggestion) { return ListTile( title: Text(suggestion.properties!["location_name"]), - subtitle: suggestion.properties!["category"] != null ? Text(suggestion.properties!["category"]) : const Text(""), + subtitle: suggestion.properties!["category"] != null + ? Text(suggestion.properties!["category"]) + : const Text(""), //leading: getImage(index), ); }, - onSuggestionSelected: (GeoJsonFeature suggestion){ - indexController.currentFeature.clear(); - indexController.currentFeature.add(suggestion); - Get.back(); - showModalBottomSheet( - isScrollControlled: true, - context: context, - //builder: (context) => BottomSheetWidget(), - builder:((context) => BottomSheetNew()) - ); - }, ), - //title: const CupertinoSearchTextField(), - ), - //body: - // Obx(() => - // ListView.builder( - // itemCount: searchController.searchResults.length, - // itemBuilder: (context, index){ - // return ListTile( - // title: searchController.searchResults[index].properties!["location_name"] != null ? Text(searchController.searchResults[index].properties!["location_name"]) : Text(""), - // subtitle: searchController.searchResults[index].properties!["category"] != null ? Text(searchController.searchResults[index].properties!["category"]) : Text(""), - // leading: getImage(index), - // onTap: (){ - // indexController.currentFeature.clear(); - // indexController.currentFeature.add(searchController.searchResults[index]); - // Get.back(); - // showModalBottomSheet( - // isScrollControlled: true, - // context: context, - // //builder: (context) => BottomSheetWidget(), - // builder:((context) => BottomSheetNew()) - // ); - // }, - // ); - // }, - // ), - // ) ); } -} \ No newline at end of file +} diff --git a/lib/pages/settings/settings_binding.dart b/lib/pages/settings/settings_binding.dart new file mode 100644 index 0000000..d242715 --- /dev/null +++ b/lib/pages/settings/settings_binding.dart @@ -0,0 +1,12 @@ +// lib/pages/settings/settings_binding.dart + +import 'package:get/get.dart'; +import 'package:gifunavi/pages/settings/settings_controller.dart'; + +class SettingsBinding extends Bindings { + @override + void dependencies() { + Get.put(SettingsController()); // これを修正 + //Get.lazyPut(() => SettingsController()); + } +} diff --git a/lib/pages/settings/settings_controller.dart b/lib/pages/settings/settings_controller.dart new file mode 100644 index 0000000..bbb9576 --- /dev/null +++ b/lib/pages/settings/settings_controller.dart @@ -0,0 +1,26 @@ +// lib/pages/settings/settings_controller.dart + +import 'package:get/get.dart'; +import 'package:gifunavi/widgets/map_widget.dart'; + + +class SettingsController extends GetxController { + var timerDuration = const Duration(seconds: 10).obs; + var autoReturnDisabled = false.obs; + final MapResetController mapResetController = Get.put(MapResetController()); + + void updateTimerDuration(int seconds) { + timerDuration.value = Duration(seconds: seconds); + } + + void setAutoReturnDisabled(bool value) { + autoReturnDisabled.value = value; + if (!value) { + resetIdleTimer(); + } + } + + void resetIdleTimer() { + mapResetController.resetIdleTimer!(); + } +} diff --git a/lib/pages/settings/settings_page.dart b/lib/pages/settings/settings_page.dart new file mode 100644 index 0000000..548f08c --- /dev/null +++ b/lib/pages/settings/settings_page.dart @@ -0,0 +1,63 @@ +// lib/pages/settings/settings_page.dart + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/pages/settings/settings_controller.dart'; + +class SettingsPage extends GetView { + const SettingsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('設定'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'timer_duration'.tr, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Obx( + () => controller.autoReturnDisabled.value + ? Container() + : Slider( + value: controller.timerDuration.value.inSeconds.toDouble(), + min: 5, + max: 30, + divisions: 5, + label: '${controller.timerDuration.value.inSeconds}秒', + onChanged: (value) { + controller.updateTimerDuration(value.toInt()); + }, + ), + ), + const SizedBox(height: 8), + const Text( + 'マップ操作がなければ自動的に現在地に復帰します。そのタイマー秒数を入れて下さい。チェックボックスをチェックすると、自動復帰は行われなくなります。', + style: TextStyle(fontSize: 14), + ), + const SizedBox(height: 16), + Obx( + () => CheckboxListTile( + title: const Text('自動復帰なし'), + value: controller.autoReturnDisabled.value, + onChanged: (value) { + controller.setAutoReturnDisabled(value!); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/subperf/subperf_page.dart b/lib/pages/subperf/subperf_page.dart index a1fe067..744d880 100644 --- a/lib/pages/subperf/subperf_page.dart +++ b/lib/pages/subperf/subperf_page.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; class SubPerfPage extends StatelessWidget { - SubPerfPage({Key? key}) : super(key: key); + SubPerfPage({super.key}); IndexController indexController = Get.find(); @override Widget build(BuildContext context) { + debugPrint("SubPerfPage ---->"); return Scaffold( appBar: AppBar( title: const Text("Select Sub Perfecture"), diff --git a/lib/pages/team/member_binding.dart b/lib/pages/team/member_binding.dart new file mode 100644 index 0000000..b06e7d2 --- /dev/null +++ b/lib/pages/team/member_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:gifunavi/pages/team/member_controller.dart'; +import 'package:gifunavi/services/api_service.dart'; + +class MemberBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => ApiService()); + Get.lazyPut(() => MemberController()); + } +} \ No newline at end of file diff --git a/lib/pages/team/member_controller.dart b/lib/pages/team/member_controller.dart new file mode 100644 index 0000000..29e77c8 --- /dev/null +++ b/lib/pages/team/member_controller.dart @@ -0,0 +1,270 @@ +// lib/controllers/member_controller.dart + +import 'package:get/get.dart'; +import 'package:gifunavi/model/user.dart'; +import 'package:gifunavi/services/api_service.dart'; +import 'package:gifunavi/pages/team/team_controller.dart'; + +class MemberController extends GetxController { + late final ApiService _apiService; + late final TeamController _teamController; + + final selectedMember = Rx(null); + int teamId = 0; + final member = Rx(null); + final email = ''.obs; + final firstname = ''.obs; + final lastname = ''.obs; + final female = false.obs; + final dateOfBirth = Rx(null); + final isLoading = false.obs; // isLoadingプロパティを追加 + final isActive = false.obs; + + //MemberController(this._apiService); + + @override + void onInit() { + super.onInit(); + _apiService = Get.find(); + _teamController = Get.find(); + ever(member, (_) => _initializeMemberData()); + loadInitialData(); + } + + bool get isDummyEmail => email.value.startsWith('dummy_'); + bool get isApproved => !email.value.startsWith('dummy_') && member.value?.isActive == true; + + Future loadInitialData() async { + try { + isLoading.value = true; + if (Get.arguments != null) { + if (Get.arguments['member'] != null) { + member.value = Get.arguments['member']; + } + if (Get.arguments['teamId'] != null) { + teamId = Get.arguments['teamId']; + } + } + // 他の必要な初期データの取得をここで行う + } catch (e) { + print('Error loading initial data: $e'); + } finally { + isLoading.value = false; + } + } + + + void _initializeMemberData() { + if (member.value != null) { + email.value = member.value!.email ?? ''; + firstname.value = member.value!.firstname ?? ''; + lastname.value = member.value!.lastname ?? ''; + dateOfBirth.value = member.value!.dateOfBirth; + } + } + + + void setSelectedMember(User member) { + this.member.value = member; + email.value = member.email ?? ''; + firstname.value = member.firstname ?? ''; + lastname.value = member.lastname ?? ''; + dateOfBirth.value = member.dateOfBirth; + female.value = member.female ?? false; + isActive.value = member.isActive ?? false; + } + + bool validateInputs() { + if (email.value.isNotEmpty && !isDummyEmail) { + return true; // Emailのみの場合は有効 + } + if (firstname.value.isEmpty || lastname.value.isEmpty || dateOfBirth.value == null) { + Get.snackbar('エラー', 'Emailが空の場合、姓名と生年月日及び性別は必須です', snackPosition: SnackPosition.BOTTOM); + return false; + } + return true; + } + + void updateFirstName(String value) { + firstname.value = value; + } + + void updateLastName(String value) { + lastname.value = value; + } + + + Future saveMember() async { + if (!validateInputs()) return false; + + try { + isLoading.value = true; + User updatedMember; + if (member.value == null || member.value!.id == null) { + // 新規メンバー作成 + updatedMember = await _apiService.createTeamMember( + teamId, + isDummyEmail ? null : email.value, // dummy_メールの場合はnullを送信 + firstname.value, + lastname.value, + dateOfBirth.value, + female.value, + ); + } else { + // 既存メンバー更新 + updatedMember = await _apiService.updateTeamMember( + teamId, + member.value!.id!, + firstname.value, + lastname.value, + dateOfBirth.value, + female.value, + ); + } + member.value = updatedMember; + await _teamController.updateTeamComposition(); + + Get.snackbar('成功', 'メンバーが保存されました', snackPosition: SnackPosition.BOTTOM); + return true; + } catch (e) { + print('Error saving member: $e'); + Get.snackbar('エラー', 'メンバーの保存に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM); + return false; + } finally { + isLoading.value = false; + } + } + + + String getDisplayName() { + if (!isActive.value && !isDummyEmail) { + final displayName = email.value.split('@')[0]; + return '$displayName(未承認)'; + } + return '${lastname.value} ${firstname.value}'.trim(); + } + + Future updateMember() async { + if (member.value == null) return false; + int? memberId = member.value?.id; + try { + final updatedMember = await _apiService.updateTeamMember( + teamId, + memberId, + firstname.value, + lastname.value, + dateOfBirth.value, + female.value, + ); + member.value = updatedMember; + return true; + } catch (e) { + print('Error updating member: $e'); + return false; + } + } + + Future deleteMember() async { + if (member.value == null || member.value!.id == null) { + Get.snackbar('エラー', 'メンバー情報が不正です', snackPosition: SnackPosition.BOTTOM); + return false; + } + + try { + isLoading.value = true; + await _apiService.deleteTeamMember(teamId, member.value!.id!); + + await _teamController.updateTeamComposition(); + + Get.snackbar('成功', 'メンバーが削除されました', snackPosition: SnackPosition.BOTTOM); + member.value = null; + + return true; + + } catch (e) { + print('Error deleting member: $e'); + Get.snackbar('エラー', 'メンバーの削除に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM); + isLoading.value = false; + return false; + } finally { + isLoading.value = false; + } + } + + Future resendInvitation() async { + if (isDummyEmail) { + Get.snackbar('エラー', 'ダミーメールアドレスには招待メールを送信できません', snackPosition: SnackPosition.BOTTOM); + return; + } + + if (member.value == null || member.value!.email == null) return; + int? memberId = member.value?.id; + try { + await _apiService.resendMemberInvitation(memberId!); + Get.snackbar('Success', 'Invitation resent successfully'); + } catch (e) { + print('Error resending invitation: $e'); + Get.snackbar('Error', 'Failed to resend invitation'); + } + } + + void updateEmail(String value) => email.value = value; + void updateFirstname(String value) => firstname.value = value; + void updateLastname(String value) => lastname.value = value; + void updateDateOfBirth(DateTime value) => dateOfBirth.value = value; + + String getMemberStatus() { + if (member.value == null) return ''; + if (member.value!.email == null) return '未登録'; + if (member.value!.isActive) return '承認済'; + return '招待中'; + } + + int calculateAge() { + if (dateOfBirth.value == null) return 0; + final today = DateTime.now(); + int age = today.year - dateOfBirth.value!.year; + if (today.month < dateOfBirth.value!.month || + (today.month == dateOfBirth.value!.month && today.day < dateOfBirth.value!.day)) { + age--; + } + return age; + } + + String calculateGrade() { + if (dateOfBirth.value == null) return '不明'; + + final today = DateTime.now(); + final birthDate = dateOfBirth.value!; + + // 今年の4月1日 + final thisYearSchoolStart = DateTime(today.year, 4, 1); + + // 生まれた年の翌年の4月1日(学齢期の始まり) + final schoolStartDate = DateTime(birthDate.year + 1, 4, 1); + + // 学齢期の開始からの年数 + int yearsFromSchoolStart = today.year - schoolStartDate.year; + + // 今年の4月1日より前なら1年引く + if (today.isBefore(thisYearSchoolStart)) { + yearsFromSchoolStart--; + } + + if (yearsFromSchoolStart < 7) return '未就学'; + if (yearsFromSchoolStart < 13) return '小${yearsFromSchoolStart - 6}'; + if (yearsFromSchoolStart < 16) return '中${yearsFromSchoolStart - 12}'; + if (yearsFromSchoolStart < 19) return '高${yearsFromSchoolStart - 15}'; + return '成人'; + } + + String getAgeAndGrade() { + final age = calculateAge(); + final grade = calculateGrade(); + return '$age歳/$grade'; + } + + bool isOver18() { + return calculateAge() >= 18; + } +} \ No newline at end of file diff --git a/lib/pages/team/member_detail_page.dart b/lib/pages/team/member_detail_page.dart new file mode 100644 index 0000000..54571d7 --- /dev/null +++ b/lib/pages/team/member_detail_page.dart @@ -0,0 +1,325 @@ +// lib/pages/team/member_detail_page.dart + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/pages/team/member_controller.dart'; +import 'package:gifunavi/pages/team/team_controller.dart'; +import 'package:intl/intl.dart'; // この行を追加 +import 'package:gifunavi/widgets/custom_date_picker.dart'; +// 追加 + +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/model/user.dart'; + +class MemberDetailPage extends StatefulWidget { + const MemberDetailPage({super.key}); + + @override + _MemberDetailPageState createState() => _MemberDetailPageState(); +} + +class _MemberDetailPageState extends State { + final MemberController controller = Get.find(); + final TeamController teamController = Get.find(); + late TextEditingController _firstNameController; + late TextEditingController _lastNameController; + late TextEditingController _emailController; + + + @override + void initState() { + super.initState(); + _initializeControllers(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + final mode = Get.arguments['mode']; + final member = Get.arguments['member']; + if (mode == 'edit' && member != null) { + controller.setSelectedMember(member); + } + }); + } + + void _initializeControllers() { + _firstNameController = + TextEditingController(text: controller.firstname.value); + _lastNameController = + TextEditingController(text: controller.lastname.value); + _emailController = TextEditingController(text: controller.email.value); + + controller.firstname.listen((value) { + if (_firstNameController.text != value) { + _firstNameController.value = TextEditingValue( + text: value, + selection: TextSelection.fromPosition( + TextPosition(offset: value.length)), + ); + } + }); + + controller.lastname.listen((value) { + if (_lastNameController.text != value) { + _lastNameController.value = TextEditingValue( + text: value, + selection: TextSelection.fromPosition( + TextPosition(offset: value.length)), + ); + } + }); + + controller.email.listen((value) { + if (_emailController.text != value) { + _emailController.value = TextEditingValue( + text: value, + selection: TextSelection.fromPosition( + TextPosition(offset: value.length)), + ); + } + }); + } + + Future _handleSaveAndNavigateBack_old() async { + bool success = await controller.saveMember(); + if (success) { + Get.until((route) => Get.currentRoute == AppPages.TEAM_DETAIL); + // スナックバーが表示されるのを待つ + await Future.delayed(const Duration(seconds: 1)); + + // 現在のスナックバーを安全に閉じる + if (Get.isSnackbarOpen) { + await Get.closeCurrentSnackbar(); + } + + // リストページに戻る + //Get.until((route) => Get.currentRoute == '/team'); + // または、リストページの具体的なルート名がある場合は以下を使用 + // Get.offNamed('/team'); + } + } + + Future _handleSaveAndNavigateBack() async { + + if (!controller.validateInputs()) return; + + try { + await controller.saveMember(); + await teamController.updateTeamComposition(); + + Get.until((route) => Get.currentRoute == AppPages.TEAM_DETAIL); + await Future.delayed(const Duration(seconds: 1)); + if (Get.isSnackbarOpen) { + await Get.closeCurrentSnackbar(); + } + } catch (e) { + print('Error saving member: $e'); + Get.snackbar('エラー', 'メンバーの保存に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM); + } + } + + + Future _handleDeleteAndNavigateBack() async { + final confirmed = await Get.dialog( + AlertDialog( + title: const Text('確認'), + content: const Text('このメンバーを削除してもよろしいですか?'), + actions: [ + TextButton( + child: const Text('キャンセル'), + onPressed: () => Get.back(result: false), + ), + TextButton( + child: const Text('削除'), + onPressed: () => Get.back(result: true), + ), + ], + ), + ); + if (confirmed == true) { + try { + if (controller.member.value != null && controller.member.value!.id != null) { + await teamController.removeMember(controller.member.value!.id!); + + // スナックバーの処理を安全に行う + await _safelyCloseSnackbar(); + + // 画面遷移 + Get.until((route) => Get.currentRoute == AppPages.TEAM_DETAIL); + + } else { + Get.snackbar('エラー', 'メンバー情報が不正です', snackPosition: SnackPosition.BOTTOM); + } + } catch (e) { + print('Error deleting member: $e'); + Get.snackbar('エラー', 'メンバーの削除に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM); + } + } + } + + Future _safelyCloseSnackbar() async { + if (Get.isSnackbarOpen) { + try { + await Get.closeCurrentSnackbar(); + } catch (e) { + print('Error closing snackbar: $e'); + } + } + } + + @override + Widget build(BuildContext context) { + final mode = Get.arguments['mode'] as String; + //final member = Get.arguments['member']; + final teamId = Get.arguments['teamId'] as int; + + /* + return MaterialApp( // MaterialApp をここに追加 + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: [ + const Locale('ja', 'JP'), + ], + home:Scaffold( + */ + return Scaffold( + appBar: AppBar( + title: Text(mode == 'new' ? 'メンバー追加' : 'メンバー詳細'), + ), + body: Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + // TextEditingControllerをObxの中で作成 + final emailController = TextEditingController(text: controller.email.value); + // カーソル位置を保持 + emailController.selection = TextSelection.fromPosition( + TextPosition(offset: controller.email.value.length), + ); + + return Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (mode == 'new') + TextFormField( + controller: _emailController, + onChanged: (value) => controller.updateEmail(value), + decoration: const InputDecoration(labelText: 'メールアドレス'), + keyboardType: TextInputType.emailAddress, // メールアドレス用のキーボードを表示 + autocorrect: false, // 自動修正を無効化 + enableSuggestions: false, + ) + else if (controller.isDummyEmail) + const Text('メールアドレス: (メアド無し)') + else + Text('メールアドレス: ${controller.email.value}'), + + if (controller.email.value.isEmpty || controller.isDummyEmail || mode == 'edit') ...[ + TextFormField( + decoration: const InputDecoration(labelText: '姓'), + controller: _lastNameController, + onChanged: (value) => controller.updateLastName(value), + + //controller: TextEditingController(text: controller.lastname.value), + ), + TextFormField( + decoration: const InputDecoration(labelText: '名'), + controller: _firstNameController, + //onChanged: (value) => controller.firstname.value = value, + onChanged: (value) => controller.updateFirstName(value), + //controller: TextEditingController(text: controller.firstname.value), + ), + // 生年月日 + if (controller.isDummyEmail || !controller.isOver18()) + ListTile( + title: const Text('生年月日'), + subtitle: Text(controller.dateOfBirth.value != null + ? '${DateFormat('yyyy年MM月dd日').format(controller.dateOfBirth.value!)} (${controller.getAgeAndGrade()})' + : '未設定'), + onTap: () async { + final date = await showDialog( + context: context, + builder: (BuildContext context) { + return CustomDatePicker( + initialDate: controller.dateOfBirth.value ?? DateTime.now(), + firstDate: DateTime(1920), + lastDate: DateTime.now(), + currentDateText: controller.dateOfBirth.value != null + ? DateFormat('yyyy/MM/dd').format(controller.dateOfBirth.value!) + : '', + ); + }, + ); + if (date != null) { + controller.dateOfBirth.value = date; + } + }, + ) + else + const Text('18歳以上'), + + SwitchListTile( + title: const Text('性別'), + subtitle: Text(controller.female.value ? '女性' : '男性'), + value: controller.female.value, + onChanged: (value) => controller.female.value = value, + ), + ], + ]), + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: _handleDeleteAndNavigateBack, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + ), + child: const Text('削除'), + ), + if (!controller.isDummyEmail && !controller.isApproved && mode == 'edit') + ElevatedButton( + onPressed: () => controller.resendInvitation(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + ), + child: const Text('招待再送信'), + ), + ElevatedButton( + onPressed: _handleSaveAndNavigateBack, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + ), + child: Text(controller.isDummyEmail ? '保存' : + (mode == 'new' && !controller.isDummyEmail) ? '保存・招待' : + '保存'), + ), + ], + ), + ), + ], + ); + }), + ); + } + + @override + void dispose() { + _firstNameController.dispose(); + _lastNameController.dispose(); + _emailController.dispose(); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/pages/team/team_binding.dart b/lib/pages/team/team_binding.dart new file mode 100644 index 0000000..e41d819 --- /dev/null +++ b/lib/pages/team/team_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:gifunavi/pages/team/team_controller.dart'; +import 'package:gifunavi/services/api_service.dart'; + +class TeamBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => ApiService()); + Get.lazyPut(() => TeamController()); + } +} \ No newline at end of file diff --git a/lib/pages/team/team_controller.dart b/lib/pages/team/team_controller.dart new file mode 100644 index 0000000..cae0cf8 --- /dev/null +++ b/lib/pages/team/team_controller.dart @@ -0,0 +1,692 @@ +// lib/controllers/team_controller.dart +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/model/team.dart'; +import 'package:gifunavi/model/category.dart'; +import 'package:gifunavi/model/user.dart'; +import 'package:gifunavi/model/entry.dart'; + +import 'package:gifunavi/services/api_service.dart'; +import 'package:gifunavi/widgets/category_change_dialog.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/pages/entry/entry_controller.dart'; + +import 'package:gifunavi/model/event.dart'; + + +class TeamController extends GetxController { + late final ApiService _apiService; + late final EntryController _entryController; + + final teams = [].obs; + final categories = [].obs; + final RxList teamMembers = [].obs; + final teamEntries = [].obs; + + final selectedCategory = Rx(null); + final selectedTeam = Rx(null); + final currentUser = Rx(null); + + final teamName = ''.obs; + final isLoading = false.obs; + final error = RxString(''); + + @override + void onInit() async { + super.onInit(); + try { + _apiService = Get.find(); + + if (!Get.isRegistered()) { + Get.put(EntryController()); + } + _entryController = Get.find(); + + await loadInitialData(); + } catch (e) { + error.value = e.toString(); + print('Error in TeamController onInit: $e'); + } + } + + Future loadInitialData() async { + try { + isLoading.value = true; + await Future.wait([ + fetchCategories(), + fetchTeams(), + getCurrentUser(), + ]); + if (categories.isNotEmpty && selectedCategory.value == null) { + selectedCategory.value = categories.first; + } + } catch (e) { + error.value = e.toString(); + } finally { + isLoading.value = false; + } + } + + + + void setSelectedTeam(Team team) { + selectedTeam.value = team; + teamName.value = team.teamName; + if (categories.isNotEmpty) { + selectedCategory.value = categories.firstWhere( + (category) => category.id == team.category.id, + orElse: () => categories.first, + ); + } else { + // カテゴリリストが空の場合、teamのカテゴリをそのまま使用 + selectedCategory.value = team.category; + } + fetchTeamMembers(team.id); + } + + void resetForm() { + selectedTeam.value = null; + teamName.value = ''; + if (categories.isNotEmpty) { + selectedCategory.value = categories.first; + } else { + selectedCategory.value = null; + // カテゴリが空の場合、エラーメッセージをセット + error.value = 'カテゴリデータが取得できませんでした。'; + } teamMembers.clear(); + } + + void cleanupForNavigation() { + selectedTeam.value = null; + teamName.value = ''; + selectedCategory.value = categories.isNotEmpty ? categories.first : null; + teamMembers.clear(); + //teamMembersはクリアしない + // 必要に応じて他のクリーンアップ処理を追加 + } + + Future fetchTeams() async { + try { + isLoading.value = true; + final fetchedTeams = await _apiService.getTeams(); + teams.assignAll(fetchedTeams); + } catch (e) { + error.value = 'チームの取得に失敗しました: $e'; + print('Error fetching teams: $e'); + } finally { + isLoading.value = false; + } + } + + bool checkIfUserHasEntryData(){ + if (teams.isEmpty) { + return false; + }else { + return true; + } + } + + Future fetchCategories() async { + try { + final fetchedCategories = await _apiService.getCategories(); + categories.assignAll(fetchedCategories); + print("Fetched categories: ${categories.length}"); // デバッグ用 + + } catch (e) { + print('Error fetching categories: $e'); + } + } + + Future getCurrentUser() async { + try { + final user = await _apiService.getCurrentUser(); + currentUser.value = user; + } catch (e) { + print('Error getting current user: $e'); + } + } + + Future createTeam(String teamName, int categoryId) async { + final newTeam = await _apiService.createTeam(teamName, categoryId); + + // 自分自身をメンバーとして自動登録 + await _apiService.createTeamMember( + newTeam.id, + currentUser.value?.email, + currentUser.value!.firstname, + currentUser.value!.lastname, + currentUser.value?.dateOfBirth, + currentUser.value?.female, + ); + + return newTeam; + } + + Future updateTeam(int teamId, String teamName, int categoryId) async { + // APIサービスを使用してチームを更新 + final updatedTeam = await _apiService.updateTeam(teamId, teamName, categoryId); + return updatedTeam; + } + + Future deleteTeam(int teamId) async { + bool confirmDelete = await Get.dialog( + AlertDialog( + title: const Text('チーム削除の確認'), + content: const Text('このチームとそのすべてのメンバーを削除しますか?この操作は取り消せません。'), + actions: [ + TextButton( + child: const Text('キャンセル'), + onPressed: () => Get.back(result: false), + ), + TextButton( + child: const Text('削除'), + onPressed: () => Get.back(result: true), + ), + ], + ), + ) ?? false; + + if (confirmDelete) { + try { + // まず、チームのメンバーを全て削除 + await _apiService.deleteAllTeamMembers(teamId); + + // その後、チームを削除 + await _apiService.deleteTeam(teamId); + + // ローカルのチームリストを更新 + teams.removeWhere((team) => team.id == teamId); + + /* + Get.snackbar( + '成功', + 'チームとそのメンバーが削除されました', + backgroundColor: Colors.green, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM, + ); + */ + + // チームリスト画面に戻る + Get.back(); + + } catch (e) { + print('Error deleting team and members: $e'); + Get.snackbar('エラー', 'チームとメンバーの削除に失敗しました'); + } + } + } + + Future fetchTeamMembers(int teamId) async { + try { + isLoading.value = true; + final members = await _apiService.getTeamMembers(teamId); + teamMembers.assignAll(members); + teamMembers.refresh(); // 明示的に更新を通知 + } catch (e) { + error.value = 'メンバーの取得に失敗しました: $e'; + print('Error fetching team members: $e'); + } finally { + isLoading.value = false; + } + } + + Future updateMember(teamId, User member) async { + try { + isLoading.value = true; + await _apiService.updateTeamMember(teamId,member.id, member.firstname, member.lastname, member.dateOfBirth, member.female); + await fetchTeamMembers(selectedTeam.value!.id); + } catch (e) { + error.value = 'メンバーの更新に失敗しました: $e'; + print('Error updating member: $e'); + } finally { + isLoading.value = false; + } + } + + Future deleteMember(int memberId, int teamId) async { + try { + isLoading.value = true; + await _apiService.deleteTeamMember(teamId,memberId); + await fetchTeamMembers(teamId); + } catch (e) { + error.value = 'メンバーの削除に失敗しました: $e'; + print('Error deleting member: $e'); + } finally { + isLoading.value = false; + } + } + + + // Future addMember(User member, int teamId) async { + // try { + // isLoading.value = true; + // await _apiService.createTeamMember(teamId, member.email, member.firstname, member.lastname, member.dateOfBirth); + // await fetchTeamMembers(teamId); + // } catch (e) { + // error.value = 'メンバーの追加に失敗しました: $e'; + // print('Error adding member: $e'); + // } finally { + // isLoading.value = false; + // } + // } + + + void updateTeamName(String value) { + teamName.value = value; + } + + void updateCategory(NewCategory? value) { + if (value != null) { + selectedCategory.value = value; + } + } + //void updateCategory(NewCategory? value) { + // if (value != null) { + // selectedCategory.value = categories.firstWhere( + // (category) => category.id == value.id, + // orElse: () => value, + // ); + // } + //} + + Future saveTeam() async { + try { + isLoading.value = true; + if (selectedCategory.value == null) { + throw Exception('カテゴリを選択してください'); + } + + if (selectedTeam.value == null) { + await createTeam(teamName.value, selectedCategory.value!.id); + } else { + await updateTeam(selectedTeam.value!.id, teamName.value, selectedCategory.value!.id); + } + + // サーバーから最新のデータを再取得 + await fetchTeams(); + update(); // UIを強制的に更新 + } catch (e) { + error.value = 'チームの保存に失敗しました: $e'; + + } finally { + isLoading.value = false; + } + } + + + Future deleteSelectedTeam() async { + if (selectedTeam.value != null) { + await deleteTeam(selectedTeam.value!.id); + selectedTeam.value = null; + } + } + + List getFilteredCategories_old() { + //List teamMembers = getCurrentTeamMembers(); + return categories.where((category) { + return isCategoryValid(category, teamMembers); + }).toList(); + } + + List getFilteredCategories() { + if (teamMembers.isEmpty && currentUser.value != null) { + // ソロの場合 + String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子'; + return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); + } else { + bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger); + String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般'; + return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); + } + } + + bool isElementarySchoolOrYounger(User user) { + final now = DateTime.now(); + final age = now.year - user.dateOfBirth!.year; + return age <= 12; + } + + bool isCategoryValid(NewCategory category, List teamMembers) { + int maleCount = teamMembers.where((member) => !member.female).length; + int femaleCount = teamMembers.where((member) => member.female).length; + int totalCount = teamMembers.length; + + bool isValidGender = category.female ? (femaleCount == totalCount) : true; + bool isValidMemberCount = totalCount == category.numOfMember; + bool isValidFamily = category.family ? areAllMembersFamily(teamMembers) : true; + + return isValidGender && isValidMemberCount && isValidFamily; + } + + bool areAllMembersFamily(List teamMembers) { + // 家族かどうかを判断するロジック(例: 同じ姓を持つメンバーが2人以上いる場合は家族とみなす) + Set familyNames = teamMembers.map((member) => member.lastname).toSet(); + return familyNames.length < teamMembers.length; + } + + + Future updateTeamComposition() async { + NewCategory? oldCategory = selectedCategory.value; + String? oldTime = oldCategory?.time; + + // メンバーリストの最新状態を取得 + await fetchTeamMembers(selectedTeam.value!.id); + + List eligibleCategories = []; + if (teamMembers.isEmpty || teamMembers.length == 1) { + if (currentUser.value != null) { + String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子'; + eligibleCategories = categories.where((c) => c.baseCategory == baseCategory).toList(); + } + } else { + bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger); + String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般'; + eligibleCategories = categories.where((c) => c.baseCategory == baseCategory).toList(); + } + + // 同じ時間のカテゴリを優先的に選択 + NewCategory? newCategory = eligibleCategories.firstWhereOrNull((c) => c.time == oldTime); + + if (newCategory == null && eligibleCategories.isNotEmpty) { + // 同じ時間のカテゴリがない場合、最初のカテゴリを選択 + newCategory = eligibleCategories.first; + + // 警告メッセージを表示 + Get.dialog( + AlertDialog( + title: Text('カテゴリ変更の通知'), + content: Text('旧カテゴリの時間($oldTime)と一致するカテゴリがないため、${newCategory.time ?? "時間指定なし"}を選択しました。'), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () => Get.back(), + ), + ], + ), + ); + } + + if (newCategory != null && oldCategory != newCategory) { + selectedCategory.value = newCategory; + + // チームのカテゴリを更新 + if (selectedTeam.value != null) { + try { + final updatedTeam = await updateTeam(selectedTeam.value!.id, selectedTeam.value!.teamName, newCategory.id); + selectedTeam.value = updatedTeam; + + // チームリストも更新 + final index = teams.indexWhere((team) => team.id == updatedTeam.id); + if (index != -1) { + teams[index] = updatedTeam; + } + + // エントリーの締め切りチェック + bool hasClosedEntries = await checkForClosedEntries(selectedTeam.value!.id); + if (hasClosedEntries) { + Get.dialog( + AlertDialog( + title: Text('警告'), + content: Text('締め切りを過ぎたエントリーがあります。カテゴリ変更が必要な場合は事務局にお問い合わせください。'), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () => Get.back(), + ), + ], + ), + ); + } else { + await checkAndHandleCategoryChange(oldCategory!, newCategory); + } + } catch (e) { + print('Error updating team category: $e'); + Get.snackbar('エラー', 'チームのカテゴリ更新に失敗しました'); + } + } + } + } + + Future checkForClosedEntries(int teamId) async { + try { + final entries = await _apiService.getEntries(); + return entries.any((entry) => !isEntryEditable(entry.event)); + } catch (e) { + print('Error checking for closed entries: $e'); + return false; + } + } + + bool isEntryEditable(Event event) { + return DateTime.now().isBefore(event.deadlineDateTime); + } + + + + Future updateTeamComposition_old() async { + NewCategory? oldCategory = selectedCategory.value; + String? oldTime = oldCategory?.time; + + // メンバーリストの最新状態を取得 + await fetchTeamMembers(selectedTeam.value!.id); + + List eligibleCategories = []; + if (teamMembers.isEmpty) { + if (currentUser.value != null) { + String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子'; + eligibleCategories = categories.where((c) => c.baseCategory == baseCategory).toList(); + } + } else { + bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger); + String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般'; + eligibleCategories = categories.where((c) => c.baseCategory == baseCategory).toList(); + } + + // 同じ時間のカテゴリを優先的に選択 + NewCategory? newCategory = eligibleCategories.firstWhereOrNull((c) => c.time == oldTime); + + if (newCategory == null && eligibleCategories.isNotEmpty) { + // 同じ時間のカテゴリがない場合、最初のカテゴリを選択 + newCategory = eligibleCategories.first; + + // 警告メッセージを表示 + Get.dialog( + AlertDialog( + title: Text('カテゴリ変更の通知'), + content: Text('旧カテゴリの時間($oldTime)と一致するカテゴリがないため、${newCategory.time ?? "時間指定なし"}を選択しました。'), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () => Get.back(), + ), + ], + ), + ); + } + + if (newCategory != null && oldCategory != newCategory) { + selectedCategory.value = newCategory; + + // チームのカテゴリを更新 + if (selectedTeam.value != null) { + try { + final updatedTeam = await updateTeam(selectedTeam.value!.id, selectedTeam.value!.teamName, newCategory.id); + selectedTeam.value = updatedTeam; + + // チームリストも更新 + final index = teams.indexWhere((team) => team.id == updatedTeam.id); + if (index != -1) { + teams[index] = updatedTeam; + } + } catch (e) { + print('Error updating team category: $e'); + Get.snackbar('エラー', 'チームのカテゴリ更新に失敗しました'); + } + } + + await checkAndHandleCategoryChange(oldCategory!, newCategory); + } + } + + // メンバーの追加後に呼び出すメソッド + Future addMember(User newMember) async { + try { + await _apiService.createTeamMember(selectedTeam.value!.id, newMember.email, newMember.firstname, newMember.lastname, newMember.dateOfBirth, newMember.female); + await updateTeamComposition(); + } catch (e) { + print('Error adding member: $e'); + Get.snackbar('エラー', 'メンバーの追加に失敗しました'); + } + } + + // メンバーの削除後に呼び出すメソッド + Future removeMember(int memberId) async { + try { + await _apiService.deleteTeamMember(selectedTeam.value!.id, memberId); + + // selectedTeamからメンバーを削除 + if (selectedTeam.value != null) { + selectedTeam.value!.members.removeWhere((member) => member.id == memberId); + selectedTeam.refresh(); + } + + // メンバー削除後にチーム構成を更新 + await updateTeamComposition(); + + // teamMembersを更新 + await fetchTeamMembers(selectedTeam.value!.id); + } catch (e) { + print('Error removing member: $e'); + Get.snackbar('エラー', 'メンバーの削除に失敗しました'); + } + } + + Future checkAndHandleCategoryChange(NewCategory oldCategory, NewCategory newCategory) async { + try { + if (selectedTeam.value == null) { + print('No team selected'); + return; + } + + // エントリーの存在を確認 + bool hasEntries = await checkIfTeamHasEntries(selectedTeam.value!.id); + + if (hasEntries) { + bool shouldCreateNewTeam = await Get.dialog( + CategoryChangeDialog( + oldCategory: oldCategory.categoryName, + newCategory: newCategory.categoryName, + ), + ) ?? false; + + if (shouldCreateNewTeam) { + await createNewTeamWithCurrentMembers(); + } else { + await updateEntriesWithNewCategory(); + } + }else{ + // エントリーが存在しない場合は、カテゴリの更新のみを行う + await updateTeamCategory(newCategory); + } + } catch (e) { + print('Error in checkAndHandleCategoryChange: $e'); + Get.snackbar('エラー', 'カテゴリ変更の処理中にエラーが発生しました'); + } + } + + Future checkIfTeamHasEntries(int teamId) async { + try { + final entries = await _apiService.getTeamEntries(teamId); + return entries.isNotEmpty; + } catch (e) { + print('Error checking if team has entries: $e'); + return false; + } + } + + Future updateTeamCategory(NewCategory newCategory) async { + try { + if (selectedTeam.value != null) { + final updatedTeam = await updateTeam(selectedTeam.value!.id, selectedTeam.value!.teamName, newCategory.id); + selectedTeam.value = updatedTeam; + + // チームリストも更新 + final index = teams.indexWhere((team) => team.id == updatedTeam.id); + if (index != -1) { + teams[index] = updatedTeam; + } + + Get.snackbar('成功', 'チームのカテゴリを更新しました'); + } + } catch (e) { + print('Error updating team category: $e'); + Get.snackbar('エラー', 'チームのカテゴリ更新に失敗しました'); + } + } + + Future fetchTeamEntries(int teamId) async { + try { + final fetchedEntries = await _apiService.getEntries(); + teamEntries.assignAll(fetchedEntries); + } catch (e) { + print('Error fetching team entries: $e'); + teamEntries.clear(); // エラーが発生した場合、エントリーリストをクリア + //Get.snackbar('エラー', 'チームのエントリー取得に失敗しました'); + } + } + + + Future createNewTeamWithCurrentMembers() async { + String newTeamName = '${selectedTeam.value!.teamName}_${DateTime.now().millisecondsSinceEpoch}'; + + try { + Team newTeam = await _apiService.createTeam(newTeamName, selectedCategory.value!.id); + + for (var member in teamMembers) { + await _apiService.createTeamMember( + newTeam.id, + member.email, + member.firstname, + member.lastname, + member.dateOfBirth, + member.female, + ); + } + + Get.offNamed(AppPages.TEAM_DETAIL, arguments: {'mode': 'edit', 'team': newTeam}); + } catch (e) { + print('Error creating new team: $e'); + Get.snackbar('エラー', '新しいチームの作成に失敗しました'); + } + } + + Future updateEntriesWithNewCategory() async { + try { + for (var entry in teamEntries) { + //await _apiService.updateEntry(entry.id, selectedCategory.value!.id); + final updatedEntry = await _apiService.updateEntry( + entry.id, + entry.team.id, + entry.event.id, + selectedCategory.value!.id, + entry.date!, + entry.zekkenNumber, + ); + } + Get.snackbar('成功', 'エントリーのカテゴリを更新しました'); + } catch (e) { + print('Error updating entries: $e'); + Get.snackbar('エラー', 'エントリーの更新に失敗しました'); + } + } + + void updateCurrentUserGender(bool isFemale) { + if (currentUser.value != null) { + currentUser.value!.female = isFemale; + updateTeamComposition(); + } + } + + +} \ No newline at end of file diff --git a/lib/pages/team/team_detail_page.dart b/lib/pages/team/team_detail_page.dart new file mode 100644 index 0000000..5e63022 --- /dev/null +++ b/lib/pages/team/team_detail_page.dart @@ -0,0 +1,263 @@ +// lib/pages/team/team_detail_page.dart + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/pages/team/team_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; +import 'package:gifunavi/model/team.dart'; +import 'package:gifunavi/model/category.dart'; + +class TeamDetailPage extends StatefulWidget { + const TeamDetailPage({super.key}); + + @override + _TeamDetailPageState createState() => _TeamDetailPageState(); +} + +class _TeamDetailPageState extends State { + late TeamController controller; + late TextEditingController _teamNameController = TextEditingController(); + final RxString mode = ''.obs; + final Rx team = Rx(null); + Worker? _teamNameWorker; + + @override + void initState() { + super.initState(); + controller = Get.find(); + _teamNameController = TextEditingController(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + _initializeData(); + }); + } + + void _initializeData() { + final args = Get.arguments; + if (args != null && args is Map) { + mode.value = args['mode'] as String? ?? ''; + team.value = args['team'] as Team?; + + if (mode.value == 'edit' && team.value != null) { + controller.setSelectedTeam(team.value!); + controller.fetchTeamMembers(team.value!.id); // メンバーを取得 + } else { + controller.resetForm(); + } + } else { + // 引数がない場合は新規作成モードとして扱う + mode.value = 'new'; + controller.resetForm(); + } + + _teamNameController.text = controller.teamName.value; + + // Use ever instead of direct listener + _teamNameWorker = ever(controller.teamName, (String value) { + if (_teamNameController.text != value) { + _teamNameController.text = value; + } + }); + } + + @override + void dispose() { + _teamNameWorker?.dispose(); + _teamNameController.dispose(); + //controller.resetForm(); // Reset the form when leaving the page + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + Get.arguments['mode'] == 'new' ? '新規チーム作成' : 'チーム詳細'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + controller.cleanupForNavigation(); + Get.back(); + }, + ), + actions: [ + IconButton( + icon: const Icon(Icons.save), + onPressed: () async { + try { + await controller.saveTeam(); + Get.back(); + } catch (e) { + Get.snackbar('エラー', e.toString(), + snackPosition: SnackPosition.BOTTOM); + } + }, + ), + Obx(() { + if (mode.value == 'edit') { + return IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + try { + await controller.deleteSelectedTeam(); + Get.back(); + } catch (e) { + Get.snackbar('エラー', e.toString(), + snackPosition: SnackPosition.BOTTOM); + } + }, + ); + } else { + return const SizedBox.shrink(); + } + }), + ], + ), + body: Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + + final filteredCategories = controller.getFilteredCategories(); + final categoriesToDisplay = filteredCategories.isEmpty + ? controller.categories + : filteredCategories; + + // 選択されているカテゴリが表示リストに含まれていない場合、最初の項目を選択する + if (controller.selectedCategory.value == null || + !categoriesToDisplay.contains(controller.selectedCategory.value)) { + controller.updateCategory(categoriesToDisplay.isNotEmpty + ? categoriesToDisplay.first + : null); + } + + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx(() => + TextFormField( + decoration: const InputDecoration(labelText: 'チーム名'), + controller: TextEditingController(text: controller + .selectedTeam.value?.teamName ?? ''), + onChanged: (value) => controller.updateTeamName(value), + ), + ), + const SizedBox(height: 16), + if (categoriesToDisplay.isEmpty) + const Text('カテゴリデータを読み込めませんでした。', + style: TextStyle(color: Colors.red)) + else + Obx(() => + DropdownButtonFormField( + decoration: const InputDecoration( + labelText: 'カテゴリ'), + value: controller.selectedCategory.value, + items: categoriesToDisplay.map((category) => + DropdownMenuItem( + value: category, + child: Text(category.categoryName), + )).toList(), + onChanged: (value) => controller.updateCategory(value), + ), + ), + + if (filteredCategories.isEmpty && + controller.categories.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + '注意: チーム構成に適さないカテゴリーも表示されています。', + style: TextStyle(color: Colors.orange[700], fontSize: 12), + ), + ), + + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Text('所有者: ${controller.currentUser.value?.email ?? + "未設定"}'), + ), + + if (mode.value == 'edit') ...[ + const SizedBox(height: 24), + const Text('メンバーリスト', style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + Obx(() { + final teamMembers = controller.teamMembers; + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: teamMembers.length, + itemBuilder: (context, index) { + final member = teamMembers[index]; + + final isDummyEmail = member.email?.startsWith( + 'dummy_') ?? false; + final isCurrentUser = member.email == + controller.currentUser.value?.email; + final displayName = isDummyEmail + ? '${member.lastname} ${member.firstname}' + : member.isActive + ? '${member.lastname} ${member.firstname}' + : member.email?.split('@')[0] ?? ''; //(未承認)'; + + return ListTile( + title: Text( + '$displayName${isCurrentUser ? ' (自分)' : ''}'), + subtitle: isDummyEmail + ? const Text('Email未設定') + : null, + + onTap: isCurrentUser ? null : () async { + final result = await Get.toNamed( + AppPages.MEMBER_DETAIL, + arguments: { + 'mode': 'edit', + 'member': member, + 'teamId': controller.selectedTeam.value?.id + }, + ); + + await controller.fetchTeamMembers( + controller.selectedTeam.value!.id); + }, + ); + }, + ); + }), + const SizedBox(height: 16), + ElevatedButton( + child: const Text('新しいメンバーを追加'), + onPressed: () async { + final result = await Get.toNamed( + AppPages.MEMBER_DETAIL, + arguments: { + 'mode': 'new', + 'teamId': controller.selectedTeam.value?.id + }, + ); + if (result == true && controller.selectedTeam.value != null) { + await controller.fetchTeamMembers(controller.selectedTeam.value!.id); + controller.teamMembers.refresh(); // 明示的に更新を通知 + } + }, + ), + ], + ], + ), + ), + ); + }), + ); + } +} + + + + + + diff --git a/lib/pages/team/team_list_page.dart b/lib/pages/team/team_list_page.dart new file mode 100644 index 0000000..f21e774 --- /dev/null +++ b/lib/pages/team/team_list_page.dart @@ -0,0 +1,57 @@ +// lib/pages/team/team_list_page.dart + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/pages/team/team_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; + +class TeamListPage extends GetWidget { + const TeamListPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('チーム管理'), + actions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () => Get.toNamed(AppPages.TEAM_DETAIL, arguments: {'mode': 'new'}), + ), + ], + ), + body: Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } else if (controller.error.value.isNotEmpty) { + return Center(child: Text(controller.error.value)); + } else if (controller.teams.isEmpty) { + return const Center(child: Text('チームがありません')); + } else { + return RefreshIndicator( + onRefresh: controller.fetchTeams, + child: ListView.builder( + itemCount: controller.teams.length, + itemBuilder: (context, index) { + final team = controller.teams[index]; + return ListTile( + title: Text(team.teamName), + subtitle: Text('${team.category.categoryName} '), + onTap: () async { + await Get.toNamed( + AppPages.TEAM_DETAIL, + arguments: {'mode': 'edit', 'team': team}, + ); + controller.fetchTeams(); + }, + ); + }, + ), + ); + } + }), + ); + } +} + + diff --git a/lib/provider/auth_provider.dart b/lib/provider/auth_provider.dart new file mode 100644 index 0000000..d529ed5 --- /dev/null +++ b/lib/provider/auth_provider.dart @@ -0,0 +1,31 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:gifunavi/model/auth_user.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +final authUserStateProvider = + StateNotifierProvider((ref) { + return AuthUserState(); +}); + +class AuthUserState extends StateNotifier { + AuthUserState() : super(AuthUser()); + + Future saveToDevice(String val) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setString("user_token", val); + } + + Future tokenFromDevice() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.getString("user_token"); + } + + Future addLogin(AuthUser user) async { + state = user; + await saveToDevice(user.auth_token!); + } + + void removeLogin() { + state = AuthUser(); + } +} diff --git a/lib/provider/cached_tile_provider.dart b/lib/provider/cached_tile_provider.dart new file mode 100644 index 0000000..bd0d783 --- /dev/null +++ b/lib/provider/cached_tile_provider.dart @@ -0,0 +1,24 @@ +import 'package:path_provider/path_provider.dart'; +import 'dart:io'; + +class CacheProvider { + static Future initialize() async { + final cacheDirectory = await getTemporaryDirectory(); + final mapCacheDir = Directory('${cacheDirectory.path}/map_cache'); + + if (!await mapCacheDir.exists()) { + await mapCacheDir.create(recursive: true); + } + + // ここに追加の初期化ロジックを記述できます + // 例:キャッシュサイズの制限設定、有効期限の設定など + } + + static Future getCachedTile(String url) async { + final cacheDirectory = await getTemporaryDirectory(); + final fileName = url.split('/').last; + return File('${cacheDirectory.path}/map_cache/$fileName'); + } + +// 他のキャッシュ関連メソッドをここに追加できます +} \ No newline at end of file diff --git a/lib/provider/game_state_provider.dart b/lib/provider/game_state_provider.dart new file mode 100644 index 0000000..218b7eb --- /dev/null +++ b/lib/provider/game_state_provider.dart @@ -0,0 +1,22 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:gifunavi/model/game_state_instance.dart'; + +final gameStateNotifierProvider = + StateNotifierProvider((ref) { + return GameStaticState(); +}); + +class GameStaticState extends StateNotifier { + GameStaticState() : super(GameInsStatetance()); + + @override + GameInsStatetance get state => super.state; + + void startGame(GameInsStatetance gi) { + state = gi; + } + + void doCheckin() {} + + void makeGoal() {} +} diff --git a/lib/provider/map_state_provider.dart b/lib/provider/map_state_provider.dart new file mode 100644 index 0000000..c00be36 --- /dev/null +++ b/lib/provider/map_state_provider.dart @@ -0,0 +1,18 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:gifunavi/model/map_state_instance.dart'; + +final mapStateNotifierProvider = + StateNotifierProvider((ref) { + return MapState(); +}); + +class MapState extends StateNotifier { + MapState() : super(MapStateInstance()); + + @override + MapStateInstance get state => super.state; + + void startGame(MapStateInstance mi) { + state = mi; + } +} diff --git a/lib/request.txt b/lib/request.txt deleted file mode 100644 index 3c50a48..0000000 --- a/lib/request.txt +++ /dev/null @@ -1,33 +0,0 @@ -https://rogaining.sumasen.net/api/perf_main/ - -https://rogaining.sumasen.net/api/allgifuareas/?perf=9 - -https://maps.googleapis.com/maps/api/directions/json?destination=&mode=walking&waypoints=optimize:false|&origin=&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE - -https://rogaining.sumasen.net/api/login/ - -https://rogaining.sumasen.net/api/userdetials?user_id=67 - -https://rogaining.sumasen.net/api/locsext/ - -https://rogaining.sumasen.net/api/inbound?rog=True&grp=各務原&ln1=136.80345200298189&la1=35.29461396815819&ln2=136.80345200298189&la2=35.50765110021806&ln3=136.96420197631517&la3=35.50765110021806&ln4=136.96420197631517&la4=35.29461396815819 - -https://rogaining.sumasen.net/api/cats/?ln1=136.80345200298189&la1=35.29461396815819&ln2=136.80345200298189&la2=35.50765110021806&ln3=136.96420197631517&la3=35.50765110021806&ln4=136.96420197631517&la4=35.29461396815819 - -https://natnats.mobilous.com/start_from_rogapp - -https://natnats.mobilous.com/checkin_from_rogapp - -https://maps.googleapis.com/maps/api/directions/json?destination=&mode=walking&waypoints=optimize:false|&origin=35.3997059, 136.8434708&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE - -https://natnats.mobilous.com/checkin_from_rogapp - -https://maps.googleapis.com/maps/api/directions/json?destination=35.3995266, 136.8436693&mode=walking&waypoints=optimize:false|&origin=35.3997059, 136.8434708&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE - -https://natnats.mobilous.com/goal_from_rogapp - -https://maps.googleapis.com/maps/api/directions/json?destination=&mode=walking&waypoints=optimize:false|&origin=&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE - -https://maps.googleapis.com/maps/api/directions/json?destination=&mode=walking&waypoints=optimize:false|&origin=&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE - -https://maps.googleapis.com/maps/api/directions/json?destination=&mode=walking&waypoints=optimize:false|&origin=&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE \ No newline at end of file diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 562ef86..3ce5171 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -1,36 +1,44 @@ import 'package:get/get.dart'; import 'package:get/get_navigation/src/routes/get_route.dart'; -import 'package:rogapp/pages/camera/camera_page.dart'; -import 'package:rogapp/pages/category/category_page.dart'; -import 'package:rogapp/pages/changepassword/change_password_page.dart'; -import 'package:rogapp/pages/city/city_page.dart'; -import 'package:rogapp/pages/destination/destination_binding.dart'; -import 'package:rogapp/pages/destination/destination_page.dart'; -import 'package:rogapp/pages/history/history_page.dart'; -import 'package:rogapp/pages/home/home_binding.dart'; -import 'package:rogapp/pages/home/home_page.dart'; +import 'package:gifunavi/pages/changepassword/change_password_page.dart'; +import 'package:gifunavi/pages/city/city_page.dart'; +import 'package:gifunavi/pages/gps/gps_page.dart'; +import 'package:gifunavi/pages/history/history_page.dart'; +import 'package:gifunavi/pages/home/home_binding.dart'; +import 'package:gifunavi/pages/home/home_page.dart'; -import 'package:rogapp/pages/index/index_page.dart'; -import 'package:rogapp/pages/landing/landing_page.dart'; -import 'package:rogapp/pages/loading/loading_page.dart'; -import 'package:rogapp/pages/login/login_page.dart'; -import 'package:rogapp/pages/mainperf/mainperf_page.dart'; -import 'package:rogapp/pages/permission/permission.dart'; -import 'package:rogapp/pages/progress/progress.dart'; -import 'package:rogapp/pages/register/register_page.dart'; -import 'package:rogapp/pages/search/search_binding.dart'; -import 'package:rogapp/pages/search/search_page.dart'; -import 'package:rogapp/pages/subperf/subperf_page.dart'; -import 'package:rogapp/spa/spa_binding.dart'; -import 'package:rogapp/spa/spa_page.dart'; +import 'package:gifunavi/pages/index/index_page.dart'; +import 'package:gifunavi/pages/landing/landing_page.dart'; +import 'package:gifunavi/pages/loading/loading_page.dart'; +import 'package:gifunavi/pages/login/login_page.dart'; +import 'package:gifunavi/pages/progress/progress.dart'; +import 'package:gifunavi/pages/register/register_page.dart'; +import 'package:gifunavi/pages/search/search_binding.dart'; +import 'package:gifunavi/pages/search/search_page.dart'; +import 'package:gifunavi/pages/settings/settings_page.dart'; +import 'package:gifunavi/pages/settings/settings_binding.dart'; +import 'package:gifunavi/pages/debug/debug_page.dart'; +import 'package:gifunavi/pages/debug/debug_binding.dart'; +import 'package:gifunavi/widgets/permission_handler_screen.dart'; +import 'package:gifunavi/pages/team/team_binding.dart'; +import 'package:gifunavi/pages/team/team_list_page.dart'; +import 'package:gifunavi/pages/team/team_detail_page.dart'; +import 'package:gifunavi/pages/team/member_binding.dart'; +import 'package:gifunavi/pages/team/member_detail_page.dart'; +import 'package:gifunavi/pages/entry/entry_list_page.dart'; +import 'package:gifunavi/pages/entry/entry_detail_page.dart'; +import 'package:gifunavi/pages/entry/entry_binding.dart'; + +import 'package:gifunavi/pages/entry/event_entries_page.dart'; +import 'package:gifunavi/pages/entry/event_entries_binding.dart'; +import 'package:gifunavi/pages/register/user_detail_page.dart'; part 'app_routes.dart'; class AppPages { - // ignore: constant_identifier_names - static const INITIAL = Routes.INDEX; + static const INDEX = Routes.INDEX; // ignore: constant_identifier_names static const SPA = Routes.SPA; static const LANDING = Routes.LANDING; @@ -41,6 +49,7 @@ class AppPages { static const DESTINATION_MAP = Routes.DESTINATION_MAP; static const HOME = Routes.HOME; static const PERMISSION = Routes.PERMISSION; + //static const PERMISSION = Routes.HOME; static const SEARCH = Routes.SEARCH; static const MAINPERF = Routes.MAINPERF; static const SUBPERF = Routes.SUBPERF; @@ -50,28 +59,24 @@ class AppPages { static const CAMERA_PAGE = Routes.CAMERA_PAGE; static const PROGRESS = Routes.PROGRESS; static const HISTORY = Routes.HISTORY; + static const GPS = Routes.GPS; + static const SETTINGS = Routes.SETTINGS; + static const DEBUG = Routes.DEBUG; + static const TEAM_LIST = Routes.TEAM_LIST; + static const TEAM_DETAIL = Routes.TEAM_DETAIL; + static const MEMBER_DETAIL = Routes.MEMBER_DETAIL; + static const ENTRY_LIST = Routes.ENTRY_LIST; + static const ENTRY_DETAIL = Routes.ENTRY_DETAIL; + static const EVENT_ENTRY = Routes.EVENT_ENTRIES; + static const USER_DETAILS_EDIT = Routes.USER_DETAILS_EDIT; + static final routes = [ - // GetPage( - // name: Routes.HOME, - // page: () => HomePage(), - // binding: HomeBinding(), - // ), - // GetPage( - // name: Routes.MAP, - // page: () => MapPage(), - // binding: MapBinding(), - // ) GetPage( name: Routes.INDEX, - page: () => IndexPage(), + page: () => const IndexPage(), //binding: IndexBinding(), ), - GetPage( - name: Routes.SPA, - page: () => const SpaPage(), - binding: SpaBinding(), - ), GetPage( name: Routes.LANDING, page: () => const LandingPage(), @@ -79,29 +84,19 @@ class AppPages { ), GetPage( name: Routes.LOGIN, - page: () => LoginPage(), + page: () => const LoginPage(), //binding: SpaBinding(), ), GetPage( name: Routes.REGISTER, - page: () => RegisterPage(), + page: () => const RegisterPage(), //binding: SpaBinding(), ), - GetPage( - name: Routes.TRAVEL, - page: () => DestnationPage(), - binding: DestinationBinding(), - ), GetPage( name: Routes.LOADING, page: () => const LoadingPage(), //binding: DestinationBinding(), ), - GetPage( - name: Routes.DESTINATION_MAP, - page: () => DestnationPage(), - //binding: DestinationBinding(), - ), GetPage( name: Routes.HOME, page: () => const HomePage(), @@ -109,37 +104,21 @@ class AppPages { ), GetPage( name: Routes.PERMISSION, - page: () => const PermissionHandlerScreen(), + page: () => const PermissionHandlerScreen(), ), GetPage( name: Routes.SEARCH, page: () => SearchPage(), binding: SearchBinding(), ), - GetPage( - name: Routes.MAINPERF, - page: () => MainPerfPage(), - ), - GetPage( - name: Routes.SUBPERF, - page: () => SubPerfPage(), - ), GetPage( name: Routes.CITY, page: () => const CityPage(), ), - GetPage( - name: Routes.CATEOGORY, - page: () => const CategoryPage(), - ), GetPage( name: Routes.CHANGE_PASSWORD, page: () => ChangePasswordPage(), ), - GetPage( - name: Routes.CAMERA_PAGE, - page: () => CameraPage(), - ), GetPage( name: Routes.PROGRESS, page: () => const ProgressPage(), @@ -147,6 +126,55 @@ class AppPages { GetPage( name: Routes.HISTORY, page: () => const HistoryPage(), - ) + ), + GetPage( + name: Routes.GPS, + page: () => const GpsPage(), + ), + GetPage( + name: Routes.SETTINGS, + page: () => const SettingsPage(), + binding: SettingsBinding(), + ), + GetPage( + name: Routes.DEBUG, + page: () => const DebugPage(), + binding: DebugBinding(), + ), + GetPage( + name: Routes.TEAM_LIST, + page: () => const TeamListPage(), + binding: TeamBinding(), + ), + GetPage( + name: Routes.TEAM_DETAIL, + page: () => const TeamDetailPage(), + binding: TeamBinding(), + ), + GetPage( + name: Routes.MEMBER_DETAIL, + page: () => const MemberDetailPage(), + binding: MemberBinding(), + ), + GetPage( + name: Routes.ENTRY_LIST, + page: () => const EntryListPage(), + binding: EntryBinding(), + ), + GetPage( + name: Routes.ENTRY_DETAIL, + page: () => const EntryDetailPage(), + binding: EntryBinding(), + ), + GetPage( + name: Routes.EVENT_ENTRIES, + page: () => const EventEntriesPage(), + binding: EventEntriesBinding(), + ), + GetPage( + name: Routes.USER_DETAILS_EDIT, + page: () => const UserDetailsEditPage(), + ), ]; -} \ No newline at end of file + +} diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 753b9fc..9c3bb03 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -25,4 +25,17 @@ abstract class Routes { static const CAMERA_PAGE = '/camera_page'; static const PROGRESS = '/progress'; static const HISTORY = '/history'; + static const GPS = '/gp'; + static const SETTINGS = '/settings'; + static const DEBUG = '/debug'; + + static const TEAM_LIST = '/team-list'; + static const TEAM_DETAIL = '/team-detail'; + static const MEMBER_DETAIL = '/member-detail'; + static const ENTRY_LIST = '/entry-list'; + static const ENTRY_DETAIL = '/entry-detail'; + + static const EVENT_ENTRIES = '/event-entries'; + static const USER_DETAILS_EDIT = '/user-details-edit'; + } diff --git a/lib/services/DatabaseService.dart b/lib/services/DatabaseService.dart new file mode 100644 index 0000000..828a5ce --- /dev/null +++ b/lib/services/DatabaseService.dart @@ -0,0 +1,55 @@ +import 'dart:async'; + +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/utils/database_helper.dart'; + +class DatabaseService { + // Private constructor + DatabaseService._privateConstructor(); + + // Static instance + static final DatabaseService _instance = + DatabaseService._privateConstructor(); + + // Factory constructor to return the instance + factory DatabaseService() { + return _instance; + } + + // StreamController for updates + final StreamController> _dbUpdateController = + StreamController>.broadcast(); + + // Getter for the stream + Stream> get destinationUpdatesStream => + _dbUpdateController.stream; + + // Method to fetch destinations + Future> fetchDestinations() async { + // Your database fetch logic here + List destinations = await _fetchFromDb(); + _dbUpdateController.add(destinations); + return destinations; + } + + // Method to update the database and emit an update through the stream + Future updateDatabase() async { + // Your update logic here + + // After updating, fetch the updated list and add it to the stream + fetchDestinations(); + } + + // Method to fetch data from the database (placeholder) + Future> _fetchFromDb() async { + // Simulate fetching data with a delay + DatabaseHelper db = DatabaseHelper.instance; + List destinations = await db.getDestinations(); + return destinations; // Replace with your actual fetch operation + } + + // Dispose method to close the stream controller + void dispose() { + _dbUpdateController.close(); + } +} diff --git a/lib/services/action_service.dart b/lib/services/action_service.dart index a67a9ea..745a8cf 100644 --- a/lib/services/action_service.dart +++ b/lib/services/action_service.dart @@ -1,24 +1,22 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:rogapp/utils/const.dart'; +import 'package:gifunavi/utils/const.dart'; - -class ActionService{ - - static Future> makeAction(int userId, int locationId, bool wanttogo, bool like, bool checkin) async { - print("----- action is ---- $like-- $wanttogo-- $checkin"); +class ActionService { + static Future> makeAction(int userId, int locationId, + bool wanttogo, bool like, bool checkin) async { + //print("----- action is ---- $like-- $wanttogo-- $checkin"); Map cats = {}; String serverUrl = ConstValues.currentServer(); - String url = "$serverUrl/api/makeaction/?user_id=$userId&location_id=$locationId&wanttogo=$wanttogo&like=$like&checkin=$checkin"; + String url = + "$serverUrl/api/makeaction/?user_id=$userId&location_id=$locationId&wanttogo=$wanttogo&like=$like&checkin=$checkin"; //String url = "http://localhost:8100/api/makeaction/?user_id=${user_id}&location_id=${location_id}&wanttogo=${wanttogo}&like=${like}&checkin=${checkin}"; - print('++++++++$url'); - print("url is ------ $url"); - final http.Response response = await http.get( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - } - ); + //print('++++++++$url'); + //print("url is ------ $url"); + final http.Response response = + await http.get(Uri.parse(url), headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }); if (response.statusCode == 200) { cats = json.decode(utf8.decode(response.bodyBytes)); @@ -26,27 +24,23 @@ class ActionService{ return cats; } - - static Future?> userAction(int userId, int locationId) async { + static Future?> userAction(int userId, int locationId) async { List cats = []; String serverUrl = ConstValues.currentServer(); - String url = '$serverUrl/api/useraction/?user_id=$userId&location_id=$locationId'; - print('++++++++$url'); + String url = + '$serverUrl/api/useraction/?user_id=$userId&location_id=$locationId'; + //print('++++++++$url'); //String url = 'http://localhost:8100/api/useraction/?user_id=${user_id}&location_id=${location_id}'; - final response = await http.get(Uri.parse(url), + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - cats = json.decode(utf8.decode(response.bodyBytes)); } return cats; } - - - } - diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart new file mode 100644 index 0000000..243943e --- /dev/null +++ b/lib/services/api_service.dart @@ -0,0 +1,705 @@ +// lib/services/api_service.dart +import 'package:get/get.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; + +import 'package:gifunavi/model/entry.dart'; +import 'package:gifunavi/model/event.dart'; +import 'package:gifunavi/model/team.dart'; +import 'package:gifunavi/model/category.dart'; +import 'package:gifunavi/model/user.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import '../utils/const.dart'; +import 'package:intl/intl.dart'; + + + +class ApiService extends GetxService{ + static ApiService get to => Get.find(); + String serverUrl = ''; + String baseUrl = ''; + String token = 'your-auth-token'; + + Future init() async { + try { + // ここで必要な初期化処理を行う + serverUrl = ConstValues.currentServer(); + baseUrl = '$serverUrl/api'; + //await Future.delayed(Duration(seconds: 2)); // 仮の遅延(実際の初期化処理に置き換えてください) + print('ApiService initialized successfully'); + return this; + } catch(e) { + print('Error in ApiService initialization: $e'); + rethrow; // エラーを再スローして、呼び出し元で処理できるようにする + //return this; + } + } + + /* + このメソッドは以下のように動作します: + + まず、渡された type パラメータに基づいて、どのクラスのフィールドを扱っているかを判断します。 + 次に、クラス内で fieldName に対応する期待される型を返します。 + クラスや フィールド名が予期されていないものである場合、'Unknown' または 'Unknown Type' を返します。 + + このメソッドを ApiService クラスに追加することで、_printDataComparison メソッドは各フィールドの期待される型を正確に表示できるようになります。 + さらに、このメソッドを使用することで、API レスポンスのデータ型が期待と異なる場合に簡単に検出できるようになります。例えば、Category クラスの duration フィールドが整数型(秒数)で期待されているのに対し、API が文字列を返した場合、すぐに問題を特定できます。 + 注意点として、API のレスポンス形式が変更された場合や、新しいフィールドが追加された場合は、このメソッドも更新する必要があります。そのため、API の変更とクライアントサイドのコードの同期を保つことが重要です。 + */ + + String getToken() + { + // IndexControllerの初期化を待つ + final indexController = Get.find(); + + if (indexController.currentUser.isNotEmpty) { + token = indexController.currentUser[0]['token'] ?? ''; + print("Get token = $token"); + }else{ + token = ""; + } + return token; + } + + Future> getTeams() async { + init(); + getToken(); + + try { + final response = await http.get( + Uri.parse('$baseUrl/teams/'), + headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"}, + ); + + if (response.statusCode == 200) { + // UTF-8でデコード + final decodedResponse = utf8.decode(response.bodyBytes); + //print('User Response body: $decodedResponse'); + List teamsJson = json.decode(decodedResponse); + + List teams = []; + for (var teamJson in teamsJson) { + //print('\nTeam Data:'); + //_printDataComparison(teamJson, Team); + teams.add(Team.fromJson(teamJson)); + } + return teams; + } else { + throw Exception('Failed to load teams. Status code: ${response.statusCode}'); + } + } catch (e, stackTrace) { + print('Error in getTeams: $e'); + print('Stack trace: $stackTrace'); + rethrow; + } + } + + + + Future> getCategories() async { + init(); + getToken(); + + try { + final response = await http.get( + Uri.parse('$baseUrl/categories/'), + headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"}, + ); + + if (response.statusCode == 200) { + final decodedResponse = utf8.decode(response.bodyBytes); + print('User Response body: $decodedResponse'); + List categoriesJson = json.decode(decodedResponse); + + List categories = []; + for (var categoryJson in categoriesJson) { + try { + //print('\nCategory Data:'); + //_printDataComparison(categoryJson, NewCategory); + categories.add(NewCategory.fromJson(categoryJson)); + }catch(e){ + print('Error parsing category: $e'); + print('Problematic JSON: $categoryJson'); + } + } + + return categories; + } else { + throw Exception( + 'Failed to load categories. Status code: ${response.statusCode}'); + } + }catch(e, stackTrace){ + print('Error in getCategories: $e'); + print('Stack trace: $stackTrace'); + rethrow; + } + } + + Future getZekkenNumber(int categoryId) async { + try { + final response = await http.post( + Uri.parse('$baseUrl/categories-viewset/$categoryId/get_zekken_number/'), + headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"}, + ); + if (response.statusCode == 200) { + final decodedResponse = utf8.decode(response.bodyBytes); + print('User Response body: $decodedResponse'); + final categoriesJson = json.decode(decodedResponse); + + return NewCategory.fromJson(categoriesJson); + } else { + throw Exception('Failed to increment category number'); + } + } catch (e) { + throw Exception('Error incrementing category number: $e'); + } + } + + Future getCurrentUser() async { + init(); + getToken(); + + try { + final response = await http.get( + Uri.parse('$baseUrl/user/'), + headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"}, + ); + + if (response.statusCode == 200) { + final decodedResponse = utf8.decode(response.bodyBytes); + //print('User Response body: $decodedResponse'); + final jsonData = json.decode(decodedResponse); + + //print('\nUser Data Comparison:'); + //_printDataComparison(jsonData, User); + + return User.fromJson(jsonData); + } else { + throw Exception('Failed to get current user. Status code: ${response.statusCode}'); + } + } catch (e, stackTrace) { + print('Error in getCurrentUser: $e'); + print('Stack trace: $stackTrace'); + rethrow; + } + } + + void _printDataComparison(Map data, Type expectedType) { + print('Field\t\t| Expected Type\t| Actual Type\t| Actual Value'); + print('------------------------------------------------------------'); + data.forEach((key, value) { + String expectedFieldType = _getExpectedFieldType(expectedType, key); + _printComparison(key, expectedFieldType, value); + }); + } + + String _getExpectedFieldType(Type type, String fieldName) { + // This method should return the expected type for each field based on the class definition + // You might need to implement this based on your class structures + + switch (type) { + case NewCategory: + switch (fieldName) { + case 'id': return 'int'; + case 'category_name': return 'String'; + case 'category_number': return 'int'; + case 'duration': return 'int (seconds)'; + case 'num_of_member': return 'int'; + case 'family': return 'bool'; + case 'female': return 'bool'; + default: return 'Unknown'; + } + case Team: + switch (fieldName) { + case 'id': return 'int'; + case 'zekken_number': return 'String'; + case 'team_name': return 'String'; + case 'category': return 'NewCategory (Object)'; + case 'owner': return 'User (Object)'; + default: return 'Unknown'; + } + case User: + switch (fieldName) { + case 'id': return 'int'; + case 'email': return 'String'; + case 'firstname': return 'String'; + case 'lastname': return 'String'; + case 'date_of_birth': return 'String (ISO8601)'; + case 'female': return 'bool'; + case 'is_active': return 'bool'; + default: return 'Unknown'; + } + default: + return 'Unknown Type'; + } + } + + void _printComparison(String fieldName, String expectedType, dynamic actualValue) { + String actualType = actualValue?.runtimeType.toString() ?? 'null'; + String displayValue = actualValue.toString(); + if (displayValue.length > 50) { + displayValue = '${displayValue.substring(0, 47)}...'; + } + print('$fieldName\t\t| $expectedType\t\t| $actualType\t\t| $displayValue'); + } + + Future createTeam(String teamName, int categoryId) async { + init(); + getToken(); + + final response = await http.post( + Uri.parse('$baseUrl/teams/'), + headers: { + 'Authorization': 'Token $token', + "Content-Type": "application/json; charset=UTF-8", + }, + body: json.encode({ + 'team_name': teamName, + 'category': categoryId, + }), + ); + + if (response.statusCode == 201) { + final decodedResponse = utf8.decode(response.bodyBytes); + return Team.fromJson(json.decode(decodedResponse)); + } else { + throw Exception('Failed to create team'); + } + } + + Future updateTeam(int teamId, String teamName, int categoryId) async { + init(); + getToken(); + + final response = await http.put( + Uri.parse('$baseUrl/teams/$teamId/'), + headers: { + 'Authorization': 'Token $token', + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: json.encode({ + 'team_name': teamName, + 'category': categoryId, + }), + ); + + if (response.statusCode == 200) { + final decodedResponse = utf8.decode(response.bodyBytes); + + return Team.fromJson(json.decode(decodedResponse)); + } else { + throw Exception('Failed to update team'); + } + } + + Future deleteTeam(int teamId) async { + init(); + getToken(); + + final response = await http.delete( + Uri.parse('$baseUrl/teams/$teamId/'), + headers: {'Authorization': 'Token $token','Content-Type': 'application/json; charset=UTF-8'}, + ); + + if( response.statusCode == 400) { + final decodedResponse = utf8.decode(response.bodyBytes); + print('User Response body: $decodedResponse'); + throw Exception('まだメンバーが残っているので、チームを削除できません。'); + }else if (response.statusCode != 204) { + throw Exception('Failed to delete team'); + } + } + + Future> getTeamMembers(int teamId) async { + init(); + getToken(); + + final response = await http.get( + Uri.parse('$baseUrl/teams/$teamId/members/'), + headers: {'Authorization': 'Token $token','Content-Type': 'application/json; charset=UTF-8'}, + ); + + if (response.statusCode == 200) { + final decodedResponse = utf8.decode(response.bodyBytes); + print('User Response body: $decodedResponse'); + List membersJson = json.decode(decodedResponse); + + return membersJson.map((json) => User.fromJson(json)).toList(); + } else { + throw Exception('Failed to load team members'); + } + } + + Future createTeamMember(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth,bool? female) async { + init(); + getToken(); + + // emailが値を持っている場合の処理 + if (email != null && email.isNotEmpty) { + firstname ??= "dummy"; + lastname ??= "dummy"; + dateOfBirth ??= DateTime.now(); + female ??= false; + } + + String? formattedDateOfBirth; + if (dateOfBirth != null) { + formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth); + } + + final response = await http.post( + Uri.parse('$baseUrl/teams/$teamId/members/'), + headers: { + 'Authorization': 'Token $token', + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: json.encode({ + 'email': email, + 'firstname': firstname, + 'lastname': lastname, + 'date_of_birth': formattedDateOfBirth, + 'female': female, + }), + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + final decodedResponse = utf8.decode(response.bodyBytes); + return User.fromJson(json.decode(decodedResponse)); + } else { + throw Exception('Failed to create team member'); + } + } + + Future updateTeamMember(int teamId,int? memberId, String firstname, String lastname, DateTime? dateOfBirth,bool? female) async { + init(); + getToken(); + + String? formattedDateOfBirth; + if (dateOfBirth != null) { + formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth); + } + + final response = await http.put( + Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'), + headers: { + 'Authorization': 'Token $token', + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: json.encode({ + 'firstname': firstname, + 'lastname': lastname, + 'date_of_birth': formattedDateOfBirth, + 'female': female, + }), + ); + + if (response.statusCode == 200) { + final decodedResponse = utf8.decode(response.bodyBytes); + return User.fromJson(json.decode(decodedResponse)); + } else { + throw Exception('Failed to update team member'); + } + } + + Future deleteTeamMember(int teamId,int memberId) async { + init(); + getToken(); + + final response = await http.delete( + Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'), + headers: {'Authorization': 'Token $token'}, + ); + + if (response.statusCode != 204) { + throw Exception('Failed to delete team member'); + } + } + + Future deleteAllTeamMembers(int teamId) async { + final response = await http.delete( + Uri.parse('$baseUrl/teams/$teamId/members/destroy_all/?confirm=true'), + headers: {'Authorization': 'Token $token'}, + ); + + if (response.statusCode != 200) { + throw Exception('Failed to delete team members'); + } + } + + Future resendMemberInvitation(int memberId) async { + init(); + getToken(); + + final response = await http.post( + Uri.parse('$baseUrl/members/$memberId/resend-invitation/'), + headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8', + }, + ); + + if (response.statusCode != 200) { + throw Exception('Failed to resend invitation'); + } + } + + Future> getEntries() async { + init(); + getToken(); + + final response = await http.get( + Uri.parse('$baseUrl/entry/'), + headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8', + }, + ); + + if (response.statusCode == 200) { + final decodedResponse = utf8.decode(response.bodyBytes); + List entriesJson = json.decode(decodedResponse); + return entriesJson.map((json) => Entry.fromJson(json)).toList(); + } else { + throw Exception('Failed to load entries'); + } + } + + Future> getTeamEntries(int teamId) async { + try { + final response = await http.get( + Uri.parse('$baseUrl/teams/$teamId/entries'), + headers: {'Authorization': 'Token $token'}, + ); + + if (response.statusCode == 200) { + List entriesJson = jsonDecode(response.body); + return entriesJson.map((json) => Entry.fromJson(json)).toList(); + } else { + throw Exception('Failed to load team entries: ${response.statusCode}'); + } + } catch (e) { + print('Error in getTeamEntries: $e'); + rethrow; + } + } + + Future> getEvents() async { + init(); + getToken(); + + final response = await http.get( + Uri.parse('$baseUrl/new-events/',), + headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8', + }, + ); + + if (response.statusCode == 200) { + final decodedResponse = utf8.decode(response.bodyBytes); + print('Response body: $decodedResponse'); + List eventsJson = json.decode(decodedResponse); + + return eventsJson.map((json) => Event.fromJson(json)).toList(); + } else { + throw Exception('Failed to load events'); + } + } + + Future createEntry(int teamId, int eventId, int categoryId, DateTime date,String zekkenNumber) async { + init(); + getToken(); + + String? formattedDate; + formattedDate = DateFormat('yyyy-MM-dd').format(date); + + final response = await http.post( + Uri.parse('$baseUrl/entry/'), + headers: { + 'Authorization': 'Token $token', + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: json.encode({ + 'team': teamId, + 'event': eventId, + 'category': categoryId, + 'date': formattedDate, + 'zekken_number':zekkenNumber, + }), + ); + + if (response.statusCode == 201) { + final decodedResponse = utf8.decode(response.bodyBytes); + + return Entry.fromJson(json.decode(decodedResponse)); + } else { + final decodedResponse = utf8.decode(response.bodyBytes); + print("decodedResponse = $decodedResponse"); + throw Exception('Failed to create entry'); + } + } + + Future updateUserInfo(int userId, Entry entry) async { + init(); + getToken(); + + final entryId = entry.id; + + DateTime? date = entry.date; + String? formattedDate; + formattedDate = DateFormat('yyyy-MM-dd').format(date!); + + final response = await http.put( + Uri.parse('$baseUrl/userinfo/$userId/'), + headers: { + 'Authorization': 'Token $token', + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: json.encode({ + 'zekken_number': entry.zekkenNumber, + 'event_code': entry.event.eventName, + 'group': entry.team.category.categoryName, + 'team_name': entry.team.teamName, + 'date': formattedDate, + }), + ); + if (response.statusCode == 200) { + final decodedResponse = utf8.decode(response.bodyBytes); + final updatedUserInfo = json.decode(decodedResponse); + //Get.find().updateUserInfo(updatedUserInfo); + + } else { + throw Exception('Failed to update entry'); + } + } + + + Future updateEntry(int entryId, int teamId, int eventId, int categoryId, DateTime date,int zekkenNumber) async { + init(); + getToken(); + + String? formattedDate; + formattedDate = DateFormat('yyyy-MM-dd').format(date); + + final response = await http.put( + Uri.parse('$baseUrl/entry/$entryId/'), + headers: { + 'Authorization': 'Token $token', + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: json.encode({ + 'team': teamId, + 'event': eventId, + 'category': categoryId, + 'date': formattedDate, + 'zekken_number': zekkenNumber, + }), + ); + + if (response.statusCode == 200) { + final decodedResponse = utf8.decode(response.bodyBytes); + + return Entry.fromJson(json.decode(decodedResponse)); + } else { + final decodedResponse = utf8.decode(response.bodyBytes); + final blk = json.decode(decodedResponse); + + throw Exception('Failed to update entry'); + } + } + + Future deleteEntry(int entryId) async { + init(); + getToken(); + + final response = await http.delete( + Uri.parse('$baseUrl/entry/$entryId/'), + headers: {'Authorization': 'Token $token'}, + ); + + if (response.statusCode != 204) { + throw Exception('Failed to delete entry'); + } + } + + static Future updateUserDetail(User user, String token) async { + String serverUrl = ConstValues.currentServer(); + int? userid = user.id; + String url = '$serverUrl/api/userdetail/$userid/'; + + try { + String? formattedDate; + if (user.dateOfBirth != null) { + formattedDate = DateFormat('yyyy-MM-dd').format(user.dateOfBirth!); + } + final http.Response response = await http.put( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + 'Authorization': 'Token $token' + }, + body: jsonEncode({ + 'firstname': user.firstname, + 'lastname': user.lastname, + 'date_of_birth': formattedDate, + 'female': user.female, + }), + ); + + if (response.statusCode == 200) { + return true; + } else { + print('Update failed with status code: ${response.statusCode}'); + return false; + } + } catch (e) { + print('Error in updateUserDetail: $e'); + return false; + } + } + + Future resetPassword(String email) async { + init(); + + try { + final response = await http.post( + Uri.parse('$baseUrl/password-reset/'), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: json.encode({ + 'email': email, + }), + ); + + if (response.statusCode == 200) { + return true; + } else { + print('Password reset failed with status code: ${response.statusCode}'); + return false; + } + } catch (e) { + print('Error in resetPassword: $e'); + return false; + } + } + + Future getLastGoalTime(int userId) async { + try { + final response = await http.get( + Uri.parse('$baseUrl/users/$userId/last-goal/'), + headers: { + 'Authorization': 'Token $token', + 'Content-Type': 'application/json; charset=UTF-8', + }, + ); + + if (response.statusCode == 200) { + final decodedResponse = json.decode(utf8.decode(response.bodyBytes)); + if (decodedResponse['last_goal_time'] != null) { + return DateTime.parse(decodedResponse['last_goal_time']).toLocal(); + } + } else { + print('Failed to get last goal time. Status code: ${response.statusCode}'); + } + } catch (e) { + print('Error in getLastGoalTime: $e'); + } + return null; + } + +} \ No newline at end of file diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 341a06f..5c18d72 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -1,16 +1,74 @@ import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:gifunavi/model/auth_user.dart'; +import 'package:get/get.dart'; +import 'package:flutter/material.dart'; import '../utils/const.dart'; +//import 'package:rogapp/services/team_service.dart'; +//import 'package:rogapp/services/member_service.dart'; -class AuthService{ +class AuthService { + Future userLogin(String email, String password) async { + final serverUrl = ConstValues.currentServer(); + final url = '$serverUrl/api/login/'; - static Future> changePassword(String oldpassword, String newpassword, String token) async { + try { + final http.Response response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: + jsonEncode({'email': email, 'password': password}), + ); + switch (response.statusCode) { + case 200: + final data = json.decode(utf8.decode(response.bodyBytes)); + AuthUser user = AuthUser.fromMap(data["user"]); + final String token = data["token"]; + user.auth_token = token; + return user; + default: + return null; + //throw Exception(response.reasonPhrase); + } + } on Exception catch (_) { + rethrow; + } + } + + Future userFromToken(String token) async { + final serverUrl = ConstValues.currentServer(); + final url = '$serverUrl/api/user/'; + try { + final http.Response response = + await http.get(Uri.parse(url), headers: { + 'Content-Type': 'application/json; charset=UTF-8', + 'Authorization': 'Token $token' + }); + switch (response.statusCode) { + case 200: + final data = json.decode(utf8.decode(response.bodyBytes)); + AuthUser user = AuthUser.fromMap(data); + user.auth_token = token; + return user; + default: + return null; + //throw Exception(response.reasonPhrase); + } + } on Exception catch (_) { + rethrow; + } + } + + static Future> changePassword( + String oldpassword, String newpassword, String token) async { Map changePassword = {}; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/change-password/'; - print('++++++++$url'); + //print('++++++++$url'); final http.Response response = await http.put( Uri.parse(url), headers: { @@ -24,71 +82,106 @@ class AuthService{ ); if (response.statusCode == 200) { - changePassword = json.decode(utf8.decode(response.bodyBytes)); + changePassword = json.decode(utf8.decode(response.bodyBytes)); } return changePassword; } - - static Future> login(String email, String password) async { - print("------- in logged email $email pwd $password ###### --------"); + static Future> login( + String email, String password) async { + //print("------- in logged email $email pwd $password ###### --------"); Map cats = {}; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/login/'; - print('++++++++$url'); + //print('++++++++$url'); //String url = 'http://localhost:8100/api/login/'; - final http.Response response = await http.post( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - }, - body: jsonEncode({ - 'email': email, - 'password': password - }), - ); + try { + final http.Response response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: jsonEncode( + {'email': email, 'password': password}), + ); - if (response.statusCode == 200) { - cats = json.decode(utf8.decode(response.bodyBytes)); + if (response.statusCode == 200) { + cats = json.decode(utf8.decode(response.bodyBytes)); + } else { + print('Login failed with status code: ${response.statusCode}'); + cats = {}; + } + } catch( e ){ + print('Error in login: $e'); + Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした", + backgroundColor: Colors.red, + colorText: Colors.white); + Get.snackbar( + "通信エラーがおきました", + "サーバーと通信できませんでした", + backgroundColor: Colors.red, + colorText: Colors.white, + icon: const Icon( + Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + //backgroundColor: Colors.yellow, + ); + cats = {}; } return cats; } - - static Future> register(String email, String password) async { + + // ユーザー登録 + // + /* + Future registerUser(String email, String password, bool isFemale) async { + final user = await register(email, password); + if (user != null) { + final _teamController = TeamController(); + _teamController.createTeam(String teamName, int categoryId) ; + final teamService = TeamService(); + final memberService = MemberService(); + + final team = await teamService.createSoloTeam(user.id, isFemale); + await memberService.addMember(team.id, user.id); + } + } + + */ + + static Future> register( + String email, String password, String password2) async { Map cats = {}; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/register/'; - print('++++++++$url'); + debugPrint('++++++++$url'); final http.Response response = await http.post( Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, - body: jsonEncode({ - 'email': email, - 'password': password - }), + body: jsonEncode({'email': email, 'password': password, 'password2': password2}), ); - - if (response.statusCode == 200) { - cats = json.decode(utf8.decode(response.bodyBytes)); + cats = json.decode(utf8.decode(response.bodyBytes)); + print("result=$cats"); + if (response.statusCode == 201) { + }else{ } return cats; } - static Future> deleteUser(String token) async { + static Future> deleteUser(String token) async { Map cats = {}; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/delete-account/'; - print('++++++++$url'); - final http.Response response = await http.get( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - 'Authorization': 'Token $token' - } - ); + //print('++++++++$url'); + final http.Response response = + await http.get(Uri.parse(url), headers: { + 'Content-Type': 'application/json; charset=UTF-8', + 'Authorization': 'Token $token' + }); if (response.statusCode == 200) { cats = json.decode(utf8.decode(response.bodyBytes)); @@ -96,52 +189,46 @@ class AuthService{ return cats; } - - static Future?> UserDetails(int userid) async { + + static Future?> userDetails(int userid) async { List cats = []; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/userdetials?user_id=$userid'; - print('++++++++$url'); - print("---- UserDetails url is $url"); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + //print("---- UserDetails url is $url"); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - cats = json.decode(utf8.decode(response.bodyBytes)); } return cats; } - static Future?> userForToken(String token) async { Map cats = {}; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/user/'; - print('++++++++$url'); - print("---- UserDetails url is $url"); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + //print("---- UserDetails url is $url"); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', - 'Authorization': 'Token $token' + 'Authorization': 'Token $token' }, ); if (response.statusCode == 200) { - cats = json.decode(utf8.decode(response.bodyBytes)); - print("--- eeeeee $cats ----"); + //print("--- eeeeee $cats ----"); } - return [{"user":cats}]; + return [ + {"user": cats, "token": token} + ]; } - - - - - - } - diff --git a/lib/services/cat_service.dart b/lib/services/cat_service.dart index 5ed4c94..bbd4494 100644 --- a/lib/services/cat_service.dart +++ b/lib/services/cat_service.dart @@ -3,50 +3,42 @@ import 'package:http/http.dart' as http; import '../utils/const.dart'; - -class CatService{ - - static Future?> loadCats(double lat1, double lon1, double lat2, double lon2, double lat3, double lon3, double lat4, double lon4) async { +class CatService { + static Future?> loadCats(double lat1, double lon1, double lat2, + double lon2, double lat3, double lon3, double lat4, double lon4) async { List cats = []; String serverUrl = ConstValues.currentServer(); - String url = '$serverUrl/api/cats/?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4'; - print('++++++++$url'); - final response = await http.get(Uri.parse(url), + String url = + '$serverUrl/api/cats/?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4'; + //print('++++++++$url'); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - cats = json.decode(utf8.decode(response.bodyBytes)); } return cats; } - - static Future?> loadCatByCity(String cityname)async { + static Future?> loadCatByCity(String cityname) async { List cats = []; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/catbycity/?$cityname'; - print('++++++++$url'); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - cats = json.decode(utf8.decode(response.bodyBytes)); } return cats; } - - } - - - - - diff --git a/lib/services/destination_service.dart b/lib/services/destination_service.dart index 23bf4a3..d2e6285 100644 --- a/lib/services/destination_service.dart +++ b/lib/services/destination_service.dart @@ -3,25 +3,21 @@ import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:get/get.dart'; //import 'package:google_maps_webservice/directions.dart'; import 'package:http/http.dart' as http; -import 'package:rogapp/model/destination.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; import '../utils/const.dart'; - -class DestinationService{ - - static Future> getDestinations(int userId) async { +class DestinationService { + static Future> getDestinations(int userId) async { List cats = []; String serverUrl = ConstValues.currentServer(); String url = "$serverUrl/api/destinations/?user_id=$userId"; - print('++++++++$url'); - final http.Response response = await http.get( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - } - ); + //print('++++++++$url'); + final http.Response response = + await http.get(Uri.parse(url), headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }); if (response.statusCode == 200) { cats = json.decode(utf8.decode(response.bodyBytes)); @@ -29,36 +25,33 @@ class DestinationService{ return cats; } - static Future> deleteDestination(int destId) async { + static Future> deleteDestination(int destId) async { Map cats = {}; String serverUrl = ConstValues.currentServer(); String url = "$serverUrl/api/delete_destination/?dest_id=$destId"; - print('++++++++$url'); - final http.Response response = await http.get( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - } - ); + //print('++++++++$url'); + final http.Response response = + await http.get(Uri.parse(url), headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }); if (response.statusCode == 200) { cats = json.decode(utf8.decode(response.bodyBytes)); } - print("####### ---- $cats"); + //print("####### ---- $cats"); return cats; } - static Future updateOrder(int actionId, int order, String dir) async { + static Future updateOrder(int actionId, int order, String dir) async { int cats = 0; String serverUrl = ConstValues.currentServer(); - String url = "$serverUrl/api/updateorder/?user_action_id=$actionId&order=$order&dir=$dir"; - print('++++++++$url'); - final http.Response response = await http.get( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - } - ); + String url = + "$serverUrl/api/updateorder/?user_action_id=$actionId&order=$order&dir=$dir"; + //print('++++++++$url'); + final http.Response response = + await http.get(Uri.parse(url), headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }); if (response.statusCode == 200) { cats = json.decode(utf8.decode(response.bodyBytes)); @@ -66,23 +59,25 @@ class DestinationService{ return cats; } - static Future>? getDestinationLine(List destinations, Map mtx) async{ + static Future>? getDestinationLine( + List destinations, Map mtx) async { PolylinePoints polylinePoints = PolylinePoints(); - if(destinations.isEmpty){ + if (destinations.isEmpty) { return []; } - + //print("##### @@@@@ ${destinations[0].lat}"); - PointLatLng origin = PointLatLng(destinations[0].lat!, destinations[0].lon!); - PointLatLng dest = PointLatLng(destinations[destinations.length -1].lat!, destinations[destinations.length -1].lon!); - + PointLatLng origin = + PointLatLng(destinations[0].lat!, destinations[0].lon!); + PointLatLng dest = PointLatLng(destinations[destinations.length - 1].lat!, + destinations[destinations.length - 1].lon!); List wayPoints = []; - int i=0; - for(dynamic d in destinations){ - if(i == 0 || i == (destinations.length -1)){ - i+=1; + int i = 0; + for (dynamic d in destinations) { + if (i == 0 || i == (destinations.length - 1)) { + i += 1; continue; } double la = d.lat; @@ -90,39 +85,36 @@ class DestinationService{ int j = 0; - PolylineWayPoint pwp = PolylineWayPoint(location: "$la,$ln", stopOver: false); - + PolylineWayPoint pwp = + PolylineWayPoint(location: "$la,$ln", stopOver: false); //print("----- UUUUUU ${pwp}"); //PointLatLng wp = PointLatLng(d["Location"]["geometry"][0][1], d["Location"]["geometry"][0][0]); wayPoints.add(pwp); - i+=1; + i += 1; j += 4; } - final DestinationController destinationController = Get.find(); - int travMode = destinationController.travelMode.value; - String mode = "WALKING"; - if(travMode == 1){ - //_mode = TravelMode.driving; - mode = "DRIVING"; - } - else if(travMode == 2) { - //_mode = TravelMode.transit; - mode = "TRANSIT"; - } - + final DestinationController destinationController = + Get.find(); + // int travMode = destinationController.travelMode.value; + // String mode = "WALKING"; + // if (travMode == 1) { + // //_mode = TravelMode.driving; + // mode = "DRIVING"; + // } else if (travMode == 2) { + // //_mode = TravelMode.transit; + // mode = "TRANSIT"; + // } //PolylineResult result = await polylinePoints.getRouteBetweenCoordinates("AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE", PointLatLng(35.389282, 136.498027), PointLatLng(36.285848, 137.575186)); - Map pl = destinationController.matrix["routes"][0]["overview_polyline"]; + Map pl = + destinationController.matrix["routes"][0]["overview_polyline"]; List result = polylinePoints.decodePolyline(pl["points"]); //List result = polylinePoints.decodePolyline("qkyvEq`z`Yp@DBMr@XL@Td@Eb@PREd@IFe@rKIzCY|GEvCBzCHvS@xC?HnBHtBHlBFnBFhGRtDVW~BE`@ICHLk@dE_ClPgAtHu@bFsAhPg@~Ge@bFaEtg@kEpi@oCd\\w@nIw@hGe@fBy@nBqAjC{@zBgBtFOd@M@Wv@i@`BQf@ITKCuE`@yDZqBRCHa@DKG_AHwBRiBR_Fp@y@LYBY]M@KJo@v@M@cAGoGN_Cx@}Cf@}@@mM~@qF`@gCLwBj@sBrAeAhAsAtCoF|MmAbD{@fBwAdBw@p@_Ax@BFOHAl@?`@MAQCEAOIQSaBx@{Ah@eATsAHSB?d@A`D?f@IdWy@AS??V?|BCJ}@?cA?k@Au@wBqDuKQaACg@z@gELg@GK~@uEp@{A@q@y@CHwFHcG?KDqCDK^ABABEH{AE{B{@_Ho@uFIaBFQhBaC@SQSg@k@g@q@Yw@qA{De@}B]uDCsAMEWDqAFu@@^A@TDjA@vDA`CETK|AEtAIFY@o@ALpBZ~HBlCBn@EDGPu@pASJO`@Qf@?ROr@K?qDLHnEUTsDNkENYB{Ab@I^?zA}CrCkBfBw@t@@LwA`Bo@r@eGvD}BrAGGuAj@[?i@rBVi@P}@T?F?^MxDuBhDsBzAcAn@s@zCgDAI~A{A|CsC?{A?UHItA_@DCXC~J_@TUIoEvDKTm@?Y^iALIb@k@f@aAE}AA_BC{@\\Cv@CxAEj@ExCwDDc@CYFANCh@WHEIIRQhB}B|C_E\\w@Hq@JE?a@O}CGkAIwEGmDEmDAKLA^?A}@C{@?e@E_DFQNi@LcB\\eBPsADGKOEWBOH[GCPs@Pq@\\cANs@^q@jAu@fCqAf@]HCXoCV_BVmAZmBVcDBeCCgDAaB?s@RE?aCCaEAyHAoDd@EJiD@_@AaAj@A\\A?Gp@@r@oBXm@LQ?IEy@Fy@tA[n@Gj@Tz@[~ACdAAx@Lp@Kr@]hAa@HAQoCMwCSwGSiGK_CCCKaBCgCOoCOgECwGB_OB{JHkBEmC?yCDyFF{QFue@BsYByE?oAEgAByLBiL?gLBuGXsEd@cCNA?OHa@jAuCn@eAtCyDh@k@v@EvBKr@EEkACUKaC?G~@gAlCeDFBT[jFeGZAfBEh@UpBM`AEMaFjFYIhE?hEPpCJzAPt@Fj@GNUFu@N[FyBbAuB`@_@LEIOB}@HUBQFk@FcAACGQA}@Bi@F@F[Dc@D[FQHELGhBMtDGR?D"); //PolylineResult result = await polylinePoints.getRouteBetweenCoordinates("AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE", origin,dest, travelMode: _mode, wayPoints: wayPoints); - + //print("#####@@@@@ ${result.points}"); return result; } - - } - diff --git a/lib/services/error_service.dart b/lib/services/error_service.dart new file mode 100644 index 0000000..d670325 --- /dev/null +++ b/lib/services/error_service.dart @@ -0,0 +1,63 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; + +class ErrorService { + static Future reportError(dynamic error, StackTrace stackTrace, Map deviceInfo, List operationLogs) async { + try { + final String errorMessage = error.toString(); + final String stackTraceString = stackTrace.toString(); + final String estimatedCause = _estimateErrorCause(errorMessage); + //final String deviceInfo = await _getDeviceInfo(); + + final Uri url = Uri.parse('https://rogaining.sumasen.net/report-error'); + final response = await http.post( + url, + body: { + 'error_message': errorMessage, + 'stack_trace': stackTraceString, + 'estimated_cause': estimatedCause, + 'device_info': deviceInfo, + 'operation_logs': operationLogs.join('\n'), // オペレーションログを改行で結合して送信 + }, + ); + + if (response.statusCode == 200) { + // エラー報告が成功した場合の処理(必要に応じて) + debugPrint("===== エラーログ送信成功しました。 ===="); + } else { + // エラー報告が失敗した場合の処理(必要に応じて) + debugPrint("===== エラーログ送信失敗しました。 ===="); + } + } catch (e) { + // エラー報告中にエラーが発生した場合の処理(必要に応じて) + debugPrint("===== エラーログ送信中にエラーになりました。 ===="); + } + } + + static String _estimateErrorCause(String errorMessage) { + // エラーメッセージに基づいてエラーの原因を推定するロジックを追加する + if (errorMessage.contains('NetworkException')) { + return 'ネットワーク接続エラー'; + } else if (errorMessage.contains('DatabaseException')) { + return 'データベースエラー'; + } else if (errorMessage.contains('AuthenticationException')) { + return '認証エラー'; + } else { + return '不明なエラー'; + } + } + + /* + // 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810 + // + static void reportMemoryError(String message, StackTrace stackTrace) async { + final errorDetails = FlutterErrorDetails( + exception: Exception(message), + stack: stackTrace, + ); + await reportError(errorDetails.exception, errorDetails.stack ?? StackTrace.current, deviceInfo); + } + */ +} + diff --git a/lib/services/external_service.dart b/lib/services/external_service.dart index 836fb71..4235c8f 100644 --- a/lib/services/external_service.dart +++ b/lib/services/external_service.dart @@ -1,280 +1,443 @@ import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; -import 'package:rogapp/model/rog.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/utils/database_helper.dart'; +import 'package:gifunavi/model/rog.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/utils/database_gps.dart'; +import 'package:gifunavi/utils/database_helper.dart'; import 'dart:convert'; +import '../model/gps_data.dart'; import '../utils/const.dart'; -// +// // Rog type 0- start 1- checkin 2- goal -// +// class ExternalService { static final ExternalService _instance = ExternalService._internal(); - factory ExternalService(){ + factory ExternalService() { return _instance; } ExternalService._internal(); - String getFormatedTime(DateTime datetime){ - return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime); + String getFormatedTime(DateTime datetime) { + return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime); } - Future> StartRogaining() async { - + Future> startRogaining() async { final IndexController indexController = Get.find(); + //final TeamController teamController = Get.find(); + + debugPrint("== startRogaining =="); Map res = {}; + //final teamController = TeamController(); + + //Team team0 = teamController.teams[0]; + //print("team={team0}"); + + //int teamId = indexController.currentUser[0]["user"]["team"]["id"]; + int userId = indexController.currentUser[0]["user"]["id"]; //print("--- Pressed -----"); String team = indexController.currentUser[0]["user"]['team_name']; //print("--- _team : ${_team}-----"); String eventCode = indexController.currentUser[0]["user"]["event_code"]; - if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){ + if (indexController.connectionStatusName.value != "wifi" && + indexController.connectionStatusName.value != "mobile") { + debugPrint("== No network =="); DatabaseHelper db = DatabaseHelper.instance; Rog rog = Rog( - id:1, - team_name: team, - event_code : eventCode, - user_id: userId, - cp_number: -1, - checkintime: DateTime.now().toUtc().microsecondsSinceEpoch, - image: null, - rog_action_type: 0 - ); + id: 1, + team_name: team, + event_code: eventCode, + user_id: userId, + cp_number: -1, + checkintime: DateTime.now().toUtc().microsecondsSinceEpoch, + image: null, + rog_action_type: 0); db.insertRogaining(rog); - } - else { - String url = 'https://natnats.mobilous.com/start_from_rogapp'; + } else { + debugPrint("== startRogaining processing=="); + + String serverUrl = ConstValues.currentServer(); + String url = '$serverUrl/gifuroge/start_from_rogapp'; print('++++++++$url'); final http.Response response = await http.post( Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, - body: jsonEncode({ - 'team_name': team, - 'event_code': eventCode - }), + body: jsonEncode( + {'team_name': team, 'event_code': eventCode}), ); - print("---- start rogianing api status ---- ${response.statusCode}"); + //print("---- start rogianing api status ---- ${response.statusCode}"); if (response.statusCode == 200) { - res = json.decode(utf8.decode(response.bodyBytes)); - print('----_res : $res ----'); + res = json.decode(utf8.decode(response.bodyBytes)); + //print('----_res : $res ----'); } } return res; } - Future> makeCheckpoint(int userId, String token, String checkinTime, String teamname, int cp, String eventcode, String imageurl) async { + Future> makeCheckpoint( + int userId, // 中身はteamId + String token, + String checkinTime, + String teamname, + int cp, + String eventcode, + String imageurl) async { + // print("~~~~ in API call function ~~~~"); + // print("~~~~ cp is $cp ~~~~"); + //print("--cpcp-- ${cp}"); Map res = {}; - String url = 'https://natnats.mobilous.com/checkin_from_rogapp'; - print('++++++++$url'); + String serverUrl = ConstValues.currentServer(); + String url = '$serverUrl/gifuroge/checkin_from_rogapp'; + //print('++++++++$url'); final IndexController indexController = Get.find(); + //final TeamController teamController = Get.find(); - if(imageurl != null){ - if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){ - DatabaseHelper db = DatabaseHelper.instance; - Rog rog = Rog( - id:1, - team_name: teamname, - event_code : eventcode, - user_id: userId, - cp_number: cp, - checkintime: DateTime.now().toUtc().microsecondsSinceEpoch, - image: imageurl, - rog_action_type: 1, - ); - db.insertRogaining(rog); - } - else { - String serverUrl = ConstValues.currentServer(); - String url1 = "$serverUrl/api/checkinimage/"; - final im1Bytes = File(imageurl).readAsBytesSync(); - String im1_64 = base64Encode(im1Bytes); +// Team team0 = indexController.teamController.teams[0]; +// print("team={team0}"); - final http.Response response = await http.post( - Uri.parse(url1), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - 'Authorization': 'Token $token' - }, - // 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number' - body: jsonEncode({ - 'user' : userId.toString(), - 'team_name': teamname, - 'event_code': eventcode, - 'checkinimage' : im1_64, - 'checkintime' : checkinTime, - 'cp_number' : cp.toString() - }), - ); + //int teamId = indexController.teamController.teams[0]; - res = json.decode(utf8.decode(response.bodyBytes)); + if (indexController.connectionStatusName.value != "wifi" && + indexController.connectionStatusName.value != "mobile") { + debugPrint("== checkin without network =="); - print("-----@@@@@ $res -----"); - - if(response.statusCode == 201){ - //print('---- toekn is ${token} -----'); - final http.Response response2 = await http.post( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - }, - body: jsonEncode({ - 'team_name': teamname, - 'cp_number': cp.toString(), - 'event_code': eventcode, - 'image': res["checkinimage"].toString().replaceAll('http://localhost:8100', 'http://rogaining.sumasen.net') - }), - ); - print("--- checnin response ${response2.statusCode}----"); - if (response2.statusCode == 200) { - res = json.decode(utf8.decode(response2.bodyBytes)); - print('----checkin res _res : $res ----'); - } - } - } - } - else{ - if(indexController.connectionStatusName != "wifi" || indexController.connectionStatusName != "mobile"){ - DatabaseHelper db = DatabaseHelper.instance; - Rog rog = Rog( - id:1, - team_name: teamname, - event_code : eventcode, - user_id: userId, - cp_number: cp, - checkintime: DateTime.now().toUtc().microsecondsSinceEpoch, - image: null, - rog_action_type: 1, - ); - db.insertRogaining(rog); - } - else { - final http.Response response3 = await http.post( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - }, - body: jsonEncode({ - 'team_name': teamname, - 'cp_number': cp.toString(), - 'event_code': eventcode, - 'image': "" - }), - ); - print("--- checnin response ${response3.statusCode}----"); - if (response3.statusCode == 200) { - res = json.decode(utf8.decode(response3.bodyBytes)); - print('----checkin res _res : $res ----'); - } - } - } - return res; - } - - - Future> makeGoal(int userId, String token, String teamname, String image, String goalTime, String eventcode) async { - Map res2 = {}; - - final IndexController indexController = Get.find(); - final DestinationController destinationController = Get.find(); - - //if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){ - DatabaseHelper db = DatabaseHelper.instance; - Rog rog = Rog( - id:1, - team_name: teamname, - event_code : eventcode, - user_id: userId, - cp_number: -1, - checkintime: DateTime.now().toUtc().microsecondsSinceEpoch, - image: image, - rog_action_type: 1, - ); - db.insertRogaining(rog); - // } - // else{ + DatabaseHelper db = DatabaseHelper.instance; + Rog rog = Rog( + id: 1, + team_name: teamname, + event_code: eventcode, + user_id: userId, + cp_number: cp, + checkintime: DateTime.now().toUtc().microsecondsSinceEpoch, + image: imageurl, + rog_action_type: 1, + ); + db.insertRogaining(rog); + } else { + debugPrint("== Normal Check in ==="); String serverUrl = ConstValues.currentServer(); - String url1 = "$serverUrl/api/goalimage/"; - final im1Bytes = File(image).readAsBytesSync(); + String url1 = "$serverUrl/api/checkinimage/"; + final im1Bytes = File(imageurl).readAsBytesSync(); String im1_64 = base64Encode(im1Bytes); - final http.Response response = await http.post( - Uri.parse(url1), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - 'Authorization': 'Token $token' - }, - // 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number' - body: jsonEncode({ - 'user' : userId.toString(), - 'team_name': teamname, - 'event_code': eventcode, - 'goaltime' : goalTime, - 'goalimage' : im1_64, - 'cp_number' : "-1" - }), - ); + //print("~~~~ before calling api 1 ~~~~"); - String url = 'https://natnats.mobilous.com/goal_from_rogapp'; - print('++++++++$url'); - if (response.statusCode == 201) { - Map res = json.decode(utf8.decode(response.bodyBytes)); - print('----_res : $res ----'); - print('---- image url ${res["goalimage"]} ----'); + try { + final http.Response response = await http.post( + Uri.parse(url1), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + 'Authorization': 'Token $token' + }, + // 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number' + body: jsonEncode({ + 'user': userId.toString(), + 'team_name': teamname, + 'event_code': eventcode, + 'checkinimage': im1_64, + 'checkintime': checkinTime, + 'cp_number': cp.toString() + }), + ); + + res = json.decode(utf8.decode(response.bodyBytes)); + //print("~~~~ api1 result $res ~~~~"); + //print("-----@@@@@ checkin $_res -----"); + + if (response.statusCode == 201) { + //print("~~~~ image from api1 ${res["checkinimage"].toString()} ~~~~"); + //print('---- toekn is ${token} -----'); + //print("~~~~ token is $token ~~~~"); + //print("~~~~ before callling api2 ~~~~"); final http.Response response2 = await http.post( Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: jsonEncode({ - 'team_name': teamname, - 'event_code': eventcode, - 'goal_time' : goalTime, - 'image' : res["goalimage"].toString().replaceAll('http://localhost:8100', 'http://rogaining.sumasen.net') - } - ), + 'team_name': teamname, + 'cp_number': cp.toString(), + 'event_code': eventcode, + 'image': res["checkinimage"].toString().replaceAll( + 'http://localhost:8100', serverUrl) //'http://rogaining.sumasen.net') + }), + ); + var vv = jsonEncode({ + 'team_name': teamname, + 'cp_number': cp.toString(), + 'event_code': eventcode, + 'image': res["checkinimage"].toString().replaceAll( + 'http://localhost:8100', serverUrl) //'http://rogaining.sumasen.net') + }); + //print("~~~~ api 2 values $vv ~~~~"); + //print("--json-- $vv"); + //print("--- checnin response ${response2.statusCode}----"); + if (response2.statusCode == 200) { + res = json.decode(utf8.decode(response2.bodyBytes)); + //print('----checkin res _res : $res ----'); + if (res["status"] == "ERROR" && cp>0 ) { + // スタート・ゴールはエラー除外。 + Get.snackbar("エラーがおきました", res["detail"], + backgroundColor: Colors.red, + colorText: Colors.white + ); + } + + } + } else { + Get.snackbar("サーバーエラーがおきました", "サーバーと通信できませんでした", + backgroundColor: Colors.red, + colorText: Colors.white + ); + } + } catch( e ) { + print('Error in makeCheckpoint: $e'); + Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした", + backgroundColor: Colors.red, + colorText: Colors.white ); - print('----- response2 is $response2 --------'); + } + } + //print("~~~~ done checkin ~~~~"); + return res; + } + + Future> makeGoal(int userId, String token, + String teamname, String image, String goalTime, String eventcode) async { + Map res2 = {}; + + final IndexController indexController = Get.find(); + final DestinationController destinationController = + Get.find(); + + // チームIDを取得 + + //int teamId = indexController.currentUser[0]["user"]["team"]["id"]; + + debugPrint("== goal Rogaining =="); + + //if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){ + DatabaseHelper db = DatabaseHelper.instance; + Rog rog = Rog( + id: 1, + team_name: teamname, + event_code: eventcode, + user_id: userId, // 中身はteamid + cp_number: -1, + checkintime: DateTime.now().toUtc().microsecondsSinceEpoch, + image: image, + rog_action_type: 1, + ); + db.insertRogaining(rog); + // } + // else{ + String serverUrl = ConstValues.currentServer(); + String url1 = "$serverUrl/api/goalimage/"; + final im1Bytes = File(image).readAsBytesSync(); + String im1_64 = base64Encode(im1Bytes); + + final http.Response response = await http.post( + Uri.parse(url1), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + 'Authorization': 'Token $token' + }, + // 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number' + body: jsonEncode({ + 'user': userId.toString(), //userId.toString(), + 'team_name': teamname, + 'event_code': eventcode, + 'goaltime': goalTime, + 'goalimage': im1_64, + 'cp_number': "-1" + }), + ); + + //String serverUrl = ConstValues.currentServer(); + String url = '$serverUrl/gifuroge/goal_from_rogapp'; + //print('++++++++$url'); + if (response.statusCode == 201) { + try { + Map res = json.decode(utf8.decode(response.bodyBytes)); + // print('----_res : $res ----'); + // print('---- image url ${res["goalimage"]} ----'); + final http.Response response2 = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: jsonEncode({ + 'team_name': teamname, + 'event_code': eventcode, + 'goal_time': goalTime, + 'image': res["goalimage"].toString().replaceAll( + 'http://localhost:8100', serverUrl) + //'http://rogaining.sumasen.net') + }), + ); + String rec = jsonEncode({ + 'team_name': teamname, + 'event_code': eventcode, + 'goal_time': goalTime, + 'image': res["goalimage"] + .toString() + .replaceAll('http://localhost:8100', serverUrl) + //'http://rogaining.sumasen.net') + }); + //print("-- json -- $rec"); + //print('----- response2 is $response2 --------'); if (response2.statusCode == 200) { res2 = json.decode(utf8.decode(response2.bodyBytes)); + } else { + res2 = json.decode(utf8.decode(response2.bodyBytes)); } - } + } catch(e){ + print( "Error {$e}" ); + } + } //} - destinationController.resetRogaining(); + destinationController.resetRogaining(isgoal: true); return res2; } + Future removeCheckin(int cp) async { + final IndexController indexController = Get.find(); - static Future> usersEventCode(String teamcode, String password) async { - Map res = {}; - String url = "https://natnats.mobilous.com/check_event_code?zekken_number=$teamcode&password=$password"; - print('++++++++$url'); - final http.Response response = await http.get( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', + //int userId = indexController.currentUser[0]["user"]["id"]; + //print("--- Pressed -----"); + String team = indexController.currentUser[0]["user"]['team_name']; + //print("--- _team : ${_team}-----"); + String eventCode = indexController.currentUser[0]["user"]["event_code"]; + + if (indexController.connectionStatusName.value != "wifi" && + indexController.connectionStatusName.value != "mobile") { + return Future.value(false); + } else { + String serverUrl = ConstValues.currentServer(); + String url = '$serverUrl/gifuroge/remove_checkin_from_rogapp'; + //print('++++++++$url'); + final http.Response response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: jsonEncode({ + 'event_code': eventCode, + 'team_name': team, + 'cp_number': cp.toString() + }), + ); + + //print("---- remove checkin ---- ${response.statusCode}"); + + if (response.statusCode == 200) { + return Future.value(true); + //print('----_res : $res ----'); } - ); + } + return Future.value(false); + } + + String timestampToTimeString(int timestamp) { + // Convert timestamp to DateTime and format it as needed + var dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp); + //print("^^^^ time ${dateTime}"); + // Format dateTime to a time string (e.g., '12:00:00') + // Adjust the format as needed + return "${dateTime.hour}:${dateTime.minute}:${dateTime.second}"; + } + + Future pushGPS() async { + //print("^^^^ pushed ^^^"); + final IndexController indexController = Get.find(); + + //int userId = indexController.currentUser[0]["user"]["id"]; + //print("--- Pressed -----"); + String team = indexController.currentUser[0]["user"]['team_name']; + //print("--- _team : ${_team}-----"); + String eventCode = indexController.currentUser[0]["user"]["event_code"]; + + List gpsDataList = []; + + if (indexController.connectionStatusName.value != "wifi" && + indexController.connectionStatusName.value != "mobile") { + return Future.value(false); + } else { + // Step 1: Fetch data from the local database + gpsDataList = + await GpsDatabaseHelper.instance.getUnsyncedGPSData(team, eventCode); + + // Step 2: Transform data into the required format + var payload = { + 'team_name': team, + 'event_code': eventCode, + 'waypoints': gpsDataList.map((gpsData) { + return { + 'latitude': gpsData.lat.toString(), + 'longitude': gpsData.lon.toString(), + // Convert the timestamp to a formatted time string + 'time': timestampToTimeString(gpsData.created_at), + }; + }).toList(), + }; + + //print("calling push gps step 2 ${payload}"); + + String serverUrl = ConstValues.currentServer(); + String urlS = '$serverUrl/gifuroge/get_waypoint_datas_from_rogapp'; + //print('++++++++$url'); + var url = Uri.parse(urlS); // Replace with your server URL + var response = await http.post( + url, + headers: {"Content-Type": "application/json"}, + body: json.encode(payload), + ); + + //print("GPS Data res ${response.statusCode}"); + if (response.statusCode == 200) { + // Handle success + // make local data as synced + await GpsDatabaseHelper.instance.setSyncData(gpsDataList); + //print("GPS Data sent successfully"); + } else { + // Handle error + //print("Failed to send data"); + } + } + return Future.value(false); + } + + static Future> usersEventCode( + String teamcode, String password) async { + Map res = {}; + String serverUrl = ConstValues.currentServer(); + String url = "$serverUrl/gifuroge/check_event_code?zekken_number=$teamcode&password=$password"; + //print('++++++++$url'); + final http.Response response = + await http.get(Uri.parse(url), headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }); if (response.statusCode == 200) { res = json.decode(utf8.decode(response.bodyBytes)); } return res; - } - -} \ No newline at end of file + } +} diff --git a/lib/services/location_line_service.dart b/lib/services/location_line_service.dart index a587f42..57c6514 100644 --- a/lib/services/location_line_service.dart +++ b/lib/services/location_line_service.dart @@ -1,35 +1,32 @@ -import 'package:geojson/geojson.dart'; -import 'package:http/http.dart' as http; +// import 'package:geojson/geojson.dart'; +// import 'package:http/http.dart' as http; -import '../utils/const.dart'; +// import '../utils/const.dart'; -class LocationLineService{ +// class LocationLineService { +// static Future loadLocationLines() async { +// final geo = GeoJson(); +// GeoJsonFeature? fs; +// String serverUrl = ConstValues.currentServer(); +// String url = '$serverUrl/api/location_line/'; +// //print('++++++++$url'); +// final response = await http.get( +// Uri.parse(url), +// headers: { +// 'Content-Type': 'application/json; charset=UTF-8', +// }, +// ); - static Future loadLocationLines() async { - final geo = GeoJson(); - GeoJsonFeature? fs; - String serverUrl = ConstValues.currentServer(); - String url = '$serverUrl/api/location_line/'; - print('++++++++$url'); - final response = await http.get(Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - }, - ); +// if (response.statusCode == 200) { +// geo.processedFeatures.listen((fst) { +// fs = fst; +// }); - if (response.statusCode == 200) { +// await geo.parse(response.body, verbose: true); - geo.processedFeatures.listen((fst) { - fs = fst; - }); - - await geo.parse(response.body, verbose:true); - - return fs; - - } else { - throw Exception('Failed to create album.'); - } - } - -} \ No newline at end of file +// return fs; +// } else { +// throw Exception('Failed to create album.'); +// } +// } +// } diff --git a/lib/services/location_polygon_service.dart b/lib/services/location_polygon_service.dart index 7e91145..b4c2ab5 100644 --- a/lib/services/location_polygon_service.dart +++ b/lib/services/location_polygon_service.dart @@ -1,36 +1,33 @@ -import 'package:geojson/geojson.dart'; -import 'package:http/http.dart' as http; +// import 'package:geojson/geojson.dart'; +// import 'package:http/http.dart' as http; -import '../utils/const.dart'; +// import '../utils/const.dart'; -class LocationPolygonervice{ +// class LocationPolygonervice { +// static Future loadLocationLines() async { +// final geo = GeoJson(); +// GeoJsonFeature? fs; - static Future loadLocationLines() async { - final geo = GeoJson(); - GeoJsonFeature? fs; +// String serverUrl = ConstValues.currentServer(); +// String url = '$serverUrl/api/location_polygon/'; +// //print('++++++++$url'); +// final response = await http.get( +// Uri.parse(url), +// headers: { +// 'Content-Type': 'application/json; charset=UTF-8', +// }, +// ); - String serverUrl = ConstValues.currentServer(); - String url = '$serverUrl/api/location_polygon/'; - print('++++++++$url'); - final response = await http.get(Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - }, - ); +// if (response.statusCode == 200) { +// geo.processedFeatures.listen((fst) { +// fs = fst; +// }); - if (response.statusCode == 200) { +// await geo.parse(response.body, verbose: true); - geo.processedFeatures.listen((fst) { - fs = fst; - }); - - await geo.parse(response.body, verbose:true); - - return fs; - - } else { - throw Exception('Failed to create album.'); - } - } - -} \ No newline at end of file +// return fs; +// } else { +// throw Exception('Failed to create album.'); +// } +// } +// } diff --git a/lib/services/location_service.dart b/lib/services/location_service.dart index 4a62cec..df588d8 100644 --- a/lib/services/location_service.dart +++ b/lib/services/location_service.dart @@ -1,75 +1,52 @@ import 'dart:convert'; - -import 'package:geojson/geojson.dart'; +import 'package:flutter/services.dart'; +import 'package:geojson_vi/geojson_vi.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/utils/const.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/utils/const.dart'; -class LocationService{ +class LocationService { - // static Future loadLocations() async { - // final IndexController indexController = Get.find(); - // String server_url = ConstValues.currentServer(); - // String url = ""; - // if(indexController.currentUser.length > 0){ - // url = '${server_url}/api/location/?is_rog=True'; - // } - // else { - // url = '${server_url}/api/location/'; - // } - // //String url = 'http://localhost:8100/api/location/'; - // final response = await http.get(Uri.parse(url), - // headers: { - // 'Content-Type': 'application/json; charset=UTF-8', - // }, - // ); - - // if (response.statusCode == 200) { - - // return featuresFromGeoJson(utf8.decode(response.bodyBytes)); - // } - // return null; - // } - - static Future loadLocationsFor(String perfecture, String cat) async { + static Future loadLocationsFor( + String perfecture, String cat) async { final IndexController indexController = Get.find(); String url = ""; String serverUrl = ConstValues.currentServer(); - - if(cat.isNotEmpty){ - if(indexController.currentUser.isNotEmpty){ + + if (cat.isNotEmpty) { + if (indexController.currentUser.isNotEmpty) { bool rog = indexController.currentUser[0]['user']['is_rogaining']; - String r = rog == true ? 'True': 'False'; + String r = rog == true ? 'True' : 'False'; var grp = indexController.currentUser[0]['user']['event_code']; url = '$serverUrl/api/inperf/?rog=$r&perf=$perfecture&cat=$cat'; - } - else { + } else { url = '$serverUrl/api/inperf/?perf=$perfecture&cat=$cat'; } - } - else{ - if(indexController.currentUser.isNotEmpty){ + } else { + if (indexController.currentUser.isNotEmpty) { bool rog = indexController.currentUser[0]['user']['is_rogaining']; - String r = rog == true ? 'True': 'False'; + String r = rog == true ? 'True' : 'False'; var grp = indexController.currentUser[0]['user']['event_code']; url = '$serverUrl/api/inperf/?rog=$r&perf=$perfecture'; - } - else { + } else { url = '$serverUrl/api/inperf/?perf=$perfecture'; } } - print('++++++++$url'); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - GeoJsonFeatureCollection cc = await featuresFromGeoJson(utf8.decode(response.bodyBytes)); + GeoJSONFeatureCollection cc = + GeoJSONFeatureCollection.fromJSON(utf8.decode(response.bodyBytes)); //print(cc); return cc; //featuresFromGeoJson(utf8.decode(response.bodyBytes)); } @@ -80,175 +57,182 @@ class LocationService{ List ext = []; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/locsext/'; - print('++++++++$url'); + //print('++++++++$url'); final response = await http.post(Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - 'Authorization': 'Token $token' - }, - body: jsonEncode({ - 'token': token, - }) - ); + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + 'Authorization': 'Token $token' + }, + body: jsonEncode({ + 'token': token, + })); if (response.statusCode == 200) { - ext = json.decode(utf8.decode(response.bodyBytes)); } return ext; } - - static Future loadLocationsSubFor(String subperfecture, String cat) async { + static Future loadLocationsBound( + double lat1, + double lon1, + double lat2, + double lon2, + double lat3, + double lon3, + double lat4, + double lon4, + String cat, + String eventCode) async { + //print("-------- in location for bound -------------"); final IndexController indexController = Get.find(); - String url = ""; - String serverUrl = ConstValues.currentServer(); - if(cat.isNotEmpty){ - if(indexController.currentUser.isNotEmpty){ - bool rog = indexController.currentUser[0]['user']['is_rogaining']; - String r = rog == true ? 'True': 'False'; - var grp = indexController.currentUser[0]['user']['event_code']; - url = '$serverUrl/api/insubperf?rog=$r&subperf=$subperfecture&cat=$cat'; - } - else{ - url = '$serverUrl/api/insubperf?subperf=$subperfecture&cat=$cat'; - } - } - else{ - if(indexController.currentUser.isNotEmpty){ - bool rog = indexController.currentUser[0]['user']['is_rogaining']; - String r = rog == true ? 'True': 'False'; - var grp = indexController.currentUser[0]['user']['event_code']; - url = '$serverUrl/api/insubperf?rog=$r&subperf=$subperfecture'; - } - else{ - url = '$serverUrl/api/insubperf?subperf=$subperfecture'; - } - } - print('++++++++$url'); - final response = await http.get(Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - }, - ); + final updateTime = indexController.lastUserUpdateTime.value; - if (response.statusCode == 200) { - GeoJsonFeatureCollection cc = await featuresFromGeoJson(utf8.decode(response.bodyBytes)); - //print(cc); - return cc; //featuresFromGeoJson(utf8.decode(response.bodyBytes)); - } - return null; - } + // ユーザー情報の更新を最大5秒間待つ + try { + /* + // ユーザー情報の更新を最大5秒間待つ + final newUpdateTime = await indexController.lastUserUpdateTime.stream + .firstWhere( + (time) => time.isAfter(updateTime), + orElse: () => updateTime, + ).timeout(Duration(seconds: 5)); - static Future loadLocationsBound(double lat1, double lon1, double lat2, double lon2, double lat3, double lon3, double lat4, double lon4, String cat) async { - final IndexController indexController = Get.find(); - print("-------- in location for bound -------------"); - print("-------- in location for bound current user ${indexController.currentUser} -------------"); - String url = ""; - String serverUrl = ConstValues.currentServer(); - if(cat.isNotEmpty){ - if(indexController.currentUser.isNotEmpty){ - bool rog = indexController.currentUser[0]['user']['is_rogaining']; - String r = rog == true ? 'True': 'False'; - var grp = indexController.currentUser[0]['user']['event_code']; - url = '$serverUrl/api/inbound?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat'; - } - else{ - url = '$serverUrl/api/inbound?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat'; - } - } - else{ - if(indexController.currentUser.isNotEmpty){ - bool rog = indexController.currentUser[0]['user']['is_rogaining']; - String r = rog == true ? 'True': 'False'; - var grp = indexController.currentUser[0]['user']['event_code']; - print("-------- requested user group $grp -------------"); - url = '$serverUrl/api/inbound?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4'; - } - else{ - url = '$serverUrl/api/inbound?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4'; - } - } - print('++++++++$url'); - final response = await http.get(Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - }, - ); - - if (response.statusCode == 500) { - return GeoJsonFeatureCollection(); //featuresFromGeoJson(utf8.decode(response.bodyBytes)); - } - - if (response.statusCode == 200) { - GeoJsonFeatureCollection cc = await featuresFromGeoJson(utf8.decode(response.bodyBytes)); - if(cc.collection.isEmpty){ + if (newUpdateTime == updateTime) { + print('ユーザー情報の更新がタイムアウトしました'); + // タイムアウト時の処理(例:エラー表示やリトライ) return null; } - else{ - //print("---- feature got from server is ${cc.collection[0].properties} ------"); - return cc; + */ + + /* + await indexController.lastUserUpdateTime.stream.firstWhere( + (time) => time.isAfter(updateTime), + orElse: () => updateTime, + ).timeout(Duration(seconds: 2), onTimeout: () => updateTime); + */ + + String url = ""; + String serverUrl = ConstValues.currentServer(); + if (cat.isNotEmpty) { + if (indexController.currentUser.isNotEmpty) { + bool rog = indexController.currentUser[0]['user']['is_rogaining']; + String r = rog == true ? 'True' : 'False'; + var grp = eventCode; //indexController.currentUser[0]['user']['event_code']; + print("Group=$grp"); + url = + '$serverUrl/api/inbound2?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat'; + } else { + url = + '$serverUrl/api/inbound2?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat'; + } + } else { + if (indexController.currentUser.isNotEmpty) { + bool rog = indexController.currentUser[0]['user']['is_rogaining']; + String r = rog == true ? 'True' : 'False'; + var grp = indexController.currentUser[0]['user']['event_code']; + print("-------- requested user group $grp -------------"); + url = + '$serverUrl/api/inbound2?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4'; + } else { + url = + '$serverUrl/api/inbound2?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4'; + } + print('++++++++$url'); + final response = await http.get( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + ); + + if (response.statusCode == 500) { + return null; //featuresFromGeoJson(utf8.decode(response.bodyBytes)); + } + + if (response.statusCode == 200) { + DestinationController destinationController = + Get.find(); + + GeoJSONFeatureCollection cc = + GeoJSONFeatureCollection.fromJSON(utf8.decode(response.bodyBytes)); + if (cc.features.isEmpty) { + return null; + } else { + //print("---- feature got from server is ${cc.collection[0].properties} ------"); + return cc; + } + } } + } catch(e) { + print("Error: $e"); } + return null; } - - static Future loadCustomLocations(String name, String cat) async { + static Future loadCustomLocations( + String name, String cat) async { final IndexController indexController = Get.find(); String url = ""; - if(cat == "-all-"){ + if (cat == "-all-") { cat = ""; } String serverUrl = ConstValues.currentServer(); - print("loadCustomLocations url is ----- $cat"); - if(cat.isNotEmpty){ - if(indexController.currentUser.isNotEmpty){ + //print("loadCustomLocations url is ----- $cat"); + if (cat.isNotEmpty) { + if (indexController.currentUser.isNotEmpty) { bool rog = indexController.currentUser[0]['user']['is_rogaining']; - String r = rog == true ? 'True': 'False'; + String r = rog == true ? 'True' : 'False'; var grp = indexController.currentUser[0]['user']['event_code']; url = '$serverUrl/api/custom_area/?rog=$r&&cat=$cat'; - } - else{ + } else { url = '$serverUrl/api/custom_area/?&cat=$cat'; } - } - else{ - if(indexController.currentUser.isNotEmpty){ + } else { + if (indexController.currentUser.isNotEmpty) { bool rog = indexController.currentUser[0]['user']['is_rogaining']; - String r = rog == true ? 'True': 'False'; + String r = rog == true ? 'True' : 'False'; var grp = indexController.currentUser[0]['user']['event_code']; url = '$serverUrl/api/customarea?rog=$r&name=$name'; - } - else{ + } else { url = '$serverUrl/api/customarea?name=$name'; } } - print('++++++++$url'); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 500) { - return GeoJsonFeatureCollection(); //featuresFromGeoJson(utf8.decode(response.bodyBytes)); + return null; //featuresFromGeoJson(utf8.decode(response.bodyBytes)); } if (response.statusCode == 200) { - GeoJsonFeatureCollection cc = await featuresFromGeoJson(utf8.decode(response.bodyBytes)); - if(cc.collection.isEmpty){ + GeoJSONFeatureCollection cc = + GeoJSONFeatureCollection.fromJSON(utf8.decode(response.bodyBytes)); + if (cc.features.isEmpty) { return null; - } - else{ - return cc; + } else { + return cc; } } return null; } + static const platform = MethodChannel('location'); - - -} \ No newline at end of file + static Future isLocationServiceRunning() async { + try { + final bool isRunning = await platform.invokeMethod('isLocationServiceRunning'); + return isRunning; + } catch (e) { + print("Failed to check if location service is running: $e"); + return false; + } + } +} diff --git a/lib/services/maxtrix_service.dart b/lib/services/maxtrix_service.dart index bd9b39e..3631d75 100644 --- a/lib/services/maxtrix_service.dart +++ b/lib/services/maxtrix_service.dart @@ -1,38 +1,35 @@ - import 'dart:convert'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; -import 'package:rogapp/model/destination.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; - -class MatrixService{ - - static Future> getDestinations(List destinations) async { - - final DestinationController destinationController = Get.find(); +class MatrixService { + static Future> getDestinations( + List destinations) async { + final DestinationController destinationController = + Get.find(); String locs = ""; String origin = ""; String destination = ""; int i = 0; - for(Destination d in destinations){ + for (Destination d in destinations) { + //print("---- getting matrix for $d ------------"); - print("---- getting matrix for $d ------------"); - - if(i==0){ + if (i == 0) { origin = "${d.lat}, ${d.lon}"; i++; continue; } - if(i == (destinations.length - 1)){ + if (i == (destinations.length - 1)) { destination = "${d.lat}, ${d.lon}"; i++; continue; } - print("lat is ${d.lat}, long is ${d.lon}"); + //print("lat is ${d.lat}, long is ${d.lon}"); locs += "${d.lat}, ${d.lon}|"; i++; } @@ -46,28 +43,24 @@ class MatrixService{ break; case 2: mode = "transit"; - break; + break; default: mode = "walking"; break; } Map cats = {}; - String url = "https://maps.googleapis.com/maps/api/directions/json?destination=$destination&mode=$mode&waypoints=$locs&origin=$origin&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE"; - print('++++++++$url'); - final http.Response response = await http.get( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - } - ); + String url = + "https://maps.googleapis.com/maps/api/directions/json?destination=$destination&mode=$mode&waypoints=$locs&origin=$origin&key=AIzaSyCN2xFsqFyadWwpjiFxymrxzS6G1tNzraI"; + //print('++++++++$url'); + final http.Response response = + await http.get(Uri.parse(url), headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }); if (response.statusCode == 200) { cats = json.decode(utf8.decode(response.bodyBytes)); } return cats; } - - } - diff --git a/lib/services/perfecture_service.dart b/lib/services/perfecture_service.dart index 987144b..17d2fd8 100644 --- a/lib/services/perfecture_service.dart +++ b/lib/services/perfecture_service.dart @@ -1,23 +1,21 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:rogapp/utils/const.dart'; - - -class PerfectureService{ +import 'package:gifunavi/utils/const.dart'; +class PerfectureService { static Future?> loadPerfectures() async { List perfs = []; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/perf_main/'; - print('++++++++$url'); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - perfs = json.decode(utf8.decode(response.bodyBytes)); } return perfs; @@ -27,34 +25,33 @@ class PerfectureService{ List perfs = []; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/subperfinmain/?area=$area'; - print('++++++++$url'); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - perfs = json.decode(utf8.decode(response.bodyBytes)); } return perfs; } - static Future?> getMainPerfExt(String id) async { List perfs = []; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/mainperfext/?perf=$id'; - print('++++++++$url'); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - perfs = json.decode(utf8.decode(response.bodyBytes)); } return perfs; @@ -64,15 +61,15 @@ class PerfectureService{ List perfs = []; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/allgifuareas/?perf=$perf'; - print('++++++++$url'); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - perfs = json.decode(utf8.decode(response.bodyBytes)); } return perfs; @@ -82,40 +79,35 @@ class PerfectureService{ List perfs = []; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/customareanames'; - print('++++++++$url'); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - perfs = json.decode(utf8.decode(response.bodyBytes)); } return perfs; } - static Future?> getSubExt(String id) async { List perfs = []; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/perfext/?sub_perf=$id'; - print('++++++++$url'); - final response = await http.get(Uri.parse(url), + //print('++++++++$url'); + final response = await http.get( + Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { - perfs = json.decode(utf8.decode(response.bodyBytes)); } return perfs; } - - - } - diff --git a/lib/services/reacking_service.dart b/lib/services/reacking_service.dart index fd6c882..fea3839 100644 --- a/lib/services/reacking_service.dart +++ b/lib/services/reacking_service.dart @@ -1,27 +1,22 @@ - import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:rogapp/utils/const.dart'; - +import 'package:gifunavi/utils/const.dart'; class TrackingService { - - static Future> addTrack(String userId, double lat, double lon) async { + static Future> addTrack( + String userId, double lat, double lon) async { Map cats = {}; String serverUrl = ConstValues.currentServer(); String url = '$serverUrl/api/track/'; - print('++++++++$url'); + //print('++++++++$url'); final geom = '{"type": "MULTIPOINT", "coordinates": [[$lon, $lat]]}'; final http.Response response = await http.post( Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, - body: jsonEncode({ - 'user_id': userId, - 'geom': geom - }), + body: jsonEncode({'user_id': userId, 'geom': geom}), ); if (response.statusCode == 200) { @@ -29,5 +24,4 @@ class TrackingService { } return cats; } - -} \ No newline at end of file +} diff --git a/lib/spa/spa_binding.dart b/lib/spa/spa_binding.dart index 9db567f..68464de 100644 --- a/lib/spa/spa_binding.dart +++ b/lib/spa/spa_binding.dart @@ -1,6 +1,6 @@ import 'package:get/get.dart'; -import 'package:rogapp/spa/spa_controller.dart'; +import 'package:gifunavi/spa/spa_controller.dart'; class SpaBinding extends Bindings { @override diff --git a/lib/spa/spa_page.dart b/lib/spa/spa_page.dart index 04b2a63..9fb1ef4 100644 --- a/lib/spa/spa_page.dart +++ b/lib/spa/spa_page.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/spa/spa_controller.dart'; +import 'package:gifunavi/spa/spa_controller.dart'; class SpaPage extends GetView { - const SpaPage({Key? key}) : super(key: key); + const SpaPage({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/utils/const.dart b/lib/utils/const.dart index 3127cee..fe09a7e 100644 --- a/lib/utils/const.dart +++ b/lib/utils/const.dart @@ -2,7 +2,9 @@ class ConstValues{ - static const container_svr = "http://container.intranet.sumasen.net:8100"; + //static const container_svr = "http://container.intranet.sumasen.net:8100"; + //static const server_uri = "https://rogaining.intranet.sumasen.net"; + static const container_svr = "http://container.sumasen.net:8100"; static const server_uri = "https://rogaining.sumasen.net"; static const dev_server = "http://localhost:8100"; static const dev_ip_server = "http://192.168.8.100:8100"; @@ -10,6 +12,7 @@ class ConstValues{ static const dev_home_ip_mserver = "http://192.168.1.10:8100"; static String currentServer(){ + //return dev_ip_server; return server_uri; } } diff --git a/lib/utils/database_gps.dart b/lib/utils/database_gps.dart new file mode 100644 index 0000000..b4535d9 --- /dev/null +++ b/lib/utils/database_gps.dart @@ -0,0 +1,102 @@ +import 'dart:io'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:gifunavi/model/gps_data.dart'; +import 'package:sqflite/sqflite.dart'; + +class GpsDatabaseHelper { + GpsDatabaseHelper._privateConstructor(); + static final GpsDatabaseHelper instance = + GpsDatabaseHelper._privateConstructor(); + static Database? _database; + Future get database async => _database ??= await _initDatabase(); + + Future _initDatabase() async { + Directory documentDirectory = await getApplicationDocumentsDirectory(); + String path = join(documentDirectory.path, 'rog.db'); + // return await openDatabase( + // path, + // version: 1, + // onCreate: _onCreate, + // ); + return openDatabase( + join( + await getDatabasesPath(), + 'gps.db', + ), + version: 1, + onCreate: _onCreate); + } + + Future _onCreate(Database db, int version) async { + await db.execute(''' + CREATE TABLE gps( + id INTEGER PRIMARY KEY AUTOINCREMENT, + team_name TEXT, + event_code TEXT, + lat REAL, + lon REAL, + is_checkin int, + created_at INTEGER, + is_synced INTEGER DEFAULT 0 + ) + '''); + } + + Future insertGps(GpsData gps) async { + try { + //print("---- try insering ${gps.toMap()}"); + Database db = await instance.database; + int? nextOrder = + Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(id) FROM gps')); + nextOrder = nextOrder ?? 0; + nextOrder = nextOrder + 1; + gps.id = nextOrder; + //print("---- insering ${gps.toMap()}"); + int res = await db.insert( + 'gps', + gps.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + //print("------ database helper insert $res-----------::::::::"); + return res; + } catch (err) { + print("------ error $err-----------::::::::"); + return -1; + } + } + + Future> getGPSData(String teamName, String eventCode) async { + Database db = await instance.database; + var gpss = await db.query('gps', + where: "team_name = ? and event_code = ?", + whereArgs: [teamName, eventCode], + orderBy: 'created_at'); + List gpsDatas = + gpss.isNotEmpty ? gpss.map((e) => GpsData.fromMap(e)).toList() : []; + //print("--------- db list $gpsDatas"); + return gpsDatas; + } + + Future> getUnsyncedGPSData( + String teamName, String eventCode) async { + Database db = await instance.database; + var gpss = await db.query('gps', + where: 'team_name = ? and event_code = ? and is_synced = 0', + whereArgs: [teamName, eventCode], + orderBy: 'created_at'); + return gpss.isNotEmpty ? gpss.map((e) => GpsData.fromMap(e)).toList() : []; + } + + Future setSyncData(List data) async { + Database db = await instance.database; + for (var record in data) { + await db.update( + 'gps', + {'is_synced': 1}, + where: 'id = ?', + whereArgs: [record.id], + ); + } + } +} diff --git a/lib/utils/database_helper.dart b/lib/utils/database_helper.dart index 68a4446..08b22f2 100644 --- a/lib/utils/database_helper.dart +++ b/lib/utils/database_helper.dart @@ -1,12 +1,12 @@ import 'dart:io'; import 'package:path_provider/path_provider.dart'; -import 'package:rogapp/model/destination.dart'; +import 'package:gifunavi/model/destination.dart'; import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import '../model/rog.dart'; -class DatabaseHelper{ +class DatabaseHelper { DatabaseHelper._privateConstructor(); static final DatabaseHelper instance = DatabaseHelper._privateConstructor(); @@ -21,7 +21,13 @@ class DatabaseHelper{ // version: 1, // onCreate: _onCreate, // ); - return openDatabase(join(await getDatabasesPath(), 'rog.db',), version: 1, onCreate: _onCreate); + return openDatabase( + join( + await getDatabasesPath(), + 'rog.db', + ), + version: 1, + onCreate: _onCreate); } Future _onCreate(Database db, int version) async { @@ -48,11 +54,16 @@ class DatabaseHelper{ cp REAL, checkin_point REAL, buy_point REAL, - hidden_location INTEGER + hidden_location INTEGER, + checkin_image TEXT, + buypoint_image TEXT, + forced_checkin INTEGER, + recipt_times INTEGER, + tags TEXT ) '''); - await db.execute(''' + await db.execute(''' CREATE TABLE rogaining( rog_id INTEGER PRIMARY KEY AUTOINCREMENT, course_id INTEGER, @@ -65,7 +76,7 @@ class DatabaseHelper{ ) '''); - await db.execute(''' + await db.execute(''' CREATE TABLE rog( id INTEGER PRIMARY KEY AUTOINCREMENT, team_name TEXT, @@ -77,86 +88,72 @@ class DatabaseHelper{ rog_action_type INTEGER ) '''); - } Future> allRogianing() async { Database db = await instance.database; var rog = await db.query('rog'); - List roglist = rog.isNotEmpty ? - rog.map((e) => Rog.fromMap(e)).toList() : []; - print("--------- $rog"); + List roglist = + rog.isNotEmpty ? rog.map((e) => Rog.fromMap(e)).toList() : []; + //print("--------- $rog"); return roglist; } - Future> getRogainingByLatLon(double lat, double lon) async { Database db = await instance.database; var rog = await db.query('rog', where: "lat = $lat and lon= $lon"); - List roglist = rog.isNotEmpty - ? rog.map((e) => Rog.fromMap(e)).toList() : []; + List roglist = + rog.isNotEmpty ? rog.map((e) => Rog.fromMap(e)).toList() : []; return roglist; } Future clearSelection() async { Database db = await instance.database; - Map rowClear = { - "selected": false - }; - return await db.update( - "destination", - rowClear - ); + Map rowClear = {"selected": false}; + return await db.update("destination", rowClear); } Future toggleSelecttion(Destination dest) async { Database db = await instance.database; - + bool val = !dest.selected!; - Map rowTarget = { - "selected": val - }; + Map rowTarget = {"selected": val}; await clearSelection(); - return await db.update( - "destination", - rowTarget, - where: 'location_id = ?', - whereArgs: [dest.location_id!] - ); + return await db.update("destination", rowTarget, + where: 'location_id = ?', whereArgs: [dest.location_id!]); } Future deleteRogaining(int id) async { Database db = await instance.database; var rog = await db.delete('rog', where: "id = $id"); int ret = rog > 0 ? rog : -1; - + return ret; } - Future deleteAllRogaining() async { Database db = await instance.database; await db.delete('rog'); } - - FutureisRogAlreadyAvailable(int id) async{ + Future isRogAlreadyAvailable(int id) async { Database db = await instance.database; var rog = await db.query('rog', where: "id = $id"); return rog.isNotEmpty ? true : false; } - FuturelatestGoal() async{ + Future latestGoal() async { Database db = await instance.database; - return Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(checkintime) FROM rog')); - + return Sqflite.firstIntValue( + await db.rawQuery('SELECT MAX(checkintime) FROM rog')); } Future insertRogaining(Rog rog) async { Database db = await instance.database; - int? nextOrder = Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(id) FROM rog')); + int? nextOrder = + Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(id) FROM rog')); nextOrder = nextOrder ?? 0; nextOrder = nextOrder + 1; rog.id = nextOrder; @@ -165,68 +162,63 @@ class DatabaseHelper{ rog.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); - print("------ database helper insert $res-----------::::::::"); + //print("------ database helper insert $res-----------::::::::"); return res; } - Future> getDestinations() async { Database db = await instance.database; var dest = await db.query('destination', orderBy: 'list_order'); - List destList = dest.isNotEmpty ? - dest.map((e) => Destination.fromMap(e)).toList() : []; - print("--------- $destList"); + List destList = + dest.isNotEmpty ? dest.map((e) => Destination.fromMap(e)).toList() : []; + //print("--------- $destList"); return destList; } Future> getDestinationById(int id) async { Database db = await instance.database; var rog = await db.query('destination', where: "location_id = $id"); - List deslist = rog.isNotEmpty - ? rog.map((e) => Destination.fromMap(e)).toList() : []; + List deslist = + rog.isNotEmpty ? rog.map((e) => Destination.fromMap(e)).toList() : []; return deslist; } - - Future> getDestinationByLatLon(double lat, double lon) async { + Future> getDestinationByLatLon( + double lat, double lon) async { Database db = await instance.database; - var dest = await db.query('destination', where: "lat = $lat and lon= $lon", orderBy: 'list_order'); - List destList = dest.isNotEmpty - ? dest.map((e) => Destination.fromMap(e)).toList() : []; + var dest = await db.query('destination', + where: "lat = $lat and lon= $lon", orderBy: 'list_order'); + List destList = + dest.isNotEmpty ? dest.map((e) => Destination.fromMap(e)).toList() : []; return destList; } Future deleteDestination(int locationId) async { Database db = await instance.database; - var dest = await db.delete('destination', where: "location_id = $locationId"); + var dest = + await db.delete('destination', where: "location_id = $locationId"); int ret = dest > 0 ? dest : -1; //after deleting set correct order await setOrder(); - + return ret; } - FuturesetOrder() async { + Future setOrder() async { Database db = await instance.database; var byOrder = await db.query('destination', orderBy: 'list_order'); List desDb = byOrder.isNotEmpty - ? byOrder.map((e) => Destination.fromMap(e)).toList() : []; + ? byOrder.map((e) => Destination.fromMap(e)).toList() + : []; int order = 1; - for( Destination d in desDb){ + for (Destination d in desDb) { + Map rowTarget = {"list_order": order}; - Map rowTarget = { - "list_order": order - }; - - await db.update( - "destination", - rowTarget, - where: 'location_id = ?', - whereArgs: [d.location_id] - ); + await db.update("destination", rowTarget, + where: 'location_id = ?', whereArgs: [d.location_id]); order += 1; } @@ -237,15 +229,18 @@ class DatabaseHelper{ await db.delete('destination'); } - FutureisAlreadyAvailable(int locationId) async{ + Future isAlreadyAvailable(int locationId) async { Database db = await instance.database; - var dest = await db.query('destination', where: "location_id = $locationId"); + var dest = + await db.query('destination', where: "location_id = $locationId"); return dest.isNotEmpty ? true : false; } Future insertDestination(Destination dest) async { + await deleteDestination(dest.location_id!); Database db = await instance.database; - int? nextOrder = Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(list_order) FROM destination')); + int? nextOrder = Sqflite.firstIntValue( + await db.rawQuery('SELECT MAX(list_order) FROM destination')); nextOrder = nextOrder ?? 0; nextOrder = nextOrder + 1; dest.list_order = nextOrder; @@ -258,39 +253,50 @@ class DatabaseHelper{ return res; } - Future updateAction(Destination destination, bool checkin)async { + Future updateCancelBuyPoint(Destination destination) async { + //print("---- updating puypint image in db -----"); Database db = await instance.database; - int act = checkin == false ? 0 : 1; - Map row = { - "checkedin": act - }; - return await db.update( - "destination", - row, - where: 'location_id = ?', - whereArgs: [destination.location_id!] - ); + Map row = {"buy_point": 0}; + return await db.update("destination", row, + where: 'location_id = ?', whereArgs: [destination.location_id!]); } - Future updateOrder(Destination d, int dir)async { + Future updateBuyPoint(Destination destination, String imageUrl) async { + //print("---- updating puypint image in db -----"); Database db = await instance.database; - var target = await db.query('destination', where: "list_order = ${d.list_order! + dir}"); - var dest = await db.query('destination', where: "location_id = ${d.location_id}"); + Map row = {"buypoint_image": imageUrl}; + return await db.update("destination", row, + where: 'location_id = ?', whereArgs: [destination.location_id!]); + } - print("--- target in db is $target"); - print("--- destine in db is $dest"); + Future updateAction(Destination destination, bool checkin) async { + Database db = await instance.database; + int act = checkin == false ? 0 : 1; + Map row = {"checkedin": act}; + return await db.update("destination", row, + where: 'location_id = ?', whereArgs: [destination.location_id!]); + } - if(target.isNotEmpty){ + Future updateOrder(Destination d, int dir) async { + Database db = await instance.database; + var target = await db.query('destination', + where: "list_order = ${d.list_order! + dir}"); + var dest = + await db.query('destination', where: "location_id = ${d.location_id}"); + // print("--- target in db is $target"); + // print("--- destine in db is $dest"); + + if (target.isNotEmpty) { List targetIndb = target.isNotEmpty - ? target.map((e) => Destination.fromMap(e)).toList() : []; + ? target.map((e) => Destination.fromMap(e)).toList() + : []; List destIndb = dest.isNotEmpty - ? dest.map((e) => Destination.fromMap(e)).toList() : []; + ? dest.map((e) => Destination.fromMap(e)).toList() + : []; - Map rowTarget = { - "list_order": d.list_order - }; + Map rowTarget = {"list_order": d.list_order}; Map rowDes = { "list_order": destIndb[0].list_order! + dir @@ -299,19 +305,11 @@ class DatabaseHelper{ // print("--- target destination is ${target_indb[0].location_id}"); // print("--- destine destination is is ${dest_indb[0].location_id}"); - await db.update( - "destination", - rowTarget, - where: 'location_id = ?', - whereArgs: [targetIndb[0].location_id] - ); + await db.update("destination", rowTarget, + where: 'location_id = ?', whereArgs: [targetIndb[0].location_id]); - await db.update( - "destination", - rowDes, - where: 'location_id = ?', - whereArgs: [destIndb[0].location_id] - ); + await db.update("destination", rowDes, + where: 'location_id = ?', whereArgs: [destIndb[0].location_id]); } } @@ -319,6 +317,4 @@ class DatabaseHelper{ // Database db = await instance.database; // return await Sqflite.firstIntValue(await db.rawQuery("SELECT COUNT(*) FROM incidents")); // } - } - diff --git a/lib/utils/location_controller.dart b/lib/utils/location_controller.dart new file mode 100644 index 0000000..dad666d --- /dev/null +++ b/lib/utils/location_controller.dart @@ -0,0 +1,372 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:latlong2/latlong.dart'; +//import 'package:gifunavi/widgets/debug_widget.dart'; +import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/permission/permission.dart'; + +// LocationControllerクラスは、GetxControllerを継承したクラスであり、位置情報の管理を担当しています。 +// LocationControllerは以下の機能を提供しています。 +// LocationControllerは、アプリ全体で位置情報を一元管理するための重要なコンポーネントです。 +// 他のコンポーネントは、LocationControllerから位置情報を取得し、位置情報に関連する機能を実装することができます。 +// +// Features: +// * 現在の位置情報を保持し、他のコンポーネントからアクセスできるようにします。 +// * 位置情報のストリームを管理し、位置情報の更新を監視します。 +// * 位置情報サービスの有効性と権限の確認を行い、適切な処理を行います。 +// * 位置情報のストリームを開始、停止、再開する機能を提供します。 +// * 位置マーカーの位置情報をStreamControllerを通じて他のコンポーネントに通知します。 +// +// Logic: +// 1. startPositionStreamメソッドで、Geolocator.getPositionStreamを使用して位置情報のストリームを開始します。 +// 2. ストリームから位置情報を受信すると、LocationMarkerPositionオブジェクトを作成し、locationMarkerPositionStreamControllerに追加します。 +// 3. 位置情報が取得できなかった場合や、エラーが発生した場合は、適切な処理を行います。 +// 4. stopPositionStreamメソッドで、位置情報のストリームを一時停止することができます。 +// 5. resumePositionStreamメソッドで、一時停止中の位置情報のストリームを再開することができます。 +// 6. onCloseメソッドで、コントローラーのクローズ時に位置情報のストリームをキャンセルします。 +// +class LocationController extends GetxController { + // Reactive variable to hold the current position + Rx currentPosition = Rx(null); + // 現在の位置情報を保持するReactive変数です。Rx型で宣言されています。 + + // Subscription to the position stream + StreamSubscription? positionStream; + // 位置情報のストリームを保持する変数です。StreamSubscription型で宣言されています。 + + LatLng? lastValidLocation; + DateTime lastGPSDataReceivedTime = DateTime.now(); // 最後にGPSデータを受け取った時刻 + + bool gpsDebugMode = true; + /* + // GPSシミュレーション用のメソッドを追加 + void setSimulationMode(bool value) { + isSimulationMode = value; + } + + // ====== Akira , GPS信号強度をシミュレート ==== ここから + // + + //===== Akira Added 2024-4-9 start + // GPSシミュレーション用の変数を追加 ===> 本番では false にする。 + bool isSimulationMode = false; + + // GPS信号強度をシミュレートするための変数 + final Rx _simulatedSignalStrength = Rx('high'); + + // GPS信号強度をシミュレートするための関数 + void setSimulatedSignalStrength(String strength) { + if( strength!='real') { + isSimulationMode = true; + _simulatedSignalStrength.value = strength; + latestSignalStrength.value = strength; + }else{ + isSimulationMode = false; + _simulatedSignalStrength.value = strength; + } + } + + // シミュレートされた信号強度を取得するための関数 + String getSimulatedSignalStrength() { + //debugPrint("strength : ${_simulatedSignalStrength.value}"); + return _simulatedSignalStrength.value; + } + + */ + + // + // ====== Akira , GPS信号強度をシミュレート ==== ここまで + + + // GPS信号が弱い場合のフラグ. 本番では、false,high にする。 + bool isGpsSignalWeak = false; + RxString latestSignalStrength = 'high'.obs; + //final _latestSignalStrength = 'low'.obs; // 初期値を設定 + //String get latestSignalStrength => _latestSignalStrength.value; + Stream get gpsSignalStrengthStream => latestSignalStrength.stream; + + bool isRunningBackgroundGPS=false; + int activeEngineCount = 0; + + // GPS信号の強弱を判断するメソッドを追加. 10m 以内:強、30m以内:中、それ以上:弱 + // + String getGpsSignalStrength(Position? position) { + if (isSimulationMode.value) { + return getSimulatedSignalStrength(); + } + + if (position == null) { + //gpsDebugMode ? debugPrint("getGpsSignalStrength position is null.") : null; + latestSignalStrength.value = "low"; + isGpsSignalWeak = true; + return 'low'; + } + final accuracy = position.accuracy; + //gpsDebugMode ? debugPrint("getGpsSignalStrength : ${accuracy}") : null; + /* + if(isSimulationMode){ + return _simulatedSignalStrength.value; // GPS信号強度シミュレーション + }else { + */ + if (accuracy <= 10) { + latestSignalStrength.value = "high"; + isGpsSignalWeak = false; + return 'high'; + } else if (accuracy <= 50) { + latestSignalStrength.value = "medium"; + isGpsSignalWeak = false; + return 'medium'; + } else { + latestSignalStrength.value = "low"; + isGpsSignalWeak = true; + return 'low'; + } + // } + } + + // 現在位置を調整するメソッドを追加 + LatLng? adjustCurrentLocation(Position? position) { + if (position == null) { + if( lastValidLocation!=null ) { + //debugPrint("=== adjustCurrentLocation (Position:Null and using LastValidLocation ${lastValidLocation})==="); + return LatLng(lastValidLocation!.latitude, lastValidLocation!.longitude); + }else { + print("=== adjustCurrentLocation (Position:Null and No LastValidLocation ... )==="); + return null; + } + //return lastValidLocation ?? LatLng(0, 0); + } + final signalStrength = getGpsSignalStrength(position); + if (signalStrength == 'high' || signalStrength == 'medium') { + //debugPrint("=== adjustCurrentLocation (Position:Get and return Valid location:${position} ... )==="); + lastValidLocation = LatLng(position.latitude, position.longitude); + } + return lastValidLocation ?? LatLng(lastValidLocation!.latitude, lastValidLocation!.longitude); + } + + //===== Akira Added 2024-4-9 end + + final locationMarkerPositionStreamController = + StreamController.broadcast(); + // 位置マーカーの位置情報を送信するためのStreamControllerです。 + // StreamController型で宣言されています。 + + bool isStreamPaused = false; // 位置情報のストリームが一時停止中かどうかを示すフラグです。bool型で宣言されています。 + + // 位置マーカーの位置情報のストリームを取得するゲッター関数です。 + // locationMarkerPositionStreamController.streamを返します。 + // + Stream get locationMarkerPositionStream => + locationMarkerPositionStreamController.stream; + + // コントローラーの初期化時に呼び出されるライフサイクルメソッドです。 + // startPositionStreamメソッドを呼び出して、位置情報のストリームを開始します。 + // + @override + void onInit() { + super.onInit(); + // Start listening to location updates when the controller is initialized + startPositionStream(); + + } + + // 位置情報のストリームを開始するメソッドです。 + // 位置情報サービスが有効か確認し、無効な場合はダイアログを表示します。 + // 位置情報の権限を確認し、必要な権限がない場合は権限をリクエストします。 + // 既存の位置情報のストリームをキャンセルします。 + // Geolocator.getPositionStreamを使用して、新しい位置情報のストリームを開始します。 + // ストリームから受信した位置情報をlocationMarkerPositionStreamControllerに追加します。 + // エラーが発生した場合は、locationMarkerPositionStreamControllerにエラーを追加します。 + // ストリームが一時停止中の場合は、ストリームを再開します。 + // + // 2024-4-8 Akira : See 2809 + // stopPositionStreamメソッドを追加して、既存のストリームをキャンセルするようにしました。また、ストリームが完了したらnullに設定し、エラー発生時にストリームをキャンセルするようにしました。 + // + void startPositionStream() async { + // Check for location service and permissions before starting the stream + // 位置情報サービスの有効性をチェックし、無効な場合はエラーハンドリングを行います。 + // + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + Get.snackbar('位置情報サービスが無効です', '設定から位置情報サービスを有効にしてください'); + return; + } + + await PermissionController.checkAndRequestPermissions(); + + // 位置情報の設定を行います。z11 + // Set up the location options + const locationOptions = + LocationSettings(accuracy: LocationAccuracy.medium, distanceFilter: 0); + + // 既存の位置情報のストリームをキャンセルします。 + await positionStream?.cancel(); + + // 新しい位置情報のストリームを開始します。 + // + positionStream = Geolocator.getPositionStream(locationSettings: locationOptions).listen( + (Position? position) { + //gpsDebugMode ? debugPrint("Position = ${position}"):null; + final signalStrength = getGpsSignalStrength(position); + if (signalStrength == 'low') { + isGpsSignalWeak = true; + //gpsDebugMode ? debugPrint("LocationController.getPositionStream : isGpsSignalWeak = ${isGpsSignalWeak}"):null; + } else { + isGpsSignalWeak = false; + //gpsDebugMode ? debugPrint("LocationController.getPositionStream : isGpsSignalWeak = ${isGpsSignalWeak}"):null; + } + + final DestinationController destinationController = Get.find(); + + // ロゲ開始前、終了後、GPS=low の場合は更新しない。 + // + if (isGpsSignalWeak == false) { + //if (destinationController.isInRog.value && isGpsSignalWeak == false) { + final adjustedLocation = adjustCurrentLocation(position); + if (adjustedLocation != null) { + final locationMarkerPosition = LocationMarkerPosition( + latitude: adjustedLocation.latitude, + longitude: adjustedLocation.longitude, + accuracy: position?.accuracy ?? 0, + ); + handleLocationUpdate(locationMarkerPosition); + //locationMarkerPositionStreamController.add(locationMarkerPosition); // 位置データ送信 + } else { + // 位置情報が取得できなかった場合、 + // locationMarkerPositionStreamControllerにnullを追加します。 + locationMarkerPositionStreamController.add(null); // null 送信? + //forceUpdateLocation(Position? position); + + } + //}else{ + // debugPrint("GPS処理対象外"); + + } + + }, + onError: (e) { + // エラーが発生した場合、locationMarkerPositionStreamControllerにエラーを追加します。 + locationMarkerPositionStreamController.addError(e); + }, + onDone: () { + positionStream = null; // ストリームが完了したらnullに設定 + }, + cancelOnError: true // エラー発生時にストリームをキャンセル + ); + + // Resume stream if it was paused previously + // ストリームが一時停止中の場合、ストリームを再開します。 + // + if (isStreamPaused) { + isStreamPaused = false; + positionStream!.resume(); + } + } + + // Method to stop the position stream + // 位置情報のストリームを停止するメソッドです。 + // positionStreamが存在する場合、ストリームを一時停止します。 + // isStreamPausedフラグをtrueに設定します。 + // + void stopPositionStream() async { + if (positionStream != null) { + // updated Akira 2024-4-8 + await positionStream!.cancel(); + positionStream = null; + + //positionStream!.pause(); + //isStreamPaused = true; + } + } + + // Method to resume the position stream + // 位置情報のストリームを再開するメソッドです。 + // positionStreamが存在し、ストリームが一時停止中の場合、ストリームを再開します。 + // isStreamPausedフラグをfalseに設定します。 + // + void resumePositionStream() { + if (positionStream != null && isStreamPaused) { + positionStream!.resume(); + isStreamPaused = false; + } + } + + + void handleLocationUpdate(LocationMarkerPosition? position) async { + //debugPrint("locationController.handleLocationUpdate"); + try { + if (position != null) { + double currentLat = position.latitude; + double currentLon = position.longitude; + //debugPrint("Flutter: Received GPS signal. Latitude: $currentLat, Longitude: $currentLon"); + + //debugPrint("position = ${position}"); + /* + currentPosition.value = position; + final locationMarkerPosition = LocationMarkerPosition( + latitude: position.latitude, + longitude: position.longitude, + accuracy: position.accuracy, + ); + */ + lastGPSDataReceivedTime = DateTime.now(); // 最後にGPS信号を受け取った時刻 + locationMarkerPositionStreamController.add(position); + }else{ + gpsDebugMode ? debugPrint("Flutter: No GPS signal received."):null; + } + } catch( e ) { + debugPrint("Flutter: Error in handleLocationUpdate: $e"); + } + } + + // このメソッドは、現在の位置情報を locationMarkerPositionStreamController に送信します。 + // + void forceUpdateLocation(Position? position) { + if (position != null) { + final adjustedLocation = adjustCurrentLocation(position); + if (adjustedLocation != null) { + final locationMarkerPosition = LocationMarkerPosition( + latitude: adjustedLocation.latitude, + longitude: adjustedLocation.longitude, + accuracy: position.accuracy, + ); + locationMarkerPositionStreamController.add(locationMarkerPosition); + } + } + } + + // コントローラーのクローズ時に呼び出されるライフサイクルメソッドです。 + // positionStreamをキャンセルします。 + // + @override + void onClose() { + // Cancel the position stream subscription when the controller is closed + positionStream?.cancel(); + super.onClose(); + } + + // シミュレーションモードのフラグ + RxBool isSimulationMode = RxBool(false); + + // シミュレーションモードを切り替えるための関数 + void setSimulationMode(bool value) { + isSimulationMode.value = value; + } + + // GPS信号強度をシミュレートするための変数 + final Rx _simulatedSignalStrength = Rx('high'); + + // GPS信号強度をシミュレートするための関数 + void setSimulatedSignalStrength(String strength) { + _simulatedSignalStrength.value = strength; + } + + // シミュレートされた信号強度を取得するための関数 + String getSimulatedSignalStrength() { + return _simulatedSignalStrength.value; + } + +} diff --git a/lib/utils/string_values.dart b/lib/utils/string_values.dart index b3a6fa7..03dab47 100644 --- a/lib/utils/string_values.dart +++ b/lib/utils/string_values.dart @@ -7,7 +7,8 @@ class StringValues extends Translations{ 'drawer_title':'Rogaining participants can view checkpoints by logging in', 'app_title': '- Rogaining -', 'address':'address', - 'email':'Email', + 'bib':'Bib number', + 'email':'Email address', 'password':'Password', 'web':'Web', 'wikipedia':'Wikipedia', @@ -29,9 +30,9 @@ class StringValues extends Translations{ 'visit_history': 'Visit History', 'rog_web': 'rog website', 'no_values': 'No Values', - 'email_and_password_required': 'Email and password required', + 'email_and_password_required': 'Email and password are required to register user', 'rogaining_user_need_tosign_up': "Rogaining participants do need to sign up.", - 'add_location': 'Add Location', + 'add_location': 'Gifu', 'select_travel_mode':'Select your travel mode', 'walking':'Walking', 'driving': 'Driving', @@ -70,16 +71,158 @@ class StringValues extends Translations{ "Not reached the goal yet": "Not reached the goal yet", "You have not reached the goal yet.":"You have not reached the goal yet.", "delete_account": "Delete account", + "delete_account_title": "Are you ok to delete your account?", + "delete_account_middle": "All your account information and data history will be removed from local device and server side.", "accounted_deleted": "Account deleted", "account_deleted_message": "your account has beed successfully deleted", "privacy": "Privacy policy", - "app_developed_by_gifu_dx": "This app was developed by the Gifu Prefecture DX subsidy project." + "app_developed_by_gifu_dx": "This app was developed by the Gifu Prefecture DX subsidy project.", + + 'location_permission_title': 'Location Permission', + 'location_permission_content': 'Gifu Navi app collects location data to provide better services.\nLocation data is used for automatic check-in at checkpoints and delivery of notifications.\nLocation data may be collected even when the app is closed or not in use.\nCollected location data is only used as statistical information that cannot identify individuals and is never linked to personal information.\nIf you do not allow the use of location data, please select "Do not allow" on the next screen.', + 'location_disabled_title': 'Location Service Disabled', + 'location_disabled_content': 'Location information is disabled.\nTo continue, please enable location services for Gifu Navi in Settings > Privacy and Security > Location Services.', + 'drawer_title': 'Rogaining participants can view checkpoints by logging in', + 'app_title': 'Travel Itinerary', + 'want_to_go': 'Want to Go', + 'schedule_point': 'Schedule Point', + 'rog_web': 'Rogaining Website', + 'rogaining_user_need_tosign_up': "Rogaining participants do not need to sign up.", + 'add_location': 'Gifu', + 'finish': 'Finish', + 'my_route': 'My Route', + 'visit_history': 'Visit History', + 'search': 'Search', + 'login': 'Login', + 'password': 'Password', + 'already_have_account': 'Already have an account?', + 'sign_up': 'Sign Up', + 'create_account': 'Create an account, it\'s free', + 'confirm_password': 'Confirm Password', + 'cancel_checkin': 'Cancel Check-in', + 'go_here': 'Show route', + 'cancel_route':'Clear route', + 'start_rogaining': 'Start Rogaining', + 'in_game': 'In Game', + 'finish_rogaining': 'Finish Rogaining', + 'checkin': 'Check-in', + 'rogaining_not_started': 'Rogaining not started', + 'confirm': 'Confirm', + 'clear_rog_data_message': 'Starting rogaining will clear all previous rogaining data. Are you sure you want to start?', + 'no': 'No', + 'yes': 'Yes', + 'retake': 'Retake', + 'take_photo': 'Take Photo', + 'take_receipt_photo': 'Please take a photo of the receipt', + 'buypoint_added': 'Buypoint added.', + 'no_purchase': 'No Purchase', + 'complete': 'Complete', + 'movement_history': 'Movement History', + 'pass_history': 'Pass History', + 'no_checkin_yet': 'No check-in yet', + 'game_status': 'Game Status', + 'take_cp_photo': 'This is a CP. Please take a photo.', + 'save_goal_success': 'Goal saved successfully', + 'save_goal_failed': 'Goal not added', + 'please_try_again': 'Please try again', + 'click_start_to_start_rogaining': 'Click start to start rogaining', + 'at_rogaining_point_start': 'You are at a rogaining point, start rogaining', + 'start': 'Start', + 'rogaining_started': 'Rogaining Started', + 'rogaining_session_started': 'Rogaining session started', + 'not_started_yet': 'Not started yet', + 'not_started_rogaining_yet': 'You have not started rogaining yet.', + 'not_reached_goal_yet': 'Not reached the goal yet', + 'not_reached_goal_yet_message': 'You have not reached the goal yet.', + 'reload_qr': 'Reload QR', + 'read_qr': 'Read QR', + 'read_qr_code': 'Please read the QR code', + 'canceled': 'Canceled', + 'checkin_failed_try_again': 'Check-in failed. Please tap the checkpoint again if necessary.', + 'rogaining_not_started': 'Rogaining not started', + 'need_to_start_rogaining': 'You need to tap the start button to begin rogaining', + 'no_destination': 'No destination', + 'near_cp_not_checkin': 'Near a CP or distance-ignored CP, in-game but not checked in yet.', + 'auto_checkin_case': 'Auto check-in case', + 'non_auto_checkin_case': 'Non-auto check-in case', + 'normal_cp_case': 'Normal CP case', + 'non_normal_cp_case': 'Non-normal CP case... what case?', + 'goal_clock_photo_case': 'Goal clock photo case', + 'start_case_24_hours_passed': 'Start case and 24 hours have passed since the last goal', + 'start_cp_24_hours_passed': 'At the start CP, and 24 hours have passed since the last goal,', + 'standard_cp_not_checkin': 'Standard CP not checked in yet.', + 'after_checkin_buypoint_case': 'After check-in, buypoint case.', + 'goal_case': 'Goal case', + 'start_case': 'Start case', + 'no_match_skip_process': 'Does not match any conditions, skipping process', + 'server_error_occurred': 'A server error occurred', + 'could_not_communicate_with_server': 'Could not communicate with the server', + 'communication_error_occurred': 'A communication error occurred', + 'checked_in': 'Checked in.', + 'cancel_checkin': 'Cancel Check-in', + 'checkin_canceled_for': 'Check-in canceled for', + 'error': 'Error', + 'failed_to_cancel_checkin': 'Failed to cancel check-in.', + 'buypoint_added': 'Buypoint added', + 'error_occurred': 'An error occurred', + 'failed_to_process_checkpoint': 'Failed to process the checkpoint.', + 'start_rogaining': 'Start Rogaining', + 'in_competition': 'In Competition', + 'map_auto_return_message': 'If there is no map operation, it will automatically return to the current location. Please enter the timer seconds. If you check the checkbox, auto-return will not be performed.', + 'no_auto_return': 'No Auto Return', + 'failed_to_load_markers': 'Failed to load markers', + 'screen_switching_error': 'Screen switching error', + 'failed_to_switch_screen': 'Failed to switch the screen', + 'timer_duration': 'Timer Duration', + 'user_data_deletion': 'User Data Deletion', + 'user_consent_set_for_data_deletion': 'User consent is set for data deletion. User data has been deleted from the app and server', + 'go_to_gps_signal_area': 'Please go to an area with GPS signal.', + 'location_service_disabled': 'Location service is disabled. Please enable location service from the settings screen. If you are unsure, please contact the engineering staff.', + 'location_permission_not_granted': 'Location permission is not granted. Please allow location service for Gifu Navi from the settings screen. If you are unsure, please contact the engineering staff.', + 'location_service_issue_occurred': 'An issue occurred with the location service. The location service is being restarted, please wait a moment.', + 'login_failed': 'Login Failed', + 'check_login_id_or_password': 'Please check your login ID or password.', + 'communication_error_occurred': 'A communication error occurred', + 'could_not_communicate_with_server': 'Could not communicate with the server', + 'before_game': 'Before Game', + 'location_permission_denied_title': 'Location Permission Denied', + 'location_permission_denied_message': 'This app requires location permission to function properly. Please grant location permission to continue.', + 'location_permission_permanently_denied_title': 'Location Permission Permanently Denied', + 'location_permission_permanently_denied_message': 'Location permission has been permanently denied. Please open app settings to grant location permission.', + 'open_settings': 'Open Settings', + 'location_permission_needed_title': 'Location Permission Needed', + 'location_permission_needed_main': 'Location permissions have been permanently denied. Please open app settings to enable location permissions.', + 'open_settings': 'Open Settings', + 'location_services_disabled_title': 'Location Services Disabled', + 'location_service_disabled_main': 'Please enable location services to continue using the app.', + 'location_permission_denied_title': 'Location Permission Denied', + 'location_permission_denied_main': 'This app requires location permissions to function properly. Please enable location permissions in your device settings.', + 'home': 'Home', + 'welcome': 'Welcome to Gifu Navi', + 'location_disabled_message': 'Location services are disabled. Some features may not work properly.', + 'enable_location_service': 'Enable Location Service', + 'start_app': 'Start App', + 'location_permission_required_title': 'Location Permission Required', + 'location_permission_required_message': 'This app requires access to your location. Please grant permission to continue.', + 'cancel': 'Cancel', + 'checkins': 'Check-ins', + 'reset_button': 'Reset data', + 'reset_title': 'Reset the data in this device.', + 'reset_message': 'Are you ok to reset all data in this device?', + 'reset_done': 'Reset Done.', + 'reset_explain': 'All data has been reset. You should tap start rogaining to start game.', + 'no_match': 'No match!', + 'password_does_not_match':'The passwords you entered were not match.', + 'forgot_password':'Forgot password', + 'user_registration_successful':'Sent activation mail to you. Pls click activation link on the email.', + }, 'ja_JP': { 'drawer_title':'ロゲイニング参加者はログイン するとチェックポイントが参照 できます', 'app_title': '旅行工程表', 'address':'住所', - 'email':'Eメール', + 'bib':'ゼッケン番号', + 'email':'メールアドレス', 'password':'パスワード', 'web':'ウェブ', 'wikipedia':'ウィキペディア', @@ -103,9 +246,9 @@ class StringValues extends Translations{ 'visit_history': '訪問履歴', 'rog_web': 'ロゲイニングウェブサイト', 'no_values': '値なし', - 'email_and_password_required': 'メールとパスワードが必要です', + 'email_and_password_required': 'メールとパスワードの入力が必要です', 'rogaining_user_need_tosign_up': "ロゲイニング参加者はサインアップの必要はありません。", - 'add_location': '目的地選択', + 'add_location': '岐阜', 'select_travel_mode':'移動モードを選択してください', 'walking':'歩行', 'driving': '自動車利用', @@ -115,12 +258,12 @@ class StringValues extends Translations{ 'confirm': '確認', 'cancel': 'キャンセル', 'all_destinations_are_deleted_successfully' : 'すべての宛先が正常に削除されました', - 'deleted': "削除された", + 'deleted': "削除されました", 'remarks' : '備考', 'old_password' : '以前のパスワード', 'new_password' : '新しいパスワード', 'values_required' : '必要な値', - 'failed' : '失敗した', + 'failed' : '失敗', 'password_change_failed_please_try_again' : 'パスワードの変更に失敗しました。もう一度お試しください', 'user_registration_failed_please_try_again' : 'ユーザー登録に失敗しました。もう一度お試しください', 'all': '全て', @@ -129,13 +272,13 @@ class StringValues extends Translations{ 'finishing_rogaining' : 'ロゲイニングを終えて', 'cp_pls_take_photo' : "CPです。撮影してください。", 'take_photo of the clock' : '時計の写真を撮る', - 'finish_goal': 'フィニッシュゴール', + 'finish_goal': 'ゴール完了', 'goal_saved': "目標を保存しました", 'goal_added_successfuly' : '目標が正常に追加されました', 'goal_not_added' : '目標が追加されていません', 'please_try_again' : 'もう一度お試しください', "Click start to start rogaining":"開始をクリックして、ロゲイニングを開始します", - "you are at roganing point, start rogaining":"あなたはロガニングポイントにいます、ロガニングを始めてください", + "you are at roganing point, start rogaining":"あなたはロゲイニングポイントにいます、ロゲイニングを始めてください", "Start":"始める", "Rogaining Started":"ロゲイニング開始", "Rogaining session started":"ロゲイニングセッション開始", @@ -143,11 +286,153 @@ class StringValues extends Translations{ "You have not started rogaining yet.":"あなたはまだロゲイニングを始めていません。", "Not reached the goal yet": "まだ目標に達していない", "You have not reached the goal yet.":"あなたはまだゴールに達していません。", - "delete_account": "アカウントを削除する", + "delete_account": "アカウントを削除します", + "delete_account_title": "アカウントを削除しますがよろしいですか?", + "delete_account_middle": "これにより、アカウント情報とすべてのゲーム データが削除され、すべての状態が削除されます", "accounted_deleted": "アカウントが削除されました", "account_deleted_message": "あなたのアカウントは正常に削除されました", "privacy": "プライバシーポリシー", - "app_developed_by_gifu_dx": "このアプリは岐阜県DX補助金事業で開発されました。" + "app_developed_by_gifu_dx": "※このアプリは令和4、6年度岐阜県DX補助金事業で開発されました。", + + 'location_permission_title': 'ロケーション許可', + 'location_permission_content': 'このアプリでは、位置情報の収集を行います。\n岐阜ナビアプリではチェックポイントの自動チェックインの機能を可能にするために、現在地のデータが収集されます。アプリを閉じている時や、使用していないときにも収集されます。位置情報は、個人を特定できない統計的な情報として、ユーザーの個人情報とは一切結びつかない形で送信されます。お知らせの配信、位置情報の利用を許可しない場合は、この後表示されるダイアログで「許可しない」を選択してください。', + 'location_disabled_title': '位置情報サービスが無効です', + 'location_disabled_content': '位置情報が無効になっています\nこのアプリケーションへの位置情報アクセスが無効になっています。続行するには設定>プライバシーとセキュリティ>位置情報サービス>岐阜ナビ で有効にしてください。', + 'drawer_title': 'ロゲイニング参加者はログイン するとチェックポイントが参照 できます', + 'app_title': '旅行工程表', + 'want_to_go': '行きたい', + 'schedule_point': '予定地点', + 'rog_web': 'ロゲイニングウェブサイト', + 'rogaining_user_need_tosign_up': "ロゲイニング参加者はサインアップの必要はありません。", + 'add_location': '岐阜', + 'finish': '終了する', + 'my_route': 'マイルート', + 'visit_history': '訪問履歴', + 'search': '検索', + 'login': 'ログイン', + 'password': 'パスワード', + 'already_have_account': 'すでにアカウントをお持ちですか?', + 'sign_up': 'サインアップ', + 'create_account': 'アカウントを無料で作成します', + 'confirm_password': '確認用パスワード', + 'cancel_checkin': 'チェックイン取消', + 'go_here': 'ルート表示', + 'cancel_route':'ルート消去', + 'start_rogaining': 'ロゲ開始', + 'in_game': '競技中', + 'finish_rogaining': 'ロゲゴール', + 'checkin': 'チェックイン', + 'rogaining_not_started': 'ロゲは始まっていません', + 'confirm': '確認', + 'clear_rog_data_message': 'ロゲを開始すると、今までのロゲデータが全てクリアされます。本当に開始しますか?', + 'no': 'いいえ', + 'yes': 'はい', + 'retake': '再撮影', + 'take_photo': '撮影', + 'take_receipt_photo': 'レシートの写真を撮ってください', + 'buypoint_added': 'お買い物加点を行いました。', + 'no_purchase': '買い物なし', + 'complete': '完了', + 'movement_history': '移動履歴', + 'pass_history': '通過履歴', + 'no_checkin_yet': 'チェックインはまだされてません', + 'game_status': 'ゲームステータス', + 'take_cp_photo': 'CPです。撮影してください。', + 'save_goal_success': '目標が保存されました', + 'save_goal_failed': '目標が追加されていません', + 'please_try_again': 'もう一度お試しください', + 'click_start_to_start_rogaining': 'ロゲを開始するには開始をクリックしてください', + 'at_rogaining_point_start': 'あなたはロガニングポイントにいます、ロガニングを始めてください', + 'start': '開始', + 'rogaining_started': 'ロゲイニングを開始しました', + 'rogaining_session_started': 'ロゲイニングセッションを開始しました', + 'not_started_yet': 'まだ開始されていません', + 'not_started_rogaining_yet': 'あなたはまだロゲイニングを始めていません。', + 'not_reached_goal_yet': 'まだゴールに達していません', + 'not_reached_goal_yet_message': 'あなたはまだゴールに達していません。', + 'reload_qr': '再QR読込', + 'read_qr': 'QR読込', + 'read_qr_code': 'QRコードを読み取ってください', + 'canceled': 'キャンセルされました', + 'checkin_failed_try_again': 'チェックインしていません。必要ならもう一度チェックポイントをタップして下さい。', + 'rogaining_not_started': 'ロゲが始まっていません', + 'need_to_start_rogaining': 'ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります', + 'no_destination': '目的地がない場合', + 'near_cp_not_checkin': '検知範囲または距離無視CPで、ゲーム中でまだチェックインしていない。', + 'auto_checkin_case': '自動チェックインの場合', + 'non_auto_checkin_case': '自動チェックイン以外の場合', + 'normal_cp_case': '通常CPの場合', + 'non_normal_cp_case': '通常CP以外の場合....どんな場合?', + 'goal_clock_photo_case': 'ゴールで時計撮影の場合', + 'start_case_24_hours_passed': 'スタートの場合で最後のゴールから24時間経過している場合', + 'start_cp_24_hours_passed': '開始CPで、最後にゴールしてから24時間経過していれば、', + 'standard_cp_not_checkin': '標準CP まだチェックインしていない。', + 'after_checkin_buypoint_case': 'チェックイン後で買い物ポイントの場合。', + 'goal_case': 'ゴールの場合', + 'start_case': 'スタートの場合', + 'no_match_skip_process': 'いずれにも当てはまらないので、処理スキップ', + 'server_error_occurred': 'サーバーエラーがおきました', + 'could_not_communicate_with_server': 'サーバーと通信できませんでした', + 'communication_error_occurred': '通信エラーがおきました', + 'checked_in': 'チェックインしました。', + 'cancel_checkin': 'チェックイン取消', + 'checkin_canceled_for': 'のチェックインは取り消されました', + 'error': 'エラー', + 'failed_to_cancel_checkin': 'チェックイン取り消しに失敗しました。', + 'buypoint_added': 'お買い物加点を行いました', + 'error_occurred': 'エラーがおきました', + 'failed_to_process_checkpoint': 'チェックポイントの処理に失敗しました。', + 'start_rogaining': 'ロゲ開始', + 'in_competition': '競技中', + 'map_auto_return_message': 'マップ操作がなければ自動的に現在地に復帰します。そのタイマー秒数を入れて下さい。チェックボックスをチェックすると、自動復帰は行われなくなります。', + 'no_auto_return': '自動復帰なし', + 'failed_to_load_markers': 'マーカーの読み込みに失敗しました', + 'screen_switching_error': '画面切り替えでエラー', + 'failed_to_switch_screen': '画面の切り替えができませんでした', + 'timer_duration': 'タイマーの長さ', + 'user_data_deletion': 'ユーザーデータを削除する', + 'user_consent_set_for_data_deletion': 'データを削除するためにユーザーの同意が設定されています アプリとサーバーでユーザーデータが削除されました', + 'go_to_gps_signal_area': 'GPSの届く場所に行って、信号を拾ってください。', + 'location_service_disabled': '位置情報サービスが無効になっています。設定画面から位置情報サービスを有効にして下さい。不明な場合にはエンジニアスタッフにお問い合わせください。', + 'location_permission_not_granted': '位置情報サービスが許可されていません。設定画面から岐阜ナビの位置情報サービスを許可して下さい。不明な場合にはエンジニアスタッフにお問い合わせください。', + 'location_service_issue_occurred': '位置情報サービスに問題が発生しました。位置情報サービスを再起動していますので少しお待ちください。', + 'login_failed': 'ログイン失敗', + 'check_login_id_or_password': 'ログインIDかパスワードを確認して下さい。', + 'communication_error_occurred': '通信エラーがおきました', + 'could_not_communicate_with_server': 'サーバーと通信できませんでした', + 'before_game': 'ゲーム前', + 'location_permission_denied_title': '位置情報の許可が拒否されました', + 'location_permission_denied_message': 'このアプリを適切に機能させるには、位置情報の許可が必要です。続行するには、位置情報の許可を付与してください。', + 'location_permission_permanently_denied_title': '位置情報の許可が永久に拒否されました', + 'location_permission_permanently_denied_message': '位置情報の許可が永久に拒否されました。位置情報の許可を付与するには、アプリ設定を開いてください。', + 'open_settings': '設定を開く', + 'storage_permission_needed_title': '写真ライブラリへの許可が必要です', + 'storage_permission_needed_main': '岐阜ロゲでは、写真ライブラリを使用してスタート・チェックイン・ゴール等の通過照明写真の記録のために、写真ライブラリへの書き込みを行なっています。このためチェックイン時に写真をライブラリに保存する権限が必要です。設定画面で、「岐阜ナビ」に対して、ライブラリに写真の保存を許可するように設定してください。', + 'location_permission_needed_title': '位置情報への許可が必要です', + 'location_permission_needed_main': '岐阜ロゲでは、位置情報を使用してスタート・チェックイン・ゴール等の通過照明及び移動手段の記録のために、位置情報のトラッキングを行なっています。このためバックグラウンドでもトラッキングができるように位置情報の権限が必要です。設定画面で、「岐阜ナビ」に対して、常に位置情報を許可するように設定してください。', + 'open_settings': '設定を開く', + 'location_permission_denied_title': '位置情報へのアクセスが拒否されています。', + 'location_permission_denied_main': 'この岐阜ナビアプリは正常に動かすには位置情報への許可が必要です。「設定」画面で位置情報の許可を指定してください。', + 'location_services_disabled_title': '位置情報サービスが拒否されています', + 'location_service_disabled_main': '岐阜ナビアプリを使用するには位置情報サービスを許可してください。', + 'home': 'ホーム', + 'welcome': '岐阜ナビへようこそ', + 'location_disabled_message': '位置情報サービスが無効になっています。一部の機能が正しく動作しない可能性があります。', + 'enable_location_service': '位置情報サービスを有効にする', + 'start_app': 'アプリを開始する', + 'location_permission_required_title': '位置情報の許可が必要です', + 'location_permission_required_message': 'このアプリを使用するには、位置情報へのアクセスが必要です。続行するには許可を付与してください。', + 'cancel': 'キャンセル', + 'checkins': 'チェックイン', + 'reset_button': 'リセット', + 'reset_title': 'リセットしますがよろしいですか?', + 'reset_message': 'これにより、すべてのゲーム データが削除され、すべての状態が削除されます', + 'reset_done': 'リセット完了', + 'reset_explain': 'すべてリセットされました。ロゲ開始から再開して下さい。', + 'no_match': '不一致', + 'password_does_not_match':'入力したパスワードが一致しません', + 'forgot_password':'パスワードを忘れた場合', + 'user_registration_successful':'ユーザー認証のメールをお届けしました。メール上のリンクをクリックして正式登録してください。', }, }; -} \ No newline at end of file +} diff --git a/lib/utils/text_util.dart b/lib/utils/text_util.dart index 2417aa2..0cb5761 100644 --- a/lib/utils/text_util.dart +++ b/lib/utils/text_util.dart @@ -1,42 +1,43 @@ +import 'package:geojson_vi/geojson_vi.dart'; +import 'package:gifunavi/model/destination.dart'; -import 'package:geojson/geojson.dart'; -import 'package:rogapp/model/destination.dart'; - -class TextUtils{ - - static String getDisplayTextFeture(GeoJsonFeature f){ - RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); - String txt = ""; - // if(f.properties!["cp"] > 0){ - // //print("-- sub-- ${f.properties!["cp"]} ----"); - // txt = "${f.properties!["cp"].toString().replaceAll(regex, '')}"; - // } - //if(f.properties!["buy_point"] != null && f.properties!["buy_point"] > 0){ - txt = "$txt${f.properties!["sub_loc_id"]}"; - //} - return txt; +class TextUtils { + static String getDisplayTextFeture(GeoJSONFeature f) { + RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); + String txt = ""; + if (f.properties!["sub_loc_id"] != null) { + txt = "${f.properties!["sub_loc_id"]}"; } - - - static String getDisplayText(Destination dp){ - RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); - String txt = ""; - if(dp.cp! > 0){ - txt = dp.cp.toString().replaceAll(regex, ''); - if(dp.checkin_point != null && dp.checkin_point! > 0){ - txt = "$txt{${dp.checkin_point.toString().replaceAll(regex, '')}}"; - } - if(dp.buy_point != null && dp.buy_point! > 0){ - print("^^^^^^^^^ ${dp.sub_loc_id}^^^^^^^^^^"); - txt = "#${dp.cp.toString().replaceAll(regex, '')}(${dp.checkin_point.toString().replaceAll(regex, '')}+${dp.buy_point.toString().replaceAll(regex, '')})"; - } - } - return txt; - } - - // static String getDisplayText(String num){ - // RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); - // return "${num.replaceAll(regex, '')}"; + // if(f.properties!["cp"] > 0){ + // //print("-- sub-- ${f.properties!["cp"]} ----"); + // txt = "${f.properties!["cp"].toString().replaceAll(regex, '')}"; // } + //if(f.properties!["buy_point"] != null && f.properties!["buy_point"] > 0){ + //txt = "$txt${f.properties!["sub_loc_id"]}"; + //} + //print("Text = ${txt}"); + return txt; + } -} \ No newline at end of file + static String getDisplayText(Destination dp) { + RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); + String txt = ""; + if (dp.cp! > 0) { + txt = dp.cp.toString().replaceAll(regex, ''); + if (dp.checkin_point != null && dp.checkin_point! > 0) { + txt = "$txt{${dp.checkin_point.toString().replaceAll(regex, '')}}"; + } + if (dp.buy_point != null && dp.buy_point! > 0) { + //print("^^^^^^^^^ ${dp.sub_loc_id}^^^^^^^^^^"); + txt = + "#${dp.cp.toString().replaceAll(regex, '')}(${dp.checkin_point.toString().replaceAll(regex, '')}+${dp.buy_point.toString().replaceAll(regex, '')})"; + } + } + return txt; + } + + // static String getDisplayText(String num){ + // RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); + // return "${num.replaceAll(regex, '')}"; + // } +} diff --git a/lib/widgets/GameState/CheckinState.dart b/lib/widgets/GameState/CheckinState.dart new file mode 100644 index 0000000..3e2a430 --- /dev/null +++ b/lib/widgets/GameState/CheckinState.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:gifunavi/widgets/GameState/Colors.dart'; + +class LocationVisitedWidget extends StatelessWidget { + final int count; + final bool minimized; + + const LocationVisitedWidget( + {super.key, required this.count, this.minimized = false}); + + @override + Widget build(BuildContext context) { + if (minimized) { + return Container( + height: 40, + width: 40, + decoration: const BoxDecoration( + color: JapaneseColors.mizu, + shape: BoxShape.circle, + ), + child: Center( + child: Text( + '$count', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ); + } else { + return Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), + decoration: BoxDecoration( + color: JapaneseColors.matcha, + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.check_circle_outline, color: Colors.white, size: 24), + const SizedBox(width: 8), + Text( + '$count チェックイン', // "X Check-ins" in Japanese + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16), + ), + ], + ), + ); + } + } +} diff --git a/lib/widgets/GameState/Colors.dart b/lib/widgets/GameState/Colors.dart new file mode 100644 index 0000000..ceb6f0a --- /dev/null +++ b/lib/widgets/GameState/Colors.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class JapaneseColors { + static const Color mizu = Color(0xFFA4DDED); // Mizu (light blue) + static const Color matcha = Color(0xFFC5E1A5); + static const Color ume = Color(0xFFE1A8A8); // Ume (plum) + static const Color take = Color(0xFF7B8D42); // Take (bamboo) + static const Color sora = Color(0xFFA1CAF1); + static const Color indigo = Color(0xFF264653); // Aizome + static const Color sakuraPink = Color(0xFFFAD2E1); // Sakura-iro + /// Matcha (green tea) +} diff --git a/lib/widgets/GameState/ConnectionStatus.dart b/lib/widgets/GameState/ConnectionStatus.dart new file mode 100644 index 0000000..5661a19 --- /dev/null +++ b/lib/widgets/GameState/ConnectionStatus.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:gifunavi/widgets/GameState/Colors.dart'; + +enum ConnectionStatus { none, mobile, wifi } + +class ConnectionStatusIndicator extends StatelessWidget { + final ConnectionStatus connectionStatus; + final bool minimized; + + const ConnectionStatusIndicator({ + super.key, + required this.connectionStatus, + this.minimized = false, + }); + + @override + Widget build(BuildContext context) { + Color backgroundColor; + IconData iconData; + String text; + + switch (connectionStatus) { + case ConnectionStatus.none: + backgroundColor = JapaneseColors.ume; + iconData = Icons.signal_cellular_off; + text = 'No Connection'; + break; + case ConnectionStatus.mobile: + backgroundColor = JapaneseColors.take; + iconData = Icons.signal_cellular_alt; + text = 'Mobile Data'; + break; + case ConnectionStatus.wifi: + backgroundColor = JapaneseColors.sora; + iconData = Icons.wifi; + text = 'Wi-Fi'; + break; + default: + backgroundColor = Colors.grey; // Fallback color + iconData = Icons.device_unknown; + text = 'Unknown'; + } + + return Container( + height: minimized ? 40 : null, + width: minimized ? 40 : null, + padding: + minimized ? null : const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + decoration: BoxDecoration( + color: backgroundColor, + shape: minimized ? BoxShape.circle : BoxShape.rectangle, + borderRadius: minimized ? null : BorderRadius.circular(10), + ), + child: minimized + ? Center( + child: Icon(iconData, color: Colors.white, size: 24), + ) + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(iconData, color: Colors.white), + const SizedBox(width: 8), + Text( + text, + style: const TextStyle( + color: Colors.white, fontWeight: FontWeight.bold), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/GameState/DashboardWidget.dart b/lib/widgets/GameState/DashboardWidget.dart new file mode 100644 index 0000000..ccbd8e9 --- /dev/null +++ b/lib/widgets/GameState/DashboardWidget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:gifunavi/widgets/GameState/CheckinState.dart'; +import 'package:gifunavi/widgets/GameState/game_on_off.dart'; + +class DashboardWidget extends StatelessWidget { + final bool gameStarted; + final int locationsVisited; + final bool isMinimized; + + const DashboardWidget({ + super.key, + required this.gameStarted, + required this.locationsVisited, + this.isMinimized = false, + }); + + @override + Widget build(BuildContext context) { + List widgets = [ + GameStatusIndicator(gameStarted: gameStarted, minimized: isMinimized), + SizedBox( + height: isMinimized ? 0 : 8, width: isMinimized ? 8 : 0), // Spacing + LocationVisitedWidget(count: locationsVisited, minimized: isMinimized), + ]; + + return Container( + padding: EdgeInsets.all(isMinimized ? 8 : 16), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 5, + blurRadius: 7, + offset: const Offset(0, 3), + ), + ], + borderRadius: BorderRadius.circular(10), + ), + child: isMinimized + ? Row( + mainAxisSize: MainAxisSize.min, + children: widgets, + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: widgets, + ), + ); + } +} diff --git a/lib/widgets/GameState/game_on_off.dart b/lib/widgets/GameState/game_on_off.dart new file mode 100644 index 0000000..642ba03 --- /dev/null +++ b/lib/widgets/GameState/game_on_off.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/widgets/GameState/Colors.dart'; + +class GameStatusIndicator extends StatelessWidget { + final bool gameStarted; + final bool minimized; + + const GameStatusIndicator( + {super.key, required this.gameStarted, this.minimized = true}); + + @override + Widget build(BuildContext context) { + // Icons to show based on the game status + IconData iconData = + gameStarted ? Icons.stop_circle : Icons.play_circle_filled; + // Text to show based on the game status + String text = gameStarted ? 'in_game'.tr : 'start_game'.tr; + + // Layout for minimized view + if (minimized) { + return Container( + height: 40, // Square size + width: 40, // Square size + decoration: BoxDecoration( + color: + gameStarted ? JapaneseColors.indigo : JapaneseColors.sakuraPink, + shape: BoxShape + .circle, // Making it circle when minimized for a more distinct look + ), + child: Icon(iconData, color: Colors.white), + ); + } + + // Layout for expanded view + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: gameStarted ? JapaneseColors.indigo : JapaneseColors.sakuraPink, + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(iconData, color: Colors.white), + const SizedBox(width: 8), + Text( + text, + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/base_layer_widget.dart b/lib/widgets/base_layer_widget.dart index ec43b65..b7feb48 100644 --- a/lib/widgets/base_layer_widget.dart +++ b/lib/widgets/base_layer_widget.dart @@ -1,24 +1,45 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart'; +//import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart'; class BaseLayer extends StatelessWidget { - const BaseLayer({Key? key}) : super(key: key); + const BaseLayer({super.key}); @override Widget build(BuildContext context) { return TileLayer( - urlTemplate: "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png", - tileProvider: FMTC.instance('OpenStreetMap (A)').getTileProvider( - FMTCTileProviderSettings( - behavior: CacheBehavior.values - .byName('cacheFirst'), - cachedValidDuration: const Duration( - days: 14 - ), - ), - ), - ); + urlTemplate: "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png", + /*userAgentPackageName: 'com.example.app',*/ + + /* + tileProvider: FMTC.instance('OpenStreetMap (A)').getTileProvider( + settings: FMTCTileProviderSettings( + behavior: CacheBehavior.values.byName('cacheFirst'), + cachedValidDuration: const Duration(days: 14), + ), + ), + */ + + userAgentPackageName: 'com.example.app', + tileBuilder: (context, tileWidget, tile) { + return Stack( + fit: StackFit.passthrough, + children: [ + tileWidget, + /* + if (tile.loadError != null) + Center( + child: Icon(Icons.error, color: Colors.red.withOpacity(0.5)), + ), + */ + ], + ); + }, + errorTileCallback: (tile, error, stackTrace) { + debugPrint('Tile load error: $error'); + debugPrint('Stack trace: $stackTrace'); + }, + ); } } diff --git a/lib/widgets/bottom_sheet_controller.dart b/lib/widgets/bottom_sheet_controller.dart index 94c5b5c..d736d4c 100644 --- a/lib/widgets/bottom_sheet_controller.dart +++ b/lib/widgets/bottom_sheet_controller.dart @@ -1,11 +1,8 @@ - - -import 'package:geojson/geojson.dart'; +import 'package:geojson_vi/geojson_vi.dart'; import 'package:get/get_state_manager/get_state_manager.dart'; -class BottomSheetController extends GetxController{ - - List? currentFeature = []; +class BottomSheetController extends GetxController { + List? currentFeature = []; BottomSheetController({this.currentFeature}); -} \ No newline at end of file +} diff --git a/lib/widgets/bottom_sheet_new.dart b/lib/widgets/bottom_sheet_new.dart index 8334742..40c552b 100644 --- a/lib/widgets/bottom_sheet_new.dart +++ b/lib/widgets/bottom_sheet_new.dart @@ -1,31 +1,60 @@ +import 'dart:ui' as ui; +import 'dart:io'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; import 'package:flutter/material.dart'; -import 'package:geojson/geojson.dart'; +import 'package:geojson_vi/geojson_vi.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:latlong2/latlong.dart'; -import 'package:rogapp/model/destination.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/routes/app_pages.dart'; -import 'package:rogapp/services/external_service.dart'; -import 'package:rogapp/utils/const.dart'; -import 'package:rogapp/utils/database_helper.dart'; -import 'package:rogapp/utils/text_util.dart'; -import 'package:rogapp/widgets/bottom_sheet_controller.dart'; +import 'package:gifunavi/main.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/pages/camera/camera_page.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/services/external_service.dart'; +import 'package:gifunavi/utils/const.dart'; +import 'package:gifunavi/utils/database_helper.dart'; +import 'package:gifunavi/utils/text_util.dart'; +import 'package:gifunavi/widgets/bottom_sheet_controller.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:timezone/data/latest.dart' as tz; + +// BottomSheetNewは、StatelessWidgetを継承したクラスで、目的地の詳細情報を表示するボトムシートのUIを構築します。 +// コンストラクタでは、destination(目的地オブジェクト)とisAlreadyCheckedIn(すでにチェックイン済みかどうかのフラグ)を受け取ります。 +// buildメソッドでは、detailsSheetメソッドを呼び出して、目的地の詳細情報を表示します。 +// class BottomSheetNew extends GetView { - BottomSheetNew({Key? key}) : super(key: key); + BottomSheetNew( + {this.isAlreadyCheckedIn = false, super.key, required this.destination}); final IndexController indexController = Get.find(); final DestinationController destinationController = Get.find(); + final Destination destination; // 目的地オブジェクト + final bool isAlreadyCheckedIn; // すでにチェックイン済みかどうかのフラグ + final RxBool isButtonDisabled = false.obs; + + static bool _timezoneInitialized = false; + + void _initializeTimezone() { + if (!_timezoneInitialized) { + tz.initializeTimeZones(); + _timezoneInitialized = true; + } + } + + // 目的地の画像を取得するためのメソッドです。 + // indexController.rogModeの値に基づいて、適切な画像を返します。画像が見つからない場合は、デフォルトの画像を返します。 + // Image getImage() { String serverUrl = ConstValues.currentServer(); - if (indexController.rog_mode == 1) { - //print("----- rogaining mode 1"); + + if (indexController.rogMode == 1) { if (indexController.currentDestinationFeature.isEmpty || indexController.currentDestinationFeature[0].photos! == "") { return const Image(image: AssetImage('assets/images/empty_image.png')); @@ -55,7 +84,8 @@ class BottomSheetNew extends GetView { } } } else { - GeoJsonFeature gf = indexController.currentFeature[0]; + GeoJSONFeature gf = indexController.currentFeature[0]; + //print("=== photo sss ${gf.properties!["photos"]}"); if (gf.properties!["photos"] == null || gf.properties!["photos"] == "") { return const Image(image: AssetImage('assets/images/empty_image.png')); } else { @@ -71,9 +101,11 @@ class BottomSheetNew extends GetView { }, ); } else { + String imageUrl = Uri.encodeFull( + '$serverUrl/media/compressed/${gf.properties!["photos"]}'); return Image( image: NetworkImage( - '$serverUrl/media/compressed/' + gf.properties!["photos"], + imageUrl, ), errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { @@ -85,10 +117,16 @@ class BottomSheetNew extends GetView { } } + // URLを開くためのメソッドです。 + // url_launcherパッケージを使用して、指定されたURLを開きます。 + // void _launchURL(url) async { - if (!await launch(url)) throw 'Could not launch $url'; + if (!await launchUrl(url)) throw 'Could not launch $url'; } + // 指定されたlocationidが目的地リストに含まれているかどうかを確認するメソッドです。 + // destinationController.destinationsリストを走査し、locationidが一致する目的地があるかどうかを返します。 + // bool isInDestination(String locationid) { int lid = int.parse(locationid); if (destinationController.destinations @@ -100,637 +138,714 @@ class BottomSheetNew extends GetView { } } - @override - Widget build(BuildContext context) { - destinationController.skip_gps = true; - print('---- rog_mode ----- ${indexController.rog_mode} -----'); - return indexController.rog_mode == 0 - ? detailsSheet(context) - : destinationSheet(context); + Future saveTemporaryImage(Destination destination) async { + final serverUrl = ConstValues.currentServer(); + final imagePath = '$serverUrl/media/compressed/${destination.photos}'; + + final tempDir = await getTemporaryDirectory(); + final tempFile = await File('${tempDir.path}/temp_image.jpg').create(recursive: true); + final response = await http.get(Uri.parse(imagePath)); + await tempFile.writeAsBytes(response.bodyBytes); + + destinationController.photos.clear(); + destinationController.photos.add(tempFile); } - // Show destination detais - SingleChildScrollView destinationSheet(BuildContext context) { - print( - '---- currentDestinationFeature ----- ${indexController.currentDestinationFeature[0].name} -----'); + // アクションボタン(チェックイン、ゴールなど)を表示するためのメソッドです。 + // 現在の状態に基づいて、適切なボタンを返します。 + // ボタンがタップされたときの処理も含まれています。 + // + Widget getActionButton(BuildContext context, Destination destination) { + _initializeTimezone(); // タイムゾーンの初期化 + /* + debugPrint("getActionButton ${destinationController.rogainingCounted.value}"); + debugPrint("getActionButton ${destinationController.distanceToStart()}"); + debugPrint("getActionButton ${destination.cp}"); + debugPrint("getActionButton ${DestinationController.ready_for_goal}"); + // ...2024-04-03 Akira デバッグモードのみ出力するようにした。 + */ - return SingleChildScrollView( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - child: Row( - children: [ - MaterialButton( - onPressed: () { - Get.back(); - //destinationController.makePrevious(indexController.currentDestinationFeature[0]); - }, - color: Colors.blue, - textColor: Colors.white, - padding: const EdgeInsets.all(16), - shape: const CircleBorder(), - child: const Icon( - Icons.arrow_back_ios, - size: 14, + // bool isInRog=false; + Destination cdest = destinationController + .festuretoDestination(indexController.currentFeature[0]); + var distance = const Distance(); + double distanceToDest = distance.as( + LengthUnit.Meter, + LatLng( + destinationController.currentLat, destinationController.currentLon), + LatLng(cdest.lat!, cdest.lon!)); + + // Check conditions to show confirmation dialog + if (destinationController.isInRog.value == false && + (destinationController.distanceToStart() <= 100 || destinationController.isGpsSignalWeak() ) && //追加 Akira 2024-4-5 + (destination.cp == -1 || destination.cp == 0 ) && + destinationController.rogainingCounted.value == false) { + // ゲームが始まってなければ + // ロゲ開始している && (開始地点から100m以内 又は 電波が弱い) && CP番号が 1 or 0 && rogainingCounted==false(どこにもチェックインしていない) なら + return Obx(() { + final isInRog = destinationController.isInRog.value; + + return ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.secondary, + ), + onPressed: destinationController.isInRog.value + ? null + : () async { + // Check if the event is for today + bool isEventToday = await checkIfEventIsToday(); + if (!isEventToday) { + Get.dialog( + AlertDialog( + title: const Text("警告"), + content: const Text("参加したエントリーは別日のものですので、ロゲの開始はできません。当日のエントリーを選択するか、エントリーを今日に変更してからロゲ開始を行ってください。"), + actions: [ + TextButton( + child: const Text("OK"), + onPressed: () { + Get.back(); // Close the dialog + Get.back(); // Close the bottom sheet + }, ), - ), - Expanded( - child: Container( - alignment: Alignment.center, - child: Obx(() => indexController.currentUser.isNotEmpty - ? Text( - "${TextUtils.getDisplayText(indexController.currentDestinationFeature[0])} : ${TextUtils.getDisplayText(indexController.currentDestinationFeature[0])} : ${indexController.currentDestinationFeature[0].name!}", - style: const TextStyle( - fontSize: 15.0, - fontWeight: FontWeight.bold, - ), - ) - : Text( - indexController - .currentDestinationFeature[0].name!, - style: const TextStyle( - fontSize: 15.0, - fontWeight: FontWeight.bold, - ), - )), + ], + ), + ); + return; + } + + // Check if user has already completed a rogaining event today + bool hasCompletedToday = await checkIfCompletedToday(); + if (hasCompletedToday) { + Get.dialog( + AlertDialog( + title: const Text("警告"), + content: const Text("すでにロゲの参加を行いゴールをしています。ロゲは1日1回に制限されています。ご了承ください。"), + actions: [ + TextButton( + child: const Text("OK"), + onPressed: () { + Get.back(); // Close the dialog + Get.back(); // Close the bottom sheet + }, ), - ), - ], - ), + ], + ), + ); + return; + } + + + + + destinationController.isInRog.value = true; + + + // Show confirmation dialog + Get.dialog( + AlertDialog( + title: Text("confirm".tr), //confirm + content: Text( + "clear_rog_data_message".tr), //are you sure + actions: [ + TextButton( + child: Text("no".tr), //no + onPressed: () { + // ダイアログをキャンセルした場合はボタンを再度有効化 + destinationController.isInRog.value = false; + Get.back(); // Close the dialog + Get.back(); // Close the bottom sheet + }, + ), + TextButton( + child: Text("yes".tr), //yes + onPressed: () async { + destinationController.isInRog.value = true; + await saveTemporaryImage(destination); + + // Clear data and start game logic here + destinationController.resetRogaining(); + + destinationController.addToRogaining( + destinationController.currentLat, + destinationController.currentLon, + destination.location_id!, + ); + + saveGameState(); + //int teamId = indexController.teamId.value; // teamIdを使用 + await ExternalService().startRogaining(); + Get.back(); + Get.back();// Close the dialog and potentially navigate away + }, + ), + ], + ), + barrierDismissible: false, // User must tap a button to close the dialog + ); + }, + child: Text( + isInRog ? 'in_game'.tr : 'start_rogaining'.tr, + style: const TextStyle(color: Colors.white), + ), + ); + }); + + + //print("counted ${destinationController.rogainingCounted.value}"); + + + }else if (destinationController.rogainingCounted.value == true && + // destinationController.distanceToStart() <= 500 && ... GPS信号が弱い時でもOKとする。 + (destinationController.distanceToStart() <= 500 || destinationController.isGpsSignalWeak() ) && + (destination.cp == 0 || destination.cp == -2 || destination.cp == -1) && +// (destination.cp == 0 || destination.cp == -2 ) && + DestinationController.ready_for_goal == true) { + + // ready_for_goal && (開始地点から500m以内 又は 電波が弱い) && CP番号が -1 or -2 or 0 && rogainingCounted==true なら + // Goal ボタン + //goal + + return ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.red + ), + onPressed: destinationController.rogainingCounted.value == true && + destinationController.distanceToStart() <= 500 && + (destination.cp == 0 || destination.cp == -2|| destination.cp == -1) && + DestinationController.ready_for_goal == true + ? () async { + + + destinationController.isAtGoal.value = true; + destinationController.photos.clear(); + await showModalBottomSheet( + constraints: BoxConstraints.loose( + ui.Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => CameraPage( + destination: destination, + ))).whenComplete(() { + destinationController.skipGps = false; + destinationController.chekcs = 0; + destinationController.isAtGoal.value = false; + }); + } + : null, + child: Text( + "finish_rogaining".tr, + style: const TextStyle(color: Colors.white), + )); + + } else if (distanceToDest <= + destinationController.getForcedChckinDistance(destination)) { + // cpごとの強制チェックイン以内にいれば + //start + return ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.secondary, + ), + onPressed: isAlreadyCheckedIn == true + ? null + : () async { + try{ + destinationController.isCheckingIn.value = true; // ここを追加 + Get.back(); + Get.back(); + await Future.delayed(const Duration(milliseconds: 500)); + await destinationController.callforCheckin(destination); + destinationController.isCheckingIn.value = false; + } catch (e) { + // エラーハンドリング + Get.snackbar( + 'Error', + 'An error occurred while processing check-in.', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + // 必要に応じてエラーログを記録 + print('Error processing check-in: $e'); + } + }, + child: Text( + destination.cp == -1 && + destinationController.isInRog.value == false && + destinationController.rogainingCounted.value == false + ? "ロゲ開始" + : destinationController.isInRog.value == true && + destination.cp == -1 + ? "in_game".tr + : isAlreadyCheckedIn == true + ? "in_game".tr + : (destinationController.isInRog.value == true || (destination.buy_point != null && destination.buy_point! > 0)) + ? "checkin".tr + : "rogaining_not_started".tr, + style: TextStyle(color: Theme.of(context).colorScheme.onSecondary), + ), + ); + } + return Container(); + } + + // Add these new methods to check event date and completion status + Future checkIfEventIsToday() async { + try { + final now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo')); + + // Null チェックと安全な操作を追加 + final userEventDate = indexController.currentUser.isNotEmpty && + indexController.currentUser[0]["user"] != null + ? indexController.currentUser[0]["user"]["event_date"] + : null; + + if (userEventDate == null || userEventDate.toString().isEmpty) { + print('Event date is null or empty'); + return false; // イベント日付が設定されていない場合は false を返す + } + + final eventDate = tz.TZDateTime.from( + DateTime.parse(userEventDate.toString()), + tz.getLocation('Asia/Tokyo') + ); + + return eventDate.year == now.year && + eventDate.month == now.month && + eventDate.day == now.day; + } catch (e) { + print('Error in checkIfEventIsToday: $e'); + // エラーが発生した場合はダイアログを表示 + Get.dialog( + AlertDialog( + title: const Text('エラー'), + content: const Text('イベント日付の確認中にエラーが発生しました。\nアプリを再起動するか、管理者にお問い合わせください。'), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () => Get.back(), ), - ), - Row( - children: [ - Expanded( - child: SizedBox( - height: 260.0, - child: Obx(() => getImage()), - )), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Obx( - () => indexController.currentDestinationFeature.isNotEmpty && - destinationController.is_in_checkin.value == true && - destinationController.is_at_start.value == false - ? Row( - children: [ - ElevatedButton( - onPressed: () { - if (indexController.currentDestinationFeature[0] - .checkedin == - null || - indexController.currentDestinationFeature[0] - .checkedin == - false) { - if (indexController - .currentDestinationFeature[0] - .hidden_location == - 0) { - destinationController.skip_gps = false; - destinationController.is_photo_shoot.value = - true; - Get.back(); - } else { - destinationController.makeCheckin( - indexController - .currentDestinationFeature[0], - true, - ""); - if (indexController - .currentDestinationFeature[0].cp != - -1) { - destinationController - .rogaining_counted.value = true; - } - } - } else { - destinationController.makeCheckin( - indexController - .currentDestinationFeature[0], - false, - ""); - } - //Get.back(); - }, - child: Text( - //Checkin - indexController.currentDestinationFeature[0] - .checkedin == - null || - indexController - .currentDestinationFeature[0] - .checkedin == - false - ? "チェックイン" - : "チェックアウト")), - ], - ) - : Container(), - ), - Obx( - () => destinationController.is_at_start.value == true - ? ElevatedButton( - onPressed: () { - destinationController.is_in_rog.value = true; - destinationController.addToRogaining( - destinationController.current_lat, - destinationController.current_lon, - indexController - .currentDestinationFeature[0].location_id!); - ExternalService() - .StartRogaining() - .then((value) => Get.back()); - }, - child: Text( - // start - indexController.currentDestinationFeature[0] - .checkedin != - null || - indexController.currentDestinationFeature[0] - .checkedin == - true - ? "ロゲイニングを開始" - : "間違った目的地...")) - : Container(), - ), - Obx( - () => destinationController.is_at_goal.value == true && - destinationController.rogaining_counted == true - ? ElevatedButton( - onPressed: () { - Get.toNamed(AppPages.CAMERA_PAGE); - Get.back(); - }, - child: Text( - //goal - indexController.currentDestinationFeature[0] - .checkedin != - null || - indexController.currentDestinationFeature[0] - .checkedin == - true - ? "ロゲイニングを終える" - : "間違った目的地 ...")) - : Container(), - ), - ], - ), - Obx( - () => indexController.currentDestinationFeature[0].address != - null && - indexController - .currentDestinationFeature[0].address!.isNotEmpty - ? getDetails(context, "address".tr, - indexController.currentDestinationFeature[0].address! ?? '') - : const SizedBox( - width: 0.0, - height: 0, - ), - ), - Obx( - () => indexController.currentDestinationFeature[0].phone != null && - indexController - .currentDestinationFeature[0].phone!.isNotEmpty - ? getDetails(context, "telephone".tr, - indexController.currentDestinationFeature[0].phone! ?? '') - : const SizedBox( - width: 0.0, - height: 0, - ), - ), - Obx( - () => indexController.currentDestinationFeature[0].email != null && - indexController - .currentDestinationFeature[0].email!.isNotEmpty - ? getDetails(context, "email".tr, - indexController.currentDestinationFeature[0].email! ?? '') - : const SizedBox( - width: 0.0, - height: 0, - ), - ), - Obx( - () => indexController.currentDestinationFeature[0].webcontents != - null && - indexController - .currentDestinationFeature[0].webcontents!.isNotEmpty - ? getDetails( - context, - "web".tr, - indexController.currentDestinationFeature[0].webcontents! ?? - '', - isurl: true) - : const SizedBox( - width: 0.0, - height: 0, - ), - ), - Obx( - () => indexController.currentDestinationFeature[0].videos != null && - indexController - .currentDestinationFeature[0].videos!.isNotEmpty - ? getDetails(context, "video".tr, - indexController.currentDestinationFeature[0].videos! ?? '', - isurl: true) - : const SizedBox( - width: 0.0, - height: 0, - ), - ), - const SizedBox( - height: 20.0, - ), - // Obx(() => - // //wantToGo(context), + ], + ), + ); + return false; // エラーが発生した場合は false を返す + } - // FutureBuilder( - // future: wantToGo(context), - // builder: (context, snapshot) { - // return Container( - // child: snapshot.data, - // ); - // }, - // ), + } - // ), - const SizedBox( - height: 60.0, - ) - ], - ), - ); + Future checkIfCompletedToday() async { + final IndexController indexController = Get.find(); + final lastGoalTime = await indexController.getLastGoalTime(); + if (lastGoalTime == null) return false; + + final now = DateTime.now(); + return lastGoalTime.year == now.year && + lastGoalTime.month == now.month && + lastGoalTime.day == now.day; + } + + // 継承元のbuild をオーバーライドし、detailsSheetメソッドを呼び出して、目的地の詳細情報を表示します。 + @override + Widget build(BuildContext context) { + //print("to start ${destinationController.distanceToStart()}"); + + destinationController.skipGps = true; + // print('--- c use --- ${indexController.currentUser[0].values}'); + // print('---- rog_mode ----- ${indexController.rogMode.value} -----'); + // return indexController.rogMode.value == 0 + // ? detailsSheet(context) + // : destinationSheet(context); + + return Obx(() { + if (!destinationController.isCheckingIn.value) { + return detailsSheet(context); + } else { + return Container(); // チェックイン操作中は空のコンテナを返す + } + }); + } + + // 指定された目的地がすでにチェックイン済みかどうかを確認するメソッドです。 + // DatabaseHelperを使用して、目的地の位置情報に基づいてデータベースを検索し、結果を返します。 + // + Future isDestinationCheckedIn(Destination d) async { + DatabaseHelper db = DatabaseHelper.instance; + List ds = await db.getDestinationByLatLon(d.lat!, d.lon!); + + return ds.isNotEmpty; } // show add location details + // 目的地の詳細情報を表示するためのUIを構築するメソッドです。 + // 目的地の画像、名前、住所、電話番号、Webサイト、備考などの情報を表示します。 + // また、アクションボタンや「ここへ行く」ボタンも表示されます。 + // SingleChildScrollView detailsSheet(BuildContext context) { + Destination cdest = destinationController + .festuretoDestination(indexController.currentFeature[0]); + var distance = const Distance(); + double distanceToDest = distance.as( + LengthUnit.Meter, + LatLng( + destinationController.currentLat, destinationController.currentLon), + LatLng(cdest.lat!, cdest.lon!)); + + debugPrint("Distance from current point : $distanceToDest"); + debugPrint( + "forced distance for point : ${destinationController.getForcedChckinDistance(destination)}"); + debugPrint( + "current point : ${destinationController.currentLat}, ${destinationController.currentLon} - ${DateTime.now().hour}:${DateTime.now().minute}:${DateTime.now().second}:${DateTime.now().microsecond}"); + + debugPrint("Checkin radius : ${destination.checkin_radious}"); + debugPrint("--${destination.cp}--"); + return SingleChildScrollView( child: Column( children: [ - Padding( + Padding( // 1行目 padding: const EdgeInsets.all(8.0), - child: Container( - child: Row( - children: [ - MaterialButton( - onPressed: () { - Get.back(); - //indexController.makePrevious(indexController.currentFeature[0]); - }, - color: Colors.blue, - textColor: Colors.white, - padding: const EdgeInsets.all(16), - shape: const CircleBorder(), - child: const Icon( - Icons.arrow_back_ios, - //Icons.arrow_back_ios, - size: 14, - ), + child: Row( + children: [ + MaterialButton( // キャンセルボタン + onPressed: () { + Get.back(); + }, + color: Colors.blue, + textColor: Colors.white, + padding: const EdgeInsets.all(16), + shape: const CircleBorder(), + child: const Icon( + Icons.arrow_back_ios, + size: 14, ), - Expanded( - child: Container( - alignment: Alignment.center, - child: Obx(() => Text( - indexController - .currentFeature[0].properties!["location_name"], - style: const TextStyle( - fontSize: 15.0, - fontWeight: FontWeight.bold, - ), - )), - ), + ), + Expanded( // チェックポイント番号+ポイント名 + child: Container( + alignment: Alignment.centerLeft, + child: Obx(() => Text( + "${TextUtils.getDisplayTextFeture(indexController.currentFeature[0])} : ${indexController.currentFeature[0].properties!["location_name"]}", + style: const TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + )), ), - ], - ), + ), + ], ), ), - Row( + Row( // 2行目 チェックポイント写真 children: [ Expanded( child: SizedBox( - height: 260.0, - child: Obx(() => getImage()), - )), + height: 260.0, + child: Obx(() => getImage()), + )), ], ), - Obx(() => Padding( - padding: const EdgeInsets.all(8.0), - child: Column( + Obx(() => Padding( // 3行め ボタン類 + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Row( // 開始・ゴールボタン + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Obx( - () => indexController - .currentDestinationFeature.isNotEmpty && - indexController - .currentDestinationFeature[0].cp == - -1 && - indexController.currentDestinationFeature[0] - .checkedin == - false && - destinationController.is_at_start.value == - true - ? ElevatedButton( - onPressed: () { - destinationController.is_in_rog.value = - true; - destinationController.addToRogaining( - destinationController.current_lat, - destinationController.current_lon, - indexController - .currentDestinationFeature[0] - .location_id!); - ExternalService() - .StartRogaining() - .then((value) => Get.back()); - }, - child: Text( - // start - indexController - .currentDestinationFeature[ - 0] - .checkedin != - null || - indexController - .currentDestinationFeature[ - 0] - .checkedin == - true - ? "ロゲイニングを開始" - : "間違った目的地...")) - : Container(), - ), - Obx( - () => destinationController.is_at_goal.value == - true && - destinationController.rogaining_counted == - true - ? ElevatedButton( - onPressed: () { - Get.toNamed(AppPages.CAMERA_PAGE); - Get.back(); - }, - child: Text( - //goal - indexController - .currentDestinationFeature[ - 0] - .checkedin != - null || - indexController - .currentDestinationFeature[ - 0] - .checkedin == - true - ? "ロゲイニングを終える" - : "間違った目的地 ...")) - : Container(), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - ElevatedButton( - onPressed: () async { - GeoJsonFeature mp = - indexController.currentFeature[0] - as GeoJsonFeature; - Position position = - await Geolocator.getCurrentPosition( - desiredAccuracy: LocationAccuracy.high); - Destination ds = Destination( - lat: position.latitude, - lon: position.longitude); - - Destination tp = Destination( - lat: mp.geometry!.geoSerie!.geoPoints[0] - .latitude, - lon: mp.geometry!.geoSerie!.geoPoints[0] - .longitude); - - Get.back(); - - destinationController - .destinationMatrixFromCurrentPoint([ds, tp]); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context) - .colorScheme - .onPrimaryContainer), - child: Text( - "ここへ行く", - style: TextStyle( - color: - Theme.of(context).colorScheme.onPrimary), - )), - SizedBox( - width: 10, - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () async { - destinationController.CallforCheckin( - destinationController.festuretoDestination( - indexController.currentFeature[0])); - }, - child: Text("チェックイン", - style: TextStyle( - color: Theme.of(context) - .colorScheme - .onSecondary))), - ], - ), - Row( - children: [ - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - indexController.currentDestinationFeature - .isNotEmpty && - destinationController - .is_in_checkin.value == - true - ? Container() - : FutureBuilder( - future: wantToGo(context), - builder: (context, snapshot) { - return Container( - child: snapshot.data, - ); - }, - ), - indexController.currentFeature[0] - .properties!["location_name"] != - null && - (indexController.currentFeature[0] - .properties!["location_name"] - as String) - .isNotEmpty - ? Flexible( - child: Text(indexController - .currentFeature[0] - .properties!["location_name"])) - : const SizedBox( - width: 0.0, - height: 0, - ), - ], - ), - ), - ], - ), - const SizedBox( - height: 8.0, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - const Icon(Icons.roundabout_left), - const SizedBox( - width: 8.0, - ), - indexController.currentFeature[0] - .properties!["address"] != - null && - (indexController.currentFeature[0] - .properties!["address"] as String) - .isNotEmpty - ? getDetails( - context, - "address".tr, - indexController.currentFeature[0] - .properties!["address"] ?? - '') - : const SizedBox( - width: 0.0, - height: 0, - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - const Icon(Icons.phone), - const SizedBox( - width: 8.0, - ), - indexController.currentFeature[0] - .properties!["phone"] != - null && - (indexController.currentFeature[0] - .properties!["phone"] as String) - .isNotEmpty - ? getDetails( - context, - "telephone".tr, - indexController.currentFeature[0] - .properties!["phone"] ?? - '') - : const SizedBox( - width: 0.0, - height: 0, - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - const Icon(Icons.email), - const SizedBox( - width: 8.0, - ), - indexController.currentFeature[0] - .properties!["email"] != - null && - (indexController.currentFeature[0] - .properties!["email"] as String) - .isNotEmpty - ? getDetails( - context, - "email".tr, - indexController.currentFeature[0] - .properties!["email"] ?? - '') - : const SizedBox( - width: 0.0, - height: 0, - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - const Icon(Icons.language), - const SizedBox( - width: 8.0, - ), - indexController.currentFeature[0] - .properties!["webcontents"] != - null && - (indexController.currentFeature[0] - .properties!["webcontents"] as String) - .isNotEmpty - ? getDetails( - context, - "web".tr, - indexController.currentFeature[0] - .properties!["webcontents"] ?? - '', - isurl: true) - : const SizedBox( - width: 0.0, - height: 0, - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - const SizedBox( - width: 8.0, - ), - indexController.currentFeature[0] - .properties!["remark"] != - null && - (indexController.currentFeature[0] - .properties!["remark"] as String) - .isNotEmpty - ? getDetails( - context, - "remarks".tr, - indexController.currentFeature[0] - .properties!["remark"] ?? - '', - isurl: false) - : const SizedBox( - width: 0.0, - height: 0, - ), - ], - ), - ), - // Text('${TextUtils.getDisplayText(indexController.currentFeature[0].properties!["cp"].toString())} - id: ${TextUtils.getDisplayText(indexController.currentFeature[0].properties!["checkin_point"].toString())}'), + // Finish or Goal + (destination.cp == -1 || destination.cp == 0 || destination.cp == -2) + ? getActionButton(context, destination) + : Container(), // 一般CPは空 ], ), - )), + Row( // 2列目 チェックインボタン + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + //checkin or remove checkin + (destinationController.isInRog.value == true || (destination.buy_point != null && destination.buy_point! > 0)) + && (distanceToDest <= + destinationController.getForcedChckinDistance(destination) || destination.checkin_radious==-1 ) + && destination.cp != 0 && destination.cp != -1 && destination.cp != -2 + ? (isAlreadyCheckedIn == false + ? ElevatedButton( // まだチェックインしていなければ、チェックインボタンを表示 + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red), + onPressed: () async { + try { + Get.back(); + + await destinationController.callforCheckin(destination); + + } catch (e) { + // エラーハンドリング + Get.snackbar( + 'Error', + 'An error occurred while processing check-in.', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + // 必要に応じてエラーログを記録 + print('Error processing check-in: $e'); + } + }, + child: Text( + "checkin".tr, + style: const TextStyle(color: Colors.white), + ) + ) + : ElevatedButton( // チェックインしていれば、チェックイン取消ボタン + style: ElevatedButton.styleFrom(backgroundColor: Colors.grey[300]), + onPressed: () async { + try { + await destinationController.removeCheckin(destination.cp!.toInt()); + destinationController.deleteDestination(destination); + Get.back(); + Get.snackbar( + 'チェックイン取り消し', + '${destination.name}のチェックインは取り消されました', + backgroundColor: Colors.green, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + } catch (e) { + // エラーハンドリング + Get.snackbar( + 'Error', + 'An error occurred while canceling check-in.', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + // 必要に応じてエラーログを記録 + print('Error canceling check-in: $e'); + } + }, + child: Text( + "cancel_checkin".tr, + style: const TextStyle(color: Colors.black), + ) + ) + ) : Container(), // 近くにいなければ空 + // go here or cancel route + Obx(() => ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context) + .colorScheme + .onPrimaryContainer), + onPressed: () async { + if (destinationController.isRouteShowing.value) { + destinationController.clearRoute(); + Get.back(); + } else { + Get.back(); + Position position = + await Geolocator.getCurrentPosition( + desiredAccuracy: + LocationAccuracy.bestForNavigation, + forceAndroidLocationManager: true); + Destination ds = Destination( + lat: position.latitude, + lon: position.longitude); + + Destination tp = Destination( + lat: destination.lat, lon: destination.lon); + + destinationController + .destinationMatrixFromCurrentPoint([ds, tp]); + } + }, + child: Text( //ルート表示 or ルート消去 + destinationController.isRouteShowing.value + ? "cancel_route".tr + : "go_here".tr, + style: TextStyle( + color: + Theme.of(context).colorScheme.onPrimary), + ) + ) + ), + ], + ), + Row( + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + indexController.currentDestinationFeature + .isNotEmpty && + destinationController.isInCheckin.value == + true + ? Container() + : FutureBuilder( + future: wantToGo(context), + builder: (context, snapshot) { + return Container( + child: snapshot.data, + ); + }, + ), + indexController.currentFeature[0] + .properties!["location_name"] != + null && + (indexController.currentFeature[0] + .properties!["location_name"] + as String) + .isNotEmpty + ? Flexible( + child: Text(indexController + .currentFeature[0] + .properties!["location_name"])) + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + ], + ), + const SizedBox( + height: 8.0, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Icon(Icons.roundabout_left), + const SizedBox( + width: 8.0, + ), + indexController.currentFeature[0] + .properties!["address"] != + null && + (indexController.currentFeature[0] + .properties!["address"] as String) + .isNotEmpty + ? getDetails( + context, + "address".tr, + indexController.currentFeature[0] + .properties!["address"] ?? + '') + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Icon(Icons.phone), + const SizedBox( + width: 8.0, + ), + indexController.currentFeature[0] + .properties!["phone"] != + null && + (indexController.currentFeature[0] + .properties!["phone"] as String) + .isNotEmpty + ? getDetails( + context, + "telephone".tr, + indexController.currentFeature[0] + .properties!["phone"] ?? + '') + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Icon(Icons.email), + const SizedBox( + width: 8.0, + ), + indexController.currentFeature[0] + .properties!["email"] != + null && + (indexController.currentFeature[0] + .properties!["email"] as String) + .isNotEmpty + ? getDetails( + context, + "email".tr, + indexController.currentFeature[0] + .properties!["email"] ?? + '') + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Icon(Icons.language), + const SizedBox( + width: 8.0, + ), + indexController.currentFeature[0] + .properties!["webcontents"] != + null && + (indexController.currentFeature[0] + .properties!["webcontents"] as String) + .isNotEmpty + ? getDetails( + context, + "web".tr, + indexController.currentFeature[0] + .properties!["webcontents"] ?? + '', + isurl: true) + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const SizedBox( + width: 8.0, + ), + indexController.currentFeature[0] + .properties!["remark"] != + null && + (indexController.currentFeature[0] + .properties!["remark"] as String) + .isNotEmpty + ? getDetails( + context, + "remarks".tr, + indexController.currentFeature[0] + .properties!["remark"] ?? + '', + isurl: false) + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + ], + ), + )), const SizedBox( height: 60.0, ) @@ -739,12 +854,16 @@ class BottomSheetNew extends GetView { ); } + // 「行きたい」ボタンを表示するためのUIを構築するメソッドです。 + // 目的地が選択されているかどうかに基づいて、適切なアイコンとテキストを表示します。 + // ボタンがタップされたときの処理も含まれています。 + // Future wantToGo(BuildContext context) async { bool selected = false; - print( - '---target-- ${indexController.currentFeature[0].properties!["location_id"]}----'); + // print( + // '---target-- ${indexController.currentFeature[0].properties!["location_id"]}----'); for (Destination d in destinationController.destinations) { - print('---- ${d.location_id.toString()} ----'); + //print('---- ${d.location_id.toString()} ----'); if (d.location_id == indexController.currentFeature[0].properties!["location_id"]) { selected = true; @@ -759,133 +878,112 @@ class BottomSheetNew extends GetView { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - indexController.rog_mode == 0 - ? IconButton( - icon: Icon( - Icons.pin_drop_sharp, - size: 32, - color: selected == true - ? Colors.amber - : Theme.of(context).colorScheme.primary, - ), - onPressed: () { - if (selected) { - // show remove from destination - Get.defaultDialog( - title: "本当にこのポイントを通過順から外しますか?", - middleText: "場所は目的地リストから削除されます", - backgroundColor: Theme.of(context) - .colorScheme - .secondaryContainer, - titleStyle: const TextStyle(color: Colors.white), - middleTextStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer), - textConfirm: "はい", - textCancel: "いいえ", - cancelTextColor: Colors.white, - confirmTextColor: Colors.blue, - buttonColor: Colors.white, - barrierDismissible: false, - radius: 10, - content: const Column( - children: [], - ), - onConfirm: () { - int id = indexController - .currentFeature[0].properties!["location_id"]; - Destination? d = - destinationController.destinationById(id); - print('--- des id is : $d -----'); - if (d != null) { - //print('--- des id is : ${d.location_id} -----'); - destinationController.deleteDestination(d); - Get.back(); - Get.back(); - Get.snackbar("追加した", "場所が削除されました"); - } - }); - return; - } - // show add to destination - Get.defaultDialog( - title: "この場所を登録してもよろしいですか", - middleText: "ロケーションがロガニング リストに追加されます", - backgroundColor: Colors.blue.shade300, - titleStyle: const TextStyle(color: Colors.white), - middleTextStyle: const TextStyle(color: Colors.white), - textConfirm: "はい", - textCancel: "いいえ", - cancelTextColor: Colors.white, - confirmTextColor: Colors.blue, - buttonColor: Colors.white, - barrierDismissible: false, - radius: 10, - content: const Column( - children: [], - ), - onConfirm: () { - GeoJsonMultiPoint mp = indexController - .currentFeature[0] - .geometry as GeoJsonMultiPoint; - LatLng pt = LatLng( - mp.geoSerie!.geoPoints[0].latitude, - mp.geoSerie!.geoPoints[0].longitude); + // indexController.rog_mode == 0 ? + // // IconButton( + // // icon: Icon(Icons.pin_drop_sharp, size: 32, color: _selected == true ? Colors.amber : Colors.blue,), + // // onPressed: (){ + // // if(_selected){ + // // // show remove from destination + // // Get.defaultDialog( + // // title: "本当にこのポイントを通過順から外しますか?", + // // middleText: "場所は目的地リストから削除されます", + // // backgroundColor: Colors.blue.shade300, + // // titleStyle: TextStyle(color: Colors.white), + // // middleTextStyle: TextStyle(color: Colors.white), + // // textConfirm: "はい", + // // textCancel: "いいえ", + // // cancelTextColor: Colors.white, + // // confirmTextColor: Colors.blue, + // // buttonColor: Colors.white, + // // barrierDismissible: false, + // // radius: 10, + // // content: Column( + // // children: [ + // // ], + // // ), + // // onConfirm: (){ + // // int _id = indexController.currentFeature[0].properties!["location_id"]; + // // Destination? d = destinationController.destinationById(_id); + // // print('--- des id is : ${d} -----'); + // // if(d != null) { + // // //print('--- des id is : ${d.location_id} -----'); + // // destinationController.deleteDestination(d); + // // Get.back(); + // // Get.back(); + // // Get.snackbar("追加した", "場所が削除されました"); + // // } + // // } + // // ); + // // return; + // // } + // // // show add to destination + // // Get.defaultDialog( + // // title: "この場所を登録してもよろしいですか", + // // middleText: "ロケーションがロガニング リストに追加されます", + // // backgroundColor: Colors.blue.shade300, + // // titleStyle: TextStyle(color: Colors.white), + // // middleTextStyle: TextStyle(color: Colors.white), + // // textConfirm: "はい", + // // textCancel: "いいえ", + // // cancelTextColor: Colors.white, + // // confirmTextColor: Colors.blue, + // // buttonColor: Colors.white, + // // barrierDismissible: false, + // // radius: 10, + // // content: Column( + // // children: [ + // // ], + // // ), + // // onConfirm: (){ + // // GeoJsonMultiPoint mp = indexController.currentFeature[0].geometry as GeoJsonMultiPoint; + // // LatLng pt = LatLng(mp.geoSerie!.geoPoints[0].latitude, mp.geoSerie!.geoPoints[0].longitude); - print( - "----- want to go sub location is ---- ${indexController.currentFeature[0].properties!["sub_loc_id"]} -----"); + // // print("----- want to go sub location is ---- ${indexController.currentFeature[0].properties!["sub_loc_id"]} -----"); - Destination dest = Destination( - name: indexController.currentFeature[0] - .properties!["location_name"], - address: indexController - .currentFeature[0].properties!["address"], - phone: indexController - .currentFeature[0].properties!["phone"], - email: indexController - .currentFeature[0].properties!["email"], - webcontents: indexController.currentFeature[0] - .properties!["webcontents"], - videos: indexController - .currentFeature[0].properties!["videos"], - category: indexController - .currentFeature[0].properties!["category"], - series: 1, - lat: pt.latitude, - lon: pt.longitude, - sub_loc_id: indexController.currentFeature[0] - .properties!["sub_loc_id"], - location_id: indexController.currentFeature[0] - .properties!["location_id"], - list_order: 1, - photos: indexController.currentFeature[0].properties!["photos"], - checkin_radious: indexController.currentFeature[0].properties!["checkin_radius"], - auto_checkin: indexController.currentFeature[0].properties!["auto_checkin"] == true ? 1 : 0, - cp: indexController.currentFeature[0].properties!["cp"], - checkin_point: indexController.currentFeature[0].properties!["checkin_point"], - buy_point: indexController.currentFeature[0].properties!["buy_point"], - selected: false, - checkedin: false, - hidden_location: indexController.currentFeature[0].properties!["hidden_location"] == true ? 1 : 0); - destinationController.addDestinations(dest); - Get.back(); - Get.back(); - Get.snackbar("追加した", "場所が追加されました"); - }); - }, - ) - : Container(), + // // Destination dest = Destination( + // // name: indexController.currentFeature[0].properties!["location_name"], + // // address: indexController.currentFeature[0].properties!["address"], + // // phone: indexController.currentFeature[0].properties!["phone"], + // // email: indexController.currentFeature[0].properties!["email"], + // // webcontents: indexController.currentFeature[0].properties!["webcontents"], + // // videos: indexController.currentFeature[0].properties!["videos"], + // // category: indexController.currentFeature[0].properties!["category"], + // // series: 1, + // // lat: pt.latitude, + // // lon: pt.longitude, + // // sub_loc_id: indexController.currentFeature[0].properties!["sub_loc_id"], + // // location_id: indexController.currentFeature[0].properties!["location_id"], + // // list_order: 1, + // // photos: indexController.currentFeature[0].properties!["photos"], + // // checkin_radious: indexController.currentFeature[0].properties!["checkin_radius"], + // // auto_checkin: indexController.currentFeature[0].properties!["auto_checkin"] == true ? 1 : 0, + // // cp: indexController.currentFeature[0].properties!["cp"], + // // checkin_point: indexController.currentFeature[0].properties!["checkin_point"], + // // buy_point: indexController.currentFeature[0].properties!["buy_point"], + // // selected: false, + // // checkedin: false, + // // hidden_location: indexController.currentFeature[0].properties!["hidden_location"] == true ?1 : 0 + // // ); + // // destinationController.addDestinations(dest); + // // Get.back(); + // // Get.back(); + // // Get.snackbar("追加した", "場所が追加されました"); + // // } + // // ); + + // // }, + // // ): + // // Container(), const SizedBox( width: 8.0, ), - Obx((() => indexController.rog_mode == 1 + Obx((() => indexController.rogMode.value == 1 ? ElevatedButton( onPressed: () async { Destination dest = indexController.currentDestinationFeature[0]; + //print("~~~~ before checking button ~~~~"); //print("------ curent destination is ${dest!.checkedIn}-------"); - //print("------ curent destination is ${dest!.checkedin}-------::::::::::"); destinationController.makeCheckin( dest, !dest.checkedin!, ""); }, @@ -942,7 +1040,6 @@ class BottomSheetNew extends GetView { //print("---temp---${temp}"); indexController.currentAction.add([temp]); } - indexController.makeAction(context); }, child: Text("checkin".tr)) ], @@ -958,7 +1055,6 @@ class BottomSheetNew extends GetView { //print("---temp---${temp}"); indexController.currentAction.add([temp]); } - indexController.makeAction(context); }, child: const Icon(Icons.favorite, color: Colors.red), ) @@ -968,6 +1064,9 @@ class BottomSheetNew extends GetView { ); } + // 目的地の詳細情報(住所、電話番号、Webサイトなど)を表示するためのUIを構築するメソッドです。 + // ラベルとテキストを受け取り、適切なアイコンとともに表示します。 + // Widget getDetails(BuildContext context, String label, String text, {bool isurl = false}) { return Row( @@ -980,7 +1079,7 @@ class BottomSheetNew extends GetView { InkWell( onTap: () { if (isurl) { - if (indexController.rog_mode == 0) { + if (indexController.rogMode.value == 0) { _launchURL(indexController .currentFeature[0].properties!["webcontents"]); } else { @@ -989,9 +1088,11 @@ class BottomSheetNew extends GetView { } }, child: SizedBox( - width: MediaQuery.of(context).size.width - 160, + width: MediaQuery.of(context).size.width - + (MediaQuery.of(context).size.width * 0.35), child: Text( text, + textAlign: TextAlign.justify, style: TextStyle( color: isurl ? Colors.blue : Colors.black, ), diff --git a/lib/widgets/bottom_sheet_widget.dart b/lib/widgets/bottom_sheet_widget.dart deleted file mode 100644 index 6d3c498..0000000 --- a/lib/widgets/bottom_sheet_widget.dart +++ /dev/null @@ -1,381 +0,0 @@ - -import 'package:flutter/material.dart'; -import 'package:geojson/geojson.dart'; -import 'package:get/get.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/routes/app_pages.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class BottomSheetWidget extends StatelessWidget { - //const BottomSheetWidget({ Key? key }, GeoJsonFeature? pt) : super(key: key); - - final IndexController indexController = Get.find(); - - BottomSheetWidget({Key? key}) : super(key: key); - - Image getImage(GeoJsonFeature? gf){ - if(gf!.properties!["photos"] == null || gf.properties!["photos"] == ""){ - return const Image(image: AssetImage('assets/images/empty_image.png')); - } - else{ - return Image(image: NetworkImage( - gf.properties!["photos"], - ), - errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { - return Image.asset("assets/images/empty_image.png"); - }, - ); - } - } - - void _launchURL(url) async { - if (!await launch(url)) throw 'Could not launch $url'; - } - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 5.0,), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - MaterialButton( - onPressed: () { - indexController.makePrevious(indexController.currentFeature[0]); - }, - color: Colors.blue, - textColor: Colors.white, - padding: const EdgeInsets.all(14), - shape: const CircleBorder(), - child: const Icon( - Icons.arrow_back_ios, - size: 14, - ), - ), - Expanded( - child: Container( - alignment: Alignment.center, - child: Obx(() => - Text(indexController.currentFeature[0].properties!["location_name"], style: const TextStyle( - fontSize: 15.0, - fontWeight: FontWeight.bold, - ), - ) - ), - ), - ), - MaterialButton( - onPressed: () { - indexController.makeNext(indexController.currentFeature[0]); - }, - color: Colors.blue, - textColor: Colors.white, - padding: const EdgeInsets.all(14), - shape: const CircleBorder(), - child: const Icon( - Icons.arrow_forward_ios, - size: 14, - ), - ), - ], - ), - Row( - children: [ - Expanded( - child: SizedBox( - height: 260.0, - child: Obx(() => getImage(indexController.currentFeature[0])), - ) - ), - ], - ), - Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: Column( - children: [ - indexController.currentFeature[0].properties!["address"] != null ? - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - alignment: Alignment.topRight, - child: Text("address".tr, style: const TextStyle(fontWeight: FontWeight.bold),)), - ), - const SizedBox(width: 12.0,), - Expanded( - child: Container( - alignment: Alignment.topLeft, - child: Obx(() => Text(indexController.currentFeature[0].properties!["address"] ?? '', - style: const TextStyle(color: Colors.blue,), - softWrap: true, - overflow: TextOverflow.ellipsis,) - ), - ), - ) - ], - ): const SizedBox(width: 0.0, height: 0,), - indexController.currentFeature[0].properties!["phone"] != null ? - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded(child: Container( - alignment: Alignment.topRight, - child: Text("telephone".tr, style: const TextStyle(fontWeight: FontWeight.bold),))), - const SizedBox(width: 12.0,), - Expanded( - child: Container( - alignment: Alignment.topLeft, - child: Obx(() => Text(indexController.currentFeature[0].properties!["phone"] ?? '', - style: const TextStyle(color: Colors.blue,), - overflow: TextOverflow.ellipsis,) - ), - ), - ) - ], - ): const SizedBox(width: 0, height: 0,), - indexController.currentFeature[0].properties!["email"] != null ? - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded(child: Container( - alignment: Alignment.topRight, - child: Text("email".tr, style: const TextStyle(fontWeight: FontWeight.bold),))), - const SizedBox(width: 12.0,), - Expanded( - child: Container( - alignment: Alignment.topLeft, - child: Obx(() => Text(indexController.currentFeature[0].properties!["email"] ?? '', - style: const TextStyle(color: Colors.blue,), - overflow: TextOverflow.ellipsis,) - ), - ), - ) - ], - ): const SizedBox(width: 0, height: 0,), - indexController.currentFeature[0].properties!["webcontents"] != null ? - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded(child: Container( - alignment: Alignment.topRight, - child: Text( - "web".tr, style: const TextStyle(fontWeight: FontWeight.bold)))), - const SizedBox(width: 12.0,), - Expanded( - child: Container( - alignment: Alignment.topLeft, - child: Obx(() => InkWell( - onTap: (){ - _launchURL(indexController.currentFeature[0].properties!["webcontents"]); - }, - child: Text(indexController.currentFeature[0].properties!["webcontents"] ?? '', - style: const TextStyle(color: Colors.blue,), - softWrap: false, - overflow: TextOverflow.fade,), - )), - ), - ) - ], - ): const SizedBox(width: 0.0, height: 0.0,), - indexController.currentFeature[0].properties!["videos"] != null ? - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded(child: Container( - alignment: Alignment.topRight, - child: Text("video".tr, style: const TextStyle(fontWeight: FontWeight.bold)))), - const SizedBox(width: 12.0,), - Expanded( - child: Container( - alignment: Alignment.topLeft, - child: Obx(() => Text(indexController.currentFeature[0].properties!["videos"] ?? '', - style: const TextStyle(color: Colors.blue,), - overflow: TextOverflow.ellipsis,) - ), - ), - ) - ], - ): const SizedBox(width: 0.0, height: 0.0,), - ], - ), - ), - ), - ], - ), - const SizedBox(height: 20.0,), - Obx(() => - indexController.currentAction.isNotEmpty ? - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - indexController.rog_mode.value == 0 ? - - - - - - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - indexController.currentAction[0][0]["wanttogo"] == false ? - ElevatedButton( - onPressed: (){ - if(indexController.currentAction.isNotEmpty){ - print(indexController.currentAction[0]); - indexController.currentAction[0][0]["wanttogo"] = true; - Map temp = Map.from(indexController.currentAction[0][0]); - indexController.currentAction.clear(); - print("---temp---$temp"); - indexController.currentAction.add([temp]); - } - indexController.makeAction(context); - }, - child: Text("want_to_go".tr) - ) : - ElevatedButton( - onPressed: (){ - if(indexController.currentAction.isNotEmpty){ - print(indexController.currentAction[0]); - indexController.currentAction[0][0]["wanttogo"] = false; - Map temp = Map.from(indexController.currentAction[0][0]); - indexController.currentAction.clear(); - print("---temp---$temp"); - indexController.currentAction.add([temp]); - } - indexController.makeAction(context); - }, - - child: IconButton( - icon: Icon(Icons.favorite, color: Colors.red, semanticLabel: "want_to_go".tr,), onPressed: () { - - }, - - ) - ), - indexController.currentAction[0][0]["like"] == false ? - ElevatedButton( - onPressed: (){ - if(indexController.currentAction.isNotEmpty){ - print(indexController.currentAction[0]); - indexController.currentAction[0][0]["like"] = true; - Map temp = Map.from(indexController.currentAction[0][0]); - indexController.currentAction.clear(); - print("---temp---$temp"); - indexController.currentAction.add([temp]); - } - indexController.makeAction(context); - }, - child: Text("like".tr) - ) : - ElevatedButton( - onPressed: (){ - if(indexController.currentAction.isNotEmpty){ - print(indexController.currentAction[0]); - indexController.currentAction[0][0]["like"] = false; - Map temp = Map.from(indexController.currentAction[0][0]); - indexController.currentAction.clear(); - print("---temp---$temp"); - indexController.currentAction.add([temp]); - } - indexController.makeAction(context); - }, - - child: IconButton( - icon: Icon(Icons.favorite, color: Colors.red, semanticLabel: "like".tr,), onPressed: () { - - }, - - ) - ), - ], - ) - - - - - - : - const SizedBox(width: 0, height: 0,), - indexController.rog_mode.value == 1 ? - indexController.currentAction[0][0]["checkin"] == false ? - Column( - children: [ - Row( - mainAxisSize: MainAxisSize.max, - children: [ - ElevatedButton( - child: const Text("Image"), onPressed: (){ - final ImagePicker picker = ImagePicker(); - picker.pickImage(source: ImageSource.camera).then((value){ - print("----- image---- ${value!.path}"); - }); - }, - ) - ], - ), - ElevatedButton( - onPressed: (){ - if(indexController.currentAction.isNotEmpty){ - print(indexController.currentAction[0]); - indexController.currentAction[0][0]["checkin"] = true; - Map temp = Map.from(indexController.currentAction[0][0]); - indexController.currentAction.clear(); - print("---temp---$temp"); - indexController.currentAction.add([temp]); - } - indexController.makeAction(context); - }, - child: Text("checkin".tr) - ) - ], - ) - : - ElevatedButton( - onPressed: (){ - if(indexController.currentAction.isNotEmpty){ - print(indexController.currentAction[0]); - indexController.currentAction[0][0]["checkin"] = false; - Map temp = Map.from(indexController.currentAction[0][0]); - indexController.currentAction.clear(); - print("---temp---$temp"); - indexController.currentAction.add([temp]); - } - indexController.makeAction(context); - }, - - child: const Icon( - Icons.favorite, color: Colors.red) - - , - ): - const SizedBox(width: 0, height: 0,), - ], - ): Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - onPressed: (){ - Get.toNamed(AppPages.LOGIN); - }, - child: const Flexible(child: Text("その他のオプションについてはログインしてください"))) - ], - ), - ), - const Row( - children: [ - SizedBox(height: 60.0,), - ], - ) - ], - ), - ); - } -} \ No newline at end of file diff --git a/lib/widgets/bread_crum_widget.dart b/lib/widgets/bread_crum_widget.dart deleted file mode 100644 index 63543d0..0000000 --- a/lib/widgets/bread_crum_widget.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:get/get.dart'; -import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/widgets/perfecture_widget.dart'; - -class BreadCrumbWidget extends StatelessWidget { - BreadCrumbWidget({Key? key, this.mapController}) : super(key: key); - - final MapController? mapController; - - final IndexController indexController = Get.find(); - - - @override - Widget build(BuildContext context) { - print("------ map controller is $mapController------------"); - return - Obx(()=> - indexController.perfectures.isNotEmpty && mapController != null ? - BreadCrumb.builder( - itemCount: indexController.perfectures.length, - builder: (index){ - return - BreadCrumbItem( - content: PerfectureWidget(indexController: indexController, mapController: mapController!) //Text('Item$index') - ); - } - ): - const Text("Empty") - ); - } -} \ No newline at end of file diff --git a/lib/widgets/c_form_text_field.dart b/lib/widgets/c_form_text_field.dart new file mode 100644 index 0000000..997733a --- /dev/null +++ b/lib/widgets/c_form_text_field.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; + +class CFormTextField extends StatelessWidget { + const CFormTextField({ + super.key, + required this.cFocus, + required TextEditingController cController, + }) : cTextEditingController = cController; + + final FocusNode cFocus; + final TextEditingController cTextEditingController; + + @override + Widget build(BuildContext context) { + return TextFormField( + autocorrect: false, + autofocus: true, + focusNode: cFocus, + controller: cTextEditingController, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.done, + validator: (value) { + if (value == null || value.isEmpty) { + return "Need a valied email address"; + } + return null; + }, + decoration: InputDecoration( + //filled: true, + //fillColor: Theme.of(context).colorScheme.primaryContainer, + hintText: "Enter email address", + labelText: "Email", + labelStyle: TextStyle( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontSize: 16), + prefixIcon: const Icon(Icons.email_outlined), + suffixIcon: cTextEditingController.text.isNotEmpty + ? IconButton( + onPressed: () { + cTextEditingController.clear(); + }, + icon: const Icon(Icons.clear)) + : Container( + width: 0, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + width: 1, color: Theme.of(context).colorScheme.secondary)), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + width: 2, color: Theme.of(context).colorScheme.primary))), + ); + } +} diff --git a/lib/widgets/c_password_text_filed.dart b/lib/widgets/c_password_text_filed.dart new file mode 100644 index 0000000..98fd3c7 --- /dev/null +++ b/lib/widgets/c_password_text_filed.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class CPasswordTextField extends StatefulWidget { + const CPasswordTextField( + {super.key, required this.cFocusNode, required this.cController}); + + final FocusNode cFocusNode; + final TextEditingController cController; + + @override + State createState() => _CPasswordTextFieldState(); +} + +class _CPasswordTextFieldState extends State { + var _isVisible = false; + + @override + Widget build(BuildContext context) { + return TextFormField( + controller: widget.cController, + textInputAction: TextInputAction.go, + obscureText: !_isVisible, + validator: (value) { + if (value == null || value.isEmpty || value.length < 5) { + return "Need a valied password with more than 4 charectors"; + } + return null; + }, + decoration: InputDecoration( + //filled: true, + //fillColor: Theme.of(context).colorScheme.tertiaryContainer, + hintText: "Enter password", + labelText: "Password", + labelStyle: TextStyle( + color: Theme.of(context).colorScheme.primary, fontSize: 16), + suffixIcon: IconButton( + onPressed: () { + setState(() { + _isVisible = !_isVisible; + }); + }, + icon: _isVisible + ? const Icon(Icons.visibility) + : const Icon(Icons.visibility_off)), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + width: 1, color: Theme.of(context).colorScheme.secondary)), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + width: 2, color: Theme.of(context).colorScheme.primary)))); + } +} diff --git a/lib/widgets/cat_widget.dart b/lib/widgets/cat_widget.dart deleted file mode 100644 index 9001b0a..0000000 --- a/lib/widgets/cat_widget.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; - -class CatWidget extends StatefulWidget { - CatWidget({ Key? key, required this.indexController, }) : super(key: key); - - IndexController indexController; - - @override - State createState() => _CatWidgetState(); -} - -class _CatWidgetState extends State { - String defaultValue = "---"; - - @override - Widget build(BuildContext context) { - return - PopupMenuButton( - onSelected: (value) { - widget.indexController.currentCat.clear(); - widget.indexController.currentCat.add(value.toString()); - widget.indexController.refreshLocationForCat(); - setState(() { - print(value); - //widget.indexController.is_loading.value = true; - defaultValue = value.toString(); - }); - }, - itemBuilder: (BuildContext context){ - List itms = []; - for(dynamic d in widget.indexController.cats[0]){ - PopupMenuItem itm = PopupMenuItem(value: d['category'].toString(), child: Text(d['category'].toString())); - itms.add(itm); - } - return itms; - } - - ); - } -} - - -// widget.indexController.cats.map((e) => -// PopupMenuItem( -// value: defaultValue, -// child: Text(e[0]['category'].toString()), -// ) -// ).toList(), \ No newline at end of file diff --git a/lib/widgets/category_change_dialog.dart b/lib/widgets/category_change_dialog.dart new file mode 100644 index 0000000..d9c6aa9 --- /dev/null +++ b/lib/widgets/category_change_dialog.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class CategoryChangeDialog extends StatelessWidget { + final String oldCategory; + final String newCategory; + + const CategoryChangeDialog({ + Key? key, + required this.oldCategory, + required this.newCategory, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('カテゴリ変更の警告'), + content: Text( + 'チームの構成変更により、カテゴリが $oldCategory から $newCategory に変更されます。' + 'このチームは既にエントリーしています。どのように処理しますか?' + ), + actions: [ + TextButton( + child: Text('新しいチームを作成'), + onPressed: () => Get.back(result: true), + ), + TextButton( + child: Text('既存のエントリーを更新'), + onPressed: () => Get.back(result: false), + ), + ], + ); + } +} + diff --git a/lib/widgets/current_position_widget.dart b/lib/widgets/current_position_widget.dart new file mode 100644 index 0000000..6aaf8d3 --- /dev/null +++ b/lib/widgets/current_position_widget.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/routes/app_pages.dart'; // これを追加 + +class CurrentPosition extends StatefulWidget { + const CurrentPosition({super.key}); + + @override + State createState() => _CurrentPositionState(); +} + +class _CurrentPositionState extends State { + final DestinationController destinationController = + Get.find(); + + void _onLongPress() async { + PermissionStatus status = await Permission.location.status; + if (!status.isGranted) { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('位置情報の許可が必要です'), + content: const Text('現在位置を表示するには、位置情報の許可が必要です。「設定」からアプリの権限を許可してください。'), + actions: [ + TextButton( + child: const Text('キャンセル'), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: const Text('設定'), + onPressed: () { + Navigator.of(context).pop(); + openAppSettings(); + }, + ), + ], + ); + }, + ); + } else { + Get.toNamed(AppPages.SETTINGS); + } + } + + @override + Widget build(BuildContext context) { + return GestureDetector( // GestureDetectorを追加 + onLongPress: _onLongPress, // 長押しイベントを追加 + child: Container ( +// return Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.grey, borderRadius: BorderRadius.circular(20.0)), + child: IconButton( + onPressed: () { + destinationController.centerMapToCurrentLocation(); + }, + icon: const Icon( + Icons.location_searching, + color: Colors.white, + ), + ), + ), + ); + } +} diff --git a/lib/widgets/custom_date_picker.dart b/lib/widgets/custom_date_picker.dart new file mode 100644 index 0000000..a03f108 --- /dev/null +++ b/lib/widgets/custom_date_picker.dart @@ -0,0 +1,271 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class CustomDatePicker extends StatefulWidget { + final DateTime initialDate; + final DateTime firstDate; + final DateTime lastDate; + final String currentDateText; + + CustomDatePicker({ + required this.initialDate, + required this.firstDate, + required this.lastDate, + required this.currentDateText, + }); + + @override + _CustomDatePickerState createState() => _CustomDatePickerState(); +} + +class _CustomDatePickerState extends State { + late DateTime _selectedDate; + late int _selectedYear; + late int _selectedMonth; + late int _selectedDay; + late ScrollController _yearScrollController; + late ScrollController _monthScrollController; + late ScrollController _dayScrollController; + + @override + void initState() { + super.initState(); + _selectedDate = _parseDate(widget.currentDateText) ?? widget.initialDate; + _selectedYear = _selectedDate.year; + _selectedMonth = _selectedDate.month; + _selectedDay = _selectedDate.day; + + _yearScrollController = ScrollController( + initialScrollOffset: (_selectedYear - widget.firstDate.year) * 70.0, + ); + _monthScrollController = ScrollController( + initialScrollOffset: (_selectedMonth - 1) * 50.0, + ); + _dayScrollController = ScrollController( + initialScrollOffset: (_selectedDay - 1) * 50.0, + ); + + WidgetsBinding.instance.addPostFrameCallback((_) { + _centerSelectedYear(); + _centerSelectedMonth(); + _centerSelectedDay(); + }); + } + + void _centerSelectedYear() { + final screenWidth = MediaQuery.of(context).size.width; + final centerPosition = (_selectedYear - widget.firstDate.year) * 70.0 - (screenWidth / 2) + 35.0; + _yearScrollController.animateTo( + centerPosition, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + + void _centerSelectedMonth() { + final screenWidth = MediaQuery.of(context).size.width; + final centerPosition = (_selectedMonth - 1) * 50.0 - (screenWidth / 2) + 25.0; + _monthScrollController.animateTo( + centerPosition, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + + void _centerSelectedDay() { + final screenWidth = MediaQuery.of(context).size.width; + final centerPosition = (_selectedDay - 1) * 50.0 - (screenWidth / 2) + 25.0; + _dayScrollController.animateTo( + centerPosition, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + + DateTime? _parseDate(String dateString) { + try { + return DateFormat('yyyy/MM/dd').parse(dateString); + } catch (e) { + return null; + } + } + + @override + Widget build(BuildContext context) { + return Dialog( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + '生年月日の選択', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + _buildLabeledPicker('年', _buildYearPicker()), + _buildLabeledPicker('月', _buildMonthPicker()), + _buildLabeledPicker('日', _buildDayPicker()), + _buildActionButtons(), + ], + ), + ); + } + + Widget _buildLabeledPicker(String label, Widget picker) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16.0, top: 8.0), + child: Text(label, style: TextStyle(fontWeight: FontWeight.bold)), + ), + picker, + ], + ); + } + + Widget _buildYearPicker() { + return Container( + height: 100, + child: ListView.builder( + controller: _yearScrollController, + scrollDirection: Axis.horizontal, + itemCount: widget.lastDate.year - widget.firstDate.year + 1, + itemBuilder: (context, index) { + final year = widget.firstDate.year + index; + return GestureDetector( + onTap: () { + setState(() { + _selectedYear = year; + _updateSelectedDate(); + _centerSelectedYear(); + }); + }, + child: Container( + width: 70, + padding: EdgeInsets.all(8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: _selectedYear == year ? Colors.blue : null, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + year.toString(), + style: TextStyle( + color: _selectedYear == year ? Colors.white : null, + ), + ), + ), + ); + }, + ), + ); + } + + Widget _buildMonthPicker() { + return Container( + height: 50, + child: ListView.builder( + controller: _monthScrollController, + scrollDirection: Axis.horizontal, + itemCount: 12, + itemBuilder: (context, index) { + final month = index + 1; + return GestureDetector( + onTap: () { + setState(() { + _selectedMonth = month; + _updateSelectedDate(); + _centerSelectedMonth(); + }); + }, + child: Container( + width: 50, + padding: EdgeInsets.all(8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: _selectedMonth == month ? Colors.blue : null, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + month.toString(), + style: TextStyle( + color: _selectedMonth == month ? Colors.white : null, + ), + ), + ), + ); + }, + ), + ); + } + + Widget _buildDayPicker() { + final daysInMonth = DateTime(_selectedYear, _selectedMonth + 1, 0).day; + return Container( + height: 50, + child: ListView.builder( + controller: _dayScrollController, + scrollDirection: Axis.horizontal, + itemCount: daysInMonth, + itemBuilder: (context, index) { + final day = index + 1; + return GestureDetector( + onTap: () { + setState(() { + _selectedDay = day; + _updateSelectedDate(); + _centerSelectedDay(); + }); + }, + child: Container( + width: 50, + padding: EdgeInsets.all(8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: _selectedDay == day ? Colors.blue : null, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + day.toString(), + style: TextStyle( + color: _selectedDay == day ? Colors.white : null, + ), + ), + ), + ); + }, + ), + ); + } + + void _updateSelectedDate() { + final daysInMonth = DateTime(_selectedYear, _selectedMonth + 1, 0).day; + _selectedDay = _selectedDay.clamp(1, daysInMonth); + _selectedDate = DateTime(_selectedYear, _selectedMonth, _selectedDay); + } + + Widget _buildActionButtons() { + return ButtonBar( + children: [ + TextButton( + child: Text('キャンセル'), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: Text('OK'), + onPressed: () => Navigator.of(context).pop(_selectedDate), + ), + ], + ); + } + + @override + void dispose() { + _yearScrollController.dispose(); + _monthScrollController.dispose(); + _dayScrollController.dispose(); + super.dispose(); + } +} diff --git a/lib/widgets/custom_icons.dart b/lib/widgets/custom_icons.dart new file mode 100644 index 0000000..26d9333 --- /dev/null +++ b/lib/widgets/custom_icons.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +class CustomIcons { + static const _fontFamily = 'CustomIcons'; + + static const IconData gps_signal_low = IconData(0xe900, fontFamily: _fontFamily); + static const IconData gps_signal_middle = IconData(0xe913, fontFamily: _fontFamily); + static const IconData gps_signal_high = IconData(0xe91d, fontFamily: _fontFamily); +} diff --git a/lib/widgets/debug_widget.dart b/lib/widgets/debug_widget.dart new file mode 100644 index 0000000..deac932 --- /dev/null +++ b/lib/widgets/debug_widget.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; + +class LogManager { + static final LogManager _instance = LogManager._internal(); + + factory LogManager() { + return _instance; + } + + LogManager._internal(); + + final List _logs = []; + final List _listeners = []; + + final List _operationLogs = []; + List get operationLogs => _operationLogs; + + List get logs => _logs; + + void addLog(String log) { + _logs.add(log); + _notifyListeners(); // Notify all listeners + } + + void clearLogs() { + _logs.clear(); + _notifyListeners(); // Notify all listeners + } + + void addOperationLog(String log) { + _operationLogs.add(log); + _notifyListeners(); + } + + void clearOperationLogs() { + _operationLogs.clear(); + _notifyListeners(); + } + + void addListener(VoidCallback listener) { + _listeners.add(listener); + } + + void removeListener(VoidCallback listener) { + _listeners.remove(listener); + } + + void _notifyListeners() { + for (var listener in _listeners) { + listener(); + } + } +} + +class DebugWidget extends StatefulWidget { + const DebugWidget({super.key}); + + @override + State createState() => _DebugWidgetState(); +} + +class _DebugWidgetState extends State { + final LogManager logManager = LogManager(); + + @override + void initState() { + super.initState(); + logManager.addListener(_updateLogs); + } + + @override + void dispose() { + logManager.removeListener(_updateLogs); + super.dispose(); + } + + void _updateLogs() { + Future.delayed(Duration.zero, () { + if (mounted) { + setState(() {}); + } + }); + } + + void toggleExpanded() { + setState(() { + isExpanded = !isExpanded; + }); + } + + void clearLogs() { + logManager.clearLogs(); + } + + bool isExpanded = false; + + @override + Widget build(BuildContext context) { + return Positioned( + left: 0, + right: 0, + bottom: 0, + child: GestureDetector( + onTap: toggleExpanded, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + color: Colors.black54, + height: isExpanded ? 450.0 : 50.0, // Adjust sizes as needed + child: Column( + children: [ + // Top bar with clear button + if (isExpanded) + Container( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + color: Colors.blueGrey, // Adjust color as needed + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Debug Logs', style: TextStyle(color: Colors.white)), + IconButton( + icon: const Icon(Icons.clear, color: Colors.white), + onPressed: clearLogs, + ), + ], + ), + ), + + // Log messages + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: logManager.logs.reversed + .map((log) => Text( + "${DateTime.now().hour}:${DateTime.now().minute}:${DateTime.now().second}:${DateTime.now().microsecond} - $log", + style: const TextStyle(color: Colors.white))) + .toList(), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/destination_widget.dart b/lib/widgets/destination_widget.dart index 0a01911..69def91 100644 --- a/lib/widgets/destination_widget.dart +++ b/lib/widgets/destination_widget.dart @@ -1,48 +1,55 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:rogapp/model/destination.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/utils/const.dart'; -import 'package:rogapp/utils/database_helper.dart'; -import 'package:rogapp/widgets/bottom_sheet_new.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/utils/const.dart'; +import 'package:gifunavi/utils/database_helper.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_start.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_goal.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_normal_point.dart'; +import 'package:gifunavi/widgets/bottom_sheet_new.dart'; import 'package:timeline_tile/timeline_tile.dart'; class DestinationWidget extends StatelessWidget { - DestinationWidget({ Key? key }) : super(key: key); + DestinationWidget({super.key}); - final DestinationController destinationController = Get.find(); + final DestinationController destinationController = + Get.find(); final IndexController indexController = Get.find(); final List _items = List.generate(50, (int index) => index); - Image getImage(int index){ - if(destinationController.destinations[index].photos== null || destinationController.destinations[index].photos == ""){ + Image getImage(int index) { + if (destinationController.destinations[index].photos == null || + destinationController.destinations[index].photos == "") { return const Image(image: AssetImage('assets/images/empty_image.png')); - } - else{ - print("------- image is ${destinationController.destinations[index].photos!}------"); + } else { + // print( + // "------- image is ${destinationController.destinations[index].photos!}------"); String photo = destinationController.destinations[index].photos!; - if(photo.contains('http')){ - return Image(image: NetworkImage( - destinationController.destinations[index].photos!), - errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { - return Image.asset("assets/images/empty_image.png"); - }, - ); - } - else { - String serverUrl = ConstValues.currentServer(); - //print("==== photo is ${server_url + '/media/compressed/' + destinationController.destinations[index].photos!} ==="); - return Image(image: NetworkImage( - '$serverUrl/media/compressed/${destinationController.destinations[index].photos!}'), - errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { - return Image.asset("assets/images/empty_image.png"); - }, - ); - } - + if (photo.contains('http')) { + return Image( + image: + NetworkImage(destinationController.destinations[index].photos!), + errorBuilder: + (BuildContext context, Object exception, StackTrace? stackTrace) { + return Image.asset("assets/images/empty_image.png"); + }, + ); + } else { + String serverUrl = ConstValues.currentServer(); + //print("==== photo is ${server_url + '/media/compressed/' + destinationController.destinations[index].photos!} ==="); + return Image( + image: NetworkImage( + '$serverUrl/media/compressed/${destinationController.destinations[index].photos!}'), + errorBuilder: + (BuildContext context, Object exception, StackTrace? stackTrace) { + return Image.asset("assets/images/empty_image.png"); + }, + ); + } } } @@ -73,56 +80,57 @@ class DestinationWidget extends StatelessWidget { void moveUp() { Destination? d; - for(Destination ad in destinationController.destinations){ - if(ad.selected == true){ + for (Destination ad in destinationController.destinations) { + if (ad.selected == true) { d = ad; break; } } - if(d != null){ - print("--- selected destination is ${d.list_order}"); + if (d != null) { + //print("--- selected destination is ${d.list_order}"); destinationController.makeOrder(d, -1); } } void moveDown() { Destination? d; - for(Destination ad in destinationController.destinations){ - if(ad.selected == true){ + for (Destination ad in destinationController.destinations) { + if (ad.selected == true) { d = ad; break; } } - if(d != null){ - print("--- selected destination is ${d.list_order}"); + if (d != null) { + //print("--- selected destination is ${d.list_order}"); destinationController.makeOrder(d, 1); } } - void clearall(){ + void clearall() { Get.defaultDialog( - title: "are_you_sure_want_to_delete_all".tr, - middleText: "all_added_destination_will_be_deleted".tr, - backgroundColor: Colors.blue.shade300, - titleStyle: const TextStyle(color: Colors.white), - middleTextStyle: const TextStyle(color: Colors.white), - textConfirm: "confirm".tr, - textCancel: "cancel".tr, - cancelTextColor: Colors.white, - confirmTextColor: Colors.blue, - buttonColor: Colors.white, - barrierDismissible: false, - radius: 10, - content: const Column( - children: [ - ], - ), - onConfirm: (){ - destinationController.deleteAllDestinations(); - Get.back(); - Get.snackbar("deleted".tr, "all_destinations_are_deleted_successfully".tr); - } - ); + title: "are_you_sure_want_to_delete_all".tr, + middleText: "all_added_destination_will_be_deleted".tr, + backgroundColor: Colors.blue.shade300, + titleStyle: const TextStyle(color: Colors.white), + middleTextStyle: const TextStyle(color: Colors.white), + textConfirm: "confirm".tr, + textCancel: "cancel".tr, + cancelTextColor: Colors.white, + confirmTextColor: Colors.blue, + buttonColor: Colors.white, + barrierDismissible: false, + radius: 10, + content: const Column( + children: [], + ), + onConfirm: () { + destinationController.deleteAllDestinations(); + Get.back(); + Get.snackbar( + "deleted".tr, "all_destinations_are_deleted_successfully".tr, + backgroundColor: Colors.green, + colorText: Colors.white); + }); } void interChange() { @@ -142,124 +150,169 @@ class DestinationWidget extends StatelessWidget { @override Widget build(BuildContext context) { + print( + "------ destination widget------ ${destinationController.destinationCount.value} ----------"); - print("------ destination widget------ ${destinationController.destinationCount.value} ----------"); - - return - Obx(() => - Stack( + return Obx(() => Stack( children: [ - Padding( - padding: const EdgeInsets.only(top:45.0), - child: ListView.builder( + Padding( + padding: const EdgeInsets.only(top: 45.0), + child: ListView.builder( itemCount: destinationController.destinationCount.value, itemBuilder: (BuildContext context, int index) { - return - TimelineTile( - alignment: TimelineAlign.manual, - lineXY: 0.2, - isFirst: index == 0 ? true : false, - indicatorStyle: IndicatorStyle( - indicator: CircleAvatar( - backgroundColor: Colors.red, - child: Text(destinationController.destinations[index].list_order.toString(), style: const TextStyle(color: Colors.white),), - ), - ), - key: Key(index.toString()), - endChild: Card( - child: Container( - constraints: const BoxConstraints( - minHeight: 80, - ), - child: ListTile( - onTap: () async { - { - Destination? fs = destinationController.destinations[index]; - print("----fsf-----$index"); - - if(indexController.currentDestinationFeature.isNotEmpty) { - indexController.currentDestinationFeature.clear(); - } - indexController.currentDestinationFeature.add(fs); - print("--- ndexController.currentDestinationFeature ----- ${ indexController.currentDestinationFeature[0].name} ----"); - //indexController.getAction(); - - showModalBottomSheet(context: context, isScrollControlled: true, - //builder:((context) => BottomSheetWidget()) - builder:((context) => BottomSheetNew()) - ); - } - }, - onLongPress: (){ - destinationController.toggleSelection(destinationController.destinations[index]); - }, - selectedTileColor: Colors.amberAccent, - selected:destinationController.destinations[index].selected!, - leading: getImage(index), - title: Text(destinationController.destinations[index].name!), - subtitle: Text(destinationController.destinations[index].category!), + return TimelineTile( + alignment: TimelineAlign.manual, + lineXY: 0.2, + isFirst: index == 0 ? true : false, + indicatorStyle: IndicatorStyle( + indicator: CircleAvatar( + backgroundColor: Colors.red, + child: Text( + destinationController.destinations[index].list_order + .toString(), + style: const TextStyle(color: Colors.white), + ), ), ), - ), - startChild: - index > 0 && destinationController.matrix["routes"][0]["legs"] != null ? - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text(destinationController.matrix["routes"][0]["legs"][index -1]["distance"] != null ? destinationController.matrix["routes"][0]["legs"][index-1]["distance"]["text"].toString(): ''), - Text(destinationController.matrix["routes"][0]["legs"][index -1]["duration"] != null ? destinationController.matrix["routes"][0]["legs"][index-1]["duration"]["text"].toString() : '') - ], - ): - Container() - , - ); - - } - ), + key: Key(index.toString()), + endChild: Card( + child: Container( + constraints: const BoxConstraints( + minHeight: 80, + ), + child: ListTile( + onTap: () async { + { + Destination? fs = + destinationController.destinations[index]; + //print("----fsf-----$index"); + if (indexController + .currentDestinationFeature.isNotEmpty) { + indexController.currentDestinationFeature + .clear(); + } + indexController.currentDestinationFeature + .add(fs); + // print( + // "--- ndexController.currentDestinationFeature ----- ${indexController.currentDestinationFeature[0].name} ----"); + //indexController.getAction(); + + Widget bottomSheet = BottomSheetNew(destination: fs); + /* + if (fs.cp == -1 || fs.cp == 0) { + bottomSheet = BottomSheetStart(destination: fs); + } else if (fs.cp == -2 || fs.cp == 0) { + bottomSheet = BottomSheetGoal(destination: fs); + } else { + bottomSheet = BottomSheetNormalPoint(destination: fs); + } + */ + + showModalBottomSheet( + constraints: BoxConstraints.loose( + Size(Get.width, Get.height * 0.85)), + context: context, + isScrollControlled: true, + //builder:((context) => BottomSheetWidget()) + builder: ((context) => bottomSheet) + ); + } + }, + onLongPress: () { + destinationController.toggleSelection( + destinationController.destinations[index]); + }, + selectedTileColor: Colors.amberAccent, + selected: destinationController + .destinations[index].selected!, + leading: getImage(index), + title: Text(destinationController + .destinations[index].name!), + subtitle: Text(destinationController + .destinations[index].category!), + ), + ), + ), + startChild: index > 0 && + destinationController.matrix["routes"][0] + ["legs"] != + null + ? Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text(destinationController.matrix["routes"][0] + ["legs"][index - 1]["distance"] != + null + ? destinationController.matrix["routes"][0] + ["legs"][index - 1]["distance"] + ["text"] + .toString() + : ''), + Text(destinationController.matrix["routes"][0] + ["legs"][index - 1]["duration"] != + null + ? destinationController.matrix["routes"][0] + ["legs"][index - 1]["duration"] + ["text"] + .toString() + : '') + ], + ) + : Container(), + ); + }), ), - Container( - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius: 5, - blurRadius: 3, - offset: const Offset(0, 7), // changes position of shadow - ), - ], - ), - height: 44.0, - width: MediaQuery.of(context).size.width, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - icon:const Icon(Icons.delete_forever), - //onPressed: (){doDelete();}, - onPressed: clearall, - ), - IconButton( - icon:const Icon(Icons.cancel), - //onPressed: (){doDelete();}, - onPressed: destinationController.currentSelectedDestinations.isNotEmpty ? doDelete : null, - ), - IconButton( - icon:const Icon(Icons.move_up), - onPressed: destinationController.currentSelectedDestinations.isNotEmpty ? moveUp : null, - ), - IconButton( - icon:const Icon(Icons.move_down), - onPressed: destinationController.currentSelectedDestinations.isNotEmpty ? moveDown : null, - ), - // IconButton( - // icon:Icon(Icons.sync), - // onPressed: destinationController.destination_index_data.length == 2 ? interChange : null, - // ), - ], - ), - ) + Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 5, + blurRadius: 3, + offset: const Offset(0, 7), // changes position of shadow + ), + ], + ), + height: 44.0, + width: MediaQuery.of(context).size.width, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: const Icon(Icons.delete_forever), + //onPressed: (){doDelete();}, + onPressed: clearall, + ), + IconButton( + icon: const Icon(Icons.cancel), + //onPressed: (){doDelete();}, + onPressed: destinationController + .currentSelectedDestinations.isNotEmpty + ? doDelete + : null, + ), + IconButton( + icon: const Icon(Icons.move_up), + onPressed: destinationController + .currentSelectedDestinations.isNotEmpty + ? moveUp + : null, + ), + IconButton( + icon: const Icon(Icons.move_down), + onPressed: destinationController + .currentSelectedDestinations.isNotEmpty + ? moveDown + : null, + ), + // IconButton( + // icon:Icon(Icons.sync), + // onPressed: destinationController.destination_index_data.length == 2 ? interChange : null, + // ), + ], + ), + ) ], - ) - ); + )); } -} \ No newline at end of file +} diff --git a/lib/widgets/fake_search.dart b/lib/widgets/fake_search.dart index 6c42961..aeb65a2 100644 --- a/lib/widgets/fake_search.dart +++ b/lib/widgets/fake_search.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:rogapp/pages/search/search_page.dart'; +import 'package:gifunavi/pages/search/search_page.dart'; class FakeSearch extends StatelessWidget { const FakeSearch({ - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/lib/widgets/game_state_view.dart b/lib/widgets/game_state_view.dart new file mode 100644 index 0000000..cee38a7 --- /dev/null +++ b/lib/widgets/game_state_view.dart @@ -0,0 +1,221 @@ +//import 'dart:ffi'; +//import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/services/DatabaseService.dart'; +//import 'package:gifunavi/utils/database_helper.dart'; +import 'package:gifunavi/widgets/GameState/CheckinState.dart'; +import 'package:gifunavi/widgets/GameState/ConnectionStatus.dart'; +//import 'package:gifunavi/widgets/GameState/DashboardWidget.dart'; +import 'package:gifunavi/widgets/GameState/game_on_off.dart'; +//import 'package:gifunavi/widgets/GameState/Colors.dart'; +import 'package:gifunavi/widgets/gps_status.dart'; +import 'package:gifunavi/utils/location_controller.dart'; + +class GameStateManager { + static final GameStateManager _instance = GameStateManager._internal(); + + factory GameStateManager() { + return _instance; + } + + GameStateManager._internal(); + + final List _logs = []; + final List _listeners = []; + + List get logs => _logs; + + void addLog(String log) { + _logs.add(log); + _notifyListeners(); // Notify all listeners + } + + void clearLogs() { + _logs.clear(); + _notifyListeners(); // Notify all listeners + } + + void addListener(VoidCallback listener) { + _listeners.add(listener); + } + + void removeListener(VoidCallback listener) { + _listeners.remove(listener); + } + + void _notifyListeners() { + for (var listener in _listeners) { + listener(); + } + } +} + +class GameStateWidget extends StatefulWidget { + const GameStateWidget({super.key}); + + @override + State createState() => _GameStateWidgetState(); +} + +class _GameStateWidgetState extends State { + final GameStateManager gameStateManager = GameStateManager(); + + final IndexController indexController = Get.find(); + final DestinationController destinationController = + Get.find(); + + @override + void initState() { + super.initState(); + gameStateManager.addListener(_updateLogs); + } + + @override + void dispose() { + gameStateManager.removeListener(_updateLogs); + super.dispose(); + } + + void _updateLogs() { + Future.delayed(Duration.zero, () { + if (mounted) { + setState(() {}); + } + }); + } + + void toggleExpanded() { + setState(() { + isExpanded = !isExpanded; + }); + } + + void clearLogs() { + gameStateManager.clearLogs(); + } + + bool isExpanded = false; + + @override + Widget build(BuildContext context) { + final DatabaseService dbService = DatabaseService(); + //final LocationController locationController = Get.find(); + return Container( + width: MediaQuery.of(context).size.width, + decoration: const BoxDecoration(color: Colors.black12), + child: GestureDetector( + onTap: toggleExpanded, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + color: isExpanded ? Colors.black54 : Colors.black12, + height: isExpanded ? 160.0 : 48.0, // Adjust sizes as needed + child: Column( + children: [ + // Top bar with clear button + if (isExpanded) + Container( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + color: Colors.blueGrey, // Adjust color as needed + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('game_status'.tr, style: const TextStyle(color: Colors.white)), + IconButton( + icon: const Icon(Icons.clear, color: Colors.white), + onPressed: toggleExpanded, + ), + ], + ), + ), + + // Log messages + Expanded( + child: SingleChildScrollView( + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + Obx(() => Padding( + padding: const EdgeInsets.all(4.0), + child: GameStatusIndicator( + gameStarted: destinationController.isInRog.value, + minimized: !isExpanded, + ), + )), + Padding( + padding: const EdgeInsets.all(4.0), + child: StreamBuilder>( + stream: dbService.destinationUpdatesStream, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return const CircularProgressIndicator(); + } else if (snapshot.hasError) { + return LocationVisitedWidget( + count: 0, + minimized: !isExpanded, + ); + } else if (snapshot.hasData) { + return LocationVisitedWidget( + count: snapshot.data!.length, + minimized: !isExpanded, + ); + } else { + return LocationVisitedWidget( + count: 0, + minimized: !isExpanded, + ); + } + }, + ), + + // child: LocationVisitedWidget( + // count: + // minimized: !isExpanded, + // ), + ), + Padding( + padding: const EdgeInsets.all(4.0), + child: Obx(() => ConnectionStatusIndicator( + connectionStatus: (indexController + .connectionStatusName.value == + "wifi" || + indexController + .connectionStatusName.value == + "mobile") + ? indexController.connectionStatusName.value == + "wifi" + ? ConnectionStatus.wifi + : ConnectionStatus.mobile + : ConnectionStatus.none, + minimized: !isExpanded)), + ), // Expanded view + Padding( + padding: const EdgeInsets.all(4.0), + child:GpsSignalStrengthIndicator( + locationController: Get.find(), + minimized: !isExpanded, // isExpanded はあなたのロジックに依存した変数), + ) + ), + ], + ), + // child: Obx( + // () => DashboardWidget( + // gameStarted: destinationController.isInRog.value, + // locationsVisited: 3, + // isMinimized: false, + // ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/gps_status.dart b/lib/widgets/gps_status.dart new file mode 100644 index 0000000..a908c57 --- /dev/null +++ b/lib/widgets/gps_status.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:gifunavi/utils/location_controller.dart'; + +enum GPSStatus { high, middle, low } + +class GpsSignalStrengthIndicator extends StatelessWidget { + LocationController locationController; + final bool minimized; + + // コンストラクタにminimizedパラメータを追加し、デフォルト値をfalseに設定 + GpsSignalStrengthIndicator({ + super.key, + required this.locationController, + this.minimized = false, // ここでデフォルト値を指定 + }) ; + + @override + Widget build(BuildContext context) { +// final LocationController locationController = Get.find(); + return Obx(() { + String signalStrength = locationController.latestSignalStrength.value; + //debugPrint("GpsSignalStrengthIndicator : signalStrength=${signalStrength}"); + IconData iconData; + Color backgroundColor; + String text; + + // signalStrengthに応じて、アイコン、背景色、テキストを設定 + switch (signalStrength) { + case 'high': + backgroundColor = Colors.green; + iconData = Icons.signal_cellular_alt; + // iconData = CustomIcons.gps_signal_high; + text = 'GPS 強'; + break; + case 'medium': + backgroundColor = Colors.orange; + iconData = Icons.signal_cellular_alt_2_bar; + // iconData = CustomIcons.gps_signal_middle; + text = 'GPS 中'; + break; + default: + backgroundColor = Colors.grey; // Fallback color + iconData = Icons.signal_cellular_connected_no_internet_4_bar; + // iconData = CustomIcons.gps_signal_low; + text = 'GPS 弱'; + } + + // コンテナの設定をminimizedの値に応じて調整 + return Container( + height: minimized ? 40 : null, + width: minimized ? 40 : null, + padding: minimized ? null : const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + decoration: BoxDecoration( + color: backgroundColor, + shape: minimized ? BoxShape.circle : BoxShape.rectangle, + borderRadius: minimized ? null : BorderRadius.circular(10), + ), + child: minimized + ? Center( + child: Icon(iconData, color: Colors.white, size: 24), + ) + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(iconData, color: Colors.white), + const SizedBox(width: 8), + Text( + text, + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ), + ], + ), + ); + }); + } +} + diff --git a/lib/widgets/helper_dialog.dart b/lib/widgets/helper_dialog.dart new file mode 100644 index 0000000..d0ad208 --- /dev/null +++ b/lib/widgets/helper_dialog.dart @@ -0,0 +1,73 @@ +// lib/widgets/helper_dialog.dart +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class HelperDialog extends StatefulWidget { + final String message; + final String screenKey; + + const HelperDialog({super.key, required this.message, required this.screenKey}); + + @override + _HelperDialogState createState() => _HelperDialogState(); +} + +class _HelperDialogState extends State { + bool _doNotShowAgain = false; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Row( + children: [ + Icon(Icons.help_outline, color: Colors.blue), + SizedBox(width: 10), + Text('ヘルプ'), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.message), + const SizedBox(height: 20), + Row( + children: [ + Checkbox( + value: _doNotShowAgain, + onChanged: (value) { + setState(() { + _doNotShowAgain = value!; + }); + }, + ), + const Text('この画面を二度と表示しない'), + ], + ), + ], + ), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () async { + if (_doNotShowAgain) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool('helper_${widget.screenKey}', false); + } + Get.back(); + }, + ), + ], + ); + } +} + +// ヘルパー画面を表示する関数 +Future showHelperDialog(String message, String screenKey) async { + final prefs = await SharedPreferences.getInstance(); + final showHelper = prefs.getBool('helper_$screenKey') ?? true; + if (showHelper) { + Get.dialog(HelperDialog(message: message, screenKey: screenKey)); + } +} \ No newline at end of file diff --git a/lib/widgets/list_widget.dart b/lib/widgets/list_widget.dart index a662385..2313b79 100644 --- a/lib/widgets/list_widget.dart +++ b/lib/widgets/list_widget.dart @@ -1,83 +1,213 @@ import 'package:flutter/material.dart'; -import 'package:geojson/geojson.dart'; +import 'package:geojson_vi/geojson_vi.dart'; import 'package:get/get.dart'; -import 'package:rogapp/model/destination.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/utils/const.dart'; -import 'package:rogapp/widgets/bottom_sheet_new.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/services/maxtrix_service.dart'; +import 'package:gifunavi/utils/const.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_start.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_goal.dart'; +//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_normal_point.dart'; +import 'package:gifunavi/widgets/bottom_sheet_new.dart'; -class ListWidget extends StatelessWidget { - ListWidget({ Key? key }) : super(key: key); +class ListWidget extends StatefulWidget { + const ListWidget({super.key}); + @override + State createState() => _ListWidgetState(); +} + +// IndexControllerから目的地のリストを取得し、ListView.builderを使用してリストを表示します。 +// 各リストアイテムは、目的地の画像、名前、カテゴリ、サブロケーションID、現在地からの距離を表示します。 +// リストアイテムがタップされると、changeCurrentFeatureメソッドを呼び出して現在の目的地を更新し、 BottomSheetウィジェットを表示します。 +// 主なロジック: +// IndexControllerから目的地のリストを取得し、ListView.builderを使用してリストを構築します。 +// getImageメソッドを使用して、目的地の画像を取得し表示します。画像が存在しない場合は、デフォルトの画像を表示します。 +// matrixDistanceメソッドを使用して、現在地から目的地までの距離を計算し表示します。 +// リストアイテムがタップされると、changeCurrentFeatureメソッドを呼び出して現在の目的地を更新し、showModalBottomSheetを使用してBottomSheetウィジェットを表示します。 +// +class _ListWidgetState extends State { final IndexController indexController = Get.find(); - final DestinationController destinationController = Get.find(); - Image getImage(int index){ - if(indexController.locations[0].collection[index].properties!["photos"] == null || indexController.locations[0].collection[index].properties!["photos"] == ""){ + final DestinationController destinationController = + Get.find(); + + Image getImage(int index) { + if (indexController.locations[0].features[index]!.properties!["photos"] == + null || + indexController.locations[0].features[index]!.properties!["photos"] == + "") { return const Image(image: AssetImage('assets/images/empty_image.png')); - } - else{ - print("==== photo index is $index ==="); + } else { + //print("==== photo index is $index ==="); String serverUrl = ConstValues.currentServer(); - GeoJsonFeature gf = indexController.locations[0].collection[index]; + GeoJSONFeature gf = indexController.locations[0].features[index]!; String photo = gf.properties!["photos"]; return Image( - image: NetworkImage( - '$serverUrl/media/compressed/$photo' - ), - errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { + image: NetworkImage('$serverUrl/media/compressed/$photo'), + errorBuilder: + (BuildContext context, Object exception, StackTrace? stackTrace) { return Image.asset("assets/images/empty_image.png"); }, - ); + ); } } - void changeCurrentFeature(GeoJsonFeature fs){ - if(indexController.currentFeature.isNotEmpty){ + // 未使用? + void changeCurrentFeature(GeoJSONFeature fs) { + if (indexController.currentFeature.isNotEmpty) { indexController.currentFeature.clear(); } indexController.currentFeature.add(fs); } @override - Widget build(BuildContext context) { - return Obx(() => - indexController.locations.isNotEmpty ? - ListView.builder( - itemCount: indexController.locations[0].collection.length, - shrinkWrap: true, - itemBuilder: (_, index){ - bool isFound = false; - for(Destination d in destinationController.destinations){ - if(indexController.locations[0].collection[index].properties!['location_id'] == d.location_id){ - isFound = true; - break; - } - } - return Card( - child: ListTile( - selected: isFound, - - selectedTileColor: Colors.yellow.shade200, - onTap: (){ - GeoJsonFeature gf = indexController.locations[0].collection[index]; - changeCurrentFeature(gf); - showModalBottomSheet( - isScrollControlled: true, - context: context, - //builder: (context) => BottomSheetWidget(), - builder:((context) => BottomSheetNew()) - ); - }, - leading: getImage(index), - title: indexController.locations[0].collection[index].properties!['location_name'] != null ? Text(indexController.locations[0].collection[index].properties!['location_name'].toString()) : const Text(""), - subtitle: indexController.locations[0].collection[index].properties!['category'] != null ? Text(indexController.locations[0].collection[index].properties!['category']) : const Text(""), - trailing: indexController.locations[0].collection[index].properties!['sub_loc_id'] != null ? Text(indexController.locations[0].collection[index].properties!['sub_loc_id']) : const Text(""), - ), - ); - }, - ) : const SizedBox(width: 0, height: 0,), - ); + void initState() { + super.initState(); } -} \ No newline at end of file + + Destination createDestination(GeoJSONFeature feature) { + final props = feature.properties; + GeoJSONMultiPoint pt = feature.geometry as GeoJSONMultiPoint; + + return Destination( + cp: props!['cp'], + lat: pt.coordinates[0][1], + lon: pt.coordinates[0][0], + ); + } + + Future matrixDistance(int i) async { + // Create two destinations directly from indexController.locations[0].collection + Destination desCurr = Destination( + lat: indexController.currentLat, lon: indexController.currentLon); + //Destination dest1 = createDestination(indexController.locations[0].collection[0]); + Destination dest2 = + createDestination(indexController.locations[0].features[i]!); + + // Get the distance between these two destinations + final res = await MatrixService.getDestinations([desCurr, dest2]); + + return res["routes"][0]["legs"][0]["distance"]["text"]; + //print("matrix result is ${i} : ${res["routes"][0]["legs"][0]["distance"]["text"]} "); + } + + Future _pullRefresh() async { + //print("pull to refesh"); + indexController.locations[0].features.sort((a, b) => + (a!.properties!['cp'] as Comparable) + .compareTo(b!.properties!['cp'] as Comparable)); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + debugPrint("_ListWidgetState"); + return Obx( + () => indexController.locations.isNotEmpty + ? RefreshIndicator( + onRefresh: _pullRefresh, + child: ListView.builder( + itemCount: indexController.locations[0].features.length, + shrinkWrap: true, + itemBuilder: (_, index) { + bool isFound = false; + for (Destination d in destinationController.destinations) { + if (indexController.locations[0].features[index]! + .properties!['location_id'] == + d.location_id) { + isFound = true; + break; + } + } + return Card( + child: ListTile( + selected: isFound, + selectedTileColor: Colors.yellow.shade200, + onTap: () { + GeoJSONFeature gf = + indexController.locations[0].features[index]!; + Destination des = + destinationController.festuretoDestination(gf); + changeCurrentFeature(gf); + + Widget bottomSheet = BottomSheetNew(destination: des); + /* + if (des.cp == -1 || des.cp == 0) { + bottomSheet = BottomSheetStart(destination: des); + } else if (des.cp == -2 || des.cp == 0) { + bottomSheet = BottomSheetGoal(destination: des); + } else { + bottomSheet = BottomSheetNormalPoint(destination: des); + } + */ + + showModalBottomSheet( + constraints: BoxConstraints.loose( + Size(Get.width, Get.height * 0.85)), + isScrollControlled: true, + context: context, + builder: ((context) => bottomSheet ), + ); + }, + leading: getImage(index), + title: indexController.locations[0].features[index]! + .properties!['location_name'] != + null + ? Text(indexController.locations[0].features[index]! + .properties!['location_name'] + .toString()) + : const Text(""), + subtitle: indexController.locations[0].features[index]! + .properties!['category'] != + null + ? Text(indexController.locations[0].features[index]! + .properties!['category']) + : const Text(""), + trailing: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + indexController.locations[0].features[index]! + .properties!['sub_loc_id'] != + null + ? Text(indexController.locations[0] + .features[index]!.properties!['sub_loc_id']) + : const Text(""), + SizedBox( + width: 100, + child: FutureBuilder( + future: matrixDistance(index), + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator(), + ); + } + if (snapshot.hasError) { + return const Text("-"); + } else { + return Text( + snapshot.data ?? '', + style: const TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold), + ); + } + }, + ), + ) + ], + )), + ); + }, + ), + ) + : const SizedBox( + width: 0, + height: 0, + ), + ); + } +} diff --git a/lib/widgets/map_widget.dart b/lib/widgets/map_widget.dart index d51a4fe..d807237 100644 --- a/lib/widgets/map_widget.dart +++ b/lib/widgets/map_widget.dart @@ -1,162 +1,429 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_map/plugin_api.dart'; +import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; -import 'package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart'; -import 'package:geojson/geojson.dart'; +import 'package:flutter_polyline_points/flutter_polyline_points.dart'; +import 'package:geojson_vi/geojson_vi.dart'; import 'package:get/get.dart'; import 'package:latlong2/latlong.dart'; -import 'package:rogapp/pages/destination/destination_controller.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; -import 'package:rogapp/utils/text_util.dart'; -import 'package:rogapp/widgets/base_layer_widget.dart'; -import 'package:rogapp/widgets/bottom_sheet_new.dart'; +import 'package:gifunavi/pages/permission/permission.dart'; +import 'package:gifunavi/pages/settings/settings_binding.dart'; +import 'package:gifunavi/model/destination.dart'; +import 'package:gifunavi/pages/destination/destination_controller.dart'; +import 'package:gifunavi/pages/index/index_controller.dart'; +import 'package:gifunavi/utils/database_helper.dart'; +import 'package:gifunavi/utils/location_controller.dart'; +import 'package:gifunavi/utils/text_util.dart'; +import 'package:gifunavi/widgets/base_layer_widget.dart'; +import 'package:gifunavi/widgets/bottom_sheet_new.dart'; +import 'package:gifunavi/widgets/current_position_widget.dart'; +import 'package:gifunavi/widgets/game_state_view.dart'; +import 'package:gifunavi/pages/settings/settings_controller.dart'; -class MapWidget extends StatelessWidget { +class MapResetController { + void Function()? resetIdleTimer; +} +class MapWidget extends StatefulWidget { + const MapWidget({super.key}); + + @override + State createState() => _MapWidgetState(); +} + +class _MapWidgetState extends State with WidgetsBindingObserver { +//class _MapWidgetState extends State { final IndexController indexController = Get.find(); - final DestinationController destinationController = Get.find(); + final DestinationController destinationController = + Get.find(); + final LocationController locationController = Get.find(); + final SettingsController settingsController = Get.find(); - MapWidget({ Key? key}) : super(key: key); + late MapController mapController; + final Completer mapControllerCompleter = Completer(); StreamSubscription? subscription; + Timer? _timer; + bool curr_marker_display = false; - Widget getMarkerShape(GeoJsonFeature i, BuildContext context){ - GeoJsonMultiPoint p = i.geometry as GeoJsonMultiPoint; - //print("lat is ${p.geoSerie!.geoPoints[0].latitude} and lon is ${p.geoSerie!.geoPoints[0].longitude}"); - RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - InkWell( - onTap: () { - GeoJsonFeature? fs = indexController.getFeatureForLatLong(p.geoSerie!.geoPoints[0].latitude, p.geoSerie!.geoPoints[0].longitude); - if(fs != null){ - indexController.currentFeature.clear(); - indexController.currentFeature.add(fs); - //print("----- fs is ${fs.properties!['photos']}"); - indexController.getAction(); - - showModalBottomSheet( - context: context, - isScrollControlled: true, - isDismissible: true, - builder:((context) => BottomSheetNew()) - //builder:((context) => BottomSheetWidget()) - ).whenComplete((){ - destinationController.skip_gps = false; - }); - } + final Map _markerCache = {}; + List _markers = []; + + @override + void initState() { + super.initState(); + debugPrint('MapWidget: initState called'); + SettingsBinding().dependencies(); // これを追加 + _startIdleTimer(); + mapController = MapController(); + indexController.mapController = mapController; + + // added by Akira + WidgetsBinding.instance.addObserver(this); + _startIdleTimer(); + + // マップの操作イベントをリッスンして、_resetTimerを呼び出す + mapController.mapEventStream.listen((MapEvent mapEvent) { + if (mapEvent is MapEventMove || mapEvent is MapEventFlingAnimation) { + _resetTimer(); + } + }); + + // MapControllerの初期化が完了するまで待機 + WidgetsBinding.instance.addPostFrameCallback((_) { + debugPrint("MapControllerの初期化が完了"); + setState(() { + indexController.isMapControllerReady.value = true; + }); + // MapControllerの初期化が完了したら、IndexControllerのonInitを呼び出す + //indexController.checkPermission(); + PermissionController.checkAndRequestPermissions(); + }); + + late MapResetController mapResetController = MapResetController(); + mapResetController.resetIdleTimer = _resetIdleTimer; + Get.put(mapResetController); + +// indexController.mapController = MapController(initCompleter: mapControllerCompleter); + + } + + void _resetIdleTimer() { + debugPrint("_resetIdleTimer..."); + _timer?.cancel(); + _startIdleTimer(); + } + + @override + void dispose() { + debugPrint('MapWidget: dispose called'); + WidgetsBinding.instance.removeObserver(this); // added + + mapController.dispose(); + _timer?.cancel(); + super.dispose(); + } + + // added by Akira + /* + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + debugPrint("MapWidget:didChangeAppLifecycleState...state=${state}"); + + if (state == AppLifecycleState.resumed) { + _resetTimer(); + } + } + */ + + // _centerMapOnUser を10秒間でコール + void _startIdleTimer() { + //debugPrint("_startIdleTimer ...."); + final settingsController = Get.find(); + if (!settingsController.autoReturnDisabled.value) { + _timer = Timer(settingsController.timerDuration.value, _centerMapOnUser); + } + } + + // タイマーをリセットして_startIdleTimer をコール + void _resetTimer() { + //debugPrint("_resetTimer ...."); + _timer?.cancel(); + _startIdleTimer(); + } + + // マッぷを現在位置を中心にする。 + void _centerMapOnUser() { + //debugPrint("_centerMapOnUser ...."); + if (mounted) { + //debugPrint("_centerMapOnUser => centering ...."); + destinationController.centerMapToCurrentLocation(); + } + } + + Future _initMarkers() async { + List markers = await _getMarkers(); + setState(() { + _markers = markers; + }); + } + + Future> _getMarkers() async { + debugPrint('Getting markers...'); + List markers = []; + if (indexController.locations.isNotEmpty && indexController.locations[0].features.isNotEmpty) { + for (var feature in indexController.locations[0].features) { + GeoJSONMultiPoint point = feature!.geometry as GeoJSONMultiPoint; + LatLng latLng = LatLng(point.coordinates[0][1], point.coordinates[0][0]); + + markers.add(Marker( + point: latLng, + width: 30.0, + height: 30.0, + child: getMarkerShape(feature), + )); + + } + }else{ + debugPrint('No locations or features available'); + + } + return markers; + } +// Widget getMarkerShape(GeoJSONFeature i, BuildContext context) { + Widget getMarkerShape(GeoJSONFeature i) { + GeoJSONMultiPoint p = i.geometry as GeoJSONMultiPoint; + return InkWell( + onTap: () { + GeoJSONFeature? fs = indexController.getFeatureForLatLong( + p.coordinates[0][1], p.coordinates[0][0]); + if (fs != null) { + indexController.currentFeature.clear(); + indexController.currentFeature.add(fs); + Destination des = destinationController.festuretoDestination(fs); - }, - child: Container( - height: 32, - width: 32, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.transparent, - border: Border.all( - color: i.properties!['buy_point'] > 0 ? Colors.blue : Colors.red, - width: 3, - style: BorderStyle.solid - ) - ), - child: Stack( - alignment: Alignment.center, - children: [ - const Icon(Icons.circle,size: 6.0,), - i.properties!['cp'] == -1 ? - Transform.rotate( - alignment: Alignment.centerLeft, - origin: Offset.fromDirection(1, 26), - angle: 270 * pi / 180, - child: const Icon(Icons.play_arrow_outlined, color: Colors.red, size: 70,)): - Container(color: Colors.transparent,), - ], - ) - ), - ), - Container(color: Colors.white, child: Text(TextUtils.getDisplayTextFeture(i), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color:Colors.red,))), - ], - ); + DatabaseHelper db = DatabaseHelper.instance; + db.getDestinationByLatLon(des.lat!, des.lon!).then((value) { + destinationController.shouldShowBottomSheet = false; + showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.85)), + context: context, + isScrollControlled: true, + isDismissible: true, + builder: ((context) => BottomSheetNew( + destination: des, isAlreadyCheckedIn: value.isNotEmpty)), + ).whenComplete(() { + destinationController.shouldShowBottomSheet = true; + destinationController.skipGps = false; + }); + }); + } + }, + child: Stack( + fit: StackFit.expand, + children: [ + Container( // マーカー + height: 32, + width: 32, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.transparent, + border: Border.all( + color: i.properties!['buy_point'] > 0 + ? Colors.blue + : Colors.red, + width: 3, + style: BorderStyle.solid, + ), + ), + child: Stack( + alignment: Alignment.center, + children: [ + const Icon( + Icons.circle, + size: 6.0, + ), + i.properties!['cp'] <= 0 ? Transform.translate + ( + offset: const Offset(-3, 0), //-3 + child: Transform.rotate( + alignment: Alignment.centerLeft, + origin: Offset.fromDirection(1, 26), + angle: 270 * pi / 180, + child: const Icon( + Icons.play_arrow_outlined, + color: Colors.red, + size: 70, + )), + ) + + : Container( + color: Colors.transparent, + ), + ], + ), + ), + Transform.translate( + offset: const Offset(30, 0), // 30,0 + child: Align( + alignment: Alignment.center, + child: Container ( + //width: 80, // 幅を指定 + //height: 60, // 40 + //color: Colors.purple.withOpacity(0.2), + color: Colors.transparent, + + //child: Text(' '). + //constraints: const BoxConstraints(maxWidth: 60.0), // 最大幅を設定 + //constraints: BoxConstraints(maxWidth: maxWidth), // 最大幅を設定 + //color: Colors.purple.withOpacity(0.2), + child: Stack( + children: [ + Text( // アウトライン + TextUtils.getDisplayTextFeture(i), + style: TextStyle( + fontSize: 16, // 16 + fontWeight: FontWeight.w700, + overflow: TextOverflow.visible, + //height: 1.2, + foreground: Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1 // 2 + ..color = Colors.white, + ), + maxLines: 1, // テキストを1行に制限 + softWrap: false, // テキストの折り返しを無効化 + ), + Text( // テキスト + TextUtils.getDisplayTextFeture(i), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + overflow: TextOverflow.visible, + //fontWeight: FontWeight.bold, + //height: 1.2, + color: Colors.black, + ), + maxLines: 1, // テキストを1行に制限 + softWrap: false, // テキストの折り返しを無効化 + ), + ], + ), + + ), + ), + ) + ], + ), + ); + } + + List? getPoints() { + List pts = []; + for (PointLatLng p in indexController.routePoints) { + LatLng l = LatLng(p.latitude, p.longitude); + pts.add(l); + } + return pts; } @override Widget build(BuildContext context) { - - print("---------- rog mode is ${indexController.rog_mode.value.toString()}----------"); - - final PopupController popupController = PopupController(); + final settingsController = Get.find(); // これを追加 + //final PopupController popupController = PopupController(); return Stack( - children: [ - Obx(() => - indexController.is_loading == true ? const Padding( - padding: EdgeInsets.only(top: 60.0), - child: CircularProgressIndicator(), - ): - FlutterMap( - mapController: indexController.mapController, - options: MapOptions( - maxZoom:18.4, - onMapReady: (){ - indexController.is_mapController_loaded.value = true; - subscription = indexController.mapController.mapEventStream.listen((MapEvent mapEvent) { - if (mapEvent is MapEventMoveStart) { - //print(DateTime.now().toString() + ' [MapEventMoveStart] START'); - // do something - } - if (mapEvent is MapEventMoveEnd && indexController.currentUser.isEmpty) { - //print(DateTime.now().toString() + ' [MapEventMoveStart] END'); - indexController.loadLocationsBound(); - //indexController.rogMapController!.move(c.center, c.zoom); - } - }); - - }, - //center: LatLng(37.15319600454702, 139.58765950528198), - bounds: indexController.currentBound.isNotEmpty ? indexController.currentBound[0]: LatLngBounds.fromPoints([LatLng(35.03999881162295, 136.40587119778962), LatLng(36.642756778706904, 137.95226720406063)]), - zoom: 1, - interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag, - - onPositionChanged: (MapPosition pos, isvalue){ - - }, - onTap: (_, __) => - popupController - .hideAllPopups(), // Hide popup when the map is tapped. - ), - children: [ - const BaseLayer(), - CurrentLocationLayer(), - indexController.locations.isNotEmpty && indexController.locations[0].collection.isNotEmpty ? - MarkerLayer( - markers:indexController.locations[0].collection.map((i) { - print("i si ${i.properties!['location_id']}"); - - RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); - GeoJsonMultiPoint p = i.geometry as GeoJsonMultiPoint; - print("lat is ${p.geoSerie!.geoPoints[0].latitude} and lon is ${p.geoSerie!.geoPoints[0].longitude}"); - return Marker( - anchorPos: AnchorPos.exactly(Anchor(108.0, 18.0)), - height: 32.0, - width: 120.0, - point: LatLng(p.geoSerie!.geoPoints[0].latitude, p.geoSerie!.geoPoints[0].longitude), - builder: (ctx){ - return getMarkerShape(i, context); - }, - ); - }).toList(), + children: [ + Obx(() => indexController.isLoading.value == true + ? const Padding( + padding: EdgeInsets.only(top: 60.0), + child: CircularProgressIndicator(), + ) + : FlutterMap( + mapController: mapController, + //mapController: indexController.mapController, + options: MapOptions( + maxZoom: 18.4, + onMapReady: () { + _initMarkers(); + //indexController.isMapControllerReady.value = true; + }, + initialCenter: + const LatLng(37.15319600454702, 139.58765950528198), + bounds: indexController.currentBound.isNotEmpty + ? indexController.currentBound[0] + : LatLngBounds.fromPoints([ + const LatLng(35.03999881162295, 136.40587119778962), + const LatLng(36.642756778706904, 137.95226720406063) + ]), + initialZoom: 1, + interactiveFlags: + InteractiveFlag.pinchZoom | InteractiveFlag.drag, + onPositionChanged: (MapPosition pos, hasGesture) { + if (hasGesture) { + _resetTimer(); + } + indexController.currentBound = [pos.bounds!]; + }, + onMapEvent: (MapEvent mapEvent) { + //debugPrint('Map event: ${mapEvent.runtimeType}'); + if (mapEvent is MapEventMove) { + destinationController.shouldShowBottomSheet = true; + } + }, + //onTap: (_, __) => popupController.hideAllPopups(), + ), + children: [ + const BaseLayer(), + // ルートのポリライン表示 + Obx( + () => indexController.routePointLenght > 0 + ? PolylineLayer( + polylines: [ + Polyline( + points: getPoints()!, + strokeWidth: 6.0, + color: Colors.indigo, + ), + ], ) - : - const Center(child: CircularProgressIndicator()) - , + : Container(), + ), + // 現在位置のマーカー + CurrentLocationLayer( + positionStream: locationController + .locationMarkerPositionStreamController.stream, + //alignDirectionOnUpdate: AlignOnUpdate.never, + style: const LocationMarkerStyle( + marker: Stack( + children: [ + CircleAvatar( + radius: 13.5, + backgroundColor: Colors.blue, + child: Icon(Icons.navigation, color: Colors.white), + ), ], - ) - ) - ], - ); + ), + markerSize: Size(27, 27), + markerDirection: MarkerDirection.heading, + ), + //child: const Icon(Icons.navigation), + ), + + FutureBuilder>( + future: indexController.locations.isNotEmpty ? _getMarkers() : null, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return const Center(child: Text('マーカーの読み込みに失敗しました')); + } else { + return MarkerLayer(markers: snapshot.data ?? []); + } + }, + ), + //MarkerLayer(markers: indexController.locations.isNotEmpty ? _getMarkers() : []), + ], + )), + const Positioned(top: 0, left: 0, child: GameStateWidget()), + const Positioned(bottom: 10, right: 10, child: CurrentPosition()), + StreamBuilder( + stream: locationController.locationMarkerPositionStream, + builder: (context, snapshot) { + if (!snapshot.hasData) { + //debugPrint("====== Not display current marker"); + curr_marker_display = true; + }else if(curr_marker_display){ + debugPrint("====== Displayed current marker"); + curr_marker_display = false; + } + return Container(); + }, + ) + ], + ); } -} \ No newline at end of file +} diff --git a/lib/widgets/perfecture_widget.dart b/lib/widgets/perfecture_widget.dart deleted file mode 100644 index 625b588..0000000 --- a/lib/widgets/perfecture_widget.dart +++ /dev/null @@ -1,237 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:get/get.dart'; -import 'package:rogapp/pages/index/index_controller.dart'; - - -class PerfectureWidget extends StatefulWidget { - - IndexController indexController; - MapController mapController; - - - PerfectureWidget({Key? key, required this.indexController, required this.mapController}) : super(key: key){ - //indexController.zoomtoMainPerf("9", mapController); - } - - @override - State createState() => _PerfectureWidgetState(); -} - -class _PerfectureWidgetState extends State { - - @override - void initState() { - super.initState(); - } - - - bool isNumeric(String s) { - if (s == null) { - return false; - } - return double.tryParse(s) != null; - } - - - List> getDropdownItems() { - List> dropDownItems = []; - - for (Map currency in widget.indexController.perfectures[0]) { - //print(currency["id"].toString()); - var newDropdown = DropdownMenuItem( - value: currency["id"].toString(), - child: Text(currency["adm1_ja"].toString()), - ); - - dropDownItems.add(newDropdown); - } - return dropDownItems; - } - - List> getSubDropdownItems() { - List> dropDownItems = []; - - if(widget.indexController.subPerfs.isNotEmpty){ - - for (Map currency in widget.indexController.subPerfs[0]) { - var newDropdown = DropdownMenuItem( - value: currency["id"].toString(), - child: Text(currency["adm2_ja"].toString()), - ); - dropDownItems.add(newDropdown); - } - } - return dropDownItems; - } - - List> getCustomArea(){ - List> dropDownItems = []; - if(widget.indexController.areas.isNotEmpty){ - - for (Map currency in widget.indexController.areas[0]) { - var newDropdown = DropdownMenuItem( - value: currency["id"].toString(), - child: Text(currency["area_nm"].toString()), - ); - dropDownItems.add(newDropdown); - - } - - } - - if(widget.indexController.customAreas.isNotEmpty){ - - for (Map currency in widget.indexController.customAreas[0]) { - var newDropdown = DropdownMenuItem( - value: currency["event_name"].toString(), - child: Text(currency["event_name"].toString()), - ); - - dropDownItems.add(newDropdown); - } - - } - - return dropDownItems; - } - - List> getCategory(){ - List> dropDownItems = []; - - dropDownItems.clear(); - - //print("--------cats ------############### ${widget.indexController.cats.toString()} -------------"); - for(dynamic d in widget.indexController.cats){ - //print("-------- ddd ------############### ${d} --------dddd-----"); - var newDropdown = DropdownMenuItem(value: d['category'].toString(), child: Text(d['category'].toString())); - //print("--------cats ------############### ${d['category'].toString()} -------------"); - dropDownItems.add(newDropdown); - } - - //return []; - - return dropDownItems; - } - - @override - Widget build(BuildContext context) { - - return Obx(() => - Row( - children: [ - DropdownButton( - value: widget.indexController.dropdownValue, - icon: const Icon(Icons.arrow_downward), - elevation: 16, - style: const TextStyle(color: Colors.deepPurple), - underline: Container( - height: 2, - color: Colors.deepPurpleAccent, - ), - onChanged: (String? newValue) { - //setState(() { - if(newValue != null){ - widget.indexController.is_loading.value = true; - widget.indexController.dropdownValue = newValue; - widget.indexController.populateForPerf(newValue, widget.mapController); - } - //}); - }, - items: getDropdownItems() - ), - - // Gifu areas - widget.indexController.areas.isNotEmpty ? - DropdownButton( - value: widget.indexController.areaDropdownValue, - icon: const Icon(Icons.arrow_downward), - elevation: 16, - style: const TextStyle(color: Colors.deepPurple), - hint: const Text("select"), - underline: Container( - height: 2, - color: Colors.deepPurpleAccent, - ), - onChanged: (String? newValue) { - if (isNumeric(newValue!)){ - widget.indexController.is_custom_area_selected.value = false; - } - else{ - widget.indexController.loadCustomLocation(newValue); - widget.indexController.is_custom_area_selected.value = true; - widget.indexController.subPerfs.clear(); - widget.indexController.cats.clear(); - } - setState(() { - widget.indexController.locations.clear(); - widget.indexController.is_loading.value = true; - widget.indexController.areaDropdownValue = newValue; - widget.indexController.populateSubPerForArea(newValue, widget.mapController); - }); - }, - items: getCustomArea(), - ): const Text(""), - - widget.indexController.subPerfs.isNotEmpty ? - DropdownButton( - value: widget.indexController.subDropdownValue, - icon: const Icon(Icons.arrow_downward), - elevation: 16, - style: const TextStyle(color: Colors.deepPurple), - hint: const Text("select"), - underline: Container( - height: 2, - color: Colors.deepPurpleAccent, - ), - onChanged: (String? newValue) { - setState(() { - if(newValue != null){ - //widget.indexController.is_loading.value = true; - //widget.indexController.populateForSubPerf(newValue, widget.mapController); - //widget.indexController.loadLocationforSubPerf(newValue, widget.mapController); - widget.indexController.subDropdownValue = newValue; - widget.indexController.loadCatForCity(newValue); - } - }); - }, - items: - getSubDropdownItems() - ) : - const Text(""), - //CatWidget(indexController: widget.indexController,), - widget.indexController.cats.isNotEmpty ? - DropdownButton( - value: widget.indexController.getCatText(), - icon: const Icon(Icons.arrow_downward), - elevation: 16, - style: const TextStyle(color: Colors.deepPurple), - hint: const Text("select"), - underline: Container( - height: 2, - color: Colors.deepPurpleAccent, - ), - onChanged: (String? newValue) { - setState(() { - if(newValue != null){ - widget.indexController.is_loading.value = true; - widget.indexController.cateogory = newValue; - widget.indexController.currentCat.clear(); - widget.indexController.currentCat.add(newValue); - widget.indexController.populateForSubPerf(widget.indexController.subDropdownValue, widget.mapController); - //widget.indexController.loadLocationforSubPerf(newValue, widget.mapController); - //widget.indexController.subDropdownValue = newValue; - } - }); - }, - items: - getCategory(), - ) - : - Container(), - - ], - ), - ); - } -} diff --git a/lib/widgets/permission_handler_screen.dart b/lib/widgets/permission_handler_screen.dart new file mode 100644 index 0000000..3a0b52e --- /dev/null +++ b/lib/widgets/permission_handler_screen.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:gifunavi/pages/permission/permission.dart'; + +class PermissionHandlerScreen extends StatefulWidget { + const PermissionHandlerScreen({super.key}); + + @override + _PermissionHandlerScreenState createState() => _PermissionHandlerScreenState(); +} + +class _PermissionHandlerScreenState extends State { + @override + void initState() { + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + await PermissionController.checkAndRequestPermissions(); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('権限の確認'), + ), + body: const Center( + child: Text('権限の確認中...'), + ), + ); + } +} \ No newline at end of file diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..401e750 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "gifunavi") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.gifunavi") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..7299b5c --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,19 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..786ff5c --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/main.cc b/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/my_application.cc b/linux/my_application.cc new file mode 100644 index 0000000..bdfc093 --- /dev/null +++ b/linux/my_application.cc @@ -0,0 +1,124 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "gifunavi"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "gifunavi"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/linux/my_application.h b/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..a347b3d --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,28 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import connectivity_plus +import file_selector_macos +import geolocator_apple +import package_info_plus +import path_provider_foundation +import shared_preferences_foundation +import sqflite +import url_launcher_macos +import webview_flutter_wkwebview + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) +} diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..18e45eb --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* gifunavi.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "gifunavi.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* gifunavi.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* gifunavi.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.gifunavi.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/gifunavi.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/gifunavi"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.gifunavi.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/gifunavi.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/gifunavi"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.gifunavi.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/gifunavi.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/gifunavi"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..0ee015f --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..8e02df2 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..eb39b3c --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = gifunavi + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.gifunavi + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/plugin/flutter_compass b/plugin/flutter_compass new file mode 160000 index 0000000..0dc2628 --- /dev/null +++ b/plugin/flutter_compass @@ -0,0 +1 @@ +Subproject commit 0dc2628ae5a22887fcb0f3072cd065652739226d diff --git a/plugin/image_gallery_saver b/plugin/image_gallery_saver new file mode 160000 index 0000000..8c6480b --- /dev/null +++ b/plugin/image_gallery_saver @@ -0,0 +1 @@ +Subproject commit 8c6480bf3a07834df89525e7ac6e0196ec701f73 diff --git a/plugin/qr_code_scanner/.github/ISSUE_TEMPLATE/bug_report.md b/plugin/qr_code_scanner/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..2393c04 --- /dev/null +++ b/plugin/qr_code_scanner/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] " +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Flutter information** +Always provide the output of `flutter doctor -v` as it is needed in order to know on which Flutter versions the bug exists in. + +**Device (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/plugin/qr_code_scanner/.github/ISSUE_TEMPLATE/feature_request.md b/plugin/qr_code_scanner/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..864a505 --- /dev/null +++ b/plugin/qr_code_scanner/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE] " +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/plugin/qr_code_scanner/.github/dependabot.yml b/plugin/qr_code_scanner/.github/dependabot.yml new file mode 100644 index 0000000..7825473 --- /dev/null +++ b/plugin/qr_code_scanner/.github/dependabot.yml @@ -0,0 +1,20 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + reviewers: + - "juliansteenbakker" + - package-ecosystem: gradle + directory: "/android" + schedule: + interval: "weekly" + reviewers: + - "juliansteenbakker" + - package-ecosystem: gradle + directory: "/example/android" + schedule: + interval: "weekly" + reviewers: + - "juliansteenbakker" \ No newline at end of file diff --git a/plugin/qr_code_scanner/.github/workflows/dart.yml b/plugin/qr_code_scanner/.github/workflows/dart.yml new file mode 100644 index 0000000..78bf815 --- /dev/null +++ b/plugin/qr_code_scanner/.github/workflows/dart.yml @@ -0,0 +1,24 @@ +name: dart + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '11' + - uses: subosito/flutter-action@v2.10.0 + - name: Version + run: flutter doctor -v + - name: Install dependencies + run: flutter pub get + - name: Format + run: dart format --set-exit-if-changed . + - name: Linter + run: flutter analyze diff --git a/plugin/qr_code_scanner/.gitignore b/plugin/qr_code_scanner/.gitignore new file mode 100644 index 0000000..aef8b20 --- /dev/null +++ b/plugin/qr_code_scanner/.gitignore @@ -0,0 +1,116 @@ +# Miscellaneous +*.class +*.lock +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +.last_build_id + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Visual Studio Code related +.classpath +.project +.settings/ +.vscode/ + +# Flutter repo-specific +/bin/cache/ +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/docs/doc/ +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +version + +# packages file containing multi-root paths +.packages.generated + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.jks + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/.symlinks/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/Flutter-Debug.xcconfig +**/macos/Flutter/Flutter-Release.xcconfig +**/macos/Flutter/Flutter-Profile.xcconfig + +# Coverage +coverage/ + +# Symbols +app.*.symbols + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock \ No newline at end of file diff --git a/plugin/qr_code_scanner/.resources/android-app-screen-one.jpg b/plugin/qr_code_scanner/.resources/android-app-screen-one.jpg new file mode 100755 index 0000000..f0c3992 Binary files /dev/null and b/plugin/qr_code_scanner/.resources/android-app-screen-one.jpg differ diff --git a/plugin/qr_code_scanner/.resources/android-app-screen-two.jpg b/plugin/qr_code_scanner/.resources/android-app-screen-two.jpg new file mode 100755 index 0000000..aa2cde0 Binary files /dev/null and b/plugin/qr_code_scanner/.resources/android-app-screen-two.jpg differ diff --git a/plugin/qr_code_scanner/.resources/called-multiple-times-debug01.png b/plugin/qr_code_scanner/.resources/called-multiple-times-debug01.png new file mode 100644 index 0000000..759f19a Binary files /dev/null and b/plugin/qr_code_scanner/.resources/called-multiple-times-debug01.png differ diff --git a/plugin/qr_code_scanner/.resources/called-multiple-times-debug02.png b/plugin/qr_code_scanner/.resources/called-multiple-times-debug02.png new file mode 100644 index 0000000..997cb93 Binary files /dev/null and b/plugin/qr_code_scanner/.resources/called-multiple-times-debug02.png differ diff --git a/plugin/qr_code_scanner/.resources/ios-app-screen-one.png b/plugin/qr_code_scanner/.resources/ios-app-screen-one.png new file mode 100644 index 0000000..b53fbe3 Binary files /dev/null and b/plugin/qr_code_scanner/.resources/ios-app-screen-one.png differ diff --git a/plugin/qr_code_scanner/.resources/ios-app-screen-two.png b/plugin/qr_code_scanner/.resources/ios-app-screen-two.png new file mode 100644 index 0000000..b6d0a93 Binary files /dev/null and b/plugin/qr_code_scanner/.resources/ios-app-screen-two.png differ diff --git a/plugin/qr_code_scanner/.resources/sample_qr_code.png b/plugin/qr_code_scanner/.resources/sample_qr_code.png new file mode 100644 index 0000000..45892b8 Binary files /dev/null and b/plugin/qr_code_scanner/.resources/sample_qr_code.png differ diff --git a/plugin/qr_code_scanner/CHANGELOG.md b/plugin/qr_code_scanner/CHANGELOG.md new file mode 100644 index 0000000..6f91f3c --- /dev/null +++ b/plugin/qr_code_scanner/CHANGELOG.md @@ -0,0 +1,165 @@ +## 1.0.0 +Breaking changes: +Minimum Flutter version is now Flutter 3.0.0 (Dart 2.17.0). + +#### Features +* Inverted is now mixed with normal scanning. +* onPermissionSet now works on web aswell. +* [Android] zxing core is updated to 3.5.0. +* [Android] Several code improvements. +* [Android] Several dependencies updated. + +## 0.7.0 +#### Features +* Add inverted feature for Android. See https://github.com/juliuscanute/qr_code_scanner/issues/403 + +#### Bugfixes +* Fixed permission error on devices running Android 7 or lower. +* Fixed error being thrown when user declines permission on iOS. +* Updated dependencies + +## 0.6.1 +* Fix bug which caused build to fail for iOS. (#452) + +## 0.6.0 +#### Features +* Add support for raw bytes on iOS. (#421) +* Add custom cutout width and height next to cutout size. (#432) + +#### Bugfixes +* Fix for calling permission multiple times. (#381) +* Fix for QRView Overlay cutoutbottomoffset. (#383) +* Multiple minor improvements + +## 0.5.2 +#### Bugfixes +* Increased delay to fix QRView opening zoomed in on some devices by adding small delay to updateDimensions(). (#250) +* Updated ZXING from 3.3.0 to 3.4.1 (#369) +* Fixed permission not being called correctly on Android (#351) + +## 0.5.1 +Removed web from library export. + +## 0.5.0 +* Added initial web-support. This function is still under development and not fully tested. +* Fixed permissions on iOS. +* Updated dependencies. + +## 0.4.0 +Stable null-safety support. (#278) + +## 0.3.5 +#### Bug fixes +* Fixed QRView opening zoomed in on some devices by adding small delay to updateDimensions(). (#250) +* Changed upc-A to EAN13 on iOS. (#262) +* Fixed null-pointer on BarcodeFormat array on iOS. (#262) +* Added LifecycleEventHandler to dispose(). (#265) + +## 0.3.4 +#### Bug fixes +* Fixed No barcode view found on Android when calling controller.dispose() (#257) +* Fixed Hot reload not working on Android. + +## 0.3.3 +#### Bug fixes +* Fixed updateDimensions not being called causing zoom on iOS. (#250) +* Fixed Android permission callback not working. (#251) (#252) +* Fixed null-pointers after declining permission on Android. + +## 0.3.2 +#### Bug fixes +* Fixed null-pointer when no overlay provided on iOS. (#245) +* Fixed camera not stopping (green dot on iOS 14) when navigating to other page. (#240) + +## 0.3.1 +#### Bug fixes +* Fixed permission callback on iOS & Android. +* Fixed camera facing not working on Android. +* Fixed scanArea not being honored on Android. +* Updated ShapeBorder to QrScannerOverlayShape. + +## 0.3.0 +#### Breaking change +Its not necessary anymore to wrap the QRView in a SizeChangedLayoutNotifier because this is handled inside the plugin. +#### New Features +* Added possibility to set allowed barcodes. (#135) +* Added possibility to check what features are supported by device. (hasFlash, hasBackCamera, hasFrontCamera) (#135) +* Added possibility to check if flash is on. (#135) +* Added possibility to check which camera is active. (#135) +* All functions are now async so you can await them. (#135) + +See the updated example on how to implement these features. +#### Bug fixes +* Fixed permission handling in Android. +* Native functions now returns results so exceptions can be thrown when an error occurs. + +## 0.2.1 +* Fixed critical bug where scanner wouldn't open when no scan overlay was configured. + +## 0.2.0 +#### Breaking change +* The plugin now returns Barcode object instead of QR String. This object includes the type of code, the code itself and on Android devices the raw bytes. (#63) +#### New Features +* Added possibility to provide scanArea on iOS. (#165) +#### Bug fixes +* Fixed preview going black after hot reload. (#76) +* Fixed nullpointer when plugin binding order isn't correct. (#181) +* Fixed permission being asked on startup (#185) + +## 0.1.0 +* Changed Android minSDKversion from 24 to 21 (#170) +* Fix preview size after iPad rotation (#125) +* Implemented Android Embedding V2 (#132) +* Added cutout bottom offset (#115) +* Fix Android ActivityLifecycleCallbacks (#166) +* Fix some other small bugs + +## 0.0.14 +* Fix disposing camera on iOS 14 (#113) + +## 0.0.13 +* Fix misalignment when QRView doesn't start from the top left (#45) +* Fix crash on iOS when scanning returns nil (#69, #72) +* Fix ArithmeticException on Android (#43) + +## 0.0.12 +* Add optional parameter to use a camera overlay. +* Simplify controller, expose scanDataStream. +* Fix for Android flash toggle. +* Add ability to pause/resume the camera. +* Thanks! to Luis Thein for all the above contributions. + +## 0.0.11 +* android build break fix + +## 0.0.10 +* update README.md + +## 0.0.9 +* update README.md + +## 0.0.8 +* migrated Android project to androidx (by Felipe César) +* migrated iOS to Swift 5 (by Felipe César) + +## 0.0.7 +* flash light support added + +## 0.0.6 +* camera flip added + +## 0.0.5 +* preview stretching after change screen orientation fix + +## 0.0.4 +* fix black screen orientation/unlock/focus + +## 0.0.3 +* iOS library reference fix +* Android pause/resume fix + +## 0.0.2 +* Added documentation to cover how to use the plugin. + +## 0.0.1 +* QR Code scanner embedded inside flutter. diff --git a/plugin/qr_code_scanner/LICENSE b/plugin/qr_code_scanner/LICENSE new file mode 100644 index 0000000..7279e1f --- /dev/null +++ b/plugin/qr_code_scanner/LICENSE @@ -0,0 +1,9 @@ +Copyright 2018 Julius Canute + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/plugin/qr_code_scanner/README.md b/plugin/qr_code_scanner/README.md new file mode 100644 index 0000000..8279f82 --- /dev/null +++ b/plugin/qr_code_scanner/README.md @@ -0,0 +1,209 @@ +# Project in Maintenance Mode Only + +Since the underlying frameworks of this package, [zxing for android](https://github.com/zxing/zxing) and [MTBBarcodescanner for iOS](https://github.com/mikebuss/MTBBarcodeScanner) are both not longer maintaned, this plugin is no longer up to date and in maintenance mode only. Only bug fixes and minor enhancements will be considered. + +I am developing a new plugin [mobile_scanner](https://pub.dev/packages/mobile_scanner) that uses the latest version of MLKit for detecting barcodes and QR codes. On Android it also uses the latest version of CameraX, and on iOS the native AVFoundation for best camera performance. + +# QR Code Scanner + +[![pub package](https://img.shields.io/pub/v/qr_code_scanner?include_prereleases)](https://pub.dartlang.org/packages/qr_code_scanner) +[![Join the chat](https://img.shields.io/discord/829004904600961054)](https://discord.gg/aZujk84f6V) +[![GH Actions](https://github.com/juliuscanute/qr_code_scanner/workflows/dart/badge.svg)](https://github.com/juliuscanute/qr_code_scanner/actions) + +A QR code scanner that works on both iOS and Android by natively embedding the platform view within Flutter. The integration with Flutter is seamless, much better than jumping into a native Activity or a ViewController to perform the scan. + +## Screenshots + + + + + + + + + + + + + + + + + + + +
+Android +
+

+ +

+
+

+ +

+
+iOS +
+

+ +

+
+

+ +

+
+ +## Get Scanned QR Code + +When a QR code is recognized, the text identified will be set in 'result' of type `Barcode`, which contains the output text as property 'code' of type `String` and scanned code type as property 'format' which is an enum `BarcodeFormat`, defined in the library. + +```dart +class _QRViewExampleState extends State { + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + Barcode? result; + QRViewController? controller; + + // In order to get hot reload to work we need to pause the camera if the platform + // is android, or resume the camera if the platform is iOS. + @override + void reassemble() { + super.reassemble(); + if (Platform.isAndroid) { + controller!.pauseCamera(); + } else if (Platform.isIOS) { + controller!.resumeCamera(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + Expanded( + flex: 5, + child: QRView( + key: qrKey, + onQRViewCreated: _onQRViewCreated, + ), + ), + Expanded( + flex: 1, + child: Center( + child: (result != null) + ? Text( + 'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}') + : Text('Scan a code'), + ), + ) + ], + ), + ); + } + + void _onQRViewCreated(QRViewController controller) { + this.controller = controller; + controller.scannedDataStream.listen((scanData) { + setState(() { + result = scanData; + }); + }); + } + + @override + void dispose() { + controller?.dispose(); + super.dispose(); + } +} + +``` + +## Android Integration +In order to use this plugin, please update the Gradle, Kotlin and Kotlin Gradle Plugin: + +In ```android/build.gradle``` change ```ext.kotlin_version = '1.3.50'``` to ```ext.kotlin_version = '1.5.10'``` + +In ```android/build.gradle``` change ```classpath 'com.android.tools.build:gradle:3.5.0'``` to ```classpath 'com.android.tools.build:gradle:4.2.0'``` + +In ```android/gradle/wrapper/gradle-wrapper.properties``` change ```distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip``` to ```distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip``` + +In ```android/app/build.gradle``` change +```defaultConfig{``` + ```...``` + ```minSdkVersion 16``` +```}``` to +```defaultConfig{``` + ```...``` + ```minSdkVersion 20``` +```}``` + +### *Warning* +If you are using Flutter Beta or Dev channel (1.25 or 1.26) you can get the following error: + +`java.lang.AbstractMethodError: abstract method "void io.flutter.plugin.platform.PlatformView.onFlutterViewAttached(android.view.View)"` + +This is a bug in Flutter which is being tracked here: https://github.com/flutter/flutter/issues/72185 + +There is a workaround by adding `android.enableDexingArtifactTransform=false` to your `gradle.properties` file. + +## iOS Integration +In order to use this plugin, add the following to your Info.plist file: +``` +io.flutter.embedded_views_preview + +NSCameraUsageDescription +This app needs camera access to scan QR codes +``` + +## Web Integration + +Add this to `web/index.html`: + +```html + +``` + +Please note: on web, only QR codes are supported. Other barcodes and 2D codes cannot be scanned. + +Web support is in very early stage. Features such as flash, pause or resume are not implemented. Moreover, the camera +preview does not respect the surrounding constraints. This is not at last due to Flutter's early state of platform views +on web. + +## Flip Camera (Back/Front) +The default camera is the back camera. +```dart +await controller.flipCamera(); +``` + +## Flash (Off/On) +By default, flash is OFF. +```dart +await controller.toggleFlash(); +``` + +## Resume/Pause +Pause camera stream and scanner. +```dart +await controller.pauseCamera(); +``` +Resume camera stream and scanner. +```dart +await controller.resumeCamera(); +``` + + +# SDK +Requires at least SDK 20. +Requires at least iOS 8. + +# TODOs +* iOS Native embedding is written to match what is supported in the framework as of the date of publication of this package. It needs to be improved as the framework support improves. +* In future, options will be provided for default states. +* Finally, I welcome PR's to make it better :), thanks + +# Credits +* Android: https://github.com/zxing/zxing +* iOS: https://github.com/mikebuss/MTBBarcodeScanner +* Special Thanks To: LeonDevLifeLog for his contributions towards improving this package. diff --git a/plugin/qr_code_scanner/analysis_options.yaml b/plugin/qr_code_scanner/analysis_options.yaml new file mode 100644 index 0000000..a3be6b8 --- /dev/null +++ b/plugin/qr_code_scanner/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml \ No newline at end of file diff --git a/plugin/qr_code_scanner/android/.gitignore b/plugin/qr_code_scanner/android/.gitignore new file mode 100644 index 0000000..d36c796 --- /dev/null +++ b/plugin/qr_code_scanner/android/.gitignore @@ -0,0 +1,67 @@ +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/caches + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md \ No newline at end of file diff --git a/plugin/qr_code_scanner/android/build.gradle b/plugin/qr_code_scanner/android/build.gradle new file mode 100644 index 0000000..a8dd439 --- /dev/null +++ b/plugin/qr_code_scanner/android/build.gradle @@ -0,0 +1,64 @@ +group 'net.touchcapture.qr.flutterqr' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.8.22' //''1.9.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.3.0' //8.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + namespace 'net.touchcapture.qr.flutterqr' + compileSdk 34 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { + // minSdkVersion is determined by Native View. + minSdkVersion 20 + targetSdkVersion 34 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true + } + + kotlinOptions { + jvmTarget = '11' + } + + compileOptions { + // Flag to enable support for the new language APIs + coreLibraryDesugaringEnabled true + // Sets Java compatibility to Java 11 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + if (project.android.hasProperty('namespace')) { + namespace 'net.touchcapture.qr.flutterqr' + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.zxing:core:3.5.2' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' +} diff --git a/plugin/qr_code_scanner/android/gradle.properties b/plugin/qr_code_scanner/android/gradle.properties new file mode 100644 index 0000000..08f2b5f --- /dev/null +++ b/plugin/qr_code_scanner/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableJetifier=true +android.useAndroidX=true diff --git a/plugin/qr_code_scanner/android/gradle/wrapper/gradle-wrapper.properties b/plugin/qr_code_scanner/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ae04661 --- /dev/null +++ b/plugin/qr_code_scanner/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/plugin/qr_code_scanner/android/settings.gradle b/plugin/qr_code_scanner/android/settings.gradle new file mode 100644 index 0000000..16c4243 --- /dev/null +++ b/plugin/qr_code_scanner/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_qr' diff --git a/plugin/qr_code_scanner/android/src/main/AndroidManifest.xml b/plugin/qr_code_scanner/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..28f2e86 --- /dev/null +++ b/plugin/qr_code_scanner/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/CustomFramingRectBarcodeView.kt b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/CustomFramingRectBarcodeView.kt new file mode 100644 index 0000000..3ad7431 --- /dev/null +++ b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/CustomFramingRectBarcodeView.kt @@ -0,0 +1,45 @@ +package net.touchcapture.qr.flutterqr + +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import com.journeyapps.barcodescanner.BarcodeView +import com.journeyapps.barcodescanner.Size + +class CustomFramingRectBarcodeView : BarcodeView { + private var bottomOffset = BOTTOM_OFFSET_NOT_SET_VALUE + + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) + + override fun calculateFramingRect(container: Rect, surface: Rect): Rect { + val containerArea = Rect(container) + val intersects = + containerArea.intersect(surface) //adjusts the containerArea (code from super.calculateFramingRect) + val scanAreaRect = super.calculateFramingRect(container, surface) + if (bottomOffset != BOTTOM_OFFSET_NOT_SET_VALUE) { //if the setFramingRect function was called, then we shift the scan area by Y + val scanAreaRectWithOffset = Rect(scanAreaRect) + scanAreaRectWithOffset.bottom -= bottomOffset + scanAreaRectWithOffset.top -= bottomOffset + val belongsToContainer = scanAreaRectWithOffset.intersect(containerArea) + if (belongsToContainer) { + return scanAreaRectWithOffset + } + } + return scanAreaRect + } + + fun setFramingRect(rectWidth: Int, rectHeight: Int, bottomOffset: Int) { + this.bottomOffset = bottomOffset + framingRectSize = Size(rectWidth, rectHeight) + } + + companion object { + private const val BOTTOM_OFFSET_NOT_SET_VALUE = -1 + } +} \ No newline at end of file diff --git a/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt new file mode 100644 index 0000000..97977a4 --- /dev/null +++ b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt @@ -0,0 +1,47 @@ +package net.touchcapture.qr.flutterqr + +import androidx.annotation.NonNull +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding + +class FlutterQrPlugin : FlutterPlugin, ActivityAware { + + /** Plugin registration embedding v2 */ + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + flutterPluginBinding.platformViewRegistry + .registerViewFactory( + VIEW_TYPE_ID, + QRViewFactory(flutterPluginBinding.binaryMessenger) + ) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + // Leave empty + // Nullifying QrShared.activity and QrShared.binding here will cause errors if plugin is detached by another plugin + } + + override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) { + QrShared.activity = activityPluginBinding.activity + QrShared.binding = activityPluginBinding + } + + override fun onDetachedFromActivityForConfigChanges() { + QrShared.activity = null + QrShared.binding = null + } + + override fun onReattachedToActivityForConfigChanges(activityPluginBinding: ActivityPluginBinding) { + QrShared.activity = activityPluginBinding.activity + QrShared.binding = activityPluginBinding + } + + override fun onDetachedFromActivity() { + QrShared.activity = null + QrShared.binding = null + } + + companion object { + private const val VIEW_TYPE_ID = "net.touchcapture.qr.flutterqr/qrview" + } +} diff --git a/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt new file mode 100644 index 0000000..4589f2f --- /dev/null +++ b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt @@ -0,0 +1,386 @@ +package net.touchcapture.qr.flutterqr + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import android.view.View +import androidx.core.content.ContextCompat +import com.google.zxing.BarcodeFormat +import com.google.zxing.ResultPoint +import com.journeyapps.barcodescanner.BarcodeCallback +import com.journeyapps.barcodescanner.BarcodeResult +import com.journeyapps.barcodescanner.DefaultDecoderFactory +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.PluginRegistry +import io.flutter.plugin.platform.PlatformView + +class QRView( + private val context: Context, + messenger: BinaryMessenger, + private val id: Int, + private val params: HashMap +) : PlatformView, MethodChannel.MethodCallHandler, PluginRegistry.RequestPermissionsResultListener { + + private val cameraRequestCode = QrShared.CAMERA_REQUEST_ID + this.id + + private val channel: MethodChannel = MethodChannel( + messenger, "net.touchcapture.qr.flutterqr/qrview_$id" + ) + private val cameraFacingBack = 0 + private val cameraFacingFront = 1 + + private var isRequestingPermission = false + private var isTorchOn = false + private var isPaused = false + private var barcodeView: CustomFramingRectBarcodeView? = null + private var unRegisterLifecycleCallback: UnRegisterLifecycleCallback? = null + + init { + QrShared.binding?.addRequestPermissionsResultListener(this) + + channel.setMethodCallHandler(this) + + unRegisterLifecycleCallback = QrShared.activity?.registerLifecycleCallbacks( + onPause = { + if (!isPaused && hasCameraPermission) barcodeView?.pause() + + }, + onResume = { + if (!hasCameraPermission && !isRequestingPermission) checkAndRequestPermission() + else if (!isPaused && hasCameraPermission) barcodeView?.resume() + } + ) + } + + override fun dispose() { + unRegisterLifecycleCallback?.invoke() + + QrShared.binding?.removeRequestPermissionsResultListener(this) + + barcodeView?.pause() + barcodeView = null + } + + override fun getView(): View = initBarCodeView() + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + @Suppress("UNCHECKED_CAST") + when (call.method) { + "startScan" -> startScan(call.arguments as? List, result) + + "stopScan" -> stopScan() + + "flipCamera" -> flipCamera(result) + + "toggleFlash" -> toggleFlash(result) + + "pauseCamera" -> pauseCamera(result) + + // Stopping camera is the same as pausing camera + "stopCamera" -> pauseCamera(result) + + "resumeCamera" -> resumeCamera(result) + + "requestPermissions" -> checkAndRequestPermission() + + "getCameraInfo" -> getCameraInfo(result) + + "getFlashInfo" -> getFlashInfo(result) + + "getSystemFeatures" -> getSystemFeatures(result) + + "changeScanArea" -> changeScanArea( + dpScanAreaWidth = requireNotNull(call.argument("scanAreaWidth")), + dpScanAreaHeight = requireNotNull(call.argument("scanAreaHeight")), + cutOutBottomOffset = requireNotNull(call.argument("cutOutBottomOffset")), + result = result, + ) + + "invertScan" -> setInvertScan( + isInvert = call.argument("isInvertScan") ?: false, + ) + + else -> result.notImplemented() + } + } + + private fun initBarCodeView(): CustomFramingRectBarcodeView { + var barcodeView = barcodeView + + if (barcodeView == null) { + barcodeView = CustomFramingRectBarcodeView(QrShared.activity).also { + this.barcodeView = it + } + + barcodeView.decoderFactory = DefaultDecoderFactory(null, null, null, 2) + + if (params[PARAMS_CAMERA_FACING] as Int == 1) { + barcodeView.cameraSettings?.requestedCameraId = cameraFacingFront + } + } else if (!isPaused) { + barcodeView.resume() + } + + return barcodeView + } + + // region Camera Info + + private fun getCameraInfo(result: MethodChannel.Result) { + val barcodeView = barcodeView ?: return barCodeViewNotSet(result) + + result.success(barcodeView.cameraSettings.requestedCameraId) + } + + private fun getFlashInfo(result: MethodChannel.Result) { + if (barcodeView == null) return barCodeViewNotSet(result) + + result.success(isTorchOn) + } + + private fun hasFlash(): Boolean { + return hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) + } + + @SuppressLint("UnsupportedChromeOsCameraSystemFeature") + private fun hasBackCamera(): Boolean { + return hasSystemFeature(PackageManager.FEATURE_CAMERA) + } + + private fun hasFrontCamera(): Boolean { + return hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT) + } + + private fun hasSystemFeature(feature: String): Boolean = + context.packageManager.hasSystemFeature(feature) + + private fun getSystemFeatures(result: MethodChannel.Result) { + try { + result.success( + mapOf( + "hasFrontCamera" to hasFrontCamera(), + "hasBackCamera" to hasBackCamera(), + "hasFlash" to hasFlash(), + "activeCamera" to barcodeView?.cameraSettings?.requestedCameraId + ) + ) + } catch (e: Exception) { + result.error("", e.message, null) + } + } + + // endregion + + // region Camera Controls + + private fun flipCamera(result: MethodChannel.Result) { + val barcodeView = barcodeView ?: return barCodeViewNotSet(result) + + barcodeView.pause() + + val settings = barcodeView.cameraSettings + if (settings.requestedCameraId == cameraFacingFront) { + settings.requestedCameraId = cameraFacingBack + } else settings.requestedCameraId = cameraFacingFront + + barcodeView.resume() + + result.success(settings.requestedCameraId) + } + + private fun toggleFlash(result: MethodChannel.Result) { + val barcodeView = barcodeView ?: return barCodeViewNotSet(result) + + if (hasFlash()) { + barcodeView.setTorch(!isTorchOn) + isTorchOn = !isTorchOn + result.success(isTorchOn) + } else { + result.error(ERROR_CODE_NOT_SET, ERROR_MESSAGE_FLASH_NOT_FOUND, null) + } + } + + private fun pauseCamera(result: MethodChannel.Result) { + val barcodeView = barcodeView ?: return barCodeViewNotSet(result) + + if (barcodeView.isPreviewActive) { + isPaused = true + barcodeView.pause() + } + + result.success(true) + } + + private fun resumeCamera(result: MethodChannel.Result) { + val barcodeView = barcodeView ?: return barCodeViewNotSet(result) + + if (!barcodeView.isPreviewActive) { + isPaused = false + barcodeView.resume() + } + + result.success(true) + } + + private fun startScan(arguments: List?, result: MethodChannel.Result) { + checkAndRequestPermission() + + val allowedBarcodeTypes = getAllowedBarcodeTypes(arguments, result) + + if (arguments == null) { + barcodeView?.decoderFactory = DefaultDecoderFactory(null, null, null, 2) + } else { + barcodeView?.decoderFactory = DefaultDecoderFactory(allowedBarcodeTypes, null, null, 2) + } + + barcodeView?.decodeContinuous( + object : BarcodeCallback { + override fun barcodeResult(result: BarcodeResult) { + if (allowedBarcodeTypes.isEmpty() || allowedBarcodeTypes.contains(result.barcodeFormat)) { + val code = mapOf( + "code" to result.text, + "type" to result.barcodeFormat.name, + "rawBytes" to result.rawBytes + ) + + channel.invokeMethod(CHANNEL_METHOD_ON_RECOGNIZE_QR, code) + } + } + + override fun possibleResultPoints(resultPoints: List) = Unit + } + ) + } + + private fun stopScan() { + barcodeView?.stopDecoding() + } + + private fun setInvertScan(isInvert: Boolean) { + val barcodeView = barcodeView ?: return + with(barcodeView) { + pause() + cameraSettings.isScanInverted = isInvert + resume() + } + } + + private fun changeScanArea( + dpScanAreaWidth: Double, + dpScanAreaHeight: Double, + cutOutBottomOffset: Double, + result: MethodChannel.Result + ) { + setScanAreaSize(dpScanAreaWidth, dpScanAreaHeight, cutOutBottomOffset) + + result.success(true) + } + + private fun setScanAreaSize( + dpScanAreaWidth: Double, + dpScanAreaHeight: Double, + dpCutOutBottomOffset: Double + ) { + barcodeView?.setFramingRect( + dpScanAreaWidth.convertDpToPixels(), + dpScanAreaHeight.convertDpToPixels(), + dpCutOutBottomOffset.convertDpToPixels(), + ) + } + + // endregion + + // region permissions + + private val hasCameraPermission: Boolean + get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || + ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ): Boolean { + if (requestCode != cameraRequestCode) return false + isRequestingPermission = false + + val permissionGranted = + grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED + + channel.invokeMethod(CHANNEL_METHOD_ON_PERMISSION_SET, permissionGranted) + + return permissionGranted + } + + + + private fun checkAndRequestPermission() { + if (hasCameraPermission) { + channel.invokeMethod(CHANNEL_METHOD_ON_PERMISSION_SET, true) + return + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isRequestingPermission) { + QrShared.activity?.requestPermissions( + arrayOf(Manifest.permission.CAMERA), + cameraRequestCode + ) + } + } + + // endregion + + // region barcode common + + private fun getAllowedBarcodeTypes( + arguments: List?, + result: MethodChannel.Result + ): List { + return try { + arguments?.map { + BarcodeFormat.values()[it] + }.orEmpty() + } catch (e: Exception) { + result.error("", e.message, null) + + emptyList() + } + } + + private fun barCodeViewNotSet(result: MethodChannel.Result) { + result.error( + ERROR_CODE_NOT_SET, + ERROR_MESSAGE_NOT_SET, + null + ) + } + + // endregion + + // region helpers + + private fun Double.convertDpToPixels() = + (this * context.resources.displayMetrics.density).toInt() + + // endregion + + companion object { + private const val CHANNEL_METHOD_ON_PERMISSION_SET = "onPermissionSet" + private const val CHANNEL_METHOD_ON_RECOGNIZE_QR = "onRecognizeQR" + + private const val PARAMS_CAMERA_FACING = "cameraFacing" + + private const val ERROR_CODE_NOT_SET = "404" + + private const val ERROR_MESSAGE_NOT_SET = "No barcode view found" + private const val ERROR_MESSAGE_FLASH_NOT_FOUND = "This device doesn't support flash" + } +} + diff --git a/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt new file mode 100644 index 0000000..013c725 --- /dev/null +++ b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt @@ -0,0 +1,30 @@ +package net.touchcapture.qr.flutterqr + +import android.content.Context +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory + + +class QRViewFactory( + private val messenger: BinaryMessenger +) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { + + override fun create( + context: Context?, + viewId: Int, + args: Any? + ): PlatformView { + + @Suppress("UNCHECKED_CAST") + val params = args as HashMap + + return QRView( + context = requireNotNull(context), + id = viewId, + messenger = messenger, + params = params + ) + } +} \ No newline at end of file diff --git a/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrActivityLifecycleCallbacks.kt b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrActivityLifecycleCallbacks.kt new file mode 100644 index 0000000..e22f711 --- /dev/null +++ b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrActivityLifecycleCallbacks.kt @@ -0,0 +1,41 @@ +package net.touchcapture.qr.flutterqr + +import android.app.Activity +import android.app.Application +import android.os.Bundle + +class UnRegisterLifecycleCallback( + private val application: Application, + private val callback: Application.ActivityLifecycleCallbacks, +) { + operator fun invoke() = application.unregisterActivityLifecycleCallbacks(callback) +} + +fun Activity.registerLifecycleCallbacks( + onPause: (() -> Unit)? = null, + onResume: (() -> Unit)? = null, +): UnRegisterLifecycleCallback { + val callback = object : Application.ActivityLifecycleCallbacks { + override fun onActivityPaused(p0: Activity) { + if (p0 == this@registerLifecycleCallbacks) onPause?.invoke() + } + + override fun onActivityResumed(p0: Activity) { + if (p0 == this@registerLifecycleCallbacks) onResume?.invoke() + } + + override fun onActivityStarted(p0: Activity) = Unit + + override fun onActivityDestroyed(p0: Activity) = Unit + + override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) = Unit + + override fun onActivityStopped(p0: Activity) = Unit + + override fun onActivityCreated(p0: Activity, p1: Bundle?) = Unit + } + + application.registerActivityLifecycleCallbacks(callback) + + return UnRegisterLifecycleCallback(application, callback) +} diff --git a/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrShared.kt b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrShared.kt new file mode 100644 index 0000000..6814640 --- /dev/null +++ b/plugin/qr_code_scanner/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QrShared.kt @@ -0,0 +1,15 @@ +package net.touchcapture.qr.flutterqr + +import android.annotation.SuppressLint +import android.app.Activity +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding + +@SuppressLint("StaticFieldLeak") +object QrShared { + const val CAMERA_REQUEST_ID = 513469796 + + var activity: Activity? = null + + var binding: ActivityPluginBinding? = null + +} \ No newline at end of file diff --git a/plugin/qr_code_scanner/example/.gitignore b/plugin/qr_code_scanner/example/.gitignore new file mode 100644 index 0000000..4736dd6 --- /dev/null +++ b/plugin/qr_code_scanner/example/.gitignore @@ -0,0 +1,72 @@ +# Miscellaneous +*.class +*.lock +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +.last_build_id + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Visual Studio Code related +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/plugin/qr_code_scanner/example/.metadata b/plugin/qr_code_scanner/example/.metadata new file mode 100644 index 0000000..f173f89 --- /dev/null +++ b/plugin/qr_code_scanner/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 58c8489fcdb4e4ef6c010117584c9b23d15221aa + channel: beta + +project_type: app diff --git a/plugin/qr_code_scanner/example/README.md b/plugin/qr_code_scanner/example/README.md new file mode 100644 index 0000000..cfaa9b4 --- /dev/null +++ b/plugin/qr_code_scanner/example/README.md @@ -0,0 +1,192 @@ +# qr_code_scanner + +Demonstrates how to use the qr_code_scanner plugin. + +## iOS Support: +### info.plist +```xml + + io.flutter.embedded_views_preview + + UIBackgroundModes + + fetch + remote-notification + + NSCameraUsageDescription + Can we access your camera in order to scan barcodes? + + +``` + +## Example: +```dart +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; + +void main() => runApp(MaterialApp(home: QRViewExample())); + +class QRViewExample extends StatefulWidget { + const QRViewExample({ + Key key, + }) : super(key: key); + + @override + State createState() => _QRViewExampleState(); +} + +class _QRViewExampleState extends State { + Barcode result; + QRViewController controller; + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + + // In order to get hot reload to work we need to pause the camera if the platform + // is android, or resume the camera if the platform is iOS. + @override + void reassemble() { + super.reassemble(); + if (Platform.isAndroid) { + controller.pauseCamera(); + } + controller.resumeCamera(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + Expanded(flex: 4, child: _buildQrView(context)), + Expanded( + flex: 1, + child: FittedBox( + fit: BoxFit.contain, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (result != null) + Text( + 'Barcode Type: ${describeEnum(result.format)} Data: ${result.code}') + else + Text('Scan a code'), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.all(8), + child: RaisedButton( + onPressed: () async { + await controller?.toggleFlash(); + setState(() {}); + }, + child: FutureBuilder( + future: controller?.getFlashStatus(), + builder: (context, snapshot) { + return Text('Flash: ${snapshot.data}'); + }, + )), + ), + Container( + margin: EdgeInsets.all(8), + child: RaisedButton( + onPressed: () async { + await controller?.flipCamera(); + setState(() {}); + }, + child: FutureBuilder( + future: controller?.getCameraInfo(), + builder: (context, snapshot) { + if (snapshot.data != null) { + return Text( + 'Camera facing ${describeEnum(snapshot.data)}'); + } else { + return Text('loading'); + } + }, + )), + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.all(8), + child: RaisedButton( + onPressed: () async { + await controller?.pauseCamera(); + }, + child: Text('pause', style: TextStyle(fontSize: 20)), + ), + ), + Container( + margin: EdgeInsets.all(8), + child: RaisedButton( + onPressed: () async { + await controller?.resumeCamera(); + }, + child: Text('resume', style: TextStyle(fontSize: 20)), + ), + ) + ], + ), + ], + ), + ), + ) + ], + ), + ); + } + + Widget _buildQrView(BuildContext context) { + // For this example we check how width or tall the device is and change the scanArea and overlay accordingly. + var scanArea = (MediaQuery.of(context).size.width < 400 || + MediaQuery.of(context).size.height < 400) + ? 150.0 + : 300.0; + // To ensure the Scanner view is properly sizes after rotation + // we need to listen for Flutter SizeChanged notification and update controller + return QRView( + key: qrKey, + // You can choose between CameraFacing.front or CameraFacing.back. Defaults to CameraFacing.back + // cameraFacing: CameraFacing.front, + onQRViewCreated: _onQRViewCreated, + // Choose formats you want to scan. Defaults to all formats. + // formatsAllowed: [BarcodeFormat.qrcode], + overlay: QrScannerOverlayShape( + borderColor: Colors.red, + borderRadius: 10, + borderLength: 30, + borderWidth: 10, + cutOutSize: scanArea, + ), + ); + } + + void _onQRViewCreated(QRViewController controller) { + setState(() { + this.controller = controller; + }); + controller.scannedDataStream.listen((scanData) { + setState(() { + result = scanData; + }); + }); + } + + @override + void dispose() { + controller?.dispose(); + super.dispose(); + } +} +``` + + + diff --git a/plugin/qr_code_scanner/example/analysis_options.yaml b/plugin/qr_code_scanner/example/analysis_options.yaml new file mode 100644 index 0000000..a3be6b8 --- /dev/null +++ b/plugin/qr_code_scanner/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml \ No newline at end of file diff --git a/plugin/qr_code_scanner/example/android/app/build.gradle b/plugin/qr_code_scanner/example/android/app/build.gradle new file mode 100644 index 0000000..d4b7c14 --- /dev/null +++ b/plugin/qr_code_scanner/example/android/app/build.gradle @@ -0,0 +1,63 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdk 33 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + applicationId "net.touchcapture.qr.flutterqrexample" + // minSdkVersion is determined by Native View. + minSdkVersion 20 + targetSdkVersion 33 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } + namespace 'net.touchcapture.qr.flutterqrexample' +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/plugin/qr_code_scanner/example/android/app/src/main/AndroidManifest.xml b/plugin/qr_code_scanner/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bc99518 --- /dev/null +++ b/plugin/qr_code_scanner/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + diff --git a/plugin/qr_code_scanner/example/android/app/src/main/res/drawable/launch_background.xml b/plugin/qr_code_scanner/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/plugin/qr_code_scanner/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/plugin/qr_code_scanner/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/plugin/qr_code_scanner/example/android/app/src/main/res/values/styles.xml b/plugin/qr_code_scanner/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..3257023 --- /dev/null +++ b/plugin/qr_code_scanner/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/plugin/qr_code_scanner/example/android/build.gradle b/plugin/qr_code_scanner/example/android/build.gradle new file mode 100644 index 0000000..1dd38bc --- /dev/null +++ b/plugin/qr_code_scanner/example/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + ext.kotlin_version = '1.9.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + +tasks.register('clean', Delete) { + delete rootProject.buildDir +} diff --git a/plugin/qr_code_scanner/example/android/gradle.properties b/plugin/qr_code_scanner/example/android/gradle.properties new file mode 100644 index 0000000..1310f7a --- /dev/null +++ b/plugin/qr_code_scanner/example/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false \ No newline at end of file diff --git a/plugin/qr_code_scanner/example/android/gradle/wrapper/gradle-wrapper.properties b/plugin/qr_code_scanner/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c943057 --- /dev/null +++ b/plugin/qr_code_scanner/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Aug 08 21:31:11 CEST 2023 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/plugin/qr_code_scanner/example/android/settings.gradle b/plugin/qr_code_scanner/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/plugin/qr_code_scanner/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/plugin/qr_code_scanner/example/ios/Flutter/AppFrameworkInfo.plist b/plugin/qr_code_scanner/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9367d48 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/plugin/qr_code_scanner/example/ios/Flutter/Debug.xcconfig b/plugin/qr_code_scanner/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/plugin/qr_code_scanner/example/ios/Flutter/Release.xcconfig b/plugin/qr_code_scanner/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/plugin/qr_code_scanner/example/ios/Podfile b/plugin/qr_code_scanner/example/ios/Podfile new file mode 100644 index 0000000..1e8c3c9 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/plugin/qr_code_scanner/example/ios/Runner.xcodeproj/project.pbxproj b/plugin/qr_code_scanner/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..93f92c8 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,589 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 4FA3805501F593094264678B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75B64521967C76C845268E71 /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 08F42BD28C1585A831B4FDA2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 75B64521967C76C845268E71 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DB8991B7B046D94B2E68CEAD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + EF8A0739582463E296AD6CB1 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4FA3805501F593094264678B /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + D7C58E57CAF69D9A14CC212F /* Pods */, + AF031A634C9742F16BF4F89A /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; + AF031A634C9742F16BF4F89A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 75B64521967C76C845268E71 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D7C58E57CAF69D9A14CC212F /* Pods */ = { + isa = PBXGroup; + children = ( + DB8991B7B046D94B2E68CEAD /* Pods-Runner.debug.xcconfig */, + EF8A0739582463E296AD6CB1 /* Pods-Runner.release.xcconfig */, + 08F42BD28C1585A831B4FDA2 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 7BEA526A4A0DFAAC4945EC09 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 8DB6578C615972DBF2DFAA0D /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = 5LSCTACHT8; + LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 7BEA526A4A0DFAAC4945EC09 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8DB6578C615972DBF2DFAA0D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework", + "${BUILT_PRODUCTS_DIR}/qr_code_scanner/qr_code_scanner.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/qr_code_scanner.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 5LSCTACHT8; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.qr; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 5LSCTACHT8; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.qr; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 5LSCTACHT8; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.qr; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/plugin/qr_code_scanner/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/plugin/qr_code_scanner/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/plugin/qr_code_scanner/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/plugin/qr_code_scanner/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/qr_code_scanner/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/plugin/qr_code_scanner/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/plugin/qr_code_scanner/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/plugin/qr_code_scanner/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/plugin/qr_code_scanner/example/ios/Runner/AppDelegate.swift b/plugin/qr_code_scanner/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..3d43d11 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/plugin/qr_code_scanner/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/plugin/qr_code_scanner/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/qr_code_scanner/example/ios/Runner/Base.lproj/Main.storyboard b/plugin/qr_code_scanner/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/qr_code_scanner/example/ios/Runner/Info.plist b/plugin/qr_code_scanner/example/ios/Runner/Info.plist new file mode 100644 index 0000000..d8eec84 --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner/Info.plist @@ -0,0 +1,54 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_qr_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSCameraUsageDescription + Can we access your camera in order to scan barcodes? + UIBackgroundModes + + fetch + remote-notification + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + io.flutter.embedded_views_preview + + + diff --git a/plugin/qr_code_scanner/example/ios/Runner/Runner-Bridging-Header.h b/plugin/qr_code_scanner/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..7335fdf --- /dev/null +++ b/plugin/qr_code_scanner/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/plugin/qr_code_scanner/example/lib/main.dart b/plugin/qr_code_scanner/example/lib/main.dart new file mode 100644 index 0000000..477b8d3 --- /dev/null +++ b/plugin/qr_code_scanner/example/lib/main.dart @@ -0,0 +1,192 @@ +import 'dart:developer'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; + +void main() => runApp(const MaterialApp(home: MyHome())); + +class MyHome extends StatelessWidget { + const MyHome({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Flutter Demo Home Page')), + body: Center( + child: ElevatedButton( + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const QRViewExample(), + )); + }, + child: const Text('qrView'), + ), + ), + ); + } +} + +class QRViewExample extends StatefulWidget { + const QRViewExample({Key? key}) : super(key: key); + + @override + State createState() => _QRViewExampleState(); +} + +class _QRViewExampleState extends State { + Barcode? result; + QRViewController? controller; + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + + // In order to get hot reload to work we need to pause the camera if the platform + // is android, or resume the camera if the platform is iOS. + @override + void reassemble() { + super.reassemble(); + if (Platform.isAndroid) { + controller!.pauseCamera(); + } + controller!.resumeCamera(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + Expanded(flex: 4, child: _buildQrView(context)), + Expanded( + flex: 1, + child: FittedBox( + fit: BoxFit.contain, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (result != null) + Text( + 'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}') + else + const Text('Scan a code'), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.all(8), + child: ElevatedButton( + onPressed: () async { + await controller?.toggleFlash(); + setState(() {}); + }, + child: FutureBuilder( + future: controller?.getFlashStatus(), + builder: (context, snapshot) { + return Text('Flash: ${snapshot.data}'); + }, + )), + ), + Container( + margin: const EdgeInsets.all(8), + child: ElevatedButton( + onPressed: () async { + await controller?.flipCamera(); + setState(() {}); + }, + child: FutureBuilder( + future: controller?.getCameraInfo(), + builder: (context, snapshot) { + if (snapshot.data != null) { + return Text( + 'Camera facing ${describeEnum(snapshot.data!)}'); + } else { + return const Text('loading'); + } + }, + )), + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.all(8), + child: ElevatedButton( + onPressed: () async { + await controller?.pauseCamera(); + }, + child: const Text('pause', + style: TextStyle(fontSize: 20)), + ), + ), + Container( + margin: const EdgeInsets.all(8), + child: ElevatedButton( + onPressed: () async { + await controller?.resumeCamera(); + }, + child: const Text('resume', + style: TextStyle(fontSize: 20)), + ), + ) + ], + ), + ], + ), + ), + ) + ], + ), + ); + } + + Widget _buildQrView(BuildContext context) { + // For this example we check how width or tall the device is and change the scanArea and overlay accordingly. + var scanArea = (MediaQuery.of(context).size.width < 400 || + MediaQuery.of(context).size.height < 400) + ? 150.0 + : 300.0; + // To ensure the Scanner view is properly sizes after rotation + // we need to listen for Flutter SizeChanged notification and update controller + return QRView( + key: qrKey, + onQRViewCreated: _onQRViewCreated, + overlay: QrScannerOverlayShape( + borderColor: Colors.red, + borderRadius: 10, + borderLength: 30, + borderWidth: 10, + cutOutSize: scanArea), + onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p), + ); + } + + void _onQRViewCreated(QRViewController controller) { + setState(() { + this.controller = controller; + }); + controller.scannedDataStream.listen((scanData) { + setState(() { + result = scanData; + }); + }); + } + + void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) { + log('${DateTime.now().toIso8601String()}_onPermissionSet $p'); + if (!p) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('no Permission')), + ); + } + } + + @override + void dispose() { + controller?.dispose(); + super.dispose(); + } +} diff --git a/plugin/qr_code_scanner/example/pubspec.yaml b/plugin/qr_code_scanner/example/pubspec.yaml new file mode 100644 index 0000000..30a6d72 --- /dev/null +++ b/plugin/qr_code_scanner/example/pubspec.yaml @@ -0,0 +1,60 @@ +name: qr_code_scanner_example +description: Demonstrates how to use the flutter_qr plugin. +publish_to: 'none' + +environment: + sdk: '>=2.12.0 <3.0.0' + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_lints: ^1.0.4 + flutter_test: + sdk: flutter + + qr_code_scanner: + path: ../ + +# For information on the generic Dart part of this file, see the +# following page: https://www.dartlang.org/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.io/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.io/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.io/custom-fonts/#from-packages diff --git a/plugin/qr_code_scanner/example/test/widget_test.dart b/plugin/qr_code_scanner/example/test/widget_test.dart new file mode 100644 index 0000000..eb105c3 --- /dev/null +++ b/plugin/qr_code_scanner/example/test/widget_test.dart @@ -0,0 +1,6 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. diff --git a/plugin/qr_code_scanner/example/web/favicon.png b/plugin/qr_code_scanner/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/plugin/qr_code_scanner/example/web/favicon.png differ diff --git a/plugin/qr_code_scanner/example/web/icons/Icon-192.png b/plugin/qr_code_scanner/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/plugin/qr_code_scanner/example/web/icons/Icon-192.png differ diff --git a/plugin/qr_code_scanner/example/web/icons/Icon-512.png b/plugin/qr_code_scanner/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/plugin/qr_code_scanner/example/web/icons/Icon-512.png differ diff --git a/plugin/qr_code_scanner/example/web/index.html b/plugin/qr_code_scanner/example/web/index.html new file mode 100644 index 0000000..f243f37 --- /dev/null +++ b/plugin/qr_code_scanner/example/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + + + diff --git a/plugin/qr_code_scanner/example/web/manifest.json b/plugin/qr_code_scanner/example/web/manifest.json new file mode 100644 index 0000000..8c01291 --- /dev/null +++ b/plugin/qr_code_scanner/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/plugin/qr_code_scanner/ios/.gitignore b/plugin/qr_code_scanner/ios/.gitignore new file mode 100644 index 0000000..fce96a3 --- /dev/null +++ b/plugin/qr_code_scanner/ios/.gitignore @@ -0,0 +1,78 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ \ No newline at end of file diff --git a/lib/pages/changepassword/change_password_page_controller.dart b/plugin/qr_code_scanner/ios/Assets/.gitkeep similarity index 100% rename from lib/pages/changepassword/change_password_page_controller.dart rename to plugin/qr_code_scanner/ios/Assets/.gitkeep diff --git a/plugin/qr_code_scanner/ios/Classes/FlutterQrPlugin.h b/plugin/qr_code_scanner/ios/Classes/FlutterQrPlugin.h new file mode 100644 index 0000000..c3d077d --- /dev/null +++ b/plugin/qr_code_scanner/ios/Classes/FlutterQrPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface FlutterQrPlugin : NSObject +@end diff --git a/plugin/qr_code_scanner/ios/Classes/FlutterQrPlugin.m b/plugin/qr_code_scanner/ios/Classes/FlutterQrPlugin.m new file mode 100644 index 0000000..d52bedc --- /dev/null +++ b/plugin/qr_code_scanner/ios/Classes/FlutterQrPlugin.m @@ -0,0 +1,13 @@ +#import "FlutterQrPlugin.h" + +#if __has_include() +#import +#else +#import "qr_code_scanner-Swift.h" +#endif + +@implementation FlutterQrPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftFlutterQrPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/plugin/qr_code_scanner/ios/Classes/QRView.swift b/plugin/qr_code_scanner/ios/Classes/QRView.swift new file mode 100644 index 0000000..1af7581 --- /dev/null +++ b/plugin/qr_code_scanner/ios/Classes/QRView.swift @@ -0,0 +1,304 @@ +// +// QRView.swift +// flutter_qr +// +// Created by Julius Canute on 21/12/18. +// + +import Foundation +import MTBBarcodeScanner + +public class QRView:NSObject,FlutterPlatformView { + @IBOutlet var previewView: UIView! + var scanner: MTBBarcodeScanner? + var registrar: FlutterPluginRegistrar + var channel: FlutterMethodChannel + var cameraFacing: MTBCamera + + // Codabar, maxicode, rss14 & rssexpanded not supported. Replaced with qr. + // UPCa uses ean13 object. + var QRCodeTypes = [ + 0: AVMetadataObject.ObjectType.aztec, + 1: AVMetadataObject.ObjectType.qr, + 2: AVMetadataObject.ObjectType.code39, + 3: AVMetadataObject.ObjectType.code93, + 4: AVMetadataObject.ObjectType.code128, + 5: AVMetadataObject.ObjectType.dataMatrix, + 6: AVMetadataObject.ObjectType.ean8, + 7: AVMetadataObject.ObjectType.ean13, + 8: AVMetadataObject.ObjectType.interleaved2of5, + 9: AVMetadataObject.ObjectType.qr, + 10: AVMetadataObject.ObjectType.pdf417, + 11: AVMetadataObject.ObjectType.qr, + 12: AVMetadataObject.ObjectType.qr, + 13: AVMetadataObject.ObjectType.qr, + 14: AVMetadataObject.ObjectType.ean13, + 15: AVMetadataObject.ObjectType.upce + ] + + public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64, params: Dictionary){ + self.registrar = registrar + previewView = UIView(frame: frame) + cameraFacing = MTBCamera.init(rawValue: UInt(Int(params["cameraFacing"] as! Double))) ?? MTBCamera.back + channel = FlutterMethodChannel(name: "net.touchcapture.qr.flutterqr/qrview_\(id)", binaryMessenger: registrar.messenger()) + } + + deinit { + scanner?.stopScanning() + } + + public func view() -> UIView { + channel.setMethodCallHandler({ + [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + switch(call.method){ + case "setDimensions": + let arguments = call.arguments as! Dictionary + self?.setDimensions(result, + width: arguments["width"] ?? 0, + height: arguments["height"] ?? 0, + scanAreaWidth: arguments["scanAreaWidth"] ?? 0, + scanAreaHeight: arguments["scanAreaHeight"] ?? 0, + scanAreaOffset: arguments["scanAreaOffset"] ?? 0) + case "startScan": + self?.startScan(call.arguments as! Array, result) + case "flipCamera": + self?.flipCamera(result) + case "toggleFlash": + self?.toggleFlash(result) + case "pauseCamera": + self?.pauseCamera(result) + case "stopCamera": + self?.stopCamera(result) + case "resumeCamera": + self?.resumeCamera(result) + case "getCameraInfo": + self?.getCameraInfo(result) + case "getFlashInfo": + self?.getFlashInfo(result) + case "getSystemFeatures": + self?.getSystemFeatures(result) + default: + result(FlutterMethodNotImplemented) + return + } + }) + return previewView + } + + func setDimensions(_ result: @escaping FlutterResult, width: Double, height: Double, scanAreaWidth: Double, scanAreaHeight: Double, scanAreaOffset: Double) { + // Then set the size of the preview area. + previewView.frame = CGRect(x: 0, y: 0, width: width, height: height) + + // Then set the size of the scan area. + let midX = self.view().bounds.midX + let midY = self.view().bounds.midY + + if let sc: MTBBarcodeScanner = scanner { + // Set the size of the preview if preview is already created. + if let previewLayer = sc.previewLayer { + previewLayer.frame = self.previewView.bounds + } + } else { + // Create new preview. + scanner = MTBBarcodeScanner(previewView: previewView) + } + + // Set scanArea if provided. + if (scanAreaWidth != 0 && scanAreaHeight != 0) { + scanner?.didStartScanningBlock = { + self.scanner?.scanRect = CGRect(x: Double(midX) - (scanAreaWidth / 2), y: Double(midY) - (scanAreaHeight / 2), width: scanAreaWidth, height: scanAreaHeight) + + // Set offset if provided. + if (scanAreaOffset != 0) { + let reversedOffset = -scanAreaOffset + self.scanner?.scanRect = (self.scanner?.scanRect.offsetBy(dx: 0, dy: CGFloat(reversedOffset)))! + + } + } + } + return result(width) + + } + + func startScan(_ arguments: Array, _ result: @escaping FlutterResult) { + // Check for allowed barcodes + var allowedBarcodeTypes: Array = [] + arguments.forEach { arg in + allowedBarcodeTypes.append( QRCodeTypes[arg]!) + } + MTBBarcodeScanner.requestCameraPermission(success: { [weak self] permissionGranted in + guard let self = self else { return } + + self.channel.invokeMethod("onPermissionSet", arguments: permissionGranted) + + if permissionGranted { + do { + try self.scanner?.startScanning(with: self.cameraFacing, resultBlock: { [weak self] codes in + if let codes = codes { + for code in codes { + var typeString: String; + switch(code.type) { + case AVMetadataObject.ObjectType.aztec: + typeString = "AZTEC" + case AVMetadataObject.ObjectType.code39: + typeString = "CODE_39" + case AVMetadataObject.ObjectType.code93: + typeString = "CODE_93" + case AVMetadataObject.ObjectType.code128: + typeString = "CODE_128" + case AVMetadataObject.ObjectType.dataMatrix: + typeString = "DATA_MATRIX" + case AVMetadataObject.ObjectType.ean8: + typeString = "EAN_8" + case AVMetadataObject.ObjectType.ean13: + typeString = "EAN_13" + case AVMetadataObject.ObjectType.itf14, + AVMetadataObject.ObjectType.interleaved2of5: + typeString = "ITF" + case AVMetadataObject.ObjectType.pdf417: + typeString = "PDF_417" + case AVMetadataObject.ObjectType.qr: + typeString = "QR_CODE" + case AVMetadataObject.ObjectType.upce: + typeString = "UPC_E" + default: + return + } + let bytes = { () -> Data? in + if #available(iOS 11.0, *) { + switch (code.descriptor) { + case let qrDescriptor as CIQRCodeDescriptor: + return qrDescriptor.errorCorrectedPayload + case let aztecDescriptor as CIAztecCodeDescriptor: + return aztecDescriptor.errorCorrectedPayload + case let pdf417Descriptor as CIPDF417CodeDescriptor: + return pdf417Descriptor.errorCorrectedPayload + case let dataMatrixDescriptor as CIDataMatrixCodeDescriptor: + return dataMatrixDescriptor.errorCorrectedPayload + default: + return nil + } + } else { + return nil + } + }() + let result = { () -> [String : Any]? in + guard let stringValue = code.stringValue else { + guard let safeBytes = bytes else { + return nil + } + return ["type": typeString, "rawBytes": safeBytes] + } + guard let safeBytes = bytes else { + return ["code": stringValue, "type": typeString] + } + return ["code": stringValue, "type": typeString, "rawBytes": safeBytes] + }() + guard result != nil else { continue } + if allowedBarcodeTypes.count == 0 || allowedBarcodeTypes.contains(code.type) { + self?.channel.invokeMethod("onRecognizeQR", arguments: result) + } + + } + } + + }) + } catch { + let scanError = FlutterError(code: "unknown-error", message: "Unable to start scanning", details: error) + result(scanError) + } + } + }) + } + + func stopCamera(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = self.scanner { + if sc.isScanning() { + sc.stopScanning() + } + } + } + + func getCameraInfo(_ result: @escaping FlutterResult) { + result(self.cameraFacing.rawValue) + } + + func flipCamera(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = self.scanner { + if sc.hasOppositeCamera() { + sc.flipCamera() + self.cameraFacing = sc.camera + } + return result(sc.camera.rawValue) + } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) + } + + func getFlashInfo(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = self.scanner { + result(sc.torchMode.rawValue != 0) + } else { + let error = FlutterError(code: "cameraInformationError", message: "Could not get flash information", details: nil) + result(error) + } + } + + func toggleFlash(_ result: @escaping FlutterResult){ + if let sc: MTBBarcodeScanner = self.scanner { + if sc.hasTorch() { + sc.toggleTorch() + return result(sc.torchMode == MTBTorchMode(rawValue: 1)) + } + return result(FlutterError(code: "404", message: "This device doesn\'t support flash", details: nil)) + } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) + } + + func pauseCamera(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = self.scanner { + if sc.isScanning() { + sc.freezeCapture() + } + return result(true) + } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) + } + + func resumeCamera(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = self.scanner { + if !sc.isScanning() { + sc.unfreezeCapture() + } + return result(true) + } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) + } + + func getSystemFeatures(_ result: @escaping FlutterResult) { + if let sc: MTBBarcodeScanner = scanner { + var hasBackCameraVar = false + var hasFrontCameraVar = false + let camera = sc.camera + + if(camera == MTBCamera(rawValue: 0)){ + hasBackCameraVar = true + if sc.hasOppositeCamera() { + hasFrontCameraVar = true + } + }else{ + hasFrontCameraVar = true + if sc.hasOppositeCamera() { + hasBackCameraVar = true + } + } + return result([ + "hasFrontCamera": hasFrontCameraVar, + "hasBackCamera": hasBackCameraVar, + "hasFlash": sc.hasTorch(), + "activeCamera": camera.rawValue + ]) + } + return result(FlutterError(code: "404", message: nil, details: nil)) + } + + } diff --git a/plugin/qr_code_scanner/ios/Classes/QRViewFactory.swift b/plugin/qr_code_scanner/ios/Classes/QRViewFactory.swift new file mode 100644 index 0000000..6f851f6 --- /dev/null +++ b/plugin/qr_code_scanner/ios/Classes/QRViewFactory.swift @@ -0,0 +1,27 @@ +// +// QRViewFactory.swift +// flutter_qr +// +// Created by Julius Canute on 21/12/18. +// + +import Foundation + +public class QRViewFactory: NSObject, FlutterPlatformViewFactory { + + var registrar: FlutterPluginRegistrar? + + public init(withRegistrar registrar: FlutterPluginRegistrar){ + super.init() + self.registrar = registrar + } + + public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { + let params = args as! Dictionary + return QRView(withFrame: frame, withRegistrar: registrar!,withId: viewId, params: params) + } + + public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { + return FlutterStandardMessageCodec(readerWriter: FlutterStandardReaderWriter()) + } +} diff --git a/plugin/qr_code_scanner/ios/Classes/SwiftFlutterQrPlugin.swift b/plugin/qr_code_scanner/ios/Classes/SwiftFlutterQrPlugin.swift new file mode 100644 index 0000000..f26aff6 --- /dev/null +++ b/plugin/qr_code_scanner/ios/Classes/SwiftFlutterQrPlugin.swift @@ -0,0 +1,22 @@ +import Flutter +import UIKit + +public class SwiftFlutterQrPlugin: NSObject, FlutterPlugin { + + var factory: QRViewFactory + public init(with registrar: FlutterPluginRegistrar) { + self.factory = QRViewFactory(withRegistrar: registrar) + registrar.register(factory, withId: "net.touchcapture.qr.flutterqr/qrview") + } + + public static func register(with registrar: FlutterPluginRegistrar) { + registrar.addApplicationDelegate(SwiftFlutterQrPlugin(with: registrar)) + } + + public func applicationDidEnterBackground(_ application: UIApplication) { + } + + public func applicationWillTerminate(_ application: UIApplication) { + } + +} diff --git a/plugin/qr_code_scanner/ios/qr_code_scanner.podspec b/plugin/qr_code_scanner/ios/qr_code_scanner.podspec new file mode 100644 index 0000000..f94e7cc --- /dev/null +++ b/plugin/qr_code_scanner/ios/qr_code_scanner.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'qr_code_scanner' + s.version = '0.2.0' + s.summary = 'QR Code Scanner for flutter.' + s.description = <<-DESC +A new Flutter project. + DESC + s.homepage = 'https://github.com/juliuscanute/qr_code_scanner' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'juliuscanute[*]touchcapture.net' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + s.dependency 'MTBBarcodeScanner' + s.ios.deployment_target = '8.0' + s.swift_version = '4.0' +end + diff --git a/plugin/qr_code_scanner/lib/qr_code_scanner.dart b/plugin/qr_code_scanner/lib/qr_code_scanner.dart new file mode 100644 index 0000000..4220613 --- /dev/null +++ b/plugin/qr_code_scanner/lib/qr_code_scanner.dart @@ -0,0 +1,7 @@ +export 'src/qr_code_scanner.dart'; +export 'src/qr_scanner_overlay_shape.dart'; +export 'src/types/barcode.dart'; +export 'src/types/barcode_format.dart'; +export 'src/types/camera.dart'; +export 'src/types/camera_exception.dart'; +export 'src/types/features.dart'; diff --git a/plugin/qr_code_scanner/lib/src/lifecycle_event_handler.dart b/plugin/qr_code_scanner/lib/src/lifecycle_event_handler.dart new file mode 100644 index 0000000..0580d17 --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/lifecycle_event_handler.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; + +class LifecycleEventHandler extends WidgetsBindingObserver { + LifecycleEventHandler({ + required this.resumeCallBack, + }); + + late final AsyncCallback resumeCallBack; + + @override + Future didChangeAppLifecycleState(AppLifecycleState state) async { + switch (state) { + case AppLifecycleState.resumed: + await resumeCallBack(); + break; + case AppLifecycleState.inactive: + case AppLifecycleState.paused: + case AppLifecycleState.detached: + } + } +} diff --git a/plugin/qr_code_scanner/lib/src/qr_code_scanner.dart b/plugin/qr_code_scanner/lib/src/qr_code_scanner.dart new file mode 100644 index 0000000..0a1deb3 --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/qr_code_scanner.dart @@ -0,0 +1,373 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'lifecycle_event_handler.dart'; +import 'qr_scanner_overlay_shape.dart'; +import 'types/barcode.dart'; +import 'types/barcode_format.dart'; +import 'types/camera.dart'; +import 'types/camera_exception.dart'; +import 'types/features.dart'; +import 'web/flutter_qr_stub.dart' +// ignore: uri_does_not_exist + if (dart.library.html) 'web/flutter_qr_web.dart'; + +typedef QRViewCreatedCallback = void Function(QRViewController); +typedef PermissionSetCallback = void Function(QRViewController, bool); + +/// The [QRView] is the view where the camera +/// and the barcode scanner gets displayed. +class QRView extends StatefulWidget { + const QRView({ + required Key key, + required this.onQRViewCreated, + this.overlay, + this.overlayMargin = EdgeInsets.zero, + this.cameraFacing = CameraFacing.back, + this.onPermissionSet, + this.formatsAllowed = const [], + }) : super(key: key); + + /// [onQRViewCreated] gets called when the view is created + final QRViewCreatedCallback onQRViewCreated; + + /// Use [overlay] to provide an overlay for the view. + /// This can be used to create a certain scan area. + final QrScannerOverlayShape? overlay; + + /// Use [overlayMargin] to provide a margin to [overlay] + final EdgeInsetsGeometry overlayMargin; + + /// Set which camera to use on startup. + /// + /// [cameraFacing] can either be CameraFacing.front or CameraFacing.back. + /// Defaults to CameraFacing.back + final CameraFacing cameraFacing; + + /// Calls the provided [onPermissionSet] callback when the permission is set. + final PermissionSetCallback? onPermissionSet; + + /// Use [formatsAllowed] to specify which formats needs to be scanned. + final List formatsAllowed; + + @override + State createState() => _QRViewState(); +} + +class _QRViewState extends State { + late MethodChannel _channel; + late LifecycleEventHandler _observer; + + @override + void initState() { + super.initState(); + _observer = LifecycleEventHandler(resumeCallBack: updateDimensions); + WidgetsBinding.instance.addObserver(_observer); + } + + @override + Widget build(BuildContext context) { + return NotificationListener( + onNotification: onNotification, + child: SizeChangedLayoutNotifier( + child: (widget.overlay != null) + ? _getPlatformQrViewWithOverlay() + : _getPlatformQrView(), + ), + ); + } + + @override + void dispose() { + super.dispose(); + WidgetsBinding.instance.removeObserver(_observer); + } + + Future updateDimensions() async { + await QRViewController.updateDimensions( + widget.key as GlobalKey>, _channel, + overlay: widget.overlay); + } + + bool onNotification(notification) { + updateDimensions(); + return false; + } + + Widget _getPlatformQrViewWithOverlay() { + return Stack( + children: [ + _getPlatformQrView(), + Padding( + padding: widget.overlayMargin, + child: Container( + decoration: ShapeDecoration( + shape: widget.overlay!, + ), + ), + ) + ], + ); + } + + Widget _getPlatformQrView() { + Widget _platformQrView; + if (kIsWeb) { + _platformQrView = createWebQrView( + onPlatformViewCreated: widget.onQRViewCreated, + onPermissionSet: widget.onPermissionSet, + cameraFacing: widget.cameraFacing, + ); + } else { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + _platformQrView = AndroidView( + viewType: 'net.touchcapture.qr.flutterqr/qrview', + onPlatformViewCreated: _onPlatformViewCreated, + creationParams: + _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(), + creationParamsCodec: const StandardMessageCodec(), + ); + break; + case TargetPlatform.iOS: + _platformQrView = UiKitView( + viewType: 'net.touchcapture.qr.flutterqr/qrview', + onPlatformViewCreated: _onPlatformViewCreated, + creationParams: + _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(), + creationParamsCodec: const StandardMessageCodec(), + ); + break; + default: + throw UnsupportedError( + "Trying to use the default qrview implementation for $defaultTargetPlatform but there isn't a default one"); + } + } + return _platformQrView; + } + + void _onPlatformViewCreated(int id) { + _channel = MethodChannel('net.touchcapture.qr.flutterqr/qrview_$id'); + + // Start scan after creation of the view + final controller = QRViewController._( + _channel, + widget.key as GlobalKey>?, + widget.onPermissionSet, + widget.cameraFacing) + .._startScan(widget.key as GlobalKey>, + widget.overlay, widget.formatsAllowed); + + // Initialize the controller for controlling the QRView + widget.onQRViewCreated(controller); + } +} + +class _QrCameraSettings { + _QrCameraSettings({ + this.cameraFacing = CameraFacing.unknown, + }); + + final CameraFacing cameraFacing; + + Map toMap() { + return { + 'cameraFacing': cameraFacing.index, + }; + } +} + +class QRViewController { + QRViewController._(MethodChannel channel, GlobalKey? qrKey, + PermissionSetCallback? onPermissionSet, CameraFacing cameraFacing) + : _channel = channel, + _cameraFacing = cameraFacing { + _channel.setMethodCallHandler((call) async { + switch (call.method) { + case 'onRecognizeQR': + if (call.arguments != null) { + final args = call.arguments as Map; + final code = args['code'] as String?; + final rawType = args['type'] as String; + // Raw bytes are only supported by Android. + final rawBytes = args['rawBytes'] as List?; + final format = BarcodeTypesExtension.fromString(rawType); + if (format != BarcodeFormat.unknown) { + final barcode = Barcode(code, format, rawBytes); + _scanUpdateController.sink.add(barcode); + } else { + throw Exception('Unexpected barcode type $rawType'); + } + } + break; + case 'onPermissionSet': + if (call.arguments != null && call.arguments is bool) { + _hasPermissions = call.arguments; + if (onPermissionSet != null) { + onPermissionSet(this, _hasPermissions); + } + } + break; + } + }); + } + + final MethodChannel _channel; + final CameraFacing _cameraFacing; + final StreamController _scanUpdateController = + StreamController(); + + Stream get scannedDataStream => _scanUpdateController.stream; + + bool _hasPermissions = false; + bool get hasPermissions => _hasPermissions; + + /// Starts the barcode scanner + Future _startScan(GlobalKey key, QrScannerOverlayShape? overlay, + List? barcodeFormats) async { + // We need to update the dimension before the scan is started. + try { + await QRViewController.updateDimensions(key, _channel, overlay: overlay); + return await _channel.invokeMethod( + 'startScan', barcodeFormats?.map((e) => e.asInt()).toList() ?? []); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets information about which camera is active. + Future getCameraInfo() async { + try { + var cameraFacing = await _channel.invokeMethod('getCameraInfo') as int; + if (cameraFacing == -1) return _cameraFacing; + return CameraFacing + .values[await _channel.invokeMethod('getCameraInfo') as int]; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Flips the camera between available modes + Future flipCamera() async { + try { + return CameraFacing + .values[await _channel.invokeMethod('flipCamera') as int]; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Get flashlight status + Future getFlashStatus() async { + try { + return await _channel.invokeMethod('getFlashInfo'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Toggles the flashlight between available modes + Future toggleFlash() async { + try { + await _channel.invokeMethod('toggleFlash') as bool?; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Pauses the camera and barcode scanning + Future pauseCamera() async { + try { + await _channel.invokeMethod('pauseCamera'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Stops barcode scanning and the camera + Future stopCamera() async { + try { + await _channel.invokeMethod('stopCamera'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Resumes barcode scanning + Future resumeCamera() async { + try { + await _channel.invokeMethod('resumeCamera'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Returns which features are available on device. + Future getSystemFeatures() async { + try { + var features = + await _channel.invokeMapMethod('getSystemFeatures'); + if (features != null) { + return SystemFeatures.fromJson(features); + } + throw CameraException('Error', 'Could not get system features'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Stops the camera and disposes the barcode stream. + void dispose() { + if (defaultTargetPlatform == TargetPlatform.iOS) stopCamera(); + _scanUpdateController.close(); + } + + /// Updates the view dimensions for iOS. + static Future updateDimensions(GlobalKey key, MethodChannel channel, + {QrScannerOverlayShape? overlay}) async { + if (defaultTargetPlatform == TargetPlatform.iOS) { + // Add small delay to ensure the render box is loaded + await Future.delayed(const Duration(milliseconds: 300)); + if (key.currentContext == null) return false; + final renderBox = key.currentContext!.findRenderObject() as RenderBox; + try { + await channel.invokeMethod('setDimensions', { + 'width': renderBox.size.width, + 'height': renderBox.size.height, + 'scanAreaWidth': overlay?.cutOutWidth ?? 0, + 'scanAreaHeight': overlay?.cutOutHeight ?? 0, + 'scanAreaOffset': overlay?.cutOutBottomOffset ?? 0 + }); + return true; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } else if (defaultTargetPlatform == TargetPlatform.android) { + if (overlay == null) { + return false; + } + await channel.invokeMethod('changeScanArea', { + 'scanAreaWidth': overlay.cutOutWidth, + 'scanAreaHeight': overlay.cutOutHeight, + 'cutOutBottomOffset': overlay.cutOutBottomOffset + }); + return true; + } + return false; + } + + //Starts/Stops invert scanning. + Future scanInvert(bool isScanInvert) async { + if (defaultTargetPlatform == TargetPlatform.android) { + try { + await _channel + .invokeMethod('invertScan', {"isInvertScan": isScanInvert}); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + } +} diff --git a/plugin/qr_code_scanner/lib/src/qr_scanner_overlay_shape.dart b/plugin/qr_code_scanner/lib/src/qr_scanner_overlay_shape.dart new file mode 100644 index 0000000..83fc5ef --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/qr_scanner_overlay_shape.dart @@ -0,0 +1,183 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class QrScannerOverlayShape extends ShapeBorder { + QrScannerOverlayShape({ + this.borderColor = Colors.red, + this.borderWidth = 3.0, + this.overlayColor = const Color.fromRGBO(0, 0, 0, 80), + this.borderRadius = 0, + this.borderLength = 40, + double? cutOutSize, + double? cutOutWidth, + double? cutOutHeight, + this.cutOutBottomOffset = 0, + }) : cutOutWidth = cutOutWidth ?? cutOutSize ?? 250, + cutOutHeight = cutOutHeight ?? cutOutSize ?? 250 { + assert( + borderLength <= + min(this.cutOutWidth, this.cutOutHeight) / 2 + borderWidth * 2, + "Border can't be larger than ${min(this.cutOutWidth, this.cutOutHeight) / 2 + borderWidth * 2}", + ); + assert( + (cutOutWidth == null && cutOutHeight == null) || + (cutOutSize == null && cutOutWidth != null && cutOutHeight != null), + 'Use only cutOutWidth and cutOutHeight or only cutOutSize'); + } + + final Color borderColor; + final double borderWidth; + final Color overlayColor; + final double borderRadius; + final double borderLength; + final double cutOutWidth; + final double cutOutHeight; + final double cutOutBottomOffset; + + @override + EdgeInsetsGeometry get dimensions => const EdgeInsets.all(10); + + @override + Path getInnerPath(Rect rect, {TextDirection? textDirection}) { + return Path() + ..fillType = PathFillType.evenOdd + ..addPath(getOuterPath(rect), Offset.zero); + } + + @override + Path getOuterPath(Rect rect, {TextDirection? textDirection}) { + Path _getLeftTopPath(Rect rect) { + return Path() + ..moveTo(rect.left, rect.bottom) + ..lineTo(rect.left, rect.top) + ..lineTo(rect.right, rect.top); + } + + return _getLeftTopPath(rect) + ..lineTo( + rect.right, + rect.bottom, + ) + ..lineTo( + rect.left, + rect.bottom, + ) + ..lineTo( + rect.left, + rect.top, + ); + } + + @override + void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { + final width = rect.width; + final borderWidthSize = width / 2; + final height = rect.height; + final borderOffset = borderWidth / 2; + final _borderLength = + borderLength > min(cutOutHeight, cutOutHeight) / 2 + borderWidth * 2 + ? borderWidthSize / 2 + : borderLength; + final _cutOutWidth = + cutOutWidth < width ? cutOutWidth : width - borderOffset; + final _cutOutHeight = + cutOutHeight < height ? cutOutHeight : height - borderOffset; + + final backgroundPaint = Paint() + ..color = overlayColor + ..style = PaintingStyle.fill; + + final borderPaint = Paint() + ..color = borderColor + ..style = PaintingStyle.stroke + ..strokeWidth = borderWidth; + + final boxPaint = Paint() + ..color = borderColor + ..style = PaintingStyle.fill + ..blendMode = BlendMode.dstOut; + + final cutOutRect = Rect.fromLTWH( + rect.left + width / 2 - _cutOutWidth / 2 + borderOffset, + -cutOutBottomOffset + + rect.top + + height / 2 - + _cutOutHeight / 2 + + borderOffset, + _cutOutWidth - borderOffset * 2, + _cutOutHeight - borderOffset * 2, + ); + + canvas + ..saveLayer( + rect, + backgroundPaint, + ) + ..drawRect( + rect, + backgroundPaint, + ) + // Draw top right corner + ..drawRRect( + RRect.fromLTRBAndCorners( + cutOutRect.right - _borderLength, + cutOutRect.top, + cutOutRect.right, + cutOutRect.top + _borderLength, + topRight: Radius.circular(borderRadius), + ), + borderPaint, + ) + // Draw top left corner + ..drawRRect( + RRect.fromLTRBAndCorners( + cutOutRect.left, + cutOutRect.top, + cutOutRect.left + _borderLength, + cutOutRect.top + _borderLength, + topLeft: Radius.circular(borderRadius), + ), + borderPaint, + ) + // Draw bottom right corner + ..drawRRect( + RRect.fromLTRBAndCorners( + cutOutRect.right - _borderLength, + cutOutRect.bottom - _borderLength, + cutOutRect.right, + cutOutRect.bottom, + bottomRight: Radius.circular(borderRadius), + ), + borderPaint, + ) + // Draw bottom left corner + ..drawRRect( + RRect.fromLTRBAndCorners( + cutOutRect.left, + cutOutRect.bottom - _borderLength, + cutOutRect.left + _borderLength, + cutOutRect.bottom, + bottomLeft: Radius.circular(borderRadius), + ), + borderPaint, + ) + ..drawRRect( + RRect.fromRectAndRadius( + cutOutRect, + Radius.circular(borderRadius), + ), + boxPaint, + ) + ..restore(); + } + + @override + ShapeBorder scale(double t) { + return QrScannerOverlayShape( + borderColor: borderColor, + borderWidth: borderWidth, + overlayColor: overlayColor, + ); + } +} diff --git a/plugin/qr_code_scanner/lib/src/types/barcode.dart b/plugin/qr_code_scanner/lib/src/types/barcode.dart new file mode 100644 index 0000000..c8c3105 --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/types/barcode.dart @@ -0,0 +1,16 @@ +import 'barcode_format.dart'; + +/// The [Barcode] object holds information about the barcode or qr code. +/// +/// [code] is the string-content of the barcode. +/// [format] displays which type the code is. +/// Only for Android and iOS, [rawBytes] gives a list of bytes of the result. +class Barcode { + Barcode(this.code, this.format, this.rawBytes); + + final String? code; + final BarcodeFormat format; + + /// Raw bytes are only supported by Android and iOS. + final List? rawBytes; +} diff --git a/plugin/qr_code_scanner/lib/src/types/barcode_format.dart b/plugin/qr_code_scanner/lib/src/types/barcode_format.dart new file mode 100644 index 0000000..49bcee2 --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/types/barcode_format.dart @@ -0,0 +1,148 @@ +enum BarcodeFormat { + /// Aztec 2D barcode format. + aztec, + + /// CODABAR 1D format. + /// Not supported in iOS + codabar, + + /// Code 39 1D format. + code39, + + /// Code 93 1D format. + code93, + + /// Code 128 1D format. + code128, + + /// Data Matrix 2D barcode format. + dataMatrix, + + /// EAN-8 1D format. + ean8, + + /// EAN-13 1D format. + ean13, + + /// ITF (Interleaved Two of Five) 1D format. + itf, + + /// MaxiCode 2D barcode format. + /// Not supported in iOS. + maxicode, + + /// PDF417 format. + pdf417, + + /// QR Code 2D barcode format. + qrcode, + + /// RSS 14 + /// Not supported in iOS. + rss14, + + /// RSS EXPANDED + /// Not supported in iOS. + rssExpanded, + + /// UPC-A 1D format. + /// Same as ean-13 on iOS. + upcA, + + /// UPC-E 1D format. + upcE, + + /// UPC/EAN extension format. Not a stand-alone format. + upcEanExtension, + + /// Unknown + unknown +} + +extension BarcodeTypesExtension on BarcodeFormat { + int asInt() { + return index; + } + + static BarcodeFormat fromString(String format) { + switch (format) { + case 'AZTEC': + return BarcodeFormat.aztec; + case 'CODABAR': + return BarcodeFormat.codabar; + case 'CODE_39': + return BarcodeFormat.code39; + case 'CODE_93': + return BarcodeFormat.code93; + case 'CODE_128': + return BarcodeFormat.code128; + case 'DATA_MATRIX': + return BarcodeFormat.dataMatrix; + case 'EAN_8': + return BarcodeFormat.ean8; + case 'EAN_13': + return BarcodeFormat.ean13; + case 'ITF': + return BarcodeFormat.itf; + case 'MAXICODE': + return BarcodeFormat.maxicode; + case 'PDF_417': + return BarcodeFormat.pdf417; + case 'QR_CODE': + return BarcodeFormat.qrcode; + case 'RSS14': + return BarcodeFormat.rss14; + case 'RSS_EXPANDED': + return BarcodeFormat.rssExpanded; + case 'UPC_A': + return BarcodeFormat.upcA; + case 'UPC_E': + return BarcodeFormat.upcE; + case 'UPC_EAN_EXTENSION': + return BarcodeFormat.upcEanExtension; + default: + return BarcodeFormat.unknown; + } + } + + String get formatName { + switch (this) { + case BarcodeFormat.aztec: + return 'AZTEC'; + case BarcodeFormat.codabar: + return 'CODABAR'; + case BarcodeFormat.code39: + return 'CODE_39'; + case BarcodeFormat.code93: + return 'CODE_93'; + case BarcodeFormat.code128: + return 'CODE_128'; + case BarcodeFormat.dataMatrix: + return 'DATA_MATRIX'; + case BarcodeFormat.ean8: + return 'EAN_8'; + case BarcodeFormat.ean13: + return 'EAN_13'; + case BarcodeFormat.itf: + return 'ITF'; + case BarcodeFormat.maxicode: + return 'MAXICODE'; + case BarcodeFormat.pdf417: + return 'PDF_417'; + case BarcodeFormat.qrcode: + return 'QR_CODE'; + case BarcodeFormat.rss14: + return 'RSS14'; + case BarcodeFormat.rssExpanded: + return 'RSS_EXPANDED'; + case BarcodeFormat.upcA: + return 'UPC_A'; + case BarcodeFormat.upcE: + return 'UPC_E'; + case BarcodeFormat.upcEanExtension: + return 'UPC_EAN_EXTENSION'; + default: + return 'UNKNOWN'; + } + } +} diff --git a/plugin/qr_code_scanner/lib/src/types/camera.dart b/plugin/qr_code_scanner/lib/src/types/camera.dart new file mode 100644 index 0000000..67fe8cb --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/types/camera.dart @@ -0,0 +1,10 @@ +enum CameraFacing { + /// Shows back facing camera. + back, + + /// Shows front facing camera. + front, + + /// Unknown camera + unknown +} diff --git a/plugin/qr_code_scanner/lib/src/types/camera_exception.dart b/plugin/qr_code_scanner/lib/src/types/camera_exception.dart new file mode 100644 index 0000000..0abb639 --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/types/camera_exception.dart @@ -0,0 +1,14 @@ +/// This is thrown when the plugin reports an error. +class CameraException implements Exception { + /// Creates a new camera exception with the given error code and description. + CameraException(this.code, this.description); + + /// Error code. + String code; + + /// Textual description of the error. + String? description; + + @override + String toString() => 'CameraException($code, $description)'; +} diff --git a/plugin/qr_code_scanner/lib/src/types/features.dart b/plugin/qr_code_scanner/lib/src/types/features.dart new file mode 100644 index 0000000..0c3618a --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/types/features.dart @@ -0,0 +1,13 @@ +class SystemFeatures { + SystemFeatures(this.hasFlash, this.hasBackCamera, this.hasFrontCamera); + + factory SystemFeatures.fromJson(Map features) => + SystemFeatures( + features['hasFlash'] ?? false, + features['hasBackCamera'] ?? false, + features['hasFrontCamera'] ?? false); + + final bool hasFlash; + final bool hasFrontCamera; + final bool hasBackCamera; +} diff --git a/plugin/qr_code_scanner/lib/src/web/flutter_qr_stub.dart b/plugin/qr_code_scanner/lib/src/web/flutter_qr_stub.dart new file mode 100644 index 0000000..a6bc04d --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/web/flutter_qr_stub.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:qr_code_scanner/src/types/camera.dart'; + +Widget createWebQrView( + {onPlatformViewCreated, onPermissionSet, CameraFacing? cameraFacing}) => + const SizedBox(); diff --git a/plugin/qr_code_scanner/lib/src/web/flutter_qr_web.dart b/plugin/qr_code_scanner/lib/src/web/flutter_qr_web.dart new file mode 100644 index 0000000..de368ac --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/web/flutter_qr_web.dart @@ -0,0 +1,334 @@ +// ignore_for_file: avoid_web_libraries_in_flutter + +import 'dart:async'; +import 'dart:core'; +import 'dart:html' as html; +import 'dart:js_util'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; + +import '../../qr_code_scanner.dart'; +import 'jsqr.dart'; +import 'media.dart'; + +/// Even though it has been highly modified, the origial implementation has been +/// adopted from https://github.com:treeder/jsqr_flutter +/// +/// Copyright 2020 @treeder +/// Copyright 2021 The one with the braid + +class WebQrView extends StatefulWidget { + final QRViewCreatedCallback onPlatformViewCreated; + final PermissionSetCallback? onPermissionSet; + final CameraFacing? cameraFacing; + + const WebQrView( + {Key? key, + required this.onPlatformViewCreated, + this.onPermissionSet, + this.cameraFacing = CameraFacing.front}) + : super(key: key); + + @override + _WebQrViewState createState() => _WebQrViewState(); + + static html.DivElement vidDiv = + html.DivElement(); // need a global for the registerViewFactory + + static Future cameraAvailable() async { + final sources = + await html.window.navigator.mediaDevices!.enumerateDevices(); + // List vidIds = []; + var hasCam = false; + for (final e in sources) { + if (e.kind == 'videoinput') { + // vidIds.add(e['deviceId']); + hasCam = true; + } + } + return hasCam; + } +} + +class _WebQrViewState extends State { + html.MediaStream? _localStream; + // html.CanvasElement canvas; + // html.CanvasRenderingContext2D ctx; + bool _currentlyProcessing = false; + + QRViewControllerWeb? _controller; + + late Size _size = const Size(0, 0); + Timer? timer; + String? code; + String? _errorMsg; + html.VideoElement video = html.VideoElement(); + String viewID = 'QRVIEW-' + DateTime.now().millisecondsSinceEpoch.toString(); + + final StreamController _scanUpdateController = + StreamController(); + late CameraFacing facing; + + Timer? _frameIntervall; + + @override + void initState() { + super.initState(); + + facing = widget.cameraFacing ?? CameraFacing.front; + + // video = html.VideoElement(); + WebQrView.vidDiv.children = [video]; + // ignore: UNDEFINED_PREFIXED_NAME + ui.platformViewRegistry + .registerViewFactory(viewID, (int id) => WebQrView.vidDiv); + // giving JavaScipt some time to process the DOM changes + Timer(const Duration(milliseconds: 500), () { + start(); + }); + } + + Future start() async { + await _makeCall(); + _frameIntervall?.cancel(); + _frameIntervall = + Timer.periodic(const Duration(milliseconds: 200), (timer) { + _captureFrame2(); + }); + } + + void cancel() { + if (timer != null) { + timer!.cancel(); + timer = null; + } + if (_currentlyProcessing) { + _stopStream(); + } + } + + @override + void dispose() { + cancel(); + super.dispose(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future _makeCall() async { + if (_localStream != null) { + return; + } + + try { + var constraints = UserMediaOptions( + video: VideoOptions( + facingMode: (facing == CameraFacing.front ? 'user' : 'environment'), + )); + // dart style, not working properly: + // var stream = + // await html.window.navigator.mediaDevices.getUserMedia(constraints); + // straight JS: + if (_controller == null) { + _controller = QRViewControllerWeb(this); + widget.onPlatformViewCreated(_controller!); + } + var stream = await promiseToFuture(getUserMedia(constraints)); + widget.onPermissionSet?.call(_controller!, true); + _localStream = stream; + video.srcObject = _localStream; + video.setAttribute('playsinline', + 'true'); // required to tell iOS safari we don't want fullscreen + await video.play(); + } catch (e) { + cancel(); + if (e.toString().contains("NotAllowedError")) { + widget.onPermissionSet?.call(_controller!, false); + } + setState(() { + _errorMsg = e.toString(); + }); + return; + } + if (!mounted) return; + + setState(() { + _currentlyProcessing = true; + }); + } + + Future _stopStream() async { + try { + // await _localStream.dispose(); + _localStream!.getTracks().forEach((track) { + if (track.readyState == 'live') { + track.stop(); + } + }); + // video.stop(); + video.srcObject = null; + _localStream = null; + // _localRenderer.srcObject = null; + // ignore: empty_catches + } catch (e) {} + } + + Future _captureFrame2() async { + if (_localStream == null) { + return null; + } + final canvas = + html.CanvasElement(width: video.videoWidth, height: video.videoHeight); + final ctx = canvas.context2D; + // canvas.width = video.videoWidth; + // canvas.height = video.videoHeight; + ctx.drawImage(video, 0, 0); + final imgData = ctx.getImageData(0, 0, canvas.width!, canvas.height!); + + final size = + Size(canvas.width?.toDouble() ?? 0, canvas.height?.toDouble() ?? 0); + if (size != _size) { + setState(() { + _setCanvasSize(size); + }); + } + + try { + final code = jsQR(imgData.data, canvas.width, canvas.height); + // ignore: unnecessary_null_comparison + if (code != null && code.data != null) { + _scanUpdateController + .add(Barcode(code.data, BarcodeFormat.qrcode, code.data.codeUnits)); + } + } on NoSuchMethodError { + // Do nothing, this exception occurs continously in web release when no + // code is found. + // NoSuchMethodError: method not found: 'get$data' on null + } + } + + @override + Widget build(BuildContext context) { + if (_errorMsg != null) { + return Center(child: Text(_errorMsg!)); + } + if (_localStream == null) { + return const Center(child: CircularProgressIndicator()); + } + return LayoutBuilder( + builder: (context, constraints) { + var zoom = 1.0; + + if (_size.height != 0) zoom = constraints.maxHeight / _size.height; + + if (_size.width != 0) { + final horizontalZoom = constraints.maxWidth / _size.width; + if (horizontalZoom > zoom) { + zoom = horizontalZoom; + } + } + + return SizedBox( + width: constraints.maxWidth, + height: constraints.maxHeight, + child: Center( + child: SizedBox.fromSize( + size: _size, + child: Transform.scale( + alignment: Alignment.center, + scale: zoom, + child: HtmlElementView(viewType: viewID), + ), + ), + ), + ); + }, + ); + } + + void _setCanvasSize(ui.Size size) { + setState(() { + _size = size; + }); + } +} + +class QRViewControllerWeb implements QRViewController { + final _WebQrViewState _state; + + QRViewControllerWeb(this._state); + @override + void dispose() => _state.cancel(); + + @override + Future flipCamera() async { + // TODO: improve error handling + _state.facing = _state.facing == CameraFacing.front + ? CameraFacing.back + : CameraFacing.front; + await _state.start(); + return _state.facing; + } + + @override + Future getCameraInfo() async { + return _state.facing; + } + + @override + Future getFlashStatus() async { + // TODO: flash is simply not supported by JavaScipt. To avoid issuing applications, we always return it to be off. + return false; + } + + @override + Future getSystemFeatures() { + // TODO: implement getSystemFeatures + throw UnimplementedError(); + } + + @override + // TODO: implement hasPermissions. Blocking: WebQrView.cameraAvailable() returns a Future whereas a bool is required + bool get hasPermissions => throw UnimplementedError(); + + @override + Future pauseCamera() { + // TODO: implement pauseCamera + throw UnimplementedError(); + } + + @override + Future resumeCamera() { + // TODO: implement resumeCamera + throw UnimplementedError(); + } + + @override + Stream get scannedDataStream => _state._scanUpdateController.stream; + + @override + Future stopCamera() { + // TODO: implement stopCamera + throw UnimplementedError(); + } + + @override + Future toggleFlash() async { + // TODO: flash is simply not supported by JavaScipt + return; + } + + @override + Future scanInvert(bool isScanInvert) { + // TODO: implement scanInvert + throw UnimplementedError(); + } +} + +Widget createWebQrView( + {onPlatformViewCreated, onPermissionSet, CameraFacing? cameraFacing}) => + WebQrView( + onPlatformViewCreated: onPlatformViewCreated, + onPermissionSet: onPermissionSet, + cameraFacing: cameraFacing, + ); diff --git a/plugin/qr_code_scanner/lib/src/web/jsqr.dart b/plugin/qr_code_scanner/lib/src/web/jsqr.dart new file mode 100644 index 0000000..386936c --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/web/jsqr.dart @@ -0,0 +1,12 @@ +@JS() +library jsqr; + +import 'package:js/js.dart'; + +@JS('jsQR') +external Code jsQR(var data, int? width, int? height); + +@JS() +class Code { + external String get data; +} diff --git a/plugin/qr_code_scanner/lib/src/web/media.dart b/plugin/qr_code_scanner/lib/src/web/media.dart new file mode 100644 index 0000000..cacd527 --- /dev/null +++ b/plugin/qr_code_scanner/lib/src/web/media.dart @@ -0,0 +1,36 @@ +// This is here because dart doesn't seem to support this properly +// https://stackoverflow.com/questions/61161135/adding-support-for-navigator-mediadevices-getusermedia-to-dart + +@JS('navigator.mediaDevices') +library media_devices; + +import 'package:js/js.dart'; + +@JS('getUserMedia') +external Future getUserMedia(UserMediaOptions constraints); + +@JS() +@anonymous +class UserMediaOptions { + external VideoOptions get video; + + external factory UserMediaOptions({VideoOptions? video}); +} + +@JS() +@anonymous +class VideoOptions { + external String get facingMode; + // external DeviceIdOptions get deviceId; + + external factory VideoOptions( + {String? facingMode, DeviceIdOptions? deviceId}); +} + +@JS() +@anonymous +class DeviceIdOptions { + external String get exact; + + external factory DeviceIdOptions({String? exact}); +} diff --git a/plugin/qr_code_scanner/pubspec.yaml b/plugin/qr_code_scanner/pubspec.yaml new file mode 100644 index 0000000..72f21cb --- /dev/null +++ b/plugin/qr_code_scanner/pubspec.yaml @@ -0,0 +1,31 @@ +name: qr_code_scanner +description: QR code scanner that can be embedded inside flutter. It uses zxing in Android and MTBBarcode scanner in iOS. +version: 1.0.0 +homepage: https://juliuscanute.com +repository: https://github.com/juliuscanute/qr_code_scanner + +environment: + sdk: '>=2.17.0 <3.0.0' + flutter: ">=1.12.0" + +dependencies: + js: ^0.6.3 + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + +dev_dependencies: + flutter_lints: ^1.0.4 + +flutter: + plugin: + platforms: + android: + package: net.touchcapture.qr.flutterqr + pluginClass: FlutterQrPlugin + ios: + pluginClass: FlutterQrPlugin +# web: +# pluginClass: FlutterQrPlugin +# fileName: flutter_qr_web.dart diff --git a/pubspec.lock b/pubspec.lock index 900cb79..9dd9c98 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,18 +13,18 @@ packages: dependency: transitive description: name: archive - sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.3.7" + version: "3.6.1" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: "direct main" description: @@ -42,29 +42,29 @@ packages: source: hosted version: "2.1.1" camera: - dependency: transitive + dependency: "direct main" description: name: camera - sha256: f63f2687fb1795c36f7c57b18a03071880eabb0fd8b5291b0fcd3fb979cb0fb1 + sha256: dfa8fc5a1adaeb95e7a54d86a5bd56f4bb0e035515354c8ac6d262e35cec2ec8 url: "https://pub.dev" source: hosted - version: "0.10.5+4" + version: "0.10.6" camera_android: dependency: transitive description: name: camera_android - sha256: ed4f645848074166fc3b8e20350f83ca07e09a2becc1e185040ee561f955d4df + sha256: "32f04948a284b71d938fe275616faf4957d07f9b3aab8021bfc8c418301a289e" url: "https://pub.dev" source: hosted - version: "0.10.8+8" + version: "0.10.9+11" camera_avfoundation: dependency: transitive description: name: camera_avfoundation - sha256: "718b60ed2e22b4067fe6e2c0e9ebe2856c2de5c8b1289ba95d10db85b0b00bc2" + sha256: "7c28969a975a7eb2349bc2cb2dfe3ad218a33dba9968ecfb181ce08c87486655" url: "https://pub.dev" source: hosted - version: "0.9.13+4" + version: "0.9.17+3" camera_camera: dependency: "direct main" description: @@ -77,18 +77,18 @@ packages: dependency: transitive description: name: camera_platform_interface - sha256: "8734d1c682f034bdb12d0d6ff379b0535a9b8e44266b530025bf8266d6a62f28" + sha256: b3ede1f171532e0d83111fe0980b46d17f1aa9788a07a2fbed07366bbdbb9061 url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.8.0" camera_web: dependency: transitive description: name: camera_web - sha256: d4c2c571c7af04f8b10702ca16bb9ed2a26e64534171e8f75c9349b2c004d8f1 + sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f" url: "https://pub.dev" source: hosted - version: "0.3.2+3" + version: "0.3.5" characters: dependency: transitive description: @@ -109,18 +109,18 @@ packages: dependency: "direct main" description: name: circular_menu - sha256: "253e5e7aaf107e84251b0c51fb66ae17f6caaebf973eb30049f02b999646373a" + sha256: "2969127a4fcad1a1366e667925b4492f6dda94702b426ee2c15ab1a3f1fb060e" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "4.0.0" cli_util: dependency: transitive description: name: cli_util - sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.4.1" clock: dependency: transitive description: @@ -133,82 +133,58 @@ packages: dependency: "direct main" description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" connectivity_plus: dependency: "direct main" description: name: connectivity_plus - sha256: "77a180d6938f78ca7d2382d2240eb626c0f6a735d0bfdce227d8ffb80f95c48b" + sha256: "2056db5241f96cdc0126bd94459fc4cdc13876753768fc7a31c425e50a7177d0" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "6.0.5" connectivity_plus_platform_interface: dependency: transitive description: name: connectivity_plus_platform_interface - sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" url: "https://pub.dev" source: hosted - version: "1.2.4" - convert: - dependency: transitive - description: - name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" - url: "https://pub.dev" - source: hosted - version: "3.1.1" + version: "2.0.1" cross_file: dependency: transitive description: name: cross_file - sha256: fd832b5384d0d6da4f6df60b854d33accaaeb63aa9e10e736a87381f08dee2cb + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.3+5" + version: "0.3.4+2" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" - csslib: - dependency: transitive - description: - name: csslib - sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" - url: "https://pub.dev" - source: hosted - version: "1.0.0" + version: "3.0.5" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" dbus: dependency: transitive description: name: dbus - sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" url: "https://pub.dev" source: hosted - version: "0.7.8" - extra_pedantic: - dependency: transitive - description: - name: extra_pedantic - sha256: eb9cc0842dc1c980f00fd226364456d2169d54f7118b8ae16443188063edce0b - url: "https://pub.dev" - source: hosted - version: "1.5.0" + version: "0.7.10" fake_async: dependency: transitive description: @@ -221,18 +197,18 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.3" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" file_selector_linux: dependency: transitive description: @@ -245,26 +221,34 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "182c3f8350cee659f7b115e956047ee3dc672a96665883a545e81581b9a82c72" + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.4" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface - sha256: "0aa47a725c346825a2bd396343ce63ac00bda6eff2fbc43eabe99737dede8262" + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.6.2" file_selector_windows: dependency: transitive description: name: file_selector_windows - sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" url: "https://pub.dev" source: hosted - version: "0.9.3+1" + version: "0.9.3+2" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -279,29 +263,29 @@ packages: source: hosted version: "1.0.1" flutter_compass: - dependency: transitive + dependency: "direct main" description: name: flutter_compass - sha256: "1a0121bff32df95193812b4e0f69e95f45fdec042ebd7a326ba087c0f6ec8304" + sha256: be642484f9f6975c1c6edff568281b001f2f1e604de27ecea18d97eebbdef22f url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.8.0" flutter_image: dependency: "direct main" description: name: flutter_image - sha256: b41c6cbf9462d8cbdeeb7b5dd84c7bca5176e9e4d6e8b968290e371ab758cc0c + sha256: a113b84279cbcb2579b5a269eeffc3ad03181cf5f94ea1665c4181b814d196b5 url: "https://pub.dev" source: hosted - version: "4.1.8" + version: "4.1.11" flutter_keyboard_visibility: - dependency: transitive + dependency: "direct main" description: name: flutter_keyboard_visibility - sha256: "4983655c26ab5b959252ee204c2fffa4afeb4413cd030455194ec0caa3b8e7cb" + sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" url: "https://pub.dev" source: hosted - version: "5.4.1" + version: "6.0.0" flutter_keyboard_visibility_linux: dependency: transitive description: @@ -351,69 +335,69 @@ packages: source: hosted version: "0.13.1" flutter_lints: - dependency: "direct dev" + dependency: "direct main" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "4.0.0" flutter_map: dependency: "direct main" description: name: flutter_map - sha256: "52c65a977daae42f9aae6748418dd1535eaf27186e9bac9bf431843082bc75a3" + sha256: "87cc8349b8fa5dccda5af50018c7374b6645334a0d680931c1fe11bce88fa5bb" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "6.2.1" flutter_map_location_marker: dependency: "direct main" description: name: flutter_map_location_marker - sha256: "9757dceadda71a53d2d4004cff4d53a29210086083bdfebf44a1c4feb07f8eb1" + sha256: c880c0ce6e6f64ade72f72d4d95b54ea75827818350af5596b301e0cbb350dcd url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "8.0.5" flutter_map_marker_cluster: dependency: "direct main" description: name: flutter_map_marker_cluster - sha256: "362088b16311b6743a7930857129ec6c7c807ecca777fe1033c0ad6688339e36" + sha256: a324f48da5ee83a3f29fd8d08b4b1e6e3114ff5c6cab910124d6a2e1f06f08cc url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.3.6" flutter_map_marker_popup: dependency: transitive description: name: flutter_map_marker_popup - sha256: "7cf30fab25ffe1ba04a9e0fbe1227f44a83c73256d4d70be1c26cf141ce5c41d" + sha256: ec563bcbae24a18ac16815fb75ac5ab33ccba609e14db70e252a67de19c6639c url: "https://pub.dev" source: hosted - version: "4.1.0" - flutter_map_tile_caching: - dependency: "direct main" - description: - name: flutter_map_tile_caching - sha256: "48ab77adc7adf70ec682db737b68dd5ac804e78a2e834b951e0d9735a6923971" - url: "https://pub.dev" - source: hosted - version: "8.0.1" + version: "6.1.2" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: f185ac890306b5779ecbd611f52502d8d4d63d27703ef73161ca0407e815f02c + sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.0.21" flutter_polyline_points: dependency: "direct main" description: name: flutter_polyline_points - sha256: "02699e69142f51a248d784b6e3eec524194467fca5f7c4da19699ce2368b6980" + sha256: "3a1c8c30abee9fb0fbe44c70d5d1cedb10ef28ec7ea285c669f02b3e183483aa" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "2.1.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d" + url: "https://pub.dev" + source: hosted + version: "2.5.1" flutter_test: dependency: "direct dev" description: flutter @@ -423,10 +407,10 @@ packages: dependency: "direct main" description: name: flutter_typeahead - sha256: a3539f7a90246b152f569029dedcf0b842532d3f2a440701b520e0bf2acbcf42 + sha256: d64712c65db240b1057559b952398ebb6e498077baeebf9b0731dade62438a6d url: "https://pub.dev" source: hosted - version: "4.6.2" + version: "5.2.0" flutter_web_plugins: dependency: transitive description: flutter @@ -436,90 +420,74 @@ packages: dependency: transitive description: name: font_awesome_flutter - sha256: "5fb789145cae1f4c3245c58b3f8fb287d055c26323879eab57a7bf0cfd1e45f3" + sha256: "275ff26905134bcb59417cf60ad979136f1f8257f2f449914b2c3e05bbb4cd6f" url: "https://pub.dev" source: hosted - version: "10.5.0" - geodesy: - dependency: transitive - description: - name: geodesy - sha256: d9959000de938adf760f946546ccbf9ebdff8f4f6d0b5c54e8b8b1ed350b1028 - url: "https://pub.dev" - source: hosted - version: "0.4.0-nullsafety.0" - geojson: + version: "10.7.0" + geojson_vi: dependency: "direct main" description: - name: geojson - sha256: "8aab8116d074e92ef2d1ade25ec5ae90ea8bf024a920ab46703c433ffe08878f" + name: geojson_vi + sha256: a47e0efd17617aef8b239719ea2a3ef743563e873709531ee5498ccfd04069f2 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "2.2.5" geolocator: dependency: "direct main" description: name: geolocator - sha256: "5c23f3613f50586c0bbb2b8f970240ae66b3bd992088cf60dd5ee2e6f7dde3a8" + sha256: f4efb8d3c4cdcad2e226af9661eb1a0dd38c71a9494b22526f9da80ab79520e5 url: "https://pub.dev" source: hosted - version: "9.0.2" + version: "10.1.1" geolocator_android: dependency: transitive description: name: geolocator_android - sha256: "835ff5b4888a2f8eba128996494faf9c5d422785322a81dc0565b99e0f6c379d" + sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47" url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.6.1" geolocator_apple: dependency: transitive description: name: geolocator_apple - sha256: "36527c555f4c425f7d8fa8c7c07d67b78e3ff7590d40448051959e1860c1cfb4" + sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd url: "https://pub.dev" source: hosted - version: "2.2.7" + version: "2.3.7" geolocator_platform_interface: dependency: transitive description: name: geolocator_platform_interface - sha256: af4d69231452f9620718588f41acc4cb58312368716bfff2e92e770b46ce6386 + sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012" url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "4.2.4" geolocator_web: dependency: transitive description: name: geolocator_web - sha256: f68a122da48fcfff68bbc9846bb0b74ef651afe84a1b1f6ec20939de4d6860e1 + sha256: "102e7da05b48ca6bf0a5bda0010f886b171d1a08059f01bfe02addd0175ebece" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.2.1" geolocator_windows: dependency: transitive description: name: geolocator_windows - sha256: "4f4218f122a6978d0ad655fa3541eea74c67417440b09f0657238810d5af6bdc" + sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e" url: "https://pub.dev" source: hosted - version: "0.1.3" - geopoint: - dependency: transitive - description: - name: geopoint - sha256: "594afb50a689e6584b80b7de8332c83a78e50725dc4324b2c014d19c56de5e3f" - url: "https://pub.dev" - source: hosted - version: "1.0.0" + version: "0.2.3" get: dependency: "direct main" description: name: get - sha256: "2ba20a47c8f1f233bed775ba2dd0d3ac97b4cf32fc17731b3dfc672b06b0e92a" + sha256: e4e7335ede17452b391ed3b2ede016545706c01a02292a6c97619705e7d2a85e url: "https://pub.dev" source: hosted - version: "4.6.5" + version: "4.6.6" google_api_availability: dependency: "direct main" description: @@ -548,90 +516,18 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: "6b6f10f0ce3c42f6552d1c70d2c28d764cf22bb487f50f66cca31dcd5194f4d6" + sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 url: "https://pub.dev" source: hosted - version: "4.0.4" - google_maps: - dependency: transitive - description: - name: google_maps - sha256: "555d5d736339b0478e821167ac521c810d7b51c3b2734e6802a9f046b64ea37a" - url: "https://pub.dev" - source: hosted - version: "6.3.0" - google_maps_flutter: - dependency: "direct main" - description: - name: google_maps_flutter - sha256: d4914cb38b3dcb62c39c085d968d434de0f8050f00f4d9f5ba4a7c7e004934cb - url: "https://pub.dev" - source: hosted - version: "2.5.0" - google_maps_flutter_android: - dependency: transitive - description: - name: google_maps_flutter_android - sha256: e6cb018169e49332f88d23b1d2119b09e8ab4e7d3a1b889a1b7b3fd113e034ba - url: "https://pub.dev" - source: hosted - version: "2.5.1" - google_maps_flutter_ios: - dependency: transitive - description: - name: google_maps_flutter_ios - sha256: "2a595c9789070786c654e9772ec0d1bb759ae37d2dd776291af5398531274e06" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - google_maps_flutter_platform_interface: - dependency: transitive - description: - name: google_maps_flutter_platform_interface - sha256: a3e9e6896501e566d902c6c69f010834d410ef4b7b5c18b90c77e871c86b7907 - url: "https://pub.dev" - source: hosted - version: "2.4.1" - google_maps_flutter_web: - dependency: transitive - description: - name: google_maps_flutter_web - sha256: f893d1542c6562bc8299ef768fbbe92ade83c220ab3209b9477ec9f81ad585e4 - url: "https://pub.dev" - source: hosted - version: "0.5.4+2" - google_maps_webservice: - dependency: "direct main" - description: - name: google_maps_webservice - sha256: d0ae4e4508afd74a3f051565261a3cdbae59db29448f9b6e6beb5674545e1eb7 - url: "https://pub.dev" - source: hosted - version: "0.0.20-nullsafety.5" - html: - dependency: transitive - description: - name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" - url: "https://pub.dev" - source: hosted - version: "0.15.4" + version: "6.2.1" http: - dependency: transitive + dependency: "direct main" description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "0.13.6" - http2: - dependency: transitive - description: - name: http2 - sha256: "38db0c4aa9f1cd238a5d2e86aa0cc7cc91c77e0c6c94ba64bbe85e4ff732a952" - url: "https://pub.dev" - source: hosted - version: "2.2.0" + version: "1.2.2" http_parser: dependency: transitive description: @@ -640,54 +536,55 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - http_plus: - dependency: transitive - description: - name: http_plus - sha256: "6e2af403727ea5bd0a6a45edbe8e1ebc9d5d49b38a93893e1c00e2a307035773" - url: "https://pub.dev" - source: hosted - version: "0.2.2" image: dependency: transitive description: name: image - sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "4.0.17" + version: "4.2.0" + image_gallery_saver: + dependency: "direct main" + description: + path: "." + ref: knottx-latest + resolved-ref: "24fd8207a4491c42ed907060bb5bf40c2430131f" + url: "https://github.com/knottx/image_gallery_saver.git" + source: git + version: "2.0.3" image_picker: dependency: "direct main" description: name: image_picker - sha256: "7d7f2768df2a8b0a3cefa5ef4f84636121987d403130e70b17ef7e2cf650ba84" + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: d32a997bcc4ee135aebca8e272b7c517927aa65a74b9c60a81a2764ef1a0462d + sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56" url: "https://pub.dev" source: hosted - version: "0.8.7+5" + version: "0.8.12+12" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0" + sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "3.0.5" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: c5538cacefacac733c724be7484377923b476216ad1ead35a0d2eadcdc0fc497 + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" url: "https://pub.dev" source: hosted - version: "0.8.8+2" + version: "0.8.12" image_picker_linux: dependency: transitive description: @@ -708,10 +605,10 @@ packages: dependency: transitive description: name: image_picker_platform_interface - sha256: ed9b00e63977c93b0d2d2b343685bed9c324534ba5abafbb3dfbd6a780b1b514 + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" url: "https://pub.dev" source: hosted - version: "2.9.1" + version: "2.10.0" image_picker_windows: dependency: transitive description: @@ -724,34 +621,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" - isar: - dependency: transitive - description: - name: isar - sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" - isar_flutter_libs: - dependency: transitive - description: - name: isar_flutter_libs - sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8 - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" - iso: - dependency: transitive - description: - name: iso - sha256: "7030a1a096f7924deb6cccde6c7d80473dddd54eeedf20402e3d6e51b1672b27" - url: "https://pub.dev" - source: hosted - version: "1.0.0" + version: "0.19.0" js: dependency: transitive description: @@ -760,38 +633,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" - js_wrapping: - dependency: transitive - description: - name: js_wrapping - sha256: e385980f7c76a8c1c9a560dfb623b890975841542471eade630b2871d243851c - url: "https://pub.dev" - source: hosted - version: "0.7.4" json_annotation: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" + keyboard_dismisser: + dependency: "direct main" + description: + name: keyboard_dismisser + sha256: f67e032581fc3dd1f77e1cb54c421b089e015d122aeba2490ba001cfcc42a181 + url: "https://pub.dev" + source: hosted + version: "3.0.0" latlong2: dependency: "direct main" description: name: latlong2 - sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0" + sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe" url: "https://pub.dev" source: hosted - version: "0.8.2" + version: "0.9.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "4.0.0" lists: dependency: transitive description: @@ -804,26 +701,34 @@ packages: dependency: transitive description: name: logger - sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f" + sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "2.4.0" + logging: + dependency: "direct main" + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.11.1" material_design_icons_flutter: dependency: "direct main" description: @@ -836,10 +741,10 @@ packages: dependency: "direct main" description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.15.0" mgrs_dart: dependency: transitive description: @@ -852,18 +757,18 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" modal_bottom_sheet: dependency: "direct main" description: name: modal_bottom_sheet - sha256: "3bba63c62d35c931bce7f8ae23a47f9a05836d8cb3c11122ada64e0b2f3d718f" + sha256: eac66ef8cb0461bf069a38c5eb0fa728cee525a531a8304bd3f7b2185407c67e url: "https://pub.dev" source: hosted - version: "3.0.0-pre" + version: "3.0.0" nested: dependency: transitive description: @@ -880,38 +785,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 + url: "https://pub.dev" + source: hosted + version: "8.0.2" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + url: "https://pub.dev" + source: hosted + version: "3.0.1" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_provider: dependency: "direct main" description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1" + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -924,18 +845,18 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" pedantic: dependency: transitive description: @@ -948,82 +869,106 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" + sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" url: "https://pub.dev" source: hosted - version: "10.4.3" + version: "11.3.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d74e77a5ecd38649905db0a7d05ef16bed42ff263b9efb73ed794317c5764ec3 + sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa" url: "https://pub.dev" source: hosted - version: "10.3.4" + version: "12.0.12" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" + sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 url: "https://pub.dev" source: hosted - version: "9.1.4" + version: "9.4.5" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: d220eb8476b466d58b161e10b3001d93999010a26228a3fb89c4280db1249546 + url: "https://pub.dev" + source: hosted + version: "0.1.3+1" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" + sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea url: "https://pub.dev" source: hosted - version: "3.11.3" + version: "4.2.2" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.2.1" petitparser: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.2" platform: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.8" pointer_interceptor: dependency: transitive description: name: pointer_interceptor - sha256: "7626e034489820fd599380d2bb4d3f4a0a5e3529370b62bfce53ab736b91adb2" + sha256: "57210410680379aea8b1b7ed6ae0c3ad349bfd56fe845b8ea934a53344b9d523" url: "https://pub.dev" source: hosted - version: "0.9.3+6" - pointycastle: + version: "0.10.1+2" + pointer_interceptor_ios: dependency: transitive description: - name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + name: pointer_interceptor_ios + sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917 url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "0.10.1" + pointer_interceptor_platform_interface: + dependency: transitive + description: + name: pointer_interceptor_platform_interface + sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506" + url: "https://pub.dev" + source: hosted + version: "0.10.0+1" + pointer_interceptor_web: + dependency: transitive + description: + name: pointer_interceptor_web + sha256: "7a7087782110f8c1827170660b09f8aa893e0e9a61431dbbe2ac3fc482e8c044" + url: "https://pub.dev" + source: hosted + version: "0.10.2+1" polylabel: dependency: transitive description: @@ -1040,14 +985,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" proj4dart: dependency: "direct main" description: @@ -1060,111 +997,95 @@ packages: dependency: transitive description: name: provider - sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c url: "https://pub.dev" source: hosted - version: "6.0.5" - queue: - dependency: transitive + version: "6.1.2" + qr_code_scanner: + dependency: "direct main" description: - name: queue - sha256: "9a41ecadc15db79010108c06eae229a45c56b18db699760f34e8c9ac9b831ff9" + name: qr_code_scanner + sha256: f23b68d893505a424f0bd2e324ebea71ed88465d572d26bb8d2e78a4749591fd url: "https://pub.dev" source: hosted - version: "3.1.0+2" - quiver: - dependency: transitive - description: - name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 - url: "https://pub.dev" - source: hosted - version: "3.2.1" + version: "1.0.1" rename: dependency: "direct main" description: name: rename - sha256: b0d9407186d834ad73aba9938da95a20208c4be99f151d9f376ac62a08d08bad + sha256: "6ef5daf4b11130e71d93630cfb70725e5a35b19039739cfcd2b272c834ba25fe" url: "https://pub.dev" source: hosted - version: "2.1.1" - sanitize_html: + version: "3.0.2" + riverpod: dependency: transitive description: - name: sanitize_html - sha256: "0a445f19bbaa196f5a4f93461aa066b94e6e025622eb1e9bc77872a5e25233a5" + name: riverpod + sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.5.1" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.1" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.5.2" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.4.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" - slugify: - dependency: transitive - description: - name: slugify - sha256: b272501565cb28050cac2d96b7bf28a2d24c8dae359280361d124f3093d337c3 - url: "https://pub.dev" - source: hosted - version: "2.0.0" source_span: dependency: transitive description: @@ -1173,38 +1094,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" sqflite: dependency: "direct main" description: name: sqflite - sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.3+1" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" + sha256: "7b41b6c3507854a159e24ae90a8e3e9cc01eb26a477c118d6dca065b5f55453e" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.4+2" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -1225,10 +1162,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" + sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" term_glyph: dependency: transitive description: @@ -1241,10 +1178,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.7.2" timeline_tile: dependency: "direct main" description: @@ -1253,6 +1190,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + timezone: + dependency: "direct main" + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" transparent_image: dependency: "direct main" description: @@ -1289,74 +1234,74 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.1.14" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330 + sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.3.9" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618" + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.0.20" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069" + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.1.2" uuid: dependency: transitive description: name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "4.4.2" vector_math: dependency: "direct main" description: @@ -1365,30 +1310,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - watcher: + vm_service: dependency: transitive description: - name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + name: vm_service + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "14.2.4" web: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" - win32: + version: "1.0.0" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + sha256: ec81f57aa1611f8ebecf1d2259da4ef052281cb5ad624131c93546c79ccc7736 + url: "https://pub.dev" + source: hosted + version: "4.9.0" + webview_flutter_android: dependency: transitive description: - name: win32 - sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 + name: webview_flutter_android + sha256: c66651fba15f9d7ddd31daec42da8d6bce46c85610a7127e3ebcb39a4395c3c9 url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.16.6" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d + url: "https://pub.dev" + source: hosted + version: "2.10.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: "1942a12224ab31e9508cf00c0c6347b931b023b8a4f0811e5dec3b06f94f117d" + url: "https://pub.dev" + source: hosted + version: "3.15.0" + win32: + dependency: "direct main" + description: + name: win32 + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + url: "https://pub.dev" + source: hosted + version: "5.5.4" wkt_parser: dependency: transitive description: @@ -1401,18 +1378,18 @@ packages: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" xml: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.5.0" yaml: dependency: transitive description: @@ -1422,5 +1399,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 1e2b743..701a89c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,141 +1,198 @@ -name: rogapp -description: A new Flutter project. - -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.12+12 - -environment: - sdk: '>=3.1.0 <4.0.0' - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - sqflite: ^2.0.1 - get: ^4.6.5 - flutter_map: any - geolocator: ^9.0.1 - permission_handler: ^10.0.0 - google_api_availability: ^5.0.0 - tuple: ^2.0.0 - latlong2: ^0.8.1 - positioned_tap_detector_2: ^1.0.4 - transparent_image: ^2.0.0 - async: ^2.8.2 - vector_math: ^2.1.1 - flutter_image: ^4.1.0 - proj4dart: ^2.0.0 - meta: ^1.7.0 - collection: ^1.15.0 - path_provider: ^2.0.8 - flutter_map_location_marker: any - flutter_map_marker_cluster: any - material_design_icons_flutter: ^7.0.7296 - google_fonts: ^4.0.4 - image_picker: ^1.0.4 - #geojson_vi: ^2.0.7 - geojson: ^1.0.0 - url_launcher: ^6.0.20 - flutter_breadcrumb: ^1.0.1 - timeline_tile: ^2.0.0 - google_maps_flutter: ^2.5.0 - #flutter_map_marker_popup: any - flutter_polyline_points: ^1.0.0 - google_maps_webservice: ^0.0.19 - flutter_typeahead: ^4.0.0 - flutter_launcher_icons: ^0.13.1 - rename: ^2.0.1 - circular_menu: ^2.0.1 - camera_camera: ^3.0.0-dev - intl: ^0.18.1 - modal_bottom_sheet: ^3.0.0-pre - connectivity_plus: ^4.0.2 - flutter_map_tile_caching: ^8.0.1 - shared_preferences: ^2.0.15 - -flutter_icons: - android: true - ios: true - image_path: "assets/icon.png" - - -dev_dependencies: - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^2.0.3 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - assets: - - assets/images/ - - assets/images/empty_image.png - - assets/images/gradient_japanese_temple.jpg - - assets/images/japanese_fun.jpeg - - assets/images/appicon.png - - assets/images/login_image.jpg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages +name: gifunavi +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 4.8.19+499 + +environment: + sdk: ^3.5.0 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^4.0.0 + + win32: ^5.5.3 + sqflite: ^2.0.1 + get: ^4.6.6 + flutter_map: #^6.0.1 #^5.0.0 #^6.0.1 #any #^7.0.2 #^6.2.1 #^6.0.0 #^6.0.1 + geolocator: ^10.1.0 #^9.0.2 #^11.1.0 #^10.1.0 + permission_handler: ^11.3.1 + logging: ^1.0.2 + timezone: ^0.9.1 + flutter_keyboard_visibility: ^6.0.0 # 最新バージョンを指定 + + # flutter_dev_tools: ^0.0.2 + # permission_handler: ^11.1.0 <== older + # permission_handler 11.2.0 (11.3.1 available) + # permission_handler_android 12.0.3 (12.0.5 available) + # permission_handler_apple9.3.0 (9.4.4 available) + # permission_handler_platform_interface 4.1.0 (4.2.1 available) + package_info_plus: ^8.0.2 + #device_info_plus: ^10.1.0 #^9.1.2 #^9.0.3 #^9.1.2 #^10.1.2 #^10.1.0 => 最新版に変更 => 新しすぎるので古いのに変更 + google_api_availability: ^5.0.0 + tuple: ^2.0.0 + latlong2: #^0.9.0 #^0.9.1 + positioned_tap_detector_2: ^1.0.4 + transparent_image: ^2.0.0 + async: ^2.8.2 + vector_math: ^2.1.1 + flutter_image: ^4.1.0 + proj4dart: ^2.0.0 + meta: ^1.7.0 + collection: ^1.17.0 + path_provider: ^2.0.11 #^2.0.8 + flutter_map_location_marker: #any + flutter_map_marker_cluster: #any + material_design_icons_flutter: ^7.0.7296 + google_fonts: ^6.1.0 + keyboard_dismisser: ^3.0.0 + image_picker: ^1.0.4 + geojson_vi: ^2.2.1 + #geojson: ^1.0.0 + url_launcher: ^6.0.20 + flutter_breadcrumb: ^1.0.1 + timeline_tile: ^2.0.0 + # google_maps_flutter: ^2.5.0 + #flutter_map_marker_popup: any + flutter_polyline_points: ^2.0.0 + #google_maps_webservice: ^0.0.20-nullsafety.5 + flutter_typeahead: ^5.0.1 + flutter_launcher_icons: ^0.13.1 + rename: ^3.0.1 + circular_menu: ^4.0.0 + camera: ^0.10.0+3 + camera_camera: ^3.0.0 + intl: ^0.19.0 #^0.18.1 + modal_bottom_sheet: ^3.0.0-pre + connectivity_plus: ^6.0.5 + #flutter_map_tile_caching: ^8.0.1 + shared_preferences: ^2.0.15 + # gallery_saver: ^2.3.2 + flutter_riverpod: ^2.5.1 + http: ^1.2.2 #^1.2.1 #^0.13.5 #^1.1.0 + + qr_code_scanner: ^1.0.1 #any #^1.0.1 + # path: plugin/qr_code_scanner + + webview_flutter: ^4.8.0 + + image_gallery_saver: + git: + url: https://github.com/knottx/image_gallery_saver.git + ref: knottx-latest + #ref: e319bc600a8c3e5ab4d414b890dc7844f6f9d6d9 + + flutter_compass: ^0.8.0 # added + # path: plugin/flutter_compass + +flutter_icons: + android: true + ios: true + image_path: "assets/icon.png" + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^4.0.0 + + flutter_launcher_icons: + + + #flutter_compass: #^0.8.0 + # path: plugin/flutter_compass + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # To add assets to your application, add an assets section, like this: + assets: + - assets/images/ + - assets/images/empty_image.png + - assets/images/gradient_japanese_temple.jpg + - assets/images/japanese_fun.jpeg + - assets/images/appicon.png + - assets/images/login_image.jpg + - assets/images/money.png + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package + + fonts: + - family: CustomIcons + fonts: + - asset: assets/fonts/icomoon.ttf diff --git a/test/widget_test.dart b/test/widget_test.dart index 34192c2..446663b 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,30 +1,30 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:rogapp/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget( const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:gifunavi/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/web/index.html b/web/index.html index 4c51063..245f2f0 100644 --- a/web/index.html +++ b/web/index.html @@ -1,104 +1,38 @@ - - - - - - - - - - - - - - - - - - - - 岐阜ナビ - - - - - - - + + + + + + + + + + + + + + + + + + + + gifunavi + + + + + + diff --git a/web/manifest.json b/web/manifest.json index 43af7f9..7683121 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -1,35 +1,35 @@ -{ - "name": "rogapp", - "short_name": "rogapp", - "start_url": ".", - "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "A new Flutter project.", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ] -} +{ + "name": "gifunavi", + "short_name": "gifunavi", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore index ec4098a..d492d0d 100644 --- a/windows/.gitignore +++ b/windows/.gitignore @@ -1,17 +1,17 @@ -flutter/ephemeral/ - -# Visual Studio user-specific files. -*.suo -*.user -*.userosscache -*.sln.docstates - -# Visual Studio build-related files. -x64/ -x86/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 93f03df..7f6b8ac 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -1,95 +1,108 @@ -cmake_minimum_required(VERSION 3.14) -project(rogapp LANGUAGES CXX) - -set(BINARY_NAME "rogapp") - -cmake_policy(SET CMP0063 NEW) - -set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") - -# Configure build options. -get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(IS_MULTICONFIG) - set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" - CACHE STRING "" FORCE) -else() - if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") - endif() -endif() - -set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") -set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") -set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") -set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") - -# Use Unicode for all projects. -add_definitions(-DUNICODE -D_UNICODE) - -# Compilation settings that should be applied to most targets. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_17) - target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") - target_compile_options(${TARGET} PRIVATE /EHsc) - target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") - target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") -endfunction() - -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") - -# Flutter library and tool build rules. -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# Application build -add_subdirectory("runner") - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# Support files are copied into place next to the executable, so that it can -# run in place. This is done instead of making a separate bundle (as on Linux) -# so that building and running from within Visual Studio will work. -set(BUILD_BUNDLE_DIR "$") -# Make the "install" step default, as it's required to run. -set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - CONFIGURATIONS Profile;Release - COMPONENT Runtime) +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(gifunavi LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "gifunavi") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index d4b5770..903f489 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -1,103 +1,109 @@ -cmake_minimum_required(VERSION 3.14) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. -set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") - -# === Flutter Library === -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "flutter_export.h" - "flutter_windows.h" - "flutter_messenger.h" - "flutter_plugin_registrar.h" - "flutter_texture_registrar.h" -) -list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") -add_dependencies(flutter flutter_assemble) - -# === Wrapper === -list(APPEND CPP_WRAPPER_SOURCES_CORE - "core_implementations.cc" - "standard_codec.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_PLUGIN - "plugin_registrar.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_APP - "flutter_engine.cc" - "flutter_view_controller.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") - -# Wrapper sources needed for a plugin. -add_library(flutter_wrapper_plugin STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} -) -apply_standard_settings(flutter_wrapper_plugin) -set_target_properties(flutter_wrapper_plugin PROPERTIES - POSITION_INDEPENDENT_CODE ON) -set_target_properties(flutter_wrapper_plugin PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) -target_include_directories(flutter_wrapper_plugin PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_plugin flutter_assemble) - -# Wrapper sources needed for the runner. -add_library(flutter_wrapper_app STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_APP} -) -apply_standard_settings(flutter_wrapper_app) -target_link_libraries(flutter_wrapper_app PUBLIC flutter) -target_include_directories(flutter_wrapper_app PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_app flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") -set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} - ${PHONY_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} -) +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index bc61c87..df3c158 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -20,8 +19,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FileSelectorWindows")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); - IsarFlutterLibsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 1834cc0..6f341d1 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,7 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus file_selector_windows geolocator_windows - isar_flutter_libs permission_handler_windows url_launcher_windows ) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt index 945bda7..394917c 100644 --- a/windows/runner/CMakeLists.txt +++ b/windows/runner/CMakeLists.txt @@ -1,17 +1,40 @@ -cmake_minimum_required(VERSION 3.14) -project(runner LANGUAGES CXX) - -add_executable(${BINARY_NAME} WIN32 - "flutter_window.cpp" - "main.cpp" - "utils.cpp" - "win32_window.cpp" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" - "Runner.rc" - "runner.exe.manifest" -) -apply_standard_settings(${BINARY_NAME}) -target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") -target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) -target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") -add_dependencies(${BINARY_NAME} flutter_assemble) +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index bea7343..89f54b3 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -1,121 +1,121 @@ -// Microsoft Visual C++ generated resource script. -// -#pragma code_page(65001) -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_APP_ICON ICON "resources\\app_icon.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER -#else -#define VERSION_AS_NUMBER 1,0,0 -#endif - -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME -#else -#define VERSION_AS_STRING "1.0.0" -#endif - -VS_VERSION_INFO VERSIONINFO - FILEVERSION VERSION_AS_NUMBER - PRODUCTVERSION VERSION_AS_NUMBER - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904e4" - BEGIN - VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "rogapp" "\0" - VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "rogapp" "\0" - VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" - VALUE "OriginalFilename", "rogapp.exe" "\0" - VALUE "ProductName", "rogapp" "\0" - VALUE "ProductVersion", VERSION_AS_STRING "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "gifunavi" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "gifunavi" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "gifunavi.exe" "\0" + VALUE "ProductName", "gifunavi" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index 3a11b51..955ee30 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -1,61 +1,71 @@ -#include "flutter_window.h" - -#include - -#include "flutter/generated_plugin_registrant.h" - -FlutterWindow::FlutterWindow(const flutter::DartProject& project) - : project_(project) {} - -FlutterWindow::~FlutterWindow() {} - -bool FlutterWindow::OnCreate() { - if (!Win32Window::OnCreate()) { - return false; - } - - RECT frame = GetClientArea(); - - // The size here must match the window dimensions to avoid unnecessary surface - // creation / destruction in the startup path. - flutter_controller_ = std::make_unique( - frame.right - frame.left, frame.bottom - frame.top, project_); - // Ensure that basic setup of the controller was successful. - if (!flutter_controller_->engine() || !flutter_controller_->view()) { - return false; - } - RegisterPlugins(flutter_controller_->engine()); - SetChildContent(flutter_controller_->view()->GetNativeWindow()); - return true; -} - -void FlutterWindow::OnDestroy() { - if (flutter_controller_) { - flutter_controller_ = nullptr; - } - - Win32Window::OnDestroy(); -} - -LRESULT -FlutterWindow::MessageHandler(HWND hwnd, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - // Give Flutter, including plugins, an opportunity to handle window messages. - if (flutter_controller_) { - std::optional result = - flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, - lparam); - if (result) { - return *result; - } - } - - switch (message) { - case WM_FONTCHANGE: - flutter_controller_->engine()->ReloadSystemFonts(); - break; - } - - return Win32Window::MessageHandler(hwnd, message, wparam, lparam); -} +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h index 28c2383..6da0652 100644 --- a/windows/runner/flutter_window.h +++ b/windows/runner/flutter_window.h @@ -1,33 +1,33 @@ -#ifndef RUNNER_FLUTTER_WINDOW_H_ -#define RUNNER_FLUTTER_WINDOW_H_ - -#include -#include - -#include - -#include "win32_window.h" - -// A window that does nothing but host a Flutter view. -class FlutterWindow : public Win32Window { - public: - // Creates a new FlutterWindow hosting a Flutter view running |project|. - explicit FlutterWindow(const flutter::DartProject& project); - virtual ~FlutterWindow(); - - protected: - // Win32Window: - bool OnCreate() override; - void OnDestroy() override; - LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept override; - - private: - // The project to run. - flutter::DartProject project_; - - // The Flutter instance hosted by this window. - std::unique_ptr flutter_controller_; -}; - -#endif // RUNNER_FLUTTER_WINDOW_H_ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index e2fbaf4..5ea1e8f 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -1,43 +1,43 @@ -#include -#include -#include - -#include "flutter_window.h" -#include "utils.h" - -int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t *command_line, _In_ int show_command) { - // Attach to console when present (e.g., 'flutter run') or create a - // new console when running with a debugger. - if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { - CreateAndAttachConsole(); - } - - // Initialize COM, so that it is available for use in the library and/or - // plugins. - ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - - flutter::DartProject project(L"data"); - - std::vector command_line_arguments = - GetCommandLineArguments(); - - project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - - FlutterWindow window(project); - Win32Window::Point origin(10, 10); - Win32Window::Size size(1280, 720); - if (!window.CreateAndShow(L"rogapp", origin, size)) { - return EXIT_FAILURE; - } - window.SetQuitOnClose(true); - - ::MSG msg; - while (::GetMessage(&msg, nullptr, 0, 0)) { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - - ::CoUninitialize(); - return EXIT_SUCCESS; -} +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"gifunavi", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h index ddc7f3e..66a65d1 100644 --- a/windows/runner/resource.h +++ b/windows/runner/resource.h @@ -1,16 +1,16 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by Runner.rc -// -#define IDI_APP_ICON 101 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest index 2c680b8..153653e 100644 --- a/windows/runner/runner.exe.manifest +++ b/windows/runner/runner.exe.manifest @@ -1,20 +1,14 @@ - - - - - PerMonitorV2 - - - - - - - - - - - - - - - + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp index 05b53c0..3a0b465 100644 --- a/windows/runner/utils.cpp +++ b/windows/runner/utils.cpp @@ -1,64 +1,65 @@ -#include "utils.h" - -#include -#include -#include -#include - -#include - -void CreateAndAttachConsole() { - if (::AllocConsole()) { - FILE *unused; - if (freopen_s(&unused, "CONOUT$", "w", stdout)) { - _dup2(_fileno(stdout), 1); - } - if (freopen_s(&unused, "CONOUT$", "w", stderr)) { - _dup2(_fileno(stdout), 2); - } - std::ios::sync_with_stdio(); - FlutterDesktopResyncOutputStreams(); - } -} - -std::vector GetCommandLineArguments() { - // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. - int argc; - wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - if (argv == nullptr) { - return std::vector(); - } - - std::vector command_line_arguments; - - // Skip the first argument as it's the binary name. - for (int i = 1; i < argc; i++) { - command_line_arguments.push_back(Utf8FromUtf16(argv[i])); - } - - ::LocalFree(argv); - - return command_line_arguments; -} - -std::string Utf8FromUtf16(const wchar_t* utf16_string) { - if (utf16_string == nullptr) { - return std::string(); - } - int target_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, nullptr, 0, nullptr, nullptr); - if (target_length == 0) { - return std::string(); - } - std::string utf8_string; - utf8_string.resize(target_length); - int converted_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, utf8_string.data(), - target_length, nullptr, nullptr); - if (converted_length == 0) { - return std::string(); - } - return utf8_string; -} +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h index 3f0e05c..3879d54 100644 --- a/windows/runner/utils.h +++ b/windows/runner/utils.h @@ -1,19 +1,19 @@ -#ifndef RUNNER_UTILS_H_ -#define RUNNER_UTILS_H_ - -#include -#include - -// Creates a console for the process, and redirects stdout and stderr to -// it for both the runner and the Flutter library. -void CreateAndAttachConsole(); - -// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string -// encoded in UTF-8. Returns an empty std::string on failure. -std::string Utf8FromUtf16(const wchar_t* utf16_string); - -// Gets the command line arguments passed in as a std::vector, -// encoded in UTF-8. Returns an empty std::vector on failure. -std::vector GetCommandLineArguments(); - -#endif // RUNNER_UTILS_H_ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp index 97f4439..60608d0 100644 --- a/windows/runner/win32_window.cpp +++ b/windows/runner/win32_window.cpp @@ -1,245 +1,288 @@ -#include "win32_window.h" - -#include - -#include "resource.h" - -namespace { - -constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; - -// The number of Win32Window objects that currently exist. -static int g_active_window_count = 0; - -using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); - -// Scale helper to convert logical scaler values to physical using passed in -// scale factor -int Scale(int source, double scale_factor) { - return static_cast(source * scale_factor); -} - -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; - } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); - FreeLibrary(user32_module); - } -} - -} // namespace - -// Manages the Win32Window's window class registration. -class WindowClassRegistrar { - public: - ~WindowClassRegistrar() = default; - - // Returns the singleton registar instance. - static WindowClassRegistrar* GetInstance() { - if (!instance_) { - instance_ = new WindowClassRegistrar(); - } - return instance_; - } - - // Returns the name of the window class, registering the class if it hasn't - // previously been registered. - const wchar_t* GetWindowClass(); - - // Unregisters the window class. Should only be called if there are no - // instances of the window. - void UnregisterWindowClass(); - - private: - WindowClassRegistrar() = default; - - static WindowClassRegistrar* instance_; - - bool class_registered_ = false; -}; - -WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; - -const wchar_t* WindowClassRegistrar::GetWindowClass() { - if (!class_registered_) { - WNDCLASS window_class{}; - window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); - window_class.lpszClassName = kWindowClassName; - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = GetModuleHandle(nullptr); - window_class.hIcon = - LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); - window_class.hbrBackground = 0; - window_class.lpszMenuName = nullptr; - window_class.lpfnWndProc = Win32Window::WndProc; - RegisterClass(&window_class); - class_registered_ = true; - } - return kWindowClassName; -} - -void WindowClassRegistrar::UnregisterWindowClass() { - UnregisterClass(kWindowClassName, nullptr); - class_registered_ = false; -} - -Win32Window::Win32Window() { - ++g_active_window_count; -} - -Win32Window::~Win32Window() { - --g_active_window_count; - Destroy(); -} - -bool Win32Window::CreateAndShow(const std::wstring& title, - const Point& origin, - const Size& size) { - Destroy(); - - const wchar_t* window_class = - WindowClassRegistrar::GetInstance()->GetWindowClass(); - - const POINT target_point = {static_cast(origin.x), - static_cast(origin.y)}; - HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); - UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); - double scale_factor = dpi / 96.0; - - HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, - Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), - Scale(size.width, scale_factor), Scale(size.height, scale_factor), - nullptr, nullptr, GetModuleHandle(nullptr), this); - - if (!window) { - return false; - } - - return OnCreate(); -} - -// static -LRESULT CALLBACK Win32Window::WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - if (message == WM_NCCREATE) { - auto window_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(window_struct->lpCreateParams)); - - auto that = static_cast(window_struct->lpCreateParams); - EnableFullDpiSupportIfAvailable(window); - that->window_handle_ = window; - } else if (Win32Window* that = GetThisFromHandle(window)) { - return that->MessageHandler(window, message, wparam, lparam); - } - - return DefWindowProc(window, message, wparam, lparam); -} - -LRESULT -Win32Window::MessageHandler(HWND hwnd, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - switch (message) { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; - } - - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - return 0; - } - - return DefWindowProc(window_handle_, message, wparam, lparam); -} - -void Win32Window::Destroy() { - OnDestroy(); - - if (window_handle_) { - DestroyWindow(window_handle_); - window_handle_ = nullptr; - } - if (g_active_window_count == 0) { - WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); - } -} - -Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); -} - -void Win32Window::SetChildContent(HWND content) { - child_content_ = content; - SetParent(content, window_handle_); - RECT frame = GetClientArea(); - - MoveWindow(content, frame.left, frame.top, frame.right - frame.left, - frame.bottom - frame.top, true); - - SetFocus(child_content_); -} - -RECT Win32Window::GetClientArea() { - RECT frame; - GetClientRect(window_handle_, &frame); - return frame; -} - -HWND Win32Window::GetHandle() { - return window_handle_; -} - -void Win32Window::SetQuitOnClose(bool quit_on_close) { - quit_on_close_ = quit_on_close; -} - -bool Win32Window::OnCreate() { - // No-op; provided for subclasses. - return true; -} - -void Win32Window::OnDestroy() { - // No-op; provided for subclasses. -} +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h index d9bcac1..e901dde 100644 --- a/windows/runner/win32_window.h +++ b/windows/runner/win32_window.h @@ -1,98 +1,102 @@ -#ifndef RUNNER_WIN32_WINDOW_H_ -#define RUNNER_WIN32_WINDOW_H_ - -#include - -#include -#include -#include - -// A class abstraction for a high DPI-aware Win32 Window. Intended to be -// inherited from by classes that wish to specialize with custom -// rendering and input handling -class Win32Window { - public: - struct Point { - unsigned int x; - unsigned int y; - Point(unsigned int x, unsigned int y) : x(x), y(y) {} - }; - - struct Size { - unsigned int width; - unsigned int height; - Size(unsigned int width, unsigned int height) - : width(width), height(height) {} - }; - - Win32Window(); - virtual ~Win32Window(); - - // Creates and shows a win32 window with |title| and position and size using - // |origin| and |size|. New windows are created on the default monitor. Window - // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size to will treat the width height passed in to this function - // as logical pixels and scale to appropriate for the default monitor. Returns - // true if the window was created successfully. - bool CreateAndShow(const std::wstring& title, - const Point& origin, - const Size& size); - - // Release OS resources associated with window. - void Destroy(); - - // Inserts |content| into the window tree. - void SetChildContent(HWND content); - - // Returns the backing Window handle to enable clients to set icon and other - // window properties. Returns nullptr if the window has been destroyed. - HWND GetHandle(); - - // If true, closing this window will quit the application. - void SetQuitOnClose(bool quit_on_close); - - // Return a RECT representing the bounds of the current client area. - RECT GetClientArea(); - - protected: - // Processes and route salient window messages for mouse handling, - // size change and DPI. Delegates handling of these to member overloads that - // inheriting classes can handle. - virtual LRESULT MessageHandler(HWND window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Called when CreateAndShow is called, allowing subclass window-related - // setup. Subclasses should return false if setup fails. - virtual bool OnCreate(); - - // Called when Destroy is called. - virtual void OnDestroy(); - - private: - friend class WindowClassRegistrar; - - // OS callback called by message pump. Handles the WM_NCCREATE message which - // is passed when the non-client area is being created and enables automatic - // non-client DPI scaling so that the non-client area automatically - // responsponds to changes in DPI. All other messages are handled by - // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Retrieves a class instance pointer for |window| - static Win32Window* GetThisFromHandle(HWND const window) noexcept; - - bool quit_on_close_ = false; - - // window handle for top level window. - HWND window_handle_ = nullptr; - - // window handle for hosted content. - HWND child_content_ = nullptr; -}; - -#endif // RUNNER_WIN32_WINDOW_H_ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_