Transfer repository from Gitlab
							
								
								
									
										46
									
								
								mobile/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,46 @@ | |||||||
|  | # Miscellaneous | ||||||
|  | *.class | ||||||
|  | *.log | ||||||
|  | *.pyc | ||||||
|  | *.swp | ||||||
|  | .DS_Store | ||||||
|  | .atom/ | ||||||
|  | .buildlog/ | ||||||
|  | .history | ||||||
|  | .svn/ | ||||||
|  |  | ||||||
|  | # IntelliJ related | ||||||
|  | *.iml | ||||||
|  | *.ipr | ||||||
|  | *.iws | ||||||
|  | .idea/ | ||||||
|  |  | ||||||
|  | # The .vscode folder contains launch configuration and tasks you configure in | ||||||
|  | # VS Code which you may wish to be included in version control, so this line | ||||||
|  | # is commented out by default. | ||||||
|  | #.vscode/ | ||||||
|  |  | ||||||
|  | # Flutter/Dart/Pub related | ||||||
|  | **/doc/api/ | ||||||
|  | **/ios/Flutter/.last_build_id | ||||||
|  | .dart_tool/ | ||||||
|  | .flutter-plugins | ||||||
|  | .flutter-plugins-dependencies | ||||||
|  | .packages | ||||||
|  | .pub-cache/ | ||||||
|  | .pub/ | ||||||
|  | /build/ | ||||||
|  |  | ||||||
|  | # Web related | ||||||
|  | lib/generated_plugin_registrant.dart | ||||||
|  |  | ||||||
|  | # Symbolication related | ||||||
|  | app.*.symbols | ||||||
|  |  | ||||||
|  | # Obfuscation related | ||||||
|  | app.*.map.json | ||||||
|  |  | ||||||
|  | # Android Studio will place build artifacts here | ||||||
|  | /android/app/debug | ||||||
|  | /android/app/profile | ||||||
|  | /android/app/release | ||||||
							
								
								
									
										10
									
								
								mobile/.metadata
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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: 77d935af4db863f6abd0b9c31c7e6df2a13de57b | ||||||
|  |   channel: stable | ||||||
|  |  | ||||||
|  | project_type: app | ||||||
							
								
								
									
										16
									
								
								mobile/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | # immich_mobile | ||||||
|  |  | ||||||
|  | A new Flutter project. | ||||||
|  |  | ||||||
|  | ## Getting Started | ||||||
|  |  | ||||||
|  | This project is a starting point for a Flutter application. | ||||||
|  |  | ||||||
|  | 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. | ||||||
							
								
								
									
										29
									
								
								mobile/analysis_options.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | |||||||
|  | # This file configures the analyzer, which statically analyzes Dart code to | ||||||
|  | # check for errors, warnings, and lints. | ||||||
|  | # | ||||||
|  | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled | ||||||
|  | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be | ||||||
|  | # invoked from the command line by running `flutter analyze`. | ||||||
|  |  | ||||||
|  | # The following line activates a set of recommended lints for Flutter apps, | ||||||
|  | # packages, and plugins designed to encourage good coding practices. | ||||||
|  | include: package:flutter_lints/flutter.yaml | ||||||
|  |  | ||||||
|  | linter: | ||||||
|  |   # The lint rules applied to this project can be customized in the | ||||||
|  |   # section below to disable rules from the `package:flutter_lints/flutter.yaml` | ||||||
|  |   # included above or to enable additional rules. A list of all available lints | ||||||
|  |   # and their documentation is published at | ||||||
|  |   # https://dart-lang.github.io/linter/lints/index.html. | ||||||
|  |   # | ||||||
|  |   # Instead of disabling a lint rule for the entire project in the | ||||||
|  |   # section below, it can also be suppressed for a single line of code | ||||||
|  |   # or a specific dart file by using the `// ignore: name_of_lint` and | ||||||
|  |   # `// ignore_for_file: name_of_lint` syntax on the line or in the file | ||||||
|  |   # producing the lint. | ||||||
|  |   rules: | ||||||
|  |     # avoid_print: false  # Uncomment to disable the `avoid_print` rule | ||||||
|  |     # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule | ||||||
|  |  | ||||||
|  | # Additional information about this file can be found at | ||||||
|  | # https://dart.dev/guides/language/analysis-options | ||||||
							
								
								
									
										13
									
								
								mobile/android/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | |||||||
|  | gradle-wrapper.jar | ||||||
|  | /.gradle | ||||||
|  | /captures/ | ||||||
|  | /gradlew | ||||||
|  | /gradlew.bat | ||||||
|  | /local.properties | ||||||
|  | GeneratedPluginRegistrant.java | ||||||
|  |  | ||||||
|  | # Remember to never publicly share your keystore. | ||||||
|  | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app | ||||||
|  | key.properties | ||||||
|  | **/*.keystore | ||||||
|  | **/*.jks | ||||||
							
								
								
									
										68
									
								
								mobile/android/app/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,68 @@ | |||||||
|  | def localProperties = new Properties() | ||||||
|  | def localPropertiesFile = rootProject.file('local.properties') | ||||||
|  | if (localPropertiesFile.exists()) { | ||||||
|  |     localPropertiesFile.withReader('UTF-8') { reader -> | ||||||
|  |         localProperties.load(reader) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | def flutterRoot = localProperties.getProperty('flutter.sdk') | ||||||
|  | if (flutterRoot == null) { | ||||||
|  |     throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') | ||||||
|  | if (flutterVersionCode == null) { | ||||||
|  |     flutterVersionCode = '1' | ||||||
|  | } | ||||||
|  |  | ||||||
|  | def flutterVersionName = localProperties.getProperty('flutter.versionName') | ||||||
|  | if (flutterVersionName == null) { | ||||||
|  |     flutterVersionName = '1.0' | ||||||
|  | } | ||||||
|  |  | ||||||
|  | apply plugin: 'com.android.application' | ||||||
|  | apply plugin: 'kotlin-android' | ||||||
|  | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" | ||||||
|  |  | ||||||
|  | android { | ||||||
|  |     compileSdkVersion flutter.compileSdkVersion | ||||||
|  |  | ||||||
|  |     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.example.immich_mobile" | ||||||
|  |         minSdkVersion flutter.minSdkVersion | ||||||
|  |         targetSdkVersion flutter.targetSdkVersion | ||||||
|  |         versionCode flutterVersionCode.toInteger() | ||||||
|  |         versionName flutterVersionName | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buildTypes { | ||||||
|  |         release { | ||||||
|  |             // TODO: Add your own signing config for the release build. | ||||||
|  |             // Signing with the debug keys for now, so `flutter run --release` works. | ||||||
|  |             signingConfig signingConfigs.debug | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | flutter { | ||||||
|  |     source '../..' | ||||||
|  | } | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								mobile/android/app/src/debug/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | |||||||
|  | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     package="com.example.immich_mobile"> | ||||||
|  |     <!-- Flutter needs it to communicate with the running application | ||||||
|  |          to allow setting breakpoints, to provide hot reload, etc. | ||||||
|  |     --> | ||||||
|  |     <uses-permission android:name="android.permission.INTERNET"/> | ||||||
|  | </manifest> | ||||||
							
								
								
									
										38
									
								
								mobile/android/app/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | |||||||
|  | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |           package="com.example.immich_mobile"> | ||||||
|  |     <application | ||||||
|  |             android:label="immich_mobile" | ||||||
|  |             android:name="${applicationName}" | ||||||
|  |             android:icon="@mipmap/ic_launcher"> | ||||||
|  |         <activity | ||||||
|  |                 android:name=".MainActivity" | ||||||
|  |                 android:exported="true" | ||||||
|  |                 android:launchMode="singleTop" | ||||||
|  |                 android:theme="@style/LaunchTheme" | ||||||
|  |                 android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" | ||||||
|  |                 android:hardwareAccelerated="true" | ||||||
|  |                 android:windowSoftInputMode="adjustResize"> | ||||||
|  |             <!-- Specifies an Android theme to apply to this Activity as soon as | ||||||
|  |                  the Android process has started. This theme is visible to the user | ||||||
|  |                  while the Flutter UI initializes. After that, this theme continues | ||||||
|  |                  to determine the Window background behind the Flutter UI. --> | ||||||
|  |             <meta-data | ||||||
|  |                     android:name="io.flutter.embedding.android.NormalTheme" | ||||||
|  |                     android:resource="@style/NormalTheme" | ||||||
|  |             /> | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.MAIN"/> | ||||||
|  |                 <category android:name="android.intent.category.LAUNCHER"/> | ||||||
|  |             </intent-filter> | ||||||
|  |               | ||||||
|  |         </activity> | ||||||
|  |         <!-- Don't delete the meta-data below. | ||||||
|  |              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> | ||||||
|  |         <meta-data | ||||||
|  |                 android:name="flutterEmbedding" | ||||||
|  |                 android:value="2"/> | ||||||
|  |  | ||||||
|  |         | ||||||
|  |     </application> | ||||||
|  |     <uses-permission android:name="android.permission.INTERNET"/> | ||||||
|  | </manifest> | ||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | package com.example.immich_mobile | ||||||
|  |  | ||||||
|  | import io.flutter.embedding.android.FlutterActivity | ||||||
|  |  | ||||||
|  | class MainActivity: FlutterActivity() { | ||||||
|  | } | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <!-- Modify this file to customize your launch splash screen --> | ||||||
|  | <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <item android:drawable="?android:colorBackground" /> | ||||||
|  |  | ||||||
|  |     <!-- You can insert your own image assets here --> | ||||||
|  |     <!-- <item> | ||||||
|  |         <bitmap | ||||||
|  |             android:gravity="center" | ||||||
|  |             android:src="@mipmap/launch_image" /> | ||||||
|  |     </item> --> | ||||||
|  | </layer-list> | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <!-- Modify this file to customize your launch splash screen --> | ||||||
|  | <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <item android:drawable="@android:color/white" /> | ||||||
|  |  | ||||||
|  |     <!-- You can insert your own image assets here --> | ||||||
|  |     <!-- <item> | ||||||
|  |         <bitmap | ||||||
|  |             android:gravity="center" | ||||||
|  |             android:src="@mipmap/launch_image" /> | ||||||
|  |     </item> --> | ||||||
|  | </layer-list> | ||||||
							
								
								
									
										
											BIN
										
									
								
								mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 544 B | 
							
								
								
									
										
											BIN
										
									
								
								mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 442 B | 
							
								
								
									
										
											BIN
										
									
								
								mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 721 B | 
							
								
								
									
										
											BIN
										
									
								
								mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										18
									
								
								mobile/android/app/src/main/res/values-night/styles.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <resources> | ||||||
|  |     <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> | ||||||
|  |     <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> | ||||||
|  |         <!-- Show a splash screen on the activity. Automatically removed when | ||||||
|  |              Flutter draws its first frame --> | ||||||
|  |         <item name="android:windowBackground">@drawable/launch_background</item> | ||||||
|  |     </style> | ||||||
|  |     <!-- Theme applied to the Android Window as soon as the process has started. | ||||||
|  |          This theme determines the color of the Android Window while your | ||||||
|  |          Flutter UI initializes, as well as behind your Flutter UI while its | ||||||
|  |          running. | ||||||
|  |  | ||||||
|  |          This Theme is only used starting with V2 of Flutter's Android embedding. --> | ||||||
|  |     <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> | ||||||
|  |         <item name="android:windowBackground">?android:colorBackground</item> | ||||||
|  |     </style> | ||||||
|  | </resources> | ||||||
							
								
								
									
										18
									
								
								mobile/android/app/src/main/res/values/styles.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <resources> | ||||||
|  |     <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> | ||||||
|  |     <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> | ||||||
|  |         <!-- Show a splash screen on the activity. Automatically removed when | ||||||
|  |              Flutter draws its first frame --> | ||||||
|  |         <item name="android:windowBackground">@drawable/launch_background</item> | ||||||
|  |     </style> | ||||||
|  |     <!-- Theme applied to the Android Window as soon as the process has started. | ||||||
|  |          This theme determines the color of the Android Window while your | ||||||
|  |          Flutter UI initializes, as well as behind your Flutter UI while its | ||||||
|  |          running. | ||||||
|  |  | ||||||
|  |          This Theme is only used starting with V2 of Flutter's Android embedding. --> | ||||||
|  |     <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> | ||||||
|  |         <item name="android:windowBackground">?android:colorBackground</item> | ||||||
|  |     </style> | ||||||
|  | </resources> | ||||||
							
								
								
									
										7
									
								
								mobile/android/app/src/profile/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | |||||||
|  | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     package="com.example.immich_mobile"> | ||||||
|  |     <!-- Flutter needs it to communicate with the running application | ||||||
|  |          to allow setting breakpoints, to provide hot reload, etc. | ||||||
|  |     --> | ||||||
|  |     <uses-permission android:name="android.permission.INTERNET"/> | ||||||
|  | </manifest> | ||||||
							
								
								
									
										31
									
								
								mobile/android/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,31 @@ | |||||||
|  | buildscript { | ||||||
|  |     ext.kotlin_version = '1.6.10' | ||||||
|  |     repositories { | ||||||
|  |         google() | ||||||
|  |         mavenCentral() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     dependencies { | ||||||
|  |         classpath 'com.android.tools.build:gradle:4.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}" | ||||||
|  | } | ||||||
|  | subprojects { | ||||||
|  |     project.evaluationDependsOn(':app') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | task clean(type: Delete) { | ||||||
|  |     delete rootProject.buildDir | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								mobile/android/gradle.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | org.gradle.jvmargs=-Xmx1536M | ||||||
|  | android.useAndroidX=true | ||||||
|  | android.enableJetifier=true | ||||||
							
								
								
									
										6
									
								
								mobile/android/gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | |||||||
|  | #Fri Jun 23 08:50:38 CEST 2017 | ||||||
|  | distributionBase=GRADLE_USER_HOME | ||||||
|  | distributionPath=wrapper/dists | ||||||
|  | zipStoreBase=GRADLE_USER_HOME | ||||||
|  | zipStorePath=wrapper/dists | ||||||
|  | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip | ||||||
							
								
								
									
										11
									
								
								mobile/android/settings.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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" | ||||||
							
								
								
									
										
											BIN
										
									
								
								mobile/assets/immich-logo-no-outline.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 144 KiB | 
							
								
								
									
										
											BIN
										
									
								
								mobile/assets/immich-logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 280 KiB | 
							
								
								
									
										98
									
								
								mobile/assets/immich-logo.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,98 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||||
|  | <svg version="1.1" id="svg2781" xmlns:svg="http://www.w3.org/2000/svg" | ||||||
|  | 	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 564.2 553.5" | ||||||
|  | 	 style="enable-background:new 0 0 564.2 553.5;" xml:space="preserve"> | ||||||
|  | <style type="text/css"> | ||||||
|  | 	.st0{fill:#4081EF;stroke:#512D8C;stroke-miterlimit:10;} | ||||||
|  | 	.st1{fill:#31A452;stroke:#512D8C;stroke-miterlimit:10;} | ||||||
|  | 	.st2{fill:#DE7FB3;stroke:#512D8C;stroke-miterlimit:10;} | ||||||
|  | 	.st3{fill:#FFB800;stroke:#512D8C;stroke-miterlimit:10;} | ||||||
|  | 	.st4{fill:#E64132;stroke:#512D8C;stroke-miterlimit:10;} | ||||||
|  | 	.st5{fill:#F2F5FB;stroke:#512D8C;stroke-miterlimit:10;} | ||||||
|  | </style> | ||||||
|  | <path class="st0" d="M210.5,549.6c-2.2-0.2-5.5-1-9.7-2.2c-52.4-15.7-99-46.5-133.8-88.5c-8.8-10.7-17.2-22.4-19.4-27.5 | ||||||
|  | 	c-8.1-18.1-6.3-38.7,4.8-55.4c5-7.5,13.2-15,20.5-18.7c1.2-0.6,54.1-20,55.8-20.4c0.5-0.1,0.5,0.2-0.3,2.1c-0.7,1.7-1,3.1-1.1,5.5 | ||||||
|  | 	l-0.1,3.2l2.8,5.8c8.7,17.9,19.2,32.7,33.2,46.4c6.3,6.2,7.8,7.6,13.8,12.3c22.7,18.1,52,30.7,79.9,34.3c2.5,0.3,5,0.8,5.7,1 | ||||||
|  | 	c2.8,0.9,7.7-0.8,11-3.7l1.8-1.6l-0.2,4.8c-0.1,2.7-0.6,15.4-1,28.3c-0.6,20.3-0.8,24-1.5,27.5c-3.9,20.7-18.6,37.5-38.4,44.1 | ||||||
|  | 	c-4.6,1.5-8,2.2-13.1,2.7C216.6,550.1,215.3,550,210.5,549.6z"/> | ||||||
|  | <path class="st1" d="M339.8,549.4c-4-0.4-9.4-1.6-13.2-2.9c-3.4-1.2-10-4.4-12.5-6.1c-10.9-7.4-19-17.9-23.1-30 | ||||||
|  | 	c-2.2-6.7-2.3-7.5-3.3-36.9c-0.5-14.9-0.9-27.9-0.9-28.9l0-1.9l2.3,1.8c2.6,2,6.6,3.4,8.5,3.1c0.6-0.1,3-0.5,5.3-0.8 | ||||||
|  | 	c37.7-5.3,71.2-22.2,97.4-49.1c12.2-12.5,21.4-25.5,29.9-42.4l3.5-7l0-3.6c0-3.1-0.1-3.8-1-5.7c-0.5-1.2-0.9-2.1-0.9-2.2 | ||||||
|  | 	c0.2-0.2,55.3,20.1,56.9,20.9c2.6,1.3,6.6,4.1,9.9,7c9.2,7.7,16.1,19.4,18.8,31.8c0.7,3.1,0.8,4.8,0.8,11.3c0,8.6-0.5,11.7-2.9,18.7 | ||||||
|  | 	c-1.7,5-2.9,7.2-7.1,13.1c-7.6,11-15.3,20.5-25.2,31.2c-32.8,35.4-76.5,62.5-123.4,76.3C351.6,549.6,347.2,550.1,339.8,549.4z"/> | ||||||
|  | <path class="st2" d="M255.6,438c-25.9-4.2-50.7-14.9-71.7-31c-5.2-4-8.7-7.1-14.1-12.4c-12.7-12.5-21.9-24.9-30.5-41.4 | ||||||
|  | 	c-2.3-4.4-2.4-4.7-2.4-7.1c0-8.8,8.5-15.2,16.9-12.7c5.6,1.7,9.6,6.8,9.7,12.2c0,2.6-0.8,4.6-2.6,6.2c-1.2,1.1-3.2,1.9-4.6,1.9 | ||||||
|  | 	c-1.2,0-3.3-0.8-4.3-1.6c-2.1-1.8-2-1,0.4,3.2c19.3,33.8,52.3,59.1,90,69.1c5.7,1.5,11.5,2.7,11.8,2.4c0.1-0.1-0.4-0.8-1.3-1.6 | ||||||
|  | 	c-5.1-4.5-2.3-11.7,5-12.8c5.4-0.8,11.4,2.7,13.9,8c0.8,1.7,1,2.5,1,5.3s-0.1,3.5-1,5.3c-2,4.3-6.8,7.9-10.3,7.8 | ||||||
|  | 	C260.6,438.7,257.9,438.3,255.6,438z"/> | ||||||
|  | <path class="st0" d="M297.6,438.2c-3.4-1.3-6.4-4.3-7.8-8.1c-1.1-2.9-0.9-7.3,0.5-10.2c2.6-5.3,8.7-8.5,14.4-7.5 | ||||||
|  | 	c2.9,0.5,4.7,1.9,6,4.3c0.8,1.6,1,2.2,0.8,3.6c-0.3,2.2-0.9,3.3-2.7,4.8c-0.8,0.7-1.4,1.4-1.3,1.5c0.5,0.5,13.4-2.7,21.3-5.4 | ||||||
|  | 	c33.6-11.3,62.5-35.1,80.4-66.1c2.5-4.4,2.6-5,0.5-3.2c-2.8,2.4-7,1.9-9.6-1c-4-4.6-0.7-13.8,6.1-16.9c2-0.9,2.7-1,5.5-1 | ||||||
|  | 	c2.9,0,3.5,0.1,5.6,1.1c4.4,2.1,7.4,6.4,7.8,11c0.2,2.2,0.1,2.3-2.2,6.9c-23,45.9-67,78.1-117.2,85.9 | ||||||
|  | 	C300.2,438.8,299.4,438.9,297.6,438.2z"/> | ||||||
|  | <path class="st1" d="M211.1,398.5c-4.7-0.9-8.7-2.7-12.9-5.9c-10.8-8.1-13.5-22.3-6.6-33.7c0.7-1.2,1.1-2.2,1-2.4 | ||||||
|  | 	c-0.2-0.2-1.2-0.6-2.3-1.1c-7.6-3-13-10.6-13.5-19.1c-0.5-7.4,3.1-15,9-19.4c1-0.7,2.2-1.5,2.6-1.8c0.8-0.4,68.9-22.7,69.4-22.7 | ||||||
|  | 	c0.2,0,0.7,0.7,1.2,1.5c0.5,0.8,1.6,2.3,2.4,3.3c1.2,1.4,1.5,1.9,1.2,2.3c-0.2,0.3-6.9,9.5-14.8,20.5 | ||||||
|  | 	c-15.9,21.9-15.5,21.3-13.4,23.4c1.3,1.3,2.9,1.4,4.4,0.3c0.6-0.4,7.5-9.7,15.5-20.7c11.2-15.4,14.6-19.9,15-19.7 | ||||||
|  | 	c0.9,0.4,5.5,1.9,6.6,2.1l1,0.2l0,35.3c0,39.7,0,38.8-2.5,44c-2.6,5.3-7.2,9.3-12.7,11.2c-3.7,1.3-6.8,1.6-10.2,1 | ||||||
|  | 	c-5.5-0.9-9.8-3.2-13.7-7.4l-2.2-2.4l-0.6,0.9c-3,4.3-8.6,8.1-14,9.5C218.2,398.6,213.2,398.9,211.1,398.5z"/> | ||||||
|  | <path class="st3" d="M342.9,398.5c-5.5-0.9-9.9-3.2-14.3-7.6l-3.2-3.2l-0.7,1c-2.3,3.3-6.8,6.5-11.1,7.9c-3.7,1.2-9.2,1.4-12.6,0.3 | ||||||
|  | 	c-7.1-2.1-12.7-7.4-15.2-14.3l-0.9-2.6v-37.1v-37.1l1.8-0.4c1-0.2,2.7-0.8,3.9-1.2c1.1-0.5,2.1-0.8,2.2-0.7c0.1,0.1,6.5,9,14.4,19.9 | ||||||
|  | 	c7.8,10.9,14.7,20.1,15.2,20.5c2.2,1.9,5.4,0.4,5.4-2.6c0-1.4-1-2.9-13.8-20.5c-7.6-10.5-14.2-19.6-14.7-20.4l-0.9-1.3l1.4-1.7 | ||||||
|  | 	c0.8-0.9,1.9-2.5,2.5-3.4l1-1.6l34.4,11.2c18.9,6.2,35.1,11.6,35.9,12.1c6.8,4,11.1,11.3,11.1,19.1c0,4.1-0.5,6.4-2.4,10.2 | ||||||
|  | 	c-2,4.1-5.5,7.6-9.6,9.7c-1.6,0.8-3.2,1.5-3.4,1.5c-1,0-0.9,0.7,0.3,2.6c2.8,4.3,4,8.5,3.9,13.7c0,8.1-3.7,15.2-10.6,20.3 | ||||||
|  | 	C356.4,397.6,349.5,399.5,342.9,398.5z"/> | ||||||
|  | <path class="st2" d="M53.9,341.9c-0.5-0.1-2.3-0.4-3.9-0.7c-15.6-2.6-30.4-12.6-38.8-26.2c-3.5-5.7-6.4-13.2-7.8-19.9 | ||||||
|  | 	c-1.2-6.1-0.8-28.1,0.8-43.1c4.5-43,19-84.3,42.2-120.7c6.5-10.2,14.9-21.5,18.2-24.6c17.8-16.6,43.1-20.5,64.8-10 | ||||||
|  | 	c4.3,2.1,8.8,5.1,12.7,8.6c2.8,2.4,5.8,6.1,20.9,25.5c9.7,12.5,17.8,22.8,17.9,23c0.2,0.2-0.9,0.4-3.2,0.4c-2.5,0-4.1,0.2-5.7,0.7 | ||||||
|  | 	c-2.1,0.7-2.6,1.1-7.9,6.3c-8.2,8.1-14.4,15.3-20.3,23.9c-15.5,22.2-25.4,47.7-28.8,74.8c-2.2,16.9-1.6,37.5,1.6,52.3 | ||||||
|  | 	c0.3,1.4,0.5,2.8,0.4,3c-0.1,0.2,0.2,1.3,0.8,2.4c1.1,2.4,4.3,5.7,6.5,6.8l1.5,0.8l-1.2,0.4c-0.7,0.2-13.1,3.8-27.6,8 | ||||||
|  | 	c-16.4,4.7-27.7,7.8-29.8,8.1C64.1,342.1,56.1,342.3,53.9,341.9z"/> | ||||||
|  | <path class="st3" d="M494.7,341.7c-2.1-0.3-33.8-9.1-56.5-15.8l-2.5-0.7l1.6-0.8c3.4-1.7,7.2-6.6,7.3-9.6c0-0.7,0.4-3.3,0.8-5.8 | ||||||
|  | 	c3.9-22.7,3.1-46.1-2.5-68.4c-6.4-25.5-18.6-49.2-35.8-69.1c-4.6-5.3-14.8-15.4-16.4-16.1c-2.4-1.1-5.1-1.6-8-1.4l-2.7,0.2l1.2-1.5 | ||||||
|  | 	c0.7-0.8,8.5-10.8,17.5-22.3c8.9-11.5,17.2-21.8,18.5-23.1c2.6-2.7,7-6.2,10.3-8.2c19.3-11.6,43-11.1,61.6,1.2 | ||||||
|  | 	c5.4,3.6,8.2,6.2,12.3,11.7c26.4,34.5,44,73.7,52.3,116.2c3.4,17.6,4.9,33.3,5,52.4c0,13-0.2,14.8-2.5,21.8 | ||||||
|  | 	C547.8,328.6,521.7,345.2,494.7,341.7z"/> | ||||||
|  | <path class="st4" d="M133.9,318.5c-2-0.5-4.6-1.9-6-3.3c-2.5-2.4-3.1-3.5-3.7-7.3c-4.4-27.3-2.2-54,6.7-79.3 | ||||||
|  | 	c5.3-15.1,13.5-30.5,23-43.1c5.8-7.8,16.6-19.5,19-20.7c4.7-2.4,11.3-1.2,15.2,2.7c5.4,5.4,5.2,13.9-0.3,19.1 | ||||||
|  | 	c-4.3,4-9.4,4.4-12.6,0.9c-1.7-1.9-2.2-3.9-1.7-6.4c0.2-1.1,0.3-2,0.2-2.2c-0.3-0.3-3.6,3.3-8.3,9.1c-17.6,21.8-28.5,48-31.9,76.5 | ||||||
|  | 	c-1.1,9.3-1,26.4,0.1,34.6c0.3,1.8,0.8,1.9,1.4,0.1c0.9-2.6,4-4.7,6.8-4.7c3,0,5.9,2.2,7.5,5.7c0.6,1.3,0.8,2.3,0.8,5.2 | ||||||
|  | 	c0,3.3-0.1,3.8-1.1,5.7c-1.4,2.7-4.6,5.7-7.1,6.6C139.4,318.6,135.8,318.9,133.9,318.5z"/> | ||||||
|  | <path class="st1" d="M422.6,318.5c-3.7-0.6-7.7-3.6-9.4-7.1c-3.8-7.5,0.1-16.9,6.9-16.9c3.1,0,5.8,2,6.9,5.2 | ||||||
|  | 	c0.4,1.2,0.5,1.3,0.7,0.7c1.3-3.7,1.7-26.4,0.6-35.7c-3.6-29.6-14.5-55.3-33-77.9c-5.5-6.7-8.4-9.4-7.1-6.6c0.7,1.4,0.5,4.3-0.3,5.9 | ||||||
|  | 	c-0.9,1.7-3.2,3.5-5,3.8c-3.2,0.6-7.9-1.6-10.2-4.8c-6.5-8.8-0.5-21.2,10.4-21.4c4.6-0.1,5.2,0.3,11.2,6.4 | ||||||
|  | 	c12.1,12.3,21.1,24.9,28.8,40.3c13.2,26.3,18.6,54.9,16.1,84.5c-0.5,5.6-2,15.7-2.6,17.1c-1.3,2.8-4.8,5.5-8.4,6.5 | ||||||
|  | 	C425.9,318.9,425.1,318.9,422.6,318.5z"/> | ||||||
|  | <path class="st0" d="M178.2,307.2c-6-1.3-12.2-6.2-14.9-11.7c-3.4-7-3.1-15.1,0.9-21.6c0.7-1.2,1.2-2.3,1.1-2.4 | ||||||
|  | 	c-0.1-0.1-1.1-0.6-2.1-1c-3.9-1.5-8.1-4.8-10.7-8.3c-4.6-6.2-6.1-14.6-3.9-22.1c2.9-10.3,9.4-16.8,19.1-19.3c2.8-0.7,9-0.8,11.7,0 | ||||||
|  | 	c1.1,0.3,2.2,0.5,2.4,0.5c0.2,0,0.3-0.7,0.3-1.5c0-2.9,0.8-5.8,2.4-9.2c5.2-10.8,18.1-15.5,29-10.5c2.7,1.2,6.2,3.8,7.8,5.8 | ||||||
|  | 	c0.7,0.8,10.3,14,21.5,29.4l20.3,27.9l-1.5,1.8c-0.8,1-1.9,2.6-2.5,3.5c-0.6,1-1.2,1.7-1.5,1.6c-4.5-1.7-46.7-15-47.7-15 | ||||||
|  | 	c-1.9,0-3.1,1.3-3.1,3.2c0,1,0.2,1.7,0.8,2.3c0.6,0.6,7.8,3.1,24.5,8.5l23.7,7.7l-0.1,4.3l-0.1,4.3L223,295.9 | ||||||
|  | 	c-18,5.9-33.9,10.9-35.2,11.2C184.7,307.8,181.2,307.8,178.2,307.2z"/> | ||||||
|  | <path class="st4" d="M372.5,306.8c-1.8-0.5-17.5-5.6-35-11.3l-31.8-10.4l1-4.3v-4.3l22.6-7.7c15-4.9,24-8,24.6-8.5 | ||||||
|  | 	c0.7-0.6,0.9-1.1,0.9-2.2c0-2-1.2-3.3-3.1-3.3c-0.9,0-10.5,2.9-24.7,7.5c-12.8,4.1-23.4,7.5-23.6,7.5c-0.1,0-0.7-0.8-1.3-1.9 | ||||||
|  | 	c-0.6-1-1.6-2.5-2.2-3.2c-0.7-0.7-1.2-1.5-1.2-1.6c0-0.2,9.6-13.5,21.4-29.6c18.9-26,21.6-29.6,23.6-31.1c5.7-4.4,13.1-5.8,19.7-3.9 | ||||||
|  | 	c9,2.7,16.1,11.6,16.1,20.3c0,2.3-0.1,2.3,3.1,1.5c4.7-1.1,11.5-0.5,16,1.5c4.6,2,9,6,11.5,10.2c2.1,3.6,3.9,9.4,4.2,13.2 | ||||||
|  | 	c0.3,5.2-1.1,10.7-4,15.3c-2.6,4.1-7.8,8.3-12.1,9.8c-0.9,0.3-1.7,0.8-1.7,1c0,0.2,0.4,1,0.9,1.7c2.4,3.6,3.6,7.7,3.5,12.7 | ||||||
|  | 	c0,5.8-2.1,10.7-6.4,15.1c-4,4.1-8.9,6.3-14.9,6.5C376.3,307.7,375.3,307.6,372.5,306.8z"/> | ||||||
|  | <path class="st5" d="M276.2,298.9c-6.1-1.6-11.4-6.8-13.2-12.9c-0.7-2.4-0.7-7.5,0-9.9c1.7-5.8,6.6-10.8,12.3-12.5 | ||||||
|  | 	c2.7-0.8,7.2-0.9,10-0.2c6.2,1.6,11.6,7.1,13.2,13.3c1.6,6-0.3,12.6-5,17.3C288.9,298.6,282.2,300.5,276.2,298.9z"/> | ||||||
|  | <path class="st2" d="M248.3,229.8c-13.3-18.3-21.2-29.6-22-31.1c-1.4-3-1.9-5.5-1.9-9.4c0-14.1,13.1-24.4,27.1-21.4 | ||||||
|  | 	c1.4,0.3,2.6,0.5,2.7,0.5s0.3-1.3,0.4-2.8c0.8-10.7,8.4-19.6,18.9-22.4c3.9-1,10.6-1,14.5,0c8.9,2.3,15.9,9.3,18.2,18.2 | ||||||
|  | 	c0.4,1.5,0.7,3.7,0.7,4.9c0,1.2,0.1,2.1,0.3,2.1s1.5-0.3,3-0.6c7.4-1.6,15.2,0.7,20.5,6c4.3,4.3,6.6,9.6,6.6,15.6 | ||||||
|  | 	c0,4-0.6,6.5-2.4,10c-0.6,1.2-10.4,15-21.7,30.7c-17.8,24.5-20.8,28.5-21.4,28.3c-0.4-0.1-1.9-0.6-3.4-1.1c-1.5-0.5-2.9-0.9-3.3-0.9 | ||||||
|  | 	c-0.7,0-0.7-0.8-0.3-25.5v-25.5l-1.4-0.9c-1-1.1-2.5-1.5-3.8-0.9c-2,0.8-2-0.5-1.8,27.2v25.8h-1.2c-0.5-0.2-2.4,0.3-4,0.9 | ||||||
|  | 	s-3.1,1.1-3.2,1.1C269.2,258.5,259.8,245.6,248.3,229.8z"/> | ||||||
|  | <path class="st3" d="M210.9,164.8c-4.1-0.9-7.7-3.6-9.6-7.4c-1.4-2.8-1.7-7.3-0.5-10.3c1.7-4.5,3.9-6.1,15.6-11.2 | ||||||
|  | 	c15.8-7,31.4-11.1,49.2-12.9c7.3-0.8,23.2-0.8,30.6,0c17.4,1.8,33.3,6,49.1,13c7.3,3.2,12.5,6.1,13.6,7.5c4.3,5.6,3.8,12.7-1.1,17.6 | ||||||
|  | 	c-5.1,5.1-12.9,5.4-18.1,0.7c-2-1.8-3-3.5-3.4-5.6c-0.7-4,2.9-8.1,7.3-8.2c1.4,0,1.5-0.1,1.1-0.5c-0.3-0.3-2.2-1.2-4.3-2.1 | ||||||
|  | 	c-33.2-14.5-70.5-16.4-105-5.4c-7.5,2.4-19,7.2-18.6,7.7c0.1,0.2,0.8,0.3,1.6,0.3c5.6,0,9.1,6.2,6.1,10.8 | ||||||
|  | 	C221.6,163.3,215.9,165.9,210.9,164.8z"/> | ||||||
|  | <path class="st4" d="M174.7,123.4c-8.9-13.1-16.8-25.1-17.5-26.6c-1.6-3.3-3.6-9.2-4.4-13c-2.6-12.5-0.9-25.8,5-37.5 | ||||||
|  | 	c4.2-8.3,11.2-16.3,18.6-21.3c5-3.4,6.1-3.9,12.8-6.3c23.1-8.2,47.2-13.1,73.4-15c7.5-0.6,28.5-0.6,36.3,0 | ||||||
|  | 	c25.5,1.8,50.6,6.9,73,14.8c6.4,2.2,8.2,3.1,13.1,6.5c9.8,6.6,18.1,17.5,22,29.2c2.2,6.5,2.7,10,2.7,17.9c0,7.9-0.5,11.3-2.7,17.9 | ||||||
|  | 	c-2.3,6.8-3.7,9.1-20.3,33.6l-16.1,23.8l-0.4-2.2c-0.2-1.2-0.9-3-1.4-4c-1-1.8-4.4-5.6-4.7-5.2c-0.1,0.1-1.2-0.4-2.4-1.1 | ||||||
|  | 	c-9.1-5.2-21.9-10.5-33.2-13.9c-37-11-77.2-8.8-113,6.1c-4.9,2.1-17.7,8.4-19.2,9.5c-2.2,1.6-5.1,6.8-5.1,9c0,0.4-0.1,1-0.3,1.2 | ||||||
|  | 	C191,147,184.7,138,174.7,123.4z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 9.7 KiB | 
							
								
								
									
										34
									
								
								mobile/ios/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,34 @@ | |||||||
|  | **/dgph | ||||||
|  | *.mode1v3 | ||||||
|  | *.mode2v3 | ||||||
|  | *.moved-aside | ||||||
|  | *.pbxuser | ||||||
|  | *.perspectivev3 | ||||||
|  | **/*sync/ | ||||||
|  | .sconsign.dblite | ||||||
|  | .tags* | ||||||
|  | **/.vagrant/ | ||||||
|  | **/DerivedData/ | ||||||
|  | Icon? | ||||||
|  | **/Pods/ | ||||||
|  | **/.symlinks/ | ||||||
|  | profile | ||||||
|  | xcuserdata | ||||||
|  | **/.generated/ | ||||||
|  | Flutter/App.framework | ||||||
|  | Flutter/Flutter.framework | ||||||
|  | Flutter/Flutter.podspec | ||||||
|  | Flutter/Generated.xcconfig | ||||||
|  | Flutter/ephemeral/ | ||||||
|  | Flutter/app.flx | ||||||
|  | Flutter/app.zip | ||||||
|  | Flutter/flutter_assets/ | ||||||
|  | Flutter/flutter_export_environment.sh | ||||||
|  | ServiceDefinitions.json | ||||||
|  | Runner/GeneratedPluginRegistrant.* | ||||||
|  |  | ||||||
|  | # Exceptions to above rules. | ||||||
|  | !default.mode1v3 | ||||||
|  | !default.mode2v3 | ||||||
|  | !default.pbxuser | ||||||
|  | !default.perspectivev3 | ||||||
							
								
								
									
										26
									
								
								mobile/ios/Flutter/AppFrameworkInfo.plist
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,26 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
|  | <plist version="1.0"> | ||||||
|  | <dict> | ||||||
|  |   <key>CFBundleDevelopmentRegion</key> | ||||||
|  |   <string>en</string> | ||||||
|  |   <key>CFBundleExecutable</key> | ||||||
|  |   <string>App</string> | ||||||
|  |   <key>CFBundleIdentifier</key> | ||||||
|  |   <string>io.flutter.flutter.app</string> | ||||||
|  |   <key>CFBundleInfoDictionaryVersion</key> | ||||||
|  |   <string>6.0</string> | ||||||
|  |   <key>CFBundleName</key> | ||||||
|  |   <string>App</string> | ||||||
|  |   <key>CFBundlePackageType</key> | ||||||
|  |   <string>FMWK</string> | ||||||
|  |   <key>CFBundleShortVersionString</key> | ||||||
|  |   <string>1.0</string> | ||||||
|  |   <key>CFBundleSignature</key> | ||||||
|  |   <string>????</string> | ||||||
|  |   <key>CFBundleVersion</key> | ||||||
|  |   <string>1.0</string> | ||||||
|  |   <key>MinimumOSVersion</key> | ||||||
|  |   <string>9.0</string> | ||||||
|  | </dict> | ||||||
|  | </plist> | ||||||
							
								
								
									
										2
									
								
								mobile/ios/Flutter/Debug.xcconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" | ||||||
|  | #include "Generated.xcconfig" | ||||||
							
								
								
									
										2
									
								
								mobile/ios/Flutter/Release.xcconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" | ||||||
|  | #include "Generated.xcconfig" | ||||||
							
								
								
									
										41
									
								
								mobile/ios/Podfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||||
							
								
								
									
										50
									
								
								mobile/ios/Podfile.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,50 @@ | |||||||
|  | PODS: | ||||||
|  |   - device_info_plus (0.0.1): | ||||||
|  |     - Flutter | ||||||
|  |   - Flutter (1.0.0) | ||||||
|  |   - FMDB (2.7.5): | ||||||
|  |     - FMDB/standard (= 2.7.5) | ||||||
|  |   - FMDB/standard (2.7.5) | ||||||
|  |   - path_provider_ios (0.0.1): | ||||||
|  |     - Flutter | ||||||
|  |   - photo_manager (1.0.0): | ||||||
|  |     - Flutter | ||||||
|  |     - FlutterMacOS | ||||||
|  |   - sqflite (0.0.2): | ||||||
|  |     - Flutter | ||||||
|  |     - FMDB (>= 2.7.5) | ||||||
|  |  | ||||||
|  | DEPENDENCIES: | ||||||
|  |   - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) | ||||||
|  |   - Flutter (from `Flutter`) | ||||||
|  |   - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) | ||||||
|  |   - photo_manager (from `.symlinks/plugins/photo_manager/ios`) | ||||||
|  |   - sqflite (from `.symlinks/plugins/sqflite/ios`) | ||||||
|  |  | ||||||
|  | SPEC REPOS: | ||||||
|  |   trunk: | ||||||
|  |     - FMDB | ||||||
|  |  | ||||||
|  | EXTERNAL SOURCES: | ||||||
|  |   device_info_plus: | ||||||
|  |     :path: ".symlinks/plugins/device_info_plus/ios" | ||||||
|  |   Flutter: | ||||||
|  |     :path: Flutter | ||||||
|  |   path_provider_ios: | ||||||
|  |     :path: ".symlinks/plugins/path_provider_ios/ios" | ||||||
|  |   photo_manager: | ||||||
|  |     :path: ".symlinks/plugins/photo_manager/ios" | ||||||
|  |   sqflite: | ||||||
|  |     :path: ".symlinks/plugins/sqflite/ios" | ||||||
|  |  | ||||||
|  | SPEC CHECKSUMS: | ||||||
|  |   device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed | ||||||
|  |   Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a | ||||||
|  |   FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a | ||||||
|  |   path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5 | ||||||
|  |   photo_manager: 84fa94fbeb82e607333ea9a13c43b58e0903a463 | ||||||
|  |   sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 | ||||||
|  |  | ||||||
|  | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c | ||||||
|  |  | ||||||
|  | COCOAPODS: 1.10.1 | ||||||
							
								
								
									
										551
									
								
								mobile/ios/Runner.xcodeproj/project.pbxproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,551 @@ | |||||||
|  | // !$*UTF8*$! | ||||||
|  | { | ||||||
|  | 	archiveVersion = 1; | ||||||
|  | 	classes = { | ||||||
|  | 	}; | ||||||
|  | 	objectVersion = 51; | ||||||
|  | 	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 */; }; | ||||||
|  | 		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 */; }; | ||||||
|  | 		D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */; }; | ||||||
|  | /* End PBXBuildFile section */ | ||||||
|  |  | ||||||
|  | /* Begin PBXCopyFilesBuildPhase section */ | ||||||
|  | 		9705A1C41CF9048500538489 /* Embed Frameworks */ = { | ||||||
|  | 			isa = PBXCopyFilesBuildPhase; | ||||||
|  | 			buildActionMask = 2147483647; | ||||||
|  | 			dstPath = ""; | ||||||
|  | 			dstSubfolderSpec = 10; | ||||||
|  | 			files = ( | ||||||
|  | 			); | ||||||
|  | 			name = "Embed Frameworks"; | ||||||
|  | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
|  | 		}; | ||||||
|  | /* End PBXCopyFilesBuildPhase section */ | ||||||
|  |  | ||||||
|  | /* Begin PBXFileReference section */ | ||||||
|  | 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; | ||||||
|  | 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; | ||||||
|  | 		2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; | ||||||
|  | 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; | ||||||
|  | 		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; | ||||||
|  | 		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; | ||||||
|  | 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; | ||||||
|  | 		886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||||
|  | 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; | ||||||
|  | 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; | ||||||
|  | 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; | ||||||
|  | 		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; | ||||||
|  | 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; | ||||||
|  | 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; | ||||||
|  | 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; | ||||||
|  | 		E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; | ||||||
|  | 		F7101BB0391A314774615E89 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; }; | ||||||
|  | /* End PBXFileReference section */ | ||||||
|  |  | ||||||
|  | /* Begin PBXFrameworksBuildPhase section */ | ||||||
|  | 		97C146EB1CF9000F007C117D /* Frameworks */ = { | ||||||
|  | 			isa = PBXFrameworksBuildPhase; | ||||||
|  | 			buildActionMask = 2147483647; | ||||||
|  | 			files = ( | ||||||
|  | 				D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */, | ||||||
|  | 			); | ||||||
|  | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
|  | 		}; | ||||||
|  | /* End PBXFrameworksBuildPhase section */ | ||||||
|  |  | ||||||
|  | /* Begin PBXGroup section */ | ||||||
|  | 		0FB772A5B9601143383626CA /* Pods */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */, | ||||||
|  | 				E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */, | ||||||
|  | 				F7101BB0391A314774615E89 /* Pods-Runner.profile.xcconfig */, | ||||||
|  | 			); | ||||||
|  | 			path = Pods; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
|  | 		1754452DD81DA6620E279E51 /* Frameworks */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */, | ||||||
|  | 			); | ||||||
|  | 			name = Frameworks; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
|  | 		9740EEB11CF90186004384FC /* Flutter */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, | ||||||
|  | 				9740EEB21CF90195004384FC /* Debug.xcconfig */, | ||||||
|  | 				7AFA3C8E1D35360C0083082E /* Release.xcconfig */, | ||||||
|  | 				9740EEB31CF90195004384FC /* Generated.xcconfig */, | ||||||
|  | 			); | ||||||
|  | 			name = Flutter; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
|  | 		97C146E51CF9000F007C117D = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				9740EEB11CF90186004384FC /* Flutter */, | ||||||
|  | 				97C146F01CF9000F007C117D /* Runner */, | ||||||
|  | 				97C146EF1CF9000F007C117D /* Products */, | ||||||
|  | 				0FB772A5B9601143383626CA /* Pods */, | ||||||
|  | 				1754452DD81DA6620E279E51 /* Frameworks */, | ||||||
|  | 			); | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
|  | 		97C146EF1CF9000F007C117D /* Products */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				97C146EE1CF9000F007C117D /* Runner.app */, | ||||||
|  | 			); | ||||||
|  | 			name = Products; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
|  | 		97C146F01CF9000F007C117D /* Runner */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				97C146FA1CF9000F007C117D /* Main.storyboard */, | ||||||
|  | 				97C146FD1CF9000F007C117D /* Assets.xcassets */, | ||||||
|  | 				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, | ||||||
|  | 				97C147021CF9000F007C117D /* Info.plist */, | ||||||
|  | 				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, | ||||||
|  | 				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, | ||||||
|  | 				74858FAE1ED2DC5600515810 /* AppDelegate.swift */, | ||||||
|  | 				74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, | ||||||
|  | 			); | ||||||
|  | 			path = Runner; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
|  | /* End PBXGroup section */ | ||||||
|  |  | ||||||
|  | /* Begin PBXNativeTarget section */ | ||||||
|  | 		97C146ED1CF9000F007C117D /* Runner */ = { | ||||||
|  | 			isa = PBXNativeTarget; | ||||||
|  | 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; | ||||||
|  | 			buildPhases = ( | ||||||
|  | 				4044AF030EF7D8721844FFBA /* [CP] Check Pods Manifest.lock */, | ||||||
|  | 				9740EEB61CF901F6004384FC /* Run Script */, | ||||||
|  | 				97C146EA1CF9000F007C117D /* Sources */, | ||||||
|  | 				97C146EB1CF9000F007C117D /* Frameworks */, | ||||||
|  | 				97C146EC1CF9000F007C117D /* Resources */, | ||||||
|  | 				9705A1C41CF9048500538489 /* Embed Frameworks */, | ||||||
|  | 				3B06AD1E1E4923F5004D2608 /* Thin Binary */, | ||||||
|  | 				D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */, | ||||||
|  | 			); | ||||||
|  | 			buildRules = ( | ||||||
|  | 			); | ||||||
|  | 			dependencies = ( | ||||||
|  | 			); | ||||||
|  | 			name = Runner; | ||||||
|  | 			productName = Runner; | ||||||
|  | 			productReference = 97C146EE1CF9000F007C117D /* Runner.app */; | ||||||
|  | 			productType = "com.apple.product-type.application"; | ||||||
|  | 		}; | ||||||
|  | /* End PBXNativeTarget section */ | ||||||
|  |  | ||||||
|  | /* Begin PBXProject section */ | ||||||
|  | 		97C146E61CF9000F007C117D /* Project object */ = { | ||||||
|  | 			isa = PBXProject; | ||||||
|  | 			attributes = { | ||||||
|  | 				LastUpgradeCheck = 1300; | ||||||
|  | 				ORGANIZATIONNAME = ""; | ||||||
|  | 				TargetAttributes = { | ||||||
|  | 					97C146ED1CF9000F007C117D = { | ||||||
|  | 						CreatedOnToolsVersion = 7.3.1; | ||||||
|  | 						LastSwiftMigration = 1100; | ||||||
|  | 					}; | ||||||
|  | 				}; | ||||||
|  | 			}; | ||||||
|  | 			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; | ||||||
|  | 			compatibilityVersion = "Xcode 9.3"; | ||||||
|  | 			developmentRegion = en; | ||||||
|  | 			hasScannedForEncodings = 0; | ||||||
|  | 			knownRegions = ( | ||||||
|  | 				en, | ||||||
|  | 				Base, | ||||||
|  | 			); | ||||||
|  | 			mainGroup = 97C146E51CF9000F007C117D; | ||||||
|  | 			productRefGroup = 97C146EF1CF9000F007C117D /* Products */; | ||||||
|  | 			projectDirPath = ""; | ||||||
|  | 			projectRoot = ""; | ||||||
|  | 			targets = ( | ||||||
|  | 				97C146ED1CF9000F007C117D /* Runner */, | ||||||
|  | 			); | ||||||
|  | 		}; | ||||||
|  | /* End PBXProject section */ | ||||||
|  |  | ||||||
|  | /* Begin PBXResourcesBuildPhase section */ | ||||||
|  | 		97C146EC1CF9000F007C117D /* Resources */ = { | ||||||
|  | 			isa = PBXResourcesBuildPhase; | ||||||
|  | 			buildActionMask = 2147483647; | ||||||
|  | 			files = ( | ||||||
|  | 				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, | ||||||
|  | 				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, | ||||||
|  | 				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, | ||||||
|  | 				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, | ||||||
|  | 			); | ||||||
|  | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
|  | 		}; | ||||||
|  | /* End PBXResourcesBuildPhase section */ | ||||||
|  |  | ||||||
|  | /* Begin PBXShellScriptBuildPhase section */ | ||||||
|  | 		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"; | ||||||
|  | 		}; | ||||||
|  | 		4044AF030EF7D8721844FFBA /* [CP] Check Pods Manifest.lock */ = { | ||||||
|  | 			isa = PBXShellScriptBuildPhase; | ||||||
|  | 			buildActionMask = 2147483647; | ||||||
|  | 			files = ( | ||||||
|  | 			); | ||||||
|  | 			inputFileListPaths = ( | ||||||
|  | 			); | ||||||
|  | 			inputPaths = ( | ||||||
|  | 				"${PODS_PODFILE_DIR_PATH}/Podfile.lock", | ||||||
|  | 				"${PODS_ROOT}/Manifest.lock", | ||||||
|  | 			); | ||||||
|  | 			name = "[CP] Check Pods Manifest.lock"; | ||||||
|  | 			outputFileListPaths = ( | ||||||
|  | 			); | ||||||
|  | 			outputPaths = ( | ||||||
|  | 				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", | ||||||
|  | 			); | ||||||
|  | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
|  | 			shellPath = /bin/sh; | ||||||
|  | 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; | ||||||
|  | 			showEnvVarsInLog = 0; | ||||||
|  | 		}; | ||||||
|  | 		9740EEB61CF901F6004384FC /* Run Script */ = { | ||||||
|  | 			isa = PBXShellScriptBuildPhase; | ||||||
|  | 			buildActionMask = 2147483647; | ||||||
|  | 			files = ( | ||||||
|  | 			); | ||||||
|  | 			inputPaths = ( | ||||||
|  | 			); | ||||||
|  | 			name = "Run Script"; | ||||||
|  | 			outputPaths = ( | ||||||
|  | 			); | ||||||
|  | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
|  | 			shellPath = /bin/sh; | ||||||
|  | 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; | ||||||
|  | 		}; | ||||||
|  | 		D218A34AEE62BC1EF119F5B0 /* [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; | ||||||
|  | 		}; | ||||||
|  | /* End PBXShellScriptBuildPhase section */ | ||||||
|  |  | ||||||
|  | /* Begin PBXSourcesBuildPhase section */ | ||||||
|  | 		97C146EA1CF9000F007C117D /* Sources */ = { | ||||||
|  | 			isa = PBXSourcesBuildPhase; | ||||||
|  | 			buildActionMask = 2147483647; | ||||||
|  | 			files = ( | ||||||
|  | 				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, | ||||||
|  | 				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, | ||||||
|  | 			); | ||||||
|  | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
|  | 		}; | ||||||
|  | /* End PBXSourcesBuildPhase section */ | ||||||
|  |  | ||||||
|  | /* Begin PBXVariantGroup section */ | ||||||
|  | 		97C146FA1CF9000F007C117D /* Main.storyboard */ = { | ||||||
|  | 			isa = PBXVariantGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				97C146FB1CF9000F007C117D /* Base */, | ||||||
|  | 			); | ||||||
|  | 			name = Main.storyboard; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
|  | 		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { | ||||||
|  | 			isa = PBXVariantGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				97C147001CF9000F007C117D /* Base */, | ||||||
|  | 			); | ||||||
|  | 			name = LaunchScreen.storyboard; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
|  | /* End PBXVariantGroup section */ | ||||||
|  |  | ||||||
|  | /* Begin XCBuildConfiguration section */ | ||||||
|  | 		249021D3217E4FDB00AE95B9 /* Profile */ = { | ||||||
|  | 			isa = XCBuildConfiguration; | ||||||
|  | 			buildSettings = { | ||||||
|  | 				ALWAYS_SEARCH_USER_PATHS = NO; | ||||||
|  | 				CLANG_ANALYZER_NONNULL = YES; | ||||||
|  | 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; | ||||||
|  | 				CLANG_CXX_LIBRARY = "libc++"; | ||||||
|  | 				CLANG_ENABLE_MODULES = YES; | ||||||
|  | 				CLANG_ENABLE_OBJC_ARC = YES; | ||||||
|  | 				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; | ||||||
|  | 				CLANG_WARN_BOOL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_COMMA = YES; | ||||||
|  | 				CLANG_WARN_CONSTANT_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; | ||||||
|  | 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | ||||||
|  | 				CLANG_WARN_EMPTY_BODY = YES; | ||||||
|  | 				CLANG_WARN_ENUM_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_INFINITE_RECURSION = YES; | ||||||
|  | 				CLANG_WARN_INT_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; | ||||||
|  | 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | ||||||
|  | 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; | ||||||
|  | 				CLANG_WARN_STRICT_PROTOTYPES = YES; | ||||||
|  | 				CLANG_WARN_SUSPICIOUS_MOVE = YES; | ||||||
|  | 				CLANG_WARN_UNREACHABLE_CODE = YES; | ||||||
|  | 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||||||
|  | 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||||||
|  | 				COPY_PHASE_STRIP = NO; | ||||||
|  | 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; | ||||||
|  | 				ENABLE_NS_ASSERTIONS = NO; | ||||||
|  | 				ENABLE_STRICT_OBJC_MSGSEND = YES; | ||||||
|  | 				GCC_C_LANGUAGE_STANDARD = gnu99; | ||||||
|  | 				GCC_NO_COMMON_BLOCKS = YES; | ||||||
|  | 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES; | ||||||
|  | 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; | ||||||
|  | 				GCC_WARN_UNDECLARED_SELECTOR = YES; | ||||||
|  | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||||
|  | 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||||
|  | 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||||
|  | 				IPHONEOS_DEPLOYMENT_TARGET = 9.0; | ||||||
|  | 				MTL_ENABLE_DEBUG_INFO = NO; | ||||||
|  | 				SDKROOT = iphoneos; | ||||||
|  | 				SUPPORTED_PLATFORMS = iphoneos; | ||||||
|  | 				TARGETED_DEVICE_FAMILY = "1,2"; | ||||||
|  | 				VALIDATE_PRODUCT = YES; | ||||||
|  | 			}; | ||||||
|  | 			name = Profile; | ||||||
|  | 		}; | ||||||
|  | 		249021D4217E4FDB00AE95B9 /* Profile */ = { | ||||||
|  | 			isa = XCBuildConfiguration; | ||||||
|  | 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; | ||||||
|  | 			buildSettings = { | ||||||
|  | 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||||||
|  | 				CLANG_ENABLE_MODULES = YES; | ||||||
|  | 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | ||||||
|  | 				DEVELOPMENT_TEAM = C24486LLLU; | ||||||
|  | 				ENABLE_BITCODE = NO; | ||||||
|  | 				INFOPLIST_FILE = Runner/Info.plist; | ||||||
|  | 				LD_RUNPATH_SEARCH_PATHS = ( | ||||||
|  | 					"$(inherited)", | ||||||
|  | 					"@executable_path/Frameworks", | ||||||
|  | 				); | ||||||
|  | 				PRODUCT_BUNDLE_IDENTIFIER = com.example.immichMobile; | ||||||
|  | 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||||
|  | 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; | ||||||
|  | 				SWIFT_VERSION = 5.0; | ||||||
|  | 				VERSIONING_SYSTEM = "apple-generic"; | ||||||
|  | 			}; | ||||||
|  | 			name = Profile; | ||||||
|  | 		}; | ||||||
|  | 		97C147031CF9000F007C117D /* Debug */ = { | ||||||
|  | 			isa = XCBuildConfiguration; | ||||||
|  | 			buildSettings = { | ||||||
|  | 				ALWAYS_SEARCH_USER_PATHS = NO; | ||||||
|  | 				CLANG_ANALYZER_NONNULL = YES; | ||||||
|  | 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; | ||||||
|  | 				CLANG_CXX_LIBRARY = "libc++"; | ||||||
|  | 				CLANG_ENABLE_MODULES = YES; | ||||||
|  | 				CLANG_ENABLE_OBJC_ARC = YES; | ||||||
|  | 				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; | ||||||
|  | 				CLANG_WARN_BOOL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_COMMA = YES; | ||||||
|  | 				CLANG_WARN_CONSTANT_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; | ||||||
|  | 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | ||||||
|  | 				CLANG_WARN_EMPTY_BODY = YES; | ||||||
|  | 				CLANG_WARN_ENUM_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_INFINITE_RECURSION = YES; | ||||||
|  | 				CLANG_WARN_INT_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; | ||||||
|  | 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | ||||||
|  | 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; | ||||||
|  | 				CLANG_WARN_STRICT_PROTOTYPES = YES; | ||||||
|  | 				CLANG_WARN_SUSPICIOUS_MOVE = YES; | ||||||
|  | 				CLANG_WARN_UNREACHABLE_CODE = YES; | ||||||
|  | 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||||||
|  | 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||||||
|  | 				COPY_PHASE_STRIP = NO; | ||||||
|  | 				DEBUG_INFORMATION_FORMAT = dwarf; | ||||||
|  | 				ENABLE_STRICT_OBJC_MSGSEND = YES; | ||||||
|  | 				ENABLE_TESTABILITY = YES; | ||||||
|  | 				GCC_C_LANGUAGE_STANDARD = gnu99; | ||||||
|  | 				GCC_DYNAMIC_NO_PIC = NO; | ||||||
|  | 				GCC_NO_COMMON_BLOCKS = YES; | ||||||
|  | 				GCC_OPTIMIZATION_LEVEL = 0; | ||||||
|  | 				GCC_PREPROCESSOR_DEFINITIONS = ( | ||||||
|  | 					"DEBUG=1", | ||||||
|  | 					"$(inherited)", | ||||||
|  | 				); | ||||||
|  | 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES; | ||||||
|  | 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; | ||||||
|  | 				GCC_WARN_UNDECLARED_SELECTOR = YES; | ||||||
|  | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||||
|  | 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||||
|  | 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||||
|  | 				IPHONEOS_DEPLOYMENT_TARGET = 9.0; | ||||||
|  | 				MTL_ENABLE_DEBUG_INFO = YES; | ||||||
|  | 				ONLY_ACTIVE_ARCH = YES; | ||||||
|  | 				SDKROOT = iphoneos; | ||||||
|  | 				TARGETED_DEVICE_FAMILY = "1,2"; | ||||||
|  | 			}; | ||||||
|  | 			name = Debug; | ||||||
|  | 		}; | ||||||
|  | 		97C147041CF9000F007C117D /* Release */ = { | ||||||
|  | 			isa = XCBuildConfiguration; | ||||||
|  | 			buildSettings = { | ||||||
|  | 				ALWAYS_SEARCH_USER_PATHS = NO; | ||||||
|  | 				CLANG_ANALYZER_NONNULL = YES; | ||||||
|  | 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; | ||||||
|  | 				CLANG_CXX_LIBRARY = "libc++"; | ||||||
|  | 				CLANG_ENABLE_MODULES = YES; | ||||||
|  | 				CLANG_ENABLE_OBJC_ARC = YES; | ||||||
|  | 				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; | ||||||
|  | 				CLANG_WARN_BOOL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_COMMA = YES; | ||||||
|  | 				CLANG_WARN_CONSTANT_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; | ||||||
|  | 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; | ||||||
|  | 				CLANG_WARN_EMPTY_BODY = YES; | ||||||
|  | 				CLANG_WARN_ENUM_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_INFINITE_RECURSION = YES; | ||||||
|  | 				CLANG_WARN_INT_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; | ||||||
|  | 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; | ||||||
|  | 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; | ||||||
|  | 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; | ||||||
|  | 				CLANG_WARN_STRICT_PROTOTYPES = YES; | ||||||
|  | 				CLANG_WARN_SUSPICIOUS_MOVE = YES; | ||||||
|  | 				CLANG_WARN_UNREACHABLE_CODE = YES; | ||||||
|  | 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; | ||||||
|  | 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; | ||||||
|  | 				COPY_PHASE_STRIP = NO; | ||||||
|  | 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; | ||||||
|  | 				ENABLE_NS_ASSERTIONS = NO; | ||||||
|  | 				ENABLE_STRICT_OBJC_MSGSEND = YES; | ||||||
|  | 				GCC_C_LANGUAGE_STANDARD = gnu99; | ||||||
|  | 				GCC_NO_COMMON_BLOCKS = YES; | ||||||
|  | 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES; | ||||||
|  | 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; | ||||||
|  | 				GCC_WARN_UNDECLARED_SELECTOR = YES; | ||||||
|  | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | ||||||
|  | 				GCC_WARN_UNUSED_FUNCTION = YES; | ||||||
|  | 				GCC_WARN_UNUSED_VARIABLE = YES; | ||||||
|  | 				IPHONEOS_DEPLOYMENT_TARGET = 9.0; | ||||||
|  | 				MTL_ENABLE_DEBUG_INFO = NO; | ||||||
|  | 				SDKROOT = iphoneos; | ||||||
|  | 				SUPPORTED_PLATFORMS = iphoneos; | ||||||
|  | 				SWIFT_COMPILATION_MODE = wholemodule; | ||||||
|  | 				SWIFT_OPTIMIZATION_LEVEL = "-O"; | ||||||
|  | 				TARGETED_DEVICE_FAMILY = "1,2"; | ||||||
|  | 				VALIDATE_PRODUCT = YES; | ||||||
|  | 			}; | ||||||
|  | 			name = Release; | ||||||
|  | 		}; | ||||||
|  | 		97C147061CF9000F007C117D /* Debug */ = { | ||||||
|  | 			isa = XCBuildConfiguration; | ||||||
|  | 			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; | ||||||
|  | 			buildSettings = { | ||||||
|  | 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||||||
|  | 				CLANG_ENABLE_MODULES = YES; | ||||||
|  | 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | ||||||
|  | 				DEVELOPMENT_TEAM = C24486LLLU; | ||||||
|  | 				ENABLE_BITCODE = NO; | ||||||
|  | 				INFOPLIST_FILE = Runner/Info.plist; | ||||||
|  | 				LD_RUNPATH_SEARCH_PATHS = ( | ||||||
|  | 					"$(inherited)", | ||||||
|  | 					"@executable_path/Frameworks", | ||||||
|  | 				); | ||||||
|  | 				PRODUCT_BUNDLE_IDENTIFIER = com.example.immichMobile; | ||||||
|  | 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||||
|  | 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; | ||||||
|  | 				SWIFT_OPTIMIZATION_LEVEL = "-Onone"; | ||||||
|  | 				SWIFT_VERSION = 5.0; | ||||||
|  | 				VERSIONING_SYSTEM = "apple-generic"; | ||||||
|  | 			}; | ||||||
|  | 			name = Debug; | ||||||
|  | 		}; | ||||||
|  | 		97C147071CF9000F007C117D /* Release */ = { | ||||||
|  | 			isa = XCBuildConfiguration; | ||||||
|  | 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; | ||||||
|  | 			buildSettings = { | ||||||
|  | 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||||||
|  | 				CLANG_ENABLE_MODULES = YES; | ||||||
|  | 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | ||||||
|  | 				DEVELOPMENT_TEAM = C24486LLLU; | ||||||
|  | 				ENABLE_BITCODE = NO; | ||||||
|  | 				INFOPLIST_FILE = Runner/Info.plist; | ||||||
|  | 				LD_RUNPATH_SEARCH_PATHS = ( | ||||||
|  | 					"$(inherited)", | ||||||
|  | 					"@executable_path/Frameworks", | ||||||
|  | 				); | ||||||
|  | 				PRODUCT_BUNDLE_IDENTIFIER = com.example.immichMobile; | ||||||
|  | 				PRODUCT_NAME = "$(TARGET_NAME)"; | ||||||
|  | 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; | ||||||
|  | 				SWIFT_VERSION = 5.0; | ||||||
|  | 				VERSIONING_SYSTEM = "apple-generic"; | ||||||
|  | 			}; | ||||||
|  | 			name = Release; | ||||||
|  | 		}; | ||||||
|  | /* End XCBuildConfiguration section */ | ||||||
|  |  | ||||||
|  | /* Begin XCConfigurationList section */ | ||||||
|  | 		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { | ||||||
|  | 			isa = XCConfigurationList; | ||||||
|  | 			buildConfigurations = ( | ||||||
|  | 				97C147031CF9000F007C117D /* Debug */, | ||||||
|  | 				97C147041CF9000F007C117D /* Release */, | ||||||
|  | 				249021D3217E4FDB00AE95B9 /* Profile */, | ||||||
|  | 			); | ||||||
|  | 			defaultConfigurationIsVisible = 0; | ||||||
|  | 			defaultConfigurationName = Release; | ||||||
|  | 		}; | ||||||
|  | 		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { | ||||||
|  | 			isa = XCConfigurationList; | ||||||
|  | 			buildConfigurations = ( | ||||||
|  | 				97C147061CF9000F007C117D /* Debug */, | ||||||
|  | 				97C147071CF9000F007C117D /* Release */, | ||||||
|  | 				249021D4217E4FDB00AE95B9 /* Profile */, | ||||||
|  | 			); | ||||||
|  | 			defaultConfigurationIsVisible = 0; | ||||||
|  | 			defaultConfigurationName = Release; | ||||||
|  | 		}; | ||||||
|  | /* End XCConfigurationList section */ | ||||||
|  | 	}; | ||||||
|  | 	rootObject = 97C146E61CF9000F007C117D /* Project object */; | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <Workspace | ||||||
|  |    version = "1.0"> | ||||||
|  |    <FileRef | ||||||
|  |       location = "self:"> | ||||||
|  |    </FileRef> | ||||||
|  | </Workspace> | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
|  | <plist version="1.0"> | ||||||
|  | <dict> | ||||||
|  | 	<key>IDEDidComputeMac32BitWarning</key> | ||||||
|  | 	<true/> | ||||||
|  | </dict> | ||||||
|  | </plist> | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
|  | <plist version="1.0"> | ||||||
|  | <dict> | ||||||
|  | 	<key>PreviewsEnabled</key> | ||||||
|  | 	<false/> | ||||||
|  | </dict> | ||||||
|  | </plist> | ||||||
| @@ -0,0 +1,87 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <Scheme | ||||||
|  |    LastUpgradeVersion = "1300" | ||||||
|  |    version = "1.3"> | ||||||
|  |    <BuildAction | ||||||
|  |       parallelizeBuildables = "YES" | ||||||
|  |       buildImplicitDependencies = "YES"> | ||||||
|  |       <BuildActionEntries> | ||||||
|  |          <BuildActionEntry | ||||||
|  |             buildForTesting = "YES" | ||||||
|  |             buildForRunning = "YES" | ||||||
|  |             buildForProfiling = "YES" | ||||||
|  |             buildForArchiving = "YES" | ||||||
|  |             buildForAnalyzing = "YES"> | ||||||
|  |             <BuildableReference | ||||||
|  |                BuildableIdentifier = "primary" | ||||||
|  |                BlueprintIdentifier = "97C146ED1CF9000F007C117D" | ||||||
|  |                BuildableName = "Runner.app" | ||||||
|  |                BlueprintName = "Runner" | ||||||
|  |                ReferencedContainer = "container:Runner.xcodeproj"> | ||||||
|  |             </BuildableReference> | ||||||
|  |          </BuildActionEntry> | ||||||
|  |       </BuildActionEntries> | ||||||
|  |    </BuildAction> | ||||||
|  |    <TestAction | ||||||
|  |       buildConfiguration = "Debug" | ||||||
|  |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||||
|  |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||||
|  |       shouldUseLaunchSchemeArgsEnv = "YES"> | ||||||
|  |       <MacroExpansion> | ||||||
|  |          <BuildableReference | ||||||
|  |             BuildableIdentifier = "primary" | ||||||
|  |             BlueprintIdentifier = "97C146ED1CF9000F007C117D" | ||||||
|  |             BuildableName = "Runner.app" | ||||||
|  |             BlueprintName = "Runner" | ||||||
|  |             ReferencedContainer = "container:Runner.xcodeproj"> | ||||||
|  |          </BuildableReference> | ||||||
|  |       </MacroExpansion> | ||||||
|  |       <Testables> | ||||||
|  |       </Testables> | ||||||
|  |    </TestAction> | ||||||
|  |    <LaunchAction | ||||||
|  |       buildConfiguration = "Debug" | ||||||
|  |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||||
|  |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||||
|  |       launchStyle = "0" | ||||||
|  |       useCustomWorkingDirectory = "NO" | ||||||
|  |       ignoresPersistentStateOnLaunch = "NO" | ||||||
|  |       debugDocumentVersioning = "YES" | ||||||
|  |       debugServiceExtension = "internal" | ||||||
|  |       allowLocationSimulation = "YES"> | ||||||
|  |       <BuildableProductRunnable | ||||||
|  |          runnableDebuggingMode = "0"> | ||||||
|  |          <BuildableReference | ||||||
|  |             BuildableIdentifier = "primary" | ||||||
|  |             BlueprintIdentifier = "97C146ED1CF9000F007C117D" | ||||||
|  |             BuildableName = "Runner.app" | ||||||
|  |             BlueprintName = "Runner" | ||||||
|  |             ReferencedContainer = "container:Runner.xcodeproj"> | ||||||
|  |          </BuildableReference> | ||||||
|  |       </BuildableProductRunnable> | ||||||
|  |    </LaunchAction> | ||||||
|  |    <ProfileAction | ||||||
|  |       buildConfiguration = "Profile" | ||||||
|  |       shouldUseLaunchSchemeArgsEnv = "YES" | ||||||
|  |       savedToolIdentifier = "" | ||||||
|  |       useCustomWorkingDirectory = "NO" | ||||||
|  |       debugDocumentVersioning = "YES"> | ||||||
|  |       <BuildableProductRunnable | ||||||
|  |          runnableDebuggingMode = "0"> | ||||||
|  |          <BuildableReference | ||||||
|  |             BuildableIdentifier = "primary" | ||||||
|  |             BlueprintIdentifier = "97C146ED1CF9000F007C117D" | ||||||
|  |             BuildableName = "Runner.app" | ||||||
|  |             BlueprintName = "Runner" | ||||||
|  |             ReferencedContainer = "container:Runner.xcodeproj"> | ||||||
|  |          </BuildableReference> | ||||||
|  |       </BuildableProductRunnable> | ||||||
|  |    </ProfileAction> | ||||||
|  |    <AnalyzeAction | ||||||
|  |       buildConfiguration = "Debug"> | ||||||
|  |    </AnalyzeAction> | ||||||
|  |    <ArchiveAction | ||||||
|  |       buildConfiguration = "Release" | ||||||
|  |       revealArchiveInOrganizer = "YES"> | ||||||
|  |    </ArchiveAction> | ||||||
|  | </Scheme> | ||||||
							
								
								
									
										10
									
								
								mobile/ios/Runner.xcworkspace/contents.xcworkspacedata
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <Workspace | ||||||
|  |    version = "1.0"> | ||||||
|  |    <FileRef | ||||||
|  |       location = "group:Runner.xcodeproj"> | ||||||
|  |    </FileRef> | ||||||
|  |    <FileRef | ||||||
|  |       location = "group:Pods/Pods.xcodeproj"> | ||||||
|  |    </FileRef> | ||||||
|  | </Workspace> | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
|  | <plist version="1.0"> | ||||||
|  | <dict> | ||||||
|  | 	<key>IDEDidComputeMac32BitWarning</key> | ||||||
|  | 	<true/> | ||||||
|  | </dict> | ||||||
|  | </plist> | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
|  | <plist version="1.0"> | ||||||
|  | <dict> | ||||||
|  | 	<key>PreviewsEnabled</key> | ||||||
|  | 	<false/> | ||||||
|  | </dict> | ||||||
|  | </plist> | ||||||
							
								
								
									
										13
									
								
								mobile/ios/Runner/AppDelegate.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | |||||||
|  | import UIKit | ||||||
|  | import Flutter | ||||||
|  |  | ||||||
|  | @UIApplicationMain | ||||||
|  | @objc class AppDelegate: FlutterAppDelegate { | ||||||
|  |   override func application( | ||||||
|  |     _ application: UIApplication, | ||||||
|  |     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? | ||||||
|  |   ) -> Bool { | ||||||
|  |     GeneratedPluginRegistrant.register(with: self) | ||||||
|  |     return super.application(application, didFinishLaunchingWithOptions: launchOptions) | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -0,0 +1,122 @@ | |||||||
|  | { | ||||||
|  |   "images" : [ | ||||||
|  |     { | ||||||
|  |       "size" : "20x20", | ||||||
|  |       "idiom" : "iphone", | ||||||
|  |       "filename" : "Icon-App-20x20@2x.png", | ||||||
|  |       "scale" : "2x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "20x20", | ||||||
|  |       "idiom" : "iphone", | ||||||
|  |       "filename" : "Icon-App-20x20@3x.png", | ||||||
|  |       "scale" : "3x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "29x29", | ||||||
|  |       "idiom" : "iphone", | ||||||
|  |       "filename" : "Icon-App-29x29@1x.png", | ||||||
|  |       "scale" : "1x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "29x29", | ||||||
|  |       "idiom" : "iphone", | ||||||
|  |       "filename" : "Icon-App-29x29@2x.png", | ||||||
|  |       "scale" : "2x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "29x29", | ||||||
|  |       "idiom" : "iphone", | ||||||
|  |       "filename" : "Icon-App-29x29@3x.png", | ||||||
|  |       "scale" : "3x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "40x40", | ||||||
|  |       "idiom" : "iphone", | ||||||
|  |       "filename" : "Icon-App-40x40@2x.png", | ||||||
|  |       "scale" : "2x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "40x40", | ||||||
|  |       "idiom" : "iphone", | ||||||
|  |       "filename" : "Icon-App-40x40@3x.png", | ||||||
|  |       "scale" : "3x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "60x60", | ||||||
|  |       "idiom" : "iphone", | ||||||
|  |       "filename" : "Icon-App-60x60@2x.png", | ||||||
|  |       "scale" : "2x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "60x60", | ||||||
|  |       "idiom" : "iphone", | ||||||
|  |       "filename" : "Icon-App-60x60@3x.png", | ||||||
|  |       "scale" : "3x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "20x20", | ||||||
|  |       "idiom" : "ipad", | ||||||
|  |       "filename" : "Icon-App-20x20@1x.png", | ||||||
|  |       "scale" : "1x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "20x20", | ||||||
|  |       "idiom" : "ipad", | ||||||
|  |       "filename" : "Icon-App-20x20@2x.png", | ||||||
|  |       "scale" : "2x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "29x29", | ||||||
|  |       "idiom" : "ipad", | ||||||
|  |       "filename" : "Icon-App-29x29@1x.png", | ||||||
|  |       "scale" : "1x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "29x29", | ||||||
|  |       "idiom" : "ipad", | ||||||
|  |       "filename" : "Icon-App-29x29@2x.png", | ||||||
|  |       "scale" : "2x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "40x40", | ||||||
|  |       "idiom" : "ipad", | ||||||
|  |       "filename" : "Icon-App-40x40@1x.png", | ||||||
|  |       "scale" : "1x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "40x40", | ||||||
|  |       "idiom" : "ipad", | ||||||
|  |       "filename" : "Icon-App-40x40@2x.png", | ||||||
|  |       "scale" : "2x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "76x76", | ||||||
|  |       "idiom" : "ipad", | ||||||
|  |       "filename" : "Icon-App-76x76@1x.png", | ||||||
|  |       "scale" : "1x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "76x76", | ||||||
|  |       "idiom" : "ipad", | ||||||
|  |       "filename" : "Icon-App-76x76@2x.png", | ||||||
|  |       "scale" : "2x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "83.5x83.5", | ||||||
|  |       "idiom" : "ipad", | ||||||
|  |       "filename" : "Icon-App-83.5x83.5@2x.png", | ||||||
|  |       "scale" : "2x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "size" : "1024x1024", | ||||||
|  |       "idiom" : "ios-marketing", | ||||||
|  |       "filename" : "Icon-App-1024x1024@1x.png", | ||||||
|  |       "scale" : "1x" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "info" : { | ||||||
|  |     "version" : 1, | ||||||
|  |     "author" : "xcode" | ||||||
|  |   } | ||||||
|  | } | ||||||
| After Width: | Height: | Size: 11 KiB | 
| After Width: | Height: | Size: 564 B | 
| After Width: | Height: | Size: 1.3 KiB | 
| After Width: | Height: | Size: 1.6 KiB | 
| After Width: | Height: | Size: 1.0 KiB | 
| After Width: | Height: | Size: 1.7 KiB | 
| After Width: | Height: | Size: 1.9 KiB | 
| After Width: | Height: | Size: 1.3 KiB | 
| After Width: | Height: | Size: 1.9 KiB | 
| After Width: | Height: | Size: 2.6 KiB | 
| After Width: | Height: | Size: 2.6 KiB | 
| After Width: | Height: | Size: 3.7 KiB | 
| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 3.2 KiB | 
| After Width: | Height: | Size: 3.5 KiB | 
							
								
								
									
										23
									
								
								mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 68 B | 
							
								
								
									
										
											BIN
										
									
								
								mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 68 B | 
							
								
								
									
										
											BIN
										
									
								
								mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 68 B | 
							
								
								
									
										5
									
								
								mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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. | ||||||
							
								
								
									
										37
									
								
								mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,37 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> | ||||||
|  |     <dependencies> | ||||||
|  |         <deployment identifier="iOS"/> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/> | ||||||
|  |     </dependencies> | ||||||
|  |     <scenes> | ||||||
|  |         <!--View Controller--> | ||||||
|  |         <scene sceneID="EHf-IW-A2E"> | ||||||
|  |             <objects> | ||||||
|  |                 <viewController id="01J-lp-oVM" sceneMemberID="viewController"> | ||||||
|  |                     <layoutGuides> | ||||||
|  |                         <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/> | ||||||
|  |                         <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/> | ||||||
|  |                     </layoutGuides> | ||||||
|  |                     <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> | ||||||
|  |                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|  |                         <subviews> | ||||||
|  |                             <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"> | ||||||
|  |                             </imageView> | ||||||
|  |                         </subviews> | ||||||
|  |                         <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> | ||||||
|  |                         <constraints> | ||||||
|  |                             <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/> | ||||||
|  |                             <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/> | ||||||
|  |                         </constraints> | ||||||
|  |                     </view> | ||||||
|  |                 </viewController> | ||||||
|  |                 <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> | ||||||
|  |             </objects> | ||||||
|  |             <point key="canvasLocation" x="53" y="375"/> | ||||||
|  |         </scene> | ||||||
|  |     </scenes> | ||||||
|  |     <resources> | ||||||
|  |         <image name="LaunchImage" width="168" height="185"/> | ||||||
|  |     </resources> | ||||||
|  | </document> | ||||||
							
								
								
									
										26
									
								
								mobile/ios/Runner/Base.lproj/Main.storyboard
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,26 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> | ||||||
|  |     <dependencies> | ||||||
|  |         <deployment identifier="iOS"/> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> | ||||||
|  |     </dependencies> | ||||||
|  |     <scenes> | ||||||
|  |         <!--Flutter View Controller--> | ||||||
|  |         <scene sceneID="tne-QT-ifu"> | ||||||
|  |             <objects> | ||||||
|  |                 <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController"> | ||||||
|  |                     <layoutGuides> | ||||||
|  |                         <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> | ||||||
|  |                         <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> | ||||||
|  |                     </layoutGuides> | ||||||
|  |                     <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> | ||||||
|  |                         <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> | ||||||
|  |                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|  |                         <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> | ||||||
|  |                     </view> | ||||||
|  |                 </viewController> | ||||||
|  |                 <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> | ||||||
|  |             </objects> | ||||||
|  |         </scene> | ||||||
|  |     </scenes> | ||||||
|  | </document> | ||||||
							
								
								
									
										49
									
								
								mobile/ios/Runner/Info.plist
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,49 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
|  | <plist version="1.0"> | ||||||
|  |   <dict> | ||||||
|  |     <key>CFBundleDevelopmentRegion</key> | ||||||
|  |     <string>$(DEVELOPMENT_LANGUAGE)</string> | ||||||
|  |     <key>CFBundleDisplayName</key> | ||||||
|  |     <string>Immich Mobile</string> | ||||||
|  |     <key>CFBundleExecutable</key> | ||||||
|  |     <string>$(EXECUTABLE_NAME)</string> | ||||||
|  |     <key>CFBundleIdentifier</key> | ||||||
|  |     <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||||||
|  |     <key>CFBundleInfoDictionaryVersion</key> | ||||||
|  |     <string>6.0</string> | ||||||
|  |     <key>CFBundleName</key> | ||||||
|  |     <string>immich_mobile</string> | ||||||
|  |     <key>CFBundlePackageType</key> | ||||||
|  |     <string>APPL</string> | ||||||
|  |     <key>CFBundleShortVersionString</key> | ||||||
|  |     <string>$(FLUTTER_BUILD_NAME)</string> | ||||||
|  |     <key>CFBundleSignature</key> | ||||||
|  |     <string>????</string> | ||||||
|  |     <key>CFBundleVersion</key> | ||||||
|  |     <string>$(FLUTTER_BUILD_NUMBER)</string> | ||||||
|  |     <key>LSRequiresIPhoneOS</key> | ||||||
|  |     <true /> | ||||||
|  |     <key>UILaunchStoryboardName</key> | ||||||
|  |     <string>LaunchScreen</string> | ||||||
|  |     <key>UIMainStoryboardFile</key> | ||||||
|  |     <string>Main</string> | ||||||
|  |     <key>UISupportedInterfaceOrientations</key> | ||||||
|  |     <array> | ||||||
|  |       <string>UIInterfaceOrientationPortrait</string> | ||||||
|  |       <string>UIInterfaceOrientationLandscapeLeft</string> | ||||||
|  |       <string>UIInterfaceOrientationLandscapeRight</string> | ||||||
|  |     </array> | ||||||
|  |     <key>UISupportedInterfaceOrientations~ipad</key> | ||||||
|  |     <array> | ||||||
|  |       <string>UIInterfaceOrientationPortrait</string> | ||||||
|  |       <string>UIInterfaceOrientationPortraitUpsideDown</string> | ||||||
|  |       <string>UIInterfaceOrientationLandscapeLeft</string> | ||||||
|  |       <string>UIInterfaceOrientationLandscapeRight</string> | ||||||
|  |     </array> | ||||||
|  |     <key>UIViewControllerBasedStatusBarAppearance</key> | ||||||
|  |     <true /> | ||||||
|  |     <key>NSPhotoLibraryUsageDescription</key> | ||||||
|  |     <string>App need your agree, can visit your album</string> | ||||||
|  |   </dict> | ||||||
|  | </plist> | ||||||
							
								
								
									
										1
									
								
								mobile/ios/Runner/Runner-Bridging-Header.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | #import "GeneratedPluginRegistrant.h" | ||||||
							
								
								
									
										11
									
								
								mobile/lib/constants/hive_box.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | |||||||
|  | // Access token | ||||||
|  | const String userInfoBox = "immichBoxUserInfo"; // Box | ||||||
|  | const String accessTokenKey = "immichBoxAccessTokenKey"; // Key 1 | ||||||
|  | const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2 | ||||||
|  |  | ||||||
|  | // SERVER ENDPOINT | ||||||
|  | const String serverEndpointKey = 'immichBoxServerEndpoint'; | ||||||
|  |  | ||||||
|  | // KEY | ||||||
|  | const String hiveAllAsssetKey = "allAssets"; | ||||||
|  | const String hiveBackupProgressKey = "backupProgressAssets"; | ||||||
							
								
								
									
										92
									
								
								mobile/lib/main.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,92 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:hive_flutter/hive_flutter.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/routing/router.dart'; | ||||||
|  | import 'package:immich_mobile/shared/providers/app_state.provider.dart'; | ||||||
|  | import 'constants/hive_box.dart'; | ||||||
|  | import 'package:google_fonts/google_fonts.dart'; | ||||||
|  |  | ||||||
|  | void main() async { | ||||||
|  |   await Hive.initFlutter(); | ||||||
|  |   await Hive.openBox(userInfoBox); | ||||||
|  |   // Hive.registerAdapter(ImmichBackUpAssetAdapter()); | ||||||
|  |   // Hive.deleteBoxFromDisk(hiveImmichBox); | ||||||
|  |  | ||||||
|  |   runApp(const ProviderScope(child: ImmichApp())); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ImmichApp extends ConsumerStatefulWidget { | ||||||
|  |   const ImmichApp({Key? key}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   _ImmichAppState createState() => _ImmichAppState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserver { | ||||||
|  |   @override | ||||||
|  |   void didChangeAppLifecycleState(AppLifecycleState state) { | ||||||
|  |     switch (state) { | ||||||
|  |       case AppLifecycleState.resumed: | ||||||
|  |         debugPrint("[APP STATE] resumed"); | ||||||
|  |         ref.read(appStateProvider.notifier).state = AppStateEnum.resumed; | ||||||
|  |         break; | ||||||
|  |       case AppLifecycleState.inactive: | ||||||
|  |         debugPrint("[APP STATE] inactive"); | ||||||
|  |         ref.read(appStateProvider.notifier).state = AppStateEnum.inactive; | ||||||
|  |         break; | ||||||
|  |       case AppLifecycleState.paused: | ||||||
|  |         debugPrint("[APP STATE] paused"); | ||||||
|  |         ref.read(appStateProvider.notifier).state = AppStateEnum.paused; | ||||||
|  |         break; | ||||||
|  |       case AppLifecycleState.detached: | ||||||
|  |         debugPrint("[APP STATE] detached"); | ||||||
|  |         ref.read(appStateProvider.notifier).state = AppStateEnum.detached; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> initApp() async { | ||||||
|  |     // ! TOBE DELETE | ||||||
|  |     // Simulate Sign In And Register/Get Device ID | ||||||
|  |     // await ref.read(authenticationProvider.notifier).login(); | ||||||
|  |     // ref.read(backupProvider.notifier).getBackupInfo(); | ||||||
|  |     // WidgetsBinding.instance?.addObserver(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     initApp().then((_) => debugPrint("App Init Completed")); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     WidgetsBinding.instance?.removeObserver(this); | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   final _immichRouter = AppRouter(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return MaterialApp.router( | ||||||
|  |       title: 'Immich', | ||||||
|  |       debugShowCheckedModeBanner: false, | ||||||
|  |       theme: ThemeData( | ||||||
|  |         primarySwatch: Colors.indigo, | ||||||
|  |         textTheme: GoogleFonts.workSansTextTheme( | ||||||
|  |           Theme.of(context).textTheme.apply(fontSizeFactor: 1.0), | ||||||
|  |         ), | ||||||
|  |         scaffoldBackgroundColor: const Color(0xFFf6f8fe), | ||||||
|  |         appBarTheme: const AppBarTheme( | ||||||
|  |           backgroundColor: Colors.white, | ||||||
|  |           foregroundColor: Colors.indigo, | ||||||
|  |           elevation: 1, | ||||||
|  |           centerTitle: true, | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       routeInformationParser: _immichRouter.defaultRouteParser(), | ||||||
|  |       routerDelegate: _immichRouter.delegate(), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										0
									
								
								mobile/lib/module_template/ui/store_ui_here.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										113
									
								
								mobile/lib/modules/home/models/get_all_asset_respose.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,113 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/immich_asset.model.dart'; | ||||||
|  |  | ||||||
|  | class ImmichAssetGroupByDate { | ||||||
|  |   final String date; | ||||||
|  |   List<ImmichAsset> assets; | ||||||
|  |   ImmichAssetGroupByDate({ | ||||||
|  |     required this.date, | ||||||
|  |     required this.assets, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   ImmichAssetGroupByDate copyWith({ | ||||||
|  |     String? date, | ||||||
|  |     List<ImmichAsset>? assets, | ||||||
|  |   }) { | ||||||
|  |     return ImmichAssetGroupByDate( | ||||||
|  |       date: date ?? this.date, | ||||||
|  |       assets: assets ?? this.assets, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toMap() { | ||||||
|  |     return { | ||||||
|  |       'date': date, | ||||||
|  |       'assets': assets.map((x) => x.toMap()).toList(), | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   factory ImmichAssetGroupByDate.fromMap(Map<String, dynamic> map) { | ||||||
|  |     return ImmichAssetGroupByDate( | ||||||
|  |       date: map['date'] ?? '', | ||||||
|  |       assets: List<ImmichAsset>.from(map['assets']?.map((x) => ImmichAsset.fromMap(x))), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String toJson() => json.encode(toMap()); | ||||||
|  |  | ||||||
|  |   factory ImmichAssetGroupByDate.fromJson(String source) => ImmichAssetGroupByDate.fromMap(json.decode(source)); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => 'ImmichAssetGroupByDate(date: $date, assets: $assets)'; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  |  | ||||||
|  |     return other is ImmichAssetGroupByDate && other.date == date && listEquals(other.assets, assets); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => date.hashCode ^ assets.hashCode; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class GetAllAssetResponse { | ||||||
|  |   final int count; | ||||||
|  |   final List<ImmichAssetGroupByDate> data; | ||||||
|  |   final String nextPageKey; | ||||||
|  |   GetAllAssetResponse({ | ||||||
|  |     required this.count, | ||||||
|  |     required this.data, | ||||||
|  |     required this.nextPageKey, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   GetAllAssetResponse copyWith({ | ||||||
|  |     int? count, | ||||||
|  |     List<ImmichAssetGroupByDate>? data, | ||||||
|  |     String? nextPageKey, | ||||||
|  |   }) { | ||||||
|  |     return GetAllAssetResponse( | ||||||
|  |       count: count ?? this.count, | ||||||
|  |       data: data ?? this.data, | ||||||
|  |       nextPageKey: nextPageKey ?? this.nextPageKey, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toMap() { | ||||||
|  |     return { | ||||||
|  |       'count': count, | ||||||
|  |       'data': data.map((x) => x.toMap()).toList(), | ||||||
|  |       'nextPageKey': nextPageKey, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   factory GetAllAssetResponse.fromMap(Map<String, dynamic> map) { | ||||||
|  |     return GetAllAssetResponse( | ||||||
|  |       count: map['count']?.toInt() ?? 0, | ||||||
|  |       data: List<ImmichAssetGroupByDate>.from(map['data']?.map((x) => ImmichAssetGroupByDate.fromMap(x))), | ||||||
|  |       nextPageKey: map['nextPageKey'] ?? '', | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String toJson() => json.encode(toMap()); | ||||||
|  |  | ||||||
|  |   factory GetAllAssetResponse.fromJson(String source) => GetAllAssetResponse.fromMap(json.decode(source)); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => 'GetAllAssetResponse(count: $count, data: $data, nextPageKey: $nextPageKey)'; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  |  | ||||||
|  |     return other is GetAllAssetResponse && | ||||||
|  |         other.count == count && | ||||||
|  |         listEquals(other.data, data) && | ||||||
|  |         other.nextPageKey == nextPageKey; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => count.hashCode ^ data.hashCode ^ nextPageKey.hashCode; | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								mobile/lib/modules/home/providers/asset.provider.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,60 @@ | |||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/models/get_all_asset_respose.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/services/asset.service.dart'; | ||||||
|  |  | ||||||
|  | class AssetNotifier extends StateNotifier<List<ImmichAssetGroupByDate>> { | ||||||
|  |   final imagePerPage = 100; | ||||||
|  |   final AssetService _assetService = AssetService(); | ||||||
|  |  | ||||||
|  |   AssetNotifier() : super([]); | ||||||
|  |   late String? nextPageKey = ""; | ||||||
|  |   bool isFetching = false; | ||||||
|  |  | ||||||
|  |   getImmichAssets() async { | ||||||
|  |     GetAllAssetResponse? res = await _assetService.getAllAsset(); | ||||||
|  |     nextPageKey = res?.nextPageKey; | ||||||
|  |  | ||||||
|  |     if (res != null) { | ||||||
|  |       for (var assets in res.data) { | ||||||
|  |         state = [...state, assets]; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   getMoreAsset() async { | ||||||
|  |     if (nextPageKey != null && !isFetching) { | ||||||
|  |       isFetching = true; | ||||||
|  |       GetAllAssetResponse? res = await _assetService.getMoreAsset(nextPageKey); | ||||||
|  |  | ||||||
|  |       if (res != null) { | ||||||
|  |         nextPageKey = res.nextPageKey; | ||||||
|  |  | ||||||
|  |         List<ImmichAssetGroupByDate> previousState = state; | ||||||
|  |         List<ImmichAssetGroupByDate> currentState = []; | ||||||
|  |  | ||||||
|  |         for (var assets in res.data) { | ||||||
|  |           currentState = [...currentState, assets]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (previousState.last.date == currentState.first.date) { | ||||||
|  |           previousState.last.assets = [...previousState.last.assets, ...currentState.first.assets]; | ||||||
|  |           state = [...previousState, ...currentState.sublist(1)]; | ||||||
|  |         } else { | ||||||
|  |           state = [...previousState, ...currentState]; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       isFetching = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   clearAllAsset() { | ||||||
|  |     state = []; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | final currentLocalPageProvider = StateProvider<int>((ref) => 0); | ||||||
|  |  | ||||||
|  | final assetProvider = StateNotifierProvider<AssetNotifier, List<ImmichAssetGroupByDate>>((ref) { | ||||||
|  |   return AssetNotifier(); | ||||||
|  | }); | ||||||
							
								
								
									
										38
									
								
								mobile/lib/modules/home/services/asset.service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/models/get_all_asset_respose.model.dart'; | ||||||
|  | import 'package:immich_mobile/shared/services/network.service.dart'; | ||||||
|  |  | ||||||
|  | class AssetService { | ||||||
|  |   final NetworkService _networkService = NetworkService(); | ||||||
|  |  | ||||||
|  |   Future<GetAllAssetResponse?> getAllAsset() async { | ||||||
|  |     var res = await _networkService.getRequest(url: "asset/all"); | ||||||
|  |     try { | ||||||
|  |       Map<String, dynamic> decodedData = jsonDecode(res.toString()); | ||||||
|  |  | ||||||
|  |       GetAllAssetResponse result = GetAllAssetResponse.fromMap(decodedData); | ||||||
|  |       return result; | ||||||
|  |     } catch (e) { | ||||||
|  |       debugPrint("Error getAllAsset  ${e.toString()}"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<GetAllAssetResponse?> getMoreAsset(String? nextPageKey) async { | ||||||
|  |     try { | ||||||
|  |       var res = await _networkService.getRequest( | ||||||
|  |         url: "asset/all?nextPageKey=$nextPageKey", | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       Map<String, dynamic> decodedData = jsonDecode(res.toString()); | ||||||
|  |  | ||||||
|  |       GetAllAssetResponse result = GetAllAssetResponse.fromMap(decodedData); | ||||||
|  |       if (result.count != 0) { | ||||||
|  |         return result; | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       debugPrint("Error getAllAsset  ${e.toString()}"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								mobile/lib/modules/home/ui/image_grid.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,26 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/ui/thumbnail_image.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/immich_asset.model.dart'; | ||||||
|  |  | ||||||
|  | class ImageGrid extends StatelessWidget { | ||||||
|  |   final List<ImmichAsset> assetGroup; | ||||||
|  |  | ||||||
|  |   const ImageGrid({Key? key, required this.assetGroup}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return SliverGrid( | ||||||
|  |       gridDelegate: | ||||||
|  |           const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 5.0, mainAxisSpacing: 5), | ||||||
|  |       delegate: SliverChildBuilderDelegate( | ||||||
|  |         (BuildContext context, int index) { | ||||||
|  |           return GestureDetector( | ||||||
|  |             onTap: () {}, | ||||||
|  |             child: ThumbnailImage(asset: assetGroup[index]), | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         childCount: assetGroup.length, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										105
									
								
								mobile/lib/modules/home/ui/immich_sliver_appbar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,105 @@ | |||||||
|  | import 'package:auto_route/auto_route.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
|  | import 'package:google_fonts/google_fonts.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/providers/asset.provider.dart'; | ||||||
|  |  | ||||||
|  | import 'package:immich_mobile/routing/router.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/backup_state.model.dart'; | ||||||
|  | import 'package:immich_mobile/shared/providers/backup.provider.dart'; | ||||||
|  |  | ||||||
|  | class ImmichSliverAppBar extends ConsumerWidget { | ||||||
|  |   const ImmichSliverAppBar({ | ||||||
|  |     Key? key, | ||||||
|  |     required this.imageGridGroup, | ||||||
|  |   }) : super(key: key); | ||||||
|  |  | ||||||
|  |   final List<Widget> imageGridGroup; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final BackUpState _backupState = ref.watch(backupProvider); | ||||||
|  |  | ||||||
|  |     return SliverPadding( | ||||||
|  |       padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), | ||||||
|  |       sliver: SliverAppBar( | ||||||
|  |         centerTitle: true, | ||||||
|  |         floating: true, | ||||||
|  |         pinned: false, | ||||||
|  |         snap: false, | ||||||
|  |         backgroundColor: Colors.grey[200], | ||||||
|  |         shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))), | ||||||
|  |         leading: Builder( | ||||||
|  |           builder: (BuildContext context) { | ||||||
|  |             return IconButton( | ||||||
|  |               icon: const Icon(Icons.account_circle_rounded), | ||||||
|  |               onPressed: () { | ||||||
|  |                 Scaffold.of(context).openDrawer(); | ||||||
|  |               }, | ||||||
|  |               tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip, | ||||||
|  |             ); | ||||||
|  |           }, | ||||||
|  |         ), | ||||||
|  |         title: Text( | ||||||
|  |           'IMMICH', | ||||||
|  |           style: GoogleFonts.snowburstOne( | ||||||
|  |             textStyle: TextStyle( | ||||||
|  |               fontWeight: FontWeight.bold, | ||||||
|  |               fontSize: 18, | ||||||
|  |               color: Theme.of(context).primaryColor, | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         actions: [ | ||||||
|  |           Stack( | ||||||
|  |             alignment: AlignmentDirectional.center, | ||||||
|  |             children: [ | ||||||
|  |               _backupState.backupProgress == BackUpProgressEnum.inProgress | ||||||
|  |                   ? Positioned( | ||||||
|  |                       top: 10, | ||||||
|  |                       right: 12, | ||||||
|  |                       child: SizedBox( | ||||||
|  |                         height: 8, | ||||||
|  |                         width: 8, | ||||||
|  |                         child: CircularProgressIndicator( | ||||||
|  |                           strokeWidth: 1, | ||||||
|  |                           valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ) | ||||||
|  |                   : Container(), | ||||||
|  |               IconButton( | ||||||
|  |                 icon: const Icon(Icons.backup_rounded), | ||||||
|  |                 tooltip: 'Backup Controller', | ||||||
|  |                 onPressed: () async { | ||||||
|  |                   var onPop = await AutoRouter.of(context).push(const BackupControllerRoute()); | ||||||
|  |  | ||||||
|  |                   // Fetch new image | ||||||
|  |                   if (onPop == true) { | ||||||
|  |                     // Remove and force getting new widget again | ||||||
|  |                     if (imageGridGroup.isNotEmpty) { | ||||||
|  |                       ref.read(assetProvider.notifier).getMoreAsset(); | ||||||
|  |                     } else { | ||||||
|  |                       ref.read(assetProvider.notifier).getImmichAssets(); | ||||||
|  |                     } | ||||||
|  |                   } | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|  |               _backupState.backupProgress == BackUpProgressEnum.inProgress | ||||||
|  |                   ? Positioned( | ||||||
|  |                       bottom: 5, | ||||||
|  |                       child: Text( | ||||||
|  |                         _backupState.backingUpAssetCount.toString(), | ||||||
|  |                         style: const TextStyle(fontSize: 9, fontWeight: FontWeight.bold), | ||||||
|  |                       ), | ||||||
|  |                     ) | ||||||
|  |                   : Container() | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |         systemOverlayStyle: SystemUiOverlayStyle.dark, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								mobile/lib/modules/home/ui/profile_drawer.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,72 @@ | |||||||
|  | import 'package:auto_route/annotations.dart'; | ||||||
|  | import 'package:auto_route/auto_route.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/src/widgets/framework.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/providers/asset.provider.dart'; | ||||||
|  | import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||||
|  | import 'package:immich_mobile/routing/router.dart'; | ||||||
|  |  | ||||||
|  | class ProfileDrawer extends ConsumerWidget { | ||||||
|  |   const ProfileDrawer({Key? key}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     AuthenticationState _authState = ref.watch(authenticationProvider); | ||||||
|  |  | ||||||
|  |     return Drawer( | ||||||
|  |       shape: const RoundedRectangleBorder( | ||||||
|  |         borderRadius: BorderRadius.only( | ||||||
|  |           topRight: Radius.circular(5), | ||||||
|  |           bottomRight: Radius.circular(5), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       child: ListView( | ||||||
|  |         padding: EdgeInsets.zero, | ||||||
|  |         children: [ | ||||||
|  |           DrawerHeader( | ||||||
|  |             decoration: BoxDecoration( | ||||||
|  |               color: Colors.grey[200], | ||||||
|  |             ), | ||||||
|  |             child: Column( | ||||||
|  |               mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |               crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |               children: [ | ||||||
|  |                 const Image( | ||||||
|  |                   image: AssetImage('assets/immich-logo-no-outline.png'), | ||||||
|  |                   width: 50, | ||||||
|  |                   filterQuality: FilterQuality.high, | ||||||
|  |                 ), | ||||||
|  |                 const Padding(padding: EdgeInsets.all(8)), | ||||||
|  |                 Text( | ||||||
|  |                   _authState.userEmail, | ||||||
|  |                   style: TextStyle(color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold), | ||||||
|  |                 ) | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |           ListTile( | ||||||
|  |             tileColor: Colors.grey[100], | ||||||
|  |             leading: const Icon( | ||||||
|  |               Icons.logout_rounded, | ||||||
|  |               color: Colors.black54, | ||||||
|  |             ), | ||||||
|  |             title: const Text( | ||||||
|  |               "Sign Out", | ||||||
|  |               style: TextStyle(color: Colors.black54, fontSize: 14), | ||||||
|  |             ), | ||||||
|  |             onTap: () async { | ||||||
|  |               bool res = await ref.read(authenticationProvider.notifier).logout(); | ||||||
|  |               ref.read(assetProvider.notifier).clearAllAsset(); | ||||||
|  |  | ||||||
|  |               if (res) { | ||||||
|  |                 AutoRouter.of(context).popUntilRoot(); | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |           ) | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								mobile/lib/modules/home/ui/thumbnail_image.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,52 @@ | |||||||
|  | import 'package:auto_route/auto_route.dart'; | ||||||
|  | import 'package:cached_network_image/cached_network_image.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:hive_flutter/hive_flutter.dart'; | ||||||
|  | import 'package:immich_mobile/constants/hive_box.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/immich_asset.model.dart'; | ||||||
|  | import 'package:immich_mobile/routing/router.dart'; | ||||||
|  | import 'package:transparent_image/transparent_image.dart'; | ||||||
|  |  | ||||||
|  | class ThumbnailImage extends StatelessWidget { | ||||||
|  |   final ImmichAsset asset; | ||||||
|  |  | ||||||
|  |   const ThumbnailImage({Key? key, required this.asset}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     var box = Hive.box(userInfoBox); | ||||||
|  |     var thumbnailRequestUrl = | ||||||
|  |         '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true'; | ||||||
|  |  | ||||||
|  |     return GestureDetector( | ||||||
|  |       onTap: () { | ||||||
|  |         AutoRouter.of(context).push( | ||||||
|  |           ImageViewerRoute( | ||||||
|  |             imageUrl: | ||||||
|  |                 '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false', | ||||||
|  |             heroTag: asset.id, | ||||||
|  |             thumbnailUrl: thumbnailRequestUrl, | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |       onLongPress: () {}, | ||||||
|  |       child: Hero( | ||||||
|  |         tag: asset.id, | ||||||
|  |         child: CachedNetworkImage( | ||||||
|  |           width: 300, | ||||||
|  |           height: 300, | ||||||
|  |           memCacheHeight: 250, | ||||||
|  |           fit: BoxFit.cover, | ||||||
|  |           imageUrl: thumbnailRequestUrl, | ||||||
|  |           httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"}, | ||||||
|  |           fadeInDuration: const Duration(milliseconds: 250), | ||||||
|  |           progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale( | ||||||
|  |             scale: 0.2, | ||||||
|  |             child: CircularProgressIndicator(value: downloadProgress.progress), | ||||||
|  |           ), | ||||||
|  |           errorWidget: (context, url, error) => const Icon(Icons.error), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										165
									
								
								mobile/lib/modules/home/views/home_page.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,165 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/ui/profile_drawer.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/backup_state.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/models/get_all_asset_respose.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/ui/image_grid.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/providers/asset.provider.dart'; | ||||||
|  | import 'package:immich_mobile/shared/providers/backup.provider.dart'; | ||||||
|  | import 'package:intl/intl.dart'; | ||||||
|  |  | ||||||
|  | class HomePage extends HookConsumerWidget { | ||||||
|  |   const HomePage({Key? key}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final ValueNotifier<bool> _showBackToTopBtn = useState(false); | ||||||
|  |     ScrollController _scrollController = useScrollController(); | ||||||
|  |     List<ImmichAssetGroupByDate> assetGroup = ref.watch(assetProvider); | ||||||
|  |     BackUpState _backupState = ref.watch(backupProvider); | ||||||
|  |     List<Widget> imageGridGroup = []; | ||||||
|  |     List<GlobalKey> monthGroupKey = []; | ||||||
|  |  | ||||||
|  |     _scrollControllerCallback() { | ||||||
|  |       var endOfPage = _scrollController.position.maxScrollExtent; | ||||||
|  |  | ||||||
|  |       if (_scrollController.offset >= endOfPage - (endOfPage * 0.1) && !_scrollController.position.outOfRange) { | ||||||
|  |         ref.read(assetProvider.notifier).getMoreAsset(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (_scrollController.offset >= 400) { | ||||||
|  |         _showBackToTopBtn.value = true; | ||||||
|  |       } else { | ||||||
|  |         _showBackToTopBtn.value = false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       ref.read(assetProvider.notifier).getImmichAssets(); | ||||||
|  |  | ||||||
|  |       _scrollController.addListener(_scrollControllerCallback); | ||||||
|  |  | ||||||
|  |       return () => _scrollController.removeListener(_scrollControllerCallback); | ||||||
|  |     }, [_scrollController, key]); | ||||||
|  |  | ||||||
|  |     Widget _buildBody() { | ||||||
|  |       if (assetGroup.isNotEmpty) { | ||||||
|  |         String lastGroupDate = assetGroup[0].date; | ||||||
|  |  | ||||||
|  |         for (var group in assetGroup) { | ||||||
|  |           var dateTitle = group.date; | ||||||
|  |           var assetGroup = group.assets; | ||||||
|  |  | ||||||
|  |           int? currentMonth = DateTime.tryParse(dateTitle)?.month; | ||||||
|  |           int? previousMonth = DateTime.tryParse(lastGroupDate)?.month; | ||||||
|  |  | ||||||
|  |           if ((currentMonth! - previousMonth!) != 0) { | ||||||
|  |             var myKey = GlobalKey(); | ||||||
|  |             monthGroupKey.add(myKey); | ||||||
|  |             // debugPrint("Group Key $myKey"); | ||||||
|  |  | ||||||
|  |             imageGridGroup.add( | ||||||
|  |               SliverToBoxAdapter( | ||||||
|  |                 key: myKey, | ||||||
|  |                 child: Padding( | ||||||
|  |                   padding: const EdgeInsets.only(left: 10.0, top: 32), | ||||||
|  |                   child: Text( | ||||||
|  |                     DateFormat('MMMM, y').format( | ||||||
|  |                       DateTime.parse(dateTitle), | ||||||
|  |                     ), | ||||||
|  |                     style: TextStyle( | ||||||
|  |                       fontSize: 24, | ||||||
|  |                       fontWeight: FontWeight.bold, | ||||||
|  |                       color: Theme.of(context).primaryColor, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           imageGridGroup.add( | ||||||
|  |             _buildDateGroupTitle(dateTitle), | ||||||
|  |           ); | ||||||
|  |  | ||||||
|  |           imageGridGroup.add(ImageGrid(assetGroup: assetGroup)); | ||||||
|  |  | ||||||
|  |           lastGroupDate = dateTitle; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return SafeArea( | ||||||
|  |           child: CustomScrollView( | ||||||
|  |             controller: _scrollController, | ||||||
|  |             slivers: [ | ||||||
|  |               ImmichSliverAppBar(imageGridGroup: imageGridGroup), | ||||||
|  |               ...imageGridGroup, | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         return Container(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return Scaffold( | ||||||
|  |       drawer: const ProfileDrawer(), | ||||||
|  |       body: _buildBody(), | ||||||
|  |       bottomNavigationBar: BottomAppBar( | ||||||
|  |         child: IconButton( | ||||||
|  |           onPressed: () { | ||||||
|  |             if (monthGroupKey.isNotEmpty) { | ||||||
|  |               var targetContext = monthGroupKey.last.currentContext; | ||||||
|  |               if (targetContext != null) { | ||||||
|  |                 Scrollable.ensureVisible( | ||||||
|  |                   targetContext, | ||||||
|  |                   duration: const Duration(milliseconds: 400), | ||||||
|  |                   curve: Curves.easeInOut, | ||||||
|  |                 ); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           icon: const Icon(Icons.ac_unit_outlined), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       floatingActionButton: _showBackToTopBtn.value | ||||||
|  |           ? FloatingActionButton.small( | ||||||
|  |               enableFeedback: true, | ||||||
|  |               backgroundColor: Theme.of(context).secondaryHeaderColor, | ||||||
|  |               foregroundColor: Theme.of(context).primaryColor, | ||||||
|  |               onPressed: () { | ||||||
|  |                 _scrollController.animateTo(0, duration: const Duration(seconds: 1), curve: Curves.easeOutExpo); | ||||||
|  |               }, | ||||||
|  |               child: const Icon(Icons.keyboard_arrow_up_rounded), | ||||||
|  |             ) | ||||||
|  |           : null, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SliverToBoxAdapter _buildDateGroupTitle(String dateTitle) { | ||||||
|  |     var currentYear = DateTime.now().year; | ||||||
|  |     var groupYear = DateTime.parse(dateTitle).year; | ||||||
|  |     var formatDateTemplate = currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy'; | ||||||
|  |     return SliverToBoxAdapter( | ||||||
|  |       child: Padding( | ||||||
|  |         padding: const EdgeInsets.only(top: 24.0, bottom: 24.0, left: 3.0), | ||||||
|  |         child: Row( | ||||||
|  |           children: [ | ||||||
|  |             Padding( | ||||||
|  |               padding: const EdgeInsets.only(left: 8.0, bottom: 5.0, top: 5.0), | ||||||
|  |               child: Text( | ||||||
|  |                 DateFormat(formatDateTemplate).format(DateTime.parse(dateTitle)), | ||||||
|  |                 style: const TextStyle( | ||||||
|  |                   fontSize: 14, | ||||||
|  |                   fontWeight: FontWeight.bold, | ||||||
|  |                   color: Colors.black87, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -0,0 +1,93 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:immich_mobile/shared/models/device_info.model.dart'; | ||||||
|  |  | ||||||
|  | class AuthenticationState { | ||||||
|  |   final String deviceId; | ||||||
|  |   final String deviceType; | ||||||
|  |   final String userId; | ||||||
|  |   final String userEmail; | ||||||
|  |   final bool isAuthenticated; | ||||||
|  |   final DeviceInfoRemote deviceInfo; | ||||||
|  |  | ||||||
|  |   AuthenticationState({ | ||||||
|  |     required this.deviceId, | ||||||
|  |     required this.deviceType, | ||||||
|  |     required this.userId, | ||||||
|  |     required this.userEmail, | ||||||
|  |     required this.isAuthenticated, | ||||||
|  |     required this.deviceInfo, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   AuthenticationState copyWith({ | ||||||
|  |     String? deviceId, | ||||||
|  |     String? deviceType, | ||||||
|  |     String? userId, | ||||||
|  |     String? userEmail, | ||||||
|  |     bool? isAuthenticated, | ||||||
|  |     DeviceInfoRemote? deviceInfo, | ||||||
|  |   }) { | ||||||
|  |     return AuthenticationState( | ||||||
|  |       deviceId: deviceId ?? this.deviceId, | ||||||
|  |       deviceType: deviceType ?? this.deviceType, | ||||||
|  |       userId: userId ?? this.userId, | ||||||
|  |       userEmail: userEmail ?? this.userEmail, | ||||||
|  |       isAuthenticated: isAuthenticated ?? this.isAuthenticated, | ||||||
|  |       deviceInfo: deviceInfo ?? this.deviceInfo, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'AuthenticationState(deviceId: $deviceId, deviceType: $deviceType, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, deviceInfo: $deviceInfo)'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toMap() { | ||||||
|  |     return { | ||||||
|  |       'deviceId': deviceId, | ||||||
|  |       'deviceType': deviceType, | ||||||
|  |       'userId': userId, | ||||||
|  |       'userEmail': userEmail, | ||||||
|  |       'isAuthenticated': isAuthenticated, | ||||||
|  |       'deviceInfo': deviceInfo.toMap(), | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   factory AuthenticationState.fromMap(Map<String, dynamic> map) { | ||||||
|  |     return AuthenticationState( | ||||||
|  |       deviceId: map['deviceId'] ?? '', | ||||||
|  |       deviceType: map['deviceType'] ?? '', | ||||||
|  |       userId: map['userId'] ?? '', | ||||||
|  |       userEmail: map['userEmail'] ?? '', | ||||||
|  |       isAuthenticated: map['isAuthenticated'] ?? false, | ||||||
|  |       deviceInfo: DeviceInfoRemote.fromMap(map['deviceInfo']), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String toJson() => json.encode(toMap()); | ||||||
|  |  | ||||||
|  |   factory AuthenticationState.fromJson(String source) => AuthenticationState.fromMap(json.decode(source)); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  |  | ||||||
|  |     return other is AuthenticationState && | ||||||
|  |         other.deviceId == deviceId && | ||||||
|  |         other.deviceType == deviceType && | ||||||
|  |         other.userId == userId && | ||||||
|  |         other.userEmail == userEmail && | ||||||
|  |         other.isAuthenticated == isAuthenticated && | ||||||
|  |         other.deviceInfo == deviceInfo; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     return deviceId.hashCode ^ | ||||||
|  |         deviceType.hashCode ^ | ||||||
|  |         userId.hashCode ^ | ||||||
|  |         userEmail.hashCode ^ | ||||||
|  |         isAuthenticated.hashCode ^ | ||||||
|  |         deviceInfo.hashCode; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										61
									
								
								mobile/lib/modules/login/models/login_response.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,61 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | class LogInReponse { | ||||||
|  |   final String accessToken; | ||||||
|  |   final String userId; | ||||||
|  |   final String userEmail; | ||||||
|  |  | ||||||
|  |   LogInReponse({ | ||||||
|  |     required this.accessToken, | ||||||
|  |     required this.userId, | ||||||
|  |     required this.userEmail, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   LogInReponse copyWith({ | ||||||
|  |     String? accessToken, | ||||||
|  |     String? userId, | ||||||
|  |     String? userEmail, | ||||||
|  |   }) { | ||||||
|  |     return LogInReponse( | ||||||
|  |       accessToken: accessToken ?? this.accessToken, | ||||||
|  |       userId: userId ?? this.userId, | ||||||
|  |       userEmail: userEmail ?? this.userEmail, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toMap() { | ||||||
|  |     return { | ||||||
|  |       'accessToken': accessToken, | ||||||
|  |       'userId': userId, | ||||||
|  |       'userEmail': userEmail, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   factory LogInReponse.fromMap(Map<String, dynamic> map) { | ||||||
|  |     return LogInReponse( | ||||||
|  |       accessToken: map['accessToken'] ?? '', | ||||||
|  |       userId: map['userId'] ?? '', | ||||||
|  |       userEmail: map['userEmail'] ?? '', | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String toJson() => json.encode(toMap()); | ||||||
|  |  | ||||||
|  |   factory LogInReponse.fromJson(String source) => LogInReponse.fromMap(json.decode(source)); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() => 'LogInReponse(accessToken: $accessToken, userId: $userId, userEmail: $userEmail)'; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  |  | ||||||
|  |     return other is LogInReponse && | ||||||
|  |         other.accessToken == accessToken && | ||||||
|  |         other.userId == userId && | ||||||
|  |         other.userEmail == userEmail; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => accessToken.hashCode ^ userId.hashCode ^ userEmail.hashCode; | ||||||
|  | } | ||||||
							
								
								
									
										127
									
								
								mobile/lib/modules/login/providers/authentication.provider.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,127 @@ | |||||||
|  | import 'package:dio/dio.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:hive/hive.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/constants/hive_box.dart'; | ||||||
|  | import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/login/models/login_response.model.dart'; | ||||||
|  | import 'package:immich_mobile/shared/services/backup.service.dart'; | ||||||
|  | import 'package:immich_mobile/shared/services/device_info.service.dart'; | ||||||
|  | import 'package:immich_mobile/shared/services/network.service.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/device_info.model.dart'; | ||||||
|  | import 'package:immich_mobile/utils/dio_http_interceptor.dart'; | ||||||
|  |  | ||||||
|  | class AuthenticationNotifier extends StateNotifier<AuthenticationState> { | ||||||
|  |   AuthenticationNotifier() | ||||||
|  |       : super( | ||||||
|  |           AuthenticationState( | ||||||
|  |             deviceId: "", | ||||||
|  |             deviceType: "", | ||||||
|  |             isAuthenticated: false, | ||||||
|  |             userId: "", | ||||||
|  |             userEmail: "", | ||||||
|  |             deviceInfo: DeviceInfoRemote( | ||||||
|  |               id: 0, | ||||||
|  |               userId: "", | ||||||
|  |               deviceId: "", | ||||||
|  |               deviceType: "", | ||||||
|  |               notificationToken: "", | ||||||
|  |               createdAt: "", | ||||||
|  |               isAutoBackup: false, | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |   final DeviceInfoService _deviceInfoService = DeviceInfoService(); | ||||||
|  |   final BackupService _backupService = BackupService(); | ||||||
|  |   final NetworkService _networkService = NetworkService(); | ||||||
|  |  | ||||||
|  |   Future<bool> login(String email, String password, String serverEndpoint) async { | ||||||
|  |     // Store server endpoint to Hive and test endpoint | ||||||
|  |     if (serverEndpoint[serverEndpoint.length - 1] == "/") { | ||||||
|  |       var validUrl = serverEndpoint.substring(0, serverEndpoint.length - 1); | ||||||
|  |       Hive.box(userInfoBox).put(serverEndpointKey, validUrl); | ||||||
|  |     } else { | ||||||
|  |       Hive.box(userInfoBox).put(serverEndpointKey, serverEndpoint); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bool isServerEndpointVerified = await _networkService.pingServer(); | ||||||
|  |     if (!isServerEndpointVerified) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Store device id to local storage | ||||||
|  |     var deviceInfo = await _deviceInfoService.getDeviceInfo(); | ||||||
|  |     Hive.box(userInfoBox).put(deviceIdKey, deviceInfo["deviceId"]); | ||||||
|  |  | ||||||
|  |     state = state.copyWith( | ||||||
|  |       deviceId: deviceInfo["deviceId"], | ||||||
|  |       deviceType: deviceInfo["deviceType"], | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // Make sign-in request | ||||||
|  |     try { | ||||||
|  |       Response res = await _networkService.postRequest(url: 'auth/login', data: {'email': email, 'password': password}); | ||||||
|  |  | ||||||
|  |       var payload = LogInReponse.fromJson(res.toString()); | ||||||
|  |  | ||||||
|  |       Hive.box(userInfoBox).put(accessTokenKey, payload.accessToken); | ||||||
|  |  | ||||||
|  |       state = state.copyWith( | ||||||
|  |         isAuthenticated: true, | ||||||
|  |         userId: payload.userId, | ||||||
|  |         userEmail: payload.userEmail, | ||||||
|  |       ); | ||||||
|  |     } catch (e) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Register device info | ||||||
|  |     try { | ||||||
|  |       Response res = await _networkService | ||||||
|  |           .postRequest(url: 'device-info', data: {'deviceId': state.deviceId, 'deviceType': state.deviceType}); | ||||||
|  |  | ||||||
|  |       DeviceInfoRemote deviceInfo = DeviceInfoRemote.fromJson(res.toString()); | ||||||
|  |       state = state.copyWith(deviceInfo: deviceInfo); | ||||||
|  |     } catch (e) { | ||||||
|  |       debugPrint("ERROR Register Device Info: $e"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<bool> logout() async { | ||||||
|  |     Hive.box(userInfoBox).delete(accessTokenKey); | ||||||
|  |     state = AuthenticationState( | ||||||
|  |       deviceId: "", | ||||||
|  |       deviceType: "", | ||||||
|  |       isAuthenticated: false, | ||||||
|  |       userId: "", | ||||||
|  |       userEmail: "", | ||||||
|  |       deviceInfo: DeviceInfoRemote( | ||||||
|  |         id: 0, | ||||||
|  |         userId: "", | ||||||
|  |         deviceId: "", | ||||||
|  |         deviceType: "", | ||||||
|  |         notificationToken: "", | ||||||
|  |         createdAt: "", | ||||||
|  |         isAutoBackup: false, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   setAutoBackup(bool backupState) async { | ||||||
|  |     var deviceInfo = await _deviceInfoService.getDeviceInfo(); | ||||||
|  |     var deviceId = deviceInfo["deviceId"]; | ||||||
|  |     var deviceType = deviceInfo["deviceType"]; | ||||||
|  |  | ||||||
|  |     DeviceInfoRemote deviceInfoRemote = await _backupService.setAutoBackup(backupState, deviceId, deviceType); | ||||||
|  |     state = state.copyWith(deviceInfo: deviceInfoRemote); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | final authenticationProvider = StateNotifierProvider<AuthenticationNotifier, AuthenticationState>((ref) { | ||||||
|  |   return AuthenticationNotifier(); | ||||||
|  | }); | ||||||
							
								
								
									
										124
									
								
								mobile/lib/modules/login/ui/login_form.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,124 @@ | |||||||
|  | import 'package:auto_route/auto_route.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:google_fonts/google_fonts.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||||
|  |  | ||||||
|  | class LoginForm extends HookConsumerWidget { | ||||||
|  |   const LoginForm({Key? key}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final usernameController = useTextEditingController(text: 'testuser@email.com'); | ||||||
|  |     final passwordController = useTextEditingController(text: 'password'); | ||||||
|  |     final serverEndpointController = useTextEditingController(text: 'http://192.168.1.216'); | ||||||
|  |  | ||||||
|  |     return Center( | ||||||
|  |       child: ConstrainedBox( | ||||||
|  |         constraints: const BoxConstraints(maxWidth: 300), | ||||||
|  |         child: Wrap( | ||||||
|  |           spacing: 32, | ||||||
|  |           runSpacing: 32, | ||||||
|  |           alignment: WrapAlignment.center, | ||||||
|  |           children: [ | ||||||
|  |             const Image( | ||||||
|  |               image: AssetImage('assets/immich-logo-no-outline.png'), | ||||||
|  |               width: 128, | ||||||
|  |               filterQuality: FilterQuality.high, | ||||||
|  |             ), | ||||||
|  |             Text( | ||||||
|  |               'IMMICH', | ||||||
|  |               style: GoogleFonts.snowburstOne( | ||||||
|  |                   textStyle: | ||||||
|  |                       TextStyle(fontWeight: FontWeight.bold, fontSize: 48, color: Theme.of(context).primaryColor)), | ||||||
|  |             ), | ||||||
|  |             EmailInput(controller: usernameController), | ||||||
|  |             PasswordInput(controller: passwordController), | ||||||
|  |             ServerEndpointInput(controller: serverEndpointController), | ||||||
|  |             LoginButton( | ||||||
|  |               emailController: usernameController, | ||||||
|  |               passwordController: passwordController, | ||||||
|  |               serverEndpointController: serverEndpointController, | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ServerEndpointInput extends StatelessWidget { | ||||||
|  |   final TextEditingController controller; | ||||||
|  |  | ||||||
|  |   const ServerEndpointInput({Key? key, required this.controller}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return TextFormField( | ||||||
|  |       controller: controller, | ||||||
|  |       decoration: const InputDecoration( | ||||||
|  |           labelText: 'Server Endpoint URL', border: OutlineInputBorder(), hintText: 'http://your-server-ip:port'), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class EmailInput extends StatelessWidget { | ||||||
|  |   final TextEditingController controller; | ||||||
|  |  | ||||||
|  |   const EmailInput({Key? key, required this.controller}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return TextFormField( | ||||||
|  |       controller: controller, | ||||||
|  |       decoration: | ||||||
|  |           const InputDecoration(labelText: 'email', border: OutlineInputBorder(), hintText: 'youremail@email.com'), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class PasswordInput extends StatelessWidget { | ||||||
|  |   final TextEditingController controller; | ||||||
|  |  | ||||||
|  |   const PasswordInput({Key? key, required this.controller}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return TextFormField( | ||||||
|  |       obscureText: true, | ||||||
|  |       controller: controller, | ||||||
|  |       decoration: const InputDecoration(labelText: 'Password', border: OutlineInputBorder(), hintText: 'password'), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class LoginButton extends ConsumerWidget { | ||||||
|  |   final TextEditingController emailController; | ||||||
|  |   final TextEditingController passwordController; | ||||||
|  |   final TextEditingController serverEndpointController; | ||||||
|  |  | ||||||
|  |   const LoginButton( | ||||||
|  |       {Key? key, | ||||||
|  |       required this.emailController, | ||||||
|  |       required this.passwordController, | ||||||
|  |       required this.serverEndpointController}) | ||||||
|  |       : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     return ElevatedButton( | ||||||
|  |         onPressed: () async { | ||||||
|  |           var isAuthenicated = await ref | ||||||
|  |               .read(authenticationProvider.notifier) | ||||||
|  |               .login(emailController.text, passwordController.text, serverEndpointController.text); | ||||||
|  |  | ||||||
|  |           if (isAuthenicated) { | ||||||
|  |             AutoRouter.of(context).pushNamed("/home-page"); | ||||||
|  |           } else { | ||||||
|  |             debugPrint("BAD LOGIN TRY AGAIN - Show UI Here"); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         child: const Text("Login")); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								mobile/lib/modules/login/views/login_page.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | import 'package:auto_route/auto_route.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||||
|  | import 'package:immich_mobile/modules/login/ui/login_form.dart'; | ||||||
|  |  | ||||||
|  | class LoginPage extends HookConsumerWidget { | ||||||
|  |   const LoginPage({Key? key}) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     return const Scaffold( | ||||||
|  |       body: LoginForm(), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								mobile/lib/routing/auth_guard.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,22 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:auto_route/auto_route.dart'; | ||||||
|  | import 'package:flutter/cupertino.dart'; | ||||||
|  | import 'package:immich_mobile/shared/services/network.service.dart'; | ||||||
|  |  | ||||||
|  | class AuthGuard extends AutoRouteGuard { | ||||||
|  |   final NetworkService _networkService = NetworkService(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void onNavigation(NavigationResolver resolver, StackRouter router) async { | ||||||
|  |     try { | ||||||
|  |       var res = await _networkService.postRequest(url: 'auth/validateToken'); | ||||||
|  |       var jsonReponse = jsonDecode(res.toString()); | ||||||
|  |       if (jsonReponse['authStatus']) { | ||||||
|  |         resolver.next(true); | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       router.removeUntil((route) => route.name == "LoginRoute"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								mobile/lib/routing/router.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,22 @@ | |||||||
|  | import 'package:auto_route/auto_route.dart'; | ||||||
|  | import 'package:flutter/widgets.dart'; | ||||||
|  | import 'package:immich_mobile/modules/login/views/login_page.dart'; | ||||||
|  | import 'package:immich_mobile/modules/home/views/home_page.dart'; | ||||||
|  | import 'package:immich_mobile/routing/auth_guard.dart'; | ||||||
|  | import 'package:immich_mobile/shared/views/backup_controller_page.dart'; | ||||||
|  | import 'package:immich_mobile/shared/views/image_viewer_page.dart'; | ||||||
|  |  | ||||||
|  | part 'router.gr.dart'; | ||||||
|  |  | ||||||
|  | @MaterialAutoRouter( | ||||||
|  |   replaceInRouteName: 'Page,Route', | ||||||
|  |   routes: <AutoRoute>[ | ||||||
|  |     AutoRoute(page: LoginPage, initial: true), | ||||||
|  |     AutoRoute(page: HomePage, guards: [AuthGuard]), | ||||||
|  |     AutoRoute(page: BackupControllerPage, guards: [AuthGuard]), | ||||||
|  |     AutoRoute(page: ImageViewerPage, guards: [AuthGuard]), | ||||||
|  |   ], | ||||||
|  | ) | ||||||
|  | class AppRouter extends _$AppRouter { | ||||||
|  |   AppRouter() : super(authGuard: AuthGuard()); | ||||||
|  | } | ||||||
							
								
								
									
										122
									
								
								mobile/lib/routing/router.gr.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,122 @@ | |||||||
|  | // ************************************************************************** | ||||||
|  | // AutoRouteGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // AutoRouteGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  | // | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  |  | ||||||
|  | part of 'router.dart'; | ||||||
|  |  | ||||||
|  | class _$AppRouter extends RootStackRouter { | ||||||
|  |   _$AppRouter( | ||||||
|  |       {GlobalKey<NavigatorState>? navigatorKey, required this.authGuard}) | ||||||
|  |       : super(navigatorKey); | ||||||
|  |  | ||||||
|  |   final AuthGuard authGuard; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   final Map<String, PageFactory> pagesMap = { | ||||||
|  |     LoginRoute.name: (routeData) { | ||||||
|  |       return MaterialPageX<dynamic>( | ||||||
|  |           routeData: routeData, child: const LoginPage()); | ||||||
|  |     }, | ||||||
|  |     HomeRoute.name: (routeData) { | ||||||
|  |       return MaterialPageX<dynamic>( | ||||||
|  |           routeData: routeData, child: const HomePage()); | ||||||
|  |     }, | ||||||
|  |     BackupControllerRoute.name: (routeData) { | ||||||
|  |       return MaterialPageX<dynamic>( | ||||||
|  |           routeData: routeData, child: const BackupControllerPage()); | ||||||
|  |     }, | ||||||
|  |     ImageViewerRoute.name: (routeData) { | ||||||
|  |       final args = routeData.argsAs<ImageViewerRouteArgs>(); | ||||||
|  |       return MaterialPageX<dynamic>( | ||||||
|  |           routeData: routeData, | ||||||
|  |           child: ImageViewerPage( | ||||||
|  |               key: args.key, | ||||||
|  |               imageUrl: args.imageUrl, | ||||||
|  |               heroTag: args.heroTag, | ||||||
|  |               thumbnailUrl: args.thumbnailUrl)); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   List<RouteConfig> get routes => [ | ||||||
|  |         RouteConfig(LoginRoute.name, path: '/'), | ||||||
|  |         RouteConfig(HomeRoute.name, path: '/home-page', guards: [authGuard]), | ||||||
|  |         RouteConfig(BackupControllerRoute.name, | ||||||
|  |             path: '/backup-controller-page', guards: [authGuard]), | ||||||
|  |         RouteConfig(ImageViewerRoute.name, | ||||||
|  |             path: '/image-viewer-page', guards: [authGuard]) | ||||||
|  |       ]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// generated route for | ||||||
|  | /// [LoginPage] | ||||||
|  | class LoginRoute extends PageRouteInfo<void> { | ||||||
|  |   const LoginRoute() : super(LoginRoute.name, path: '/'); | ||||||
|  |  | ||||||
|  |   static const String name = 'LoginRoute'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// generated route for | ||||||
|  | /// [HomePage] | ||||||
|  | class HomeRoute extends PageRouteInfo<void> { | ||||||
|  |   const HomeRoute() : super(HomeRoute.name, path: '/home-page'); | ||||||
|  |  | ||||||
|  |   static const String name = 'HomeRoute'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// generated route for | ||||||
|  | /// [BackupControllerPage] | ||||||
|  | class BackupControllerRoute extends PageRouteInfo<void> { | ||||||
|  |   const BackupControllerRoute() | ||||||
|  |       : super(BackupControllerRoute.name, path: '/backup-controller-page'); | ||||||
|  |  | ||||||
|  |   static const String name = 'BackupControllerRoute'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// generated route for | ||||||
|  | /// [ImageViewerPage] | ||||||
|  | class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> { | ||||||
|  |   ImageViewerRoute( | ||||||
|  |       {Key? key, | ||||||
|  |       required String imageUrl, | ||||||
|  |       required String heroTag, | ||||||
|  |       required String thumbnailUrl}) | ||||||
|  |       : super(ImageViewerRoute.name, | ||||||
|  |             path: '/image-viewer-page', | ||||||
|  |             args: ImageViewerRouteArgs( | ||||||
|  |                 key: key, | ||||||
|  |                 imageUrl: imageUrl, | ||||||
|  |                 heroTag: heroTag, | ||||||
|  |                 thumbnailUrl: thumbnailUrl)); | ||||||
|  |  | ||||||
|  |   static const String name = 'ImageViewerRoute'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ImageViewerRouteArgs { | ||||||
|  |   const ImageViewerRouteArgs( | ||||||
|  |       {this.key, | ||||||
|  |       required this.imageUrl, | ||||||
|  |       required this.heroTag, | ||||||
|  |       required this.thumbnailUrl}); | ||||||
|  |  | ||||||
|  |   final Key? key; | ||||||
|  |  | ||||||
|  |   final String imageUrl; | ||||||
|  |  | ||||||
|  |   final String heroTag; | ||||||
|  |  | ||||||
|  |   final String thumbnailUrl; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'ImageViewerRouteArgs{key: $key, imageUrl: $imageUrl, heroTag: $heroTag, thumbnailUrl: $thumbnailUrl}'; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								mobile/lib/shared/models/backup_state.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,77 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:dio/dio.dart'; | ||||||
|  |  | ||||||
|  | import 'package:immich_mobile/shared/models/server_info.model.dart'; | ||||||
|  |  | ||||||
|  | enum BackUpProgressEnum { idle, inProgress, done } | ||||||
|  |  | ||||||
|  | class BackUpState { | ||||||
|  |   final BackUpProgressEnum backupProgress; | ||||||
|  |   final int totalAssetCount; | ||||||
|  |   final int assetOnDatabase; | ||||||
|  |   final int backingUpAssetCount; | ||||||
|  |   final double progressInPercentage; | ||||||
|  |   final CancelToken cancelToken; | ||||||
|  |   final ServerInfo serverInfo; | ||||||
|  |  | ||||||
|  |   BackUpState({ | ||||||
|  |     required this.backupProgress, | ||||||
|  |     required this.totalAssetCount, | ||||||
|  |     required this.assetOnDatabase, | ||||||
|  |     required this.backingUpAssetCount, | ||||||
|  |     required this.progressInPercentage, | ||||||
|  |     required this.cancelToken, | ||||||
|  |     required this.serverInfo, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   BackUpState copyWith({ | ||||||
|  |     BackUpProgressEnum? backupProgress, | ||||||
|  |     int? totalAssetCount, | ||||||
|  |     int? assetOnDatabase, | ||||||
|  |     int? backingUpAssetCount, | ||||||
|  |     double? progressInPercentage, | ||||||
|  |     CancelToken? cancelToken, | ||||||
|  |     ServerInfo? serverInfo, | ||||||
|  |   }) { | ||||||
|  |     return BackUpState( | ||||||
|  |       backupProgress: backupProgress ?? this.backupProgress, | ||||||
|  |       totalAssetCount: totalAssetCount ?? this.totalAssetCount, | ||||||
|  |       assetOnDatabase: assetOnDatabase ?? this.assetOnDatabase, | ||||||
|  |       backingUpAssetCount: backingUpAssetCount ?? this.backingUpAssetCount, | ||||||
|  |       progressInPercentage: progressInPercentage ?? this.progressInPercentage, | ||||||
|  |       cancelToken: cancelToken ?? this.cancelToken, | ||||||
|  |       serverInfo: serverInfo ?? this.serverInfo, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'BackUpState(backupProgress: $backupProgress, totalAssetCount: $totalAssetCount, assetOnDatabase: $assetOnDatabase, backingUpAssetCount: $backingUpAssetCount, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo)'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  |  | ||||||
|  |     return other is BackUpState && | ||||||
|  |         other.backupProgress == backupProgress && | ||||||
|  |         other.totalAssetCount == totalAssetCount && | ||||||
|  |         other.assetOnDatabase == assetOnDatabase && | ||||||
|  |         other.backingUpAssetCount == backingUpAssetCount && | ||||||
|  |         other.progressInPercentage == progressInPercentage && | ||||||
|  |         other.cancelToken == cancelToken && | ||||||
|  |         other.serverInfo == serverInfo; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     return backupProgress.hashCode ^ | ||||||
|  |         totalAssetCount.hashCode ^ | ||||||
|  |         assetOnDatabase.hashCode ^ | ||||||
|  |         backingUpAssetCount.hashCode ^ | ||||||
|  |         progressInPercentage.hashCode ^ | ||||||
|  |         cancelToken.hashCode ^ | ||||||
|  |         serverInfo.hashCode; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										100
									
								
								mobile/lib/shared/models/device_info.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,100 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  | import 'dart:ffi'; | ||||||
|  |  | ||||||
|  | class DeviceInfoRemote { | ||||||
|  |   final int id; | ||||||
|  |   final String userId; | ||||||
|  |   final String deviceId; | ||||||
|  |   final String deviceType; | ||||||
|  |   final String notificationToken; | ||||||
|  |   final String createdAt; | ||||||
|  |   final bool isAutoBackup; | ||||||
|  |  | ||||||
|  |   DeviceInfoRemote({ | ||||||
|  |     required this.id, | ||||||
|  |     required this.userId, | ||||||
|  |     required this.deviceId, | ||||||
|  |     required this.deviceType, | ||||||
|  |     required this.notificationToken, | ||||||
|  |     required this.createdAt, | ||||||
|  |     required this.isAutoBackup, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   DeviceInfoRemote copyWith({ | ||||||
|  |     int? id, | ||||||
|  |     String? userId, | ||||||
|  |     String? deviceId, | ||||||
|  |     String? deviceType, | ||||||
|  |     String? notificationToken, | ||||||
|  |     String? createdAt, | ||||||
|  |     bool? isAutoBackup, | ||||||
|  |   }) { | ||||||
|  |     return DeviceInfoRemote( | ||||||
|  |       id: id ?? this.id, | ||||||
|  |       userId: userId ?? this.userId, | ||||||
|  |       deviceId: deviceId ?? this.deviceId, | ||||||
|  |       deviceType: deviceType ?? this.deviceType, | ||||||
|  |       notificationToken: notificationToken ?? this.notificationToken, | ||||||
|  |       createdAt: createdAt ?? this.createdAt, | ||||||
|  |       isAutoBackup: isAutoBackup ?? this.isAutoBackup, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toMap() { | ||||||
|  |     return { | ||||||
|  |       'id': id, | ||||||
|  |       'userId': userId, | ||||||
|  |       'deviceId': deviceId, | ||||||
|  |       'deviceType': deviceType, | ||||||
|  |       'notificationToken': notificationToken, | ||||||
|  |       'createdAt': createdAt, | ||||||
|  |       'isAutoBackup': isAutoBackup, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   factory DeviceInfoRemote.fromMap(Map<String, dynamic> map) { | ||||||
|  |     return DeviceInfoRemote( | ||||||
|  |       id: map['id']?.toInt() ?? 0, | ||||||
|  |       userId: map['userId'] ?? '', | ||||||
|  |       deviceId: map['deviceId'] ?? '', | ||||||
|  |       deviceType: map['deviceType'] ?? '', | ||||||
|  |       notificationToken: map['notificationToken'] ?? '', | ||||||
|  |       createdAt: map['createdAt'] ?? '', | ||||||
|  |       isAutoBackup: map['isAutoBackup'] ?? false, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String toJson() => json.encode(toMap()); | ||||||
|  |  | ||||||
|  |   factory DeviceInfoRemote.fromJson(String source) => DeviceInfoRemote.fromMap(json.decode(source)); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'DeviceInfo(id: $id, userId: $userId, deviceId: $deviceId, deviceType: $deviceType, notificationToken: $notificationToken, createdAt: $createdAt, isAutoBackup: $isAutoBackup)'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  |  | ||||||
|  |     return other is DeviceInfoRemote && | ||||||
|  |         other.id == id && | ||||||
|  |         other.userId == userId && | ||||||
|  |         other.deviceId == deviceId && | ||||||
|  |         other.deviceType == deviceType && | ||||||
|  |         other.notificationToken == notificationToken && | ||||||
|  |         other.createdAt == createdAt && | ||||||
|  |         other.isAutoBackup == isAutoBackup; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     return id.hashCode ^ | ||||||
|  |         userId.hashCode ^ | ||||||
|  |         deviceId.hashCode ^ | ||||||
|  |         deviceType.hashCode ^ | ||||||
|  |         notificationToken.hashCode ^ | ||||||
|  |         createdAt.hashCode ^ | ||||||
|  |         isAutoBackup.hashCode; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								mobile/lib/shared/models/image_viewer_page_data.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | |||||||
|  | class ImageViewerPageData { | ||||||
|  |   final String heroTag; | ||||||
|  |   final String imageUrl; | ||||||
|  |   final String thumbnailUrl; | ||||||
|  |  | ||||||
|  |   ImageViewerPageData({ | ||||||
|  |     required this.heroTag, | ||||||
|  |     required this.imageUrl, | ||||||
|  |     required this.thumbnailUrl, | ||||||
|  |   }); | ||||||
|  | } | ||||||
							
								
								
									
										131
									
								
								mobile/lib/shared/models/immich_asset.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,131 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | class ImmichAsset { | ||||||
|  |   final String id; | ||||||
|  |   final String deviceAssetId; | ||||||
|  |   final String userId; | ||||||
|  |   final String deviceId; | ||||||
|  |   final String assetType; | ||||||
|  |   final String localPath; | ||||||
|  |   final String remotePath; | ||||||
|  |   final String createdAt; | ||||||
|  |   final String modifiedAt; | ||||||
|  |   final bool isFavorite; | ||||||
|  |   final String? description; | ||||||
|  |  | ||||||
|  |   ImmichAsset({ | ||||||
|  |     required this.id, | ||||||
|  |     required this.deviceAssetId, | ||||||
|  |     required this.userId, | ||||||
|  |     required this.deviceId, | ||||||
|  |     required this.assetType, | ||||||
|  |     required this.localPath, | ||||||
|  |     required this.remotePath, | ||||||
|  |     required this.createdAt, | ||||||
|  |     required this.modifiedAt, | ||||||
|  |     required this.isFavorite, | ||||||
|  |     this.description, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   ImmichAsset copyWith({ | ||||||
|  |     String? id, | ||||||
|  |     String? deviceAssetId, | ||||||
|  |     String? userId, | ||||||
|  |     String? deviceId, | ||||||
|  |     String? assetType, | ||||||
|  |     String? localPath, | ||||||
|  |     String? remotePath, | ||||||
|  |     String? createdAt, | ||||||
|  |     String? modifiedAt, | ||||||
|  |     bool? isFavorite, | ||||||
|  |     String? description, | ||||||
|  |   }) { | ||||||
|  |     return ImmichAsset( | ||||||
|  |       id: id ?? this.id, | ||||||
|  |       deviceAssetId: deviceAssetId ?? this.deviceAssetId, | ||||||
|  |       userId: userId ?? this.userId, | ||||||
|  |       deviceId: deviceId ?? this.deviceId, | ||||||
|  |       assetType: assetType ?? this.assetType, | ||||||
|  |       localPath: localPath ?? this.localPath, | ||||||
|  |       remotePath: remotePath ?? this.remotePath, | ||||||
|  |       createdAt: createdAt ?? this.createdAt, | ||||||
|  |       modifiedAt: modifiedAt ?? this.modifiedAt, | ||||||
|  |       isFavorite: isFavorite ?? this.isFavorite, | ||||||
|  |       description: description ?? this.description, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toMap() { | ||||||
|  |     return { | ||||||
|  |       'id': id, | ||||||
|  |       'deviceAssetId': deviceAssetId, | ||||||
|  |       'userId': userId, | ||||||
|  |       'deviceId': deviceId, | ||||||
|  |       'assetType': assetType, | ||||||
|  |       'localPath': localPath, | ||||||
|  |       'remotePath': remotePath, | ||||||
|  |       'createdAt': createdAt, | ||||||
|  |       'modifiedAt': modifiedAt, | ||||||
|  |       'isFavorite': isFavorite, | ||||||
|  |       'description': description, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   factory ImmichAsset.fromMap(Map<String, dynamic> map) { | ||||||
|  |     return ImmichAsset( | ||||||
|  |       id: map['id'] ?? '', | ||||||
|  |       deviceAssetId: map['deviceAssetId'] ?? '', | ||||||
|  |       userId: map['userId'] ?? '', | ||||||
|  |       deviceId: map['deviceId'] ?? '', | ||||||
|  |       assetType: map['assetType'] ?? '', | ||||||
|  |       localPath: map['localPath'] ?? '', | ||||||
|  |       remotePath: map['remotePath'] ?? '', | ||||||
|  |       createdAt: map['createdAt'] ?? '', | ||||||
|  |       modifiedAt: map['modifiedAt'] ?? '', | ||||||
|  |       isFavorite: map['isFavorite'] ?? false, | ||||||
|  |       description: map['description'], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String toJson() => json.encode(toMap()); | ||||||
|  |  | ||||||
|  |   factory ImmichAsset.fromJson(String source) => ImmichAsset.fromMap(json.decode(source)); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'ImmichAsset(id: $id, deviceAssetId: $deviceAssetId, userId: $userId, deviceId: $deviceId, assetType: $assetType, localPath: $localPath, remotePath: $remotePath, createdAt: $createdAt, modifiedAt: $modifiedAt, isFavorite: $isFavorite, description: $description)'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  |  | ||||||
|  |     return other is ImmichAsset && | ||||||
|  |         other.id == id && | ||||||
|  |         other.deviceAssetId == deviceAssetId && | ||||||
|  |         other.userId == userId && | ||||||
|  |         other.deviceId == deviceId && | ||||||
|  |         other.assetType == assetType && | ||||||
|  |         other.localPath == localPath && | ||||||
|  |         other.remotePath == remotePath && | ||||||
|  |         other.createdAt == createdAt && | ||||||
|  |         other.modifiedAt == modifiedAt && | ||||||
|  |         other.isFavorite == isFavorite && | ||||||
|  |         other.description == description; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     return id.hashCode ^ | ||||||
|  |         deviceAssetId.hashCode ^ | ||||||
|  |         userId.hashCode ^ | ||||||
|  |         deviceId.hashCode ^ | ||||||
|  |         assetType.hashCode ^ | ||||||
|  |         localPath.hashCode ^ | ||||||
|  |         remotePath.hashCode ^ | ||||||
|  |         createdAt.hashCode ^ | ||||||
|  |         modifiedAt.hashCode ^ | ||||||
|  |         isFavorite.hashCode ^ | ||||||
|  |         description.hashCode; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										98
									
								
								mobile/lib/shared/models/server_info.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,98 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | class ServerInfo { | ||||||
|  |   final String diskSize; | ||||||
|  |   final String diskUse; | ||||||
|  |   final String diskAvailable; | ||||||
|  |   final int diskSizeRaw; | ||||||
|  |   final int diskUseRaw; | ||||||
|  |   final int diskAvailableRaw; | ||||||
|  |   final double diskUsagePercentage; | ||||||
|  |   ServerInfo({ | ||||||
|  |     required this.diskSize, | ||||||
|  |     required this.diskUse, | ||||||
|  |     required this.diskAvailable, | ||||||
|  |     required this.diskSizeRaw, | ||||||
|  |     required this.diskUseRaw, | ||||||
|  |     required this.diskAvailableRaw, | ||||||
|  |     required this.diskUsagePercentage, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   ServerInfo copyWith({ | ||||||
|  |     String? diskSize, | ||||||
|  |     String? diskUse, | ||||||
|  |     String? diskAvailable, | ||||||
|  |     int? diskSizeRaw, | ||||||
|  |     int? diskUseRaw, | ||||||
|  |     int? diskAvailableRaw, | ||||||
|  |     double? diskUsagePercentage, | ||||||
|  |   }) { | ||||||
|  |     return ServerInfo( | ||||||
|  |       diskSize: diskSize ?? this.diskSize, | ||||||
|  |       diskUse: diskUse ?? this.diskUse, | ||||||
|  |       diskAvailable: diskAvailable ?? this.diskAvailable, | ||||||
|  |       diskSizeRaw: diskSizeRaw ?? this.diskSizeRaw, | ||||||
|  |       diskUseRaw: diskUseRaw ?? this.diskUseRaw, | ||||||
|  |       diskAvailableRaw: diskAvailableRaw ?? this.diskAvailableRaw, | ||||||
|  |       diskUsagePercentage: diskUsagePercentage ?? this.diskUsagePercentage, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Map<String, dynamic> toMap() { | ||||||
|  |     return { | ||||||
|  |       'diskSize': diskSize, | ||||||
|  |       'diskUse': diskUse, | ||||||
|  |       'diskAvailable': diskAvailable, | ||||||
|  |       'diskSizeRaw': diskSizeRaw, | ||||||
|  |       'diskUseRaw': diskUseRaw, | ||||||
|  |       'diskAvailableRaw': diskAvailableRaw, | ||||||
|  |       'diskUsagePercentage': diskUsagePercentage, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   factory ServerInfo.fromMap(Map<String, dynamic> map) { | ||||||
|  |     return ServerInfo( | ||||||
|  |       diskSize: map['diskSize'] ?? '', | ||||||
|  |       diskUse: map['diskUse'] ?? '', | ||||||
|  |       diskAvailable: map['diskAvailable'] ?? '', | ||||||
|  |       diskSizeRaw: map['diskSizeRaw']?.toInt() ?? 0, | ||||||
|  |       diskUseRaw: map['diskUseRaw']?.toInt() ?? 0, | ||||||
|  |       diskAvailableRaw: map['diskAvailableRaw']?.toInt() ?? 0, | ||||||
|  |       diskUsagePercentage: map['diskUsagePercentage']?.toDouble() ?? 0.0, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String toJson() => json.encode(toMap()); | ||||||
|  |  | ||||||
|  |   factory ServerInfo.fromJson(String source) => ServerInfo.fromMap(json.decode(source)); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'ServerInfo(diskSize: $diskSize, diskUse: $diskUse, diskAvailable: $diskAvailable, diskSizeRaw: $diskSizeRaw, diskUseRaw: $diskUseRaw, diskAvailableRaw: $diskAvailableRaw, diskUsagePercentage: $diskUsagePercentage)'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  |  | ||||||
|  |     return other is ServerInfo && | ||||||
|  |         other.diskSize == diskSize && | ||||||
|  |         other.diskUse == diskUse && | ||||||
|  |         other.diskAvailable == diskAvailable && | ||||||
|  |         other.diskSizeRaw == diskSizeRaw && | ||||||
|  |         other.diskUseRaw == diskUseRaw && | ||||||
|  |         other.diskAvailableRaw == diskAvailableRaw && | ||||||
|  |         other.diskUsagePercentage == diskUsagePercentage; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     return diskSize.hashCode ^ | ||||||
|  |         diskUse.hashCode ^ | ||||||
|  |         diskAvailable.hashCode ^ | ||||||
|  |         diskSizeRaw.hashCode ^ | ||||||
|  |         diskUseRaw.hashCode ^ | ||||||
|  |         diskAvailableRaw.hashCode ^ | ||||||
|  |         diskUsagePercentage.hashCode; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								mobile/lib/shared/providers/app_state.provider.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | |||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  |  | ||||||
|  | enum AppStateEnum { | ||||||
|  |   active, | ||||||
|  |   inactive, | ||||||
|  |   paused, | ||||||
|  |   resumed, | ||||||
|  |   detached, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | final appStateProvider = StateProvider<AppStateEnum>((ref) { | ||||||
|  |   return AppStateEnum.active; | ||||||
|  | }); | ||||||
							
								
								
									
										137
									
								
								mobile/lib/shared/providers/backup.provider.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,137 @@ | |||||||
|  | import 'package:dio/dio.dart'; | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/shared/services/server_info.service.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/backup_state.model.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/server_info.model.dart'; | ||||||
|  | import 'package:immich_mobile/shared/services/backup.service.dart'; | ||||||
|  | import 'package:photo_manager/photo_manager.dart'; | ||||||
|  |  | ||||||
|  | class BackupNotifier extends StateNotifier<BackUpState> { | ||||||
|  |   BackupNotifier() | ||||||
|  |       : super( | ||||||
|  |           BackUpState( | ||||||
|  |             backupProgress: BackUpProgressEnum.idle, | ||||||
|  |             backingUpAssetCount: 0, | ||||||
|  |             assetOnDatabase: 0, | ||||||
|  |             totalAssetCount: 0, | ||||||
|  |             progressInPercentage: 0, | ||||||
|  |             cancelToken: CancelToken(), | ||||||
|  |             serverInfo: ServerInfo( | ||||||
|  |               diskAvailable: "0", | ||||||
|  |               diskAvailableRaw: 0, | ||||||
|  |               diskSize: "0", | ||||||
|  |               diskSizeRaw: 0, | ||||||
|  |               diskUsagePercentage: 0.0, | ||||||
|  |               diskUse: "0", | ||||||
|  |               diskUseRaw: 0, | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |   final BackupService _backupService = BackupService(); | ||||||
|  |   final ServerInfoService _serverInfoService = ServerInfoService(); | ||||||
|  |  | ||||||
|  |   void getBackupInfo() async { | ||||||
|  |     _updateServerInfo(); | ||||||
|  |  | ||||||
|  |     List<AssetPathEntity> list = await PhotoManager.getAssetPathList(onlyAll: true, type: RequestType.image); | ||||||
|  |  | ||||||
|  |     if (list.isEmpty) { | ||||||
|  |       debugPrint("No Asset On Device"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     int totalAsset = list[0].assetCount; | ||||||
|  |     List<String> didBackupAsset = await _backupService.getDeviceBackupAsset(); | ||||||
|  |  | ||||||
|  |     state = state.copyWith(totalAssetCount: totalAsset, assetOnDatabase: didBackupAsset.length); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void startBackupProcess() async { | ||||||
|  |     _updateServerInfo(); | ||||||
|  |  | ||||||
|  |     state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress); | ||||||
|  |  | ||||||
|  |     var authResult = await PhotoManager.requestPermissionExtend(); | ||||||
|  |     if (authResult.isAuth) { | ||||||
|  |       await PhotoManager.clearFileCache(); | ||||||
|  |       // await PhotoManager.presentLimited(); | ||||||
|  |       // Gather assets info | ||||||
|  |       List<AssetPathEntity> list = | ||||||
|  |           await PhotoManager.getAssetPathList(hasAll: true, onlyAll: true, type: RequestType.image); | ||||||
|  |  | ||||||
|  |       if (list.isEmpty) { | ||||||
|  |         debugPrint("No Asset On Device - Abort Backup Process"); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       int totalAsset = list[0].assetCount; | ||||||
|  |       List<AssetEntity> currentAssets = await list[0].getAssetListRange(start: 0, end: totalAsset); | ||||||
|  |  | ||||||
|  |       // Get device assets info from database | ||||||
|  |       // Compare and find different assets that has not been backing up | ||||||
|  |       // Backup those assets | ||||||
|  |       List<String> backupAsset = await _backupService.getDeviceBackupAsset(); | ||||||
|  |  | ||||||
|  |       state = state.copyWith(totalAssetCount: totalAsset, assetOnDatabase: backupAsset.length); | ||||||
|  |       // Remove item that has already been backed up | ||||||
|  |       for (var backupAssetId in backupAsset) { | ||||||
|  |         currentAssets.removeWhere((e) => e.id == backupAssetId); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (currentAssets.isEmpty) { | ||||||
|  |         state = state.copyWith(backupProgress: BackUpProgressEnum.idle); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       state = state.copyWith(backingUpAssetCount: currentAssets.length); | ||||||
|  |  | ||||||
|  |       // Perform Packup | ||||||
|  |       state = state.copyWith(cancelToken: CancelToken()); | ||||||
|  |       _backupService.backupAsset(currentAssets, state.cancelToken, _onAssetUploaded, _onUploadProgress); | ||||||
|  |     } else { | ||||||
|  |       PhotoManager.openSetting(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void cancelBackup() { | ||||||
|  |     state.cancelToken.cancel('Cancel Backup'); | ||||||
|  |     state = state.copyWith(backupProgress: BackUpProgressEnum.idle); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void _onAssetUploaded() { | ||||||
|  |     state = | ||||||
|  |         state.copyWith(backingUpAssetCount: state.backingUpAssetCount - 1, assetOnDatabase: state.assetOnDatabase + 1); | ||||||
|  |  | ||||||
|  |     if (state.backingUpAssetCount == 0) { | ||||||
|  |       state = state.copyWith(backupProgress: BackUpProgressEnum.done, progressInPercentage: 0.0); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     _updateServerInfo(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void _onUploadProgress(int sent, int total) { | ||||||
|  |     state = state.copyWith(progressInPercentage: (sent.toDouble() / total.toDouble() * 100)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void _updateServerInfo() async { | ||||||
|  |     var serverInfo = await _serverInfoService.getServerInfo(); | ||||||
|  |  | ||||||
|  |     // Update server info | ||||||
|  |     state = state.copyWith( | ||||||
|  |       serverInfo: ServerInfo( | ||||||
|  |         diskSize: serverInfo.diskSize, | ||||||
|  |         diskUse: serverInfo.diskUse, | ||||||
|  |         diskAvailable: serverInfo.diskAvailable, | ||||||
|  |         diskSizeRaw: serverInfo.diskSizeRaw, | ||||||
|  |         diskUseRaw: serverInfo.diskUseRaw, | ||||||
|  |         diskAvailableRaw: serverInfo.diskAvailableRaw, | ||||||
|  |         diskUsagePercentage: serverInfo.diskUsagePercentage, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | final backupProvider = StateNotifierProvider<BackupNotifier, BackUpState>((ref) { | ||||||
|  |   return BackupNotifier(); | ||||||
|  | }); | ||||||
							
								
								
									
										124
									
								
								mobile/lib/shared/services/backup.service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,124 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  | import 'dart:io'; | ||||||
|  |  | ||||||
|  | import 'package:dio/dio.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:hive/hive.dart'; | ||||||
|  | import 'package:immich_mobile/constants/hive_box.dart'; | ||||||
|  | import 'package:immich_mobile/shared/services/network.service.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/device_info.model.dart'; | ||||||
|  | import 'package:immich_mobile/utils/dio_http_interceptor.dart'; | ||||||
|  | import 'package:immich_mobile/utils/files_helper.dart'; | ||||||
|  | import 'package:photo_manager/photo_manager.dart'; | ||||||
|  | import 'package:http_parser/http_parser.dart'; | ||||||
|  | import 'package:path/path.dart' as p; | ||||||
|  | import 'package:exif/exif.dart'; | ||||||
|  |  | ||||||
|  | class BackupService { | ||||||
|  |   final NetworkService _networkService = NetworkService(); | ||||||
|  |  | ||||||
|  |   Future<List<String>> getDeviceBackupAsset() async { | ||||||
|  |     String deviceId = Hive.box(userInfoBox).get(deviceIdKey); | ||||||
|  |  | ||||||
|  |     Response response = await _networkService.getRequest(url: "asset/$deviceId"); | ||||||
|  |     List<dynamic> result = jsonDecode(response.toString()); | ||||||
|  |  | ||||||
|  |     return result.cast<String>(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   backupAsset(List<AssetEntity> assetList, CancelToken cancelToken, Function singleAssetDoneCb, | ||||||
|  |       Function(int, int) uploadProgress) async { | ||||||
|  |     var dio = Dio(); | ||||||
|  |     dio.interceptors.add(AuthenticatedRequestInterceptor()); | ||||||
|  |     String deviceId = Hive.box(userInfoBox).get(deviceIdKey); | ||||||
|  |     String savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey); | ||||||
|  |     File? file; | ||||||
|  |  | ||||||
|  |     for (var entity in assetList) { | ||||||
|  |       try { | ||||||
|  |         file = await entity.file.timeout(const Duration(seconds: 5)); | ||||||
|  |  | ||||||
|  |         if (file != null) { | ||||||
|  |           // reading exif | ||||||
|  |           // var exifData = await readExifFromFile(file); | ||||||
|  |  | ||||||
|  |           // for (String key in exifData.keys) { | ||||||
|  |           //   debugPrint("- $key (${exifData[key]?.tagType}): ${exifData[key]}"); | ||||||
|  |           // } | ||||||
|  |  | ||||||
|  |           // debugPrint("------------------"); | ||||||
|  |           String originalFileName = await entity.titleAsync; | ||||||
|  |           String fileNameWithoutPath = originalFileName.toString().split(".")[0]; | ||||||
|  |           var fileExtension = p.extension(file.path); | ||||||
|  |           LatLng coordinate = await entity.latlngAsync(); | ||||||
|  |           var mimeType = FileHelper.getMimeType(file.path); | ||||||
|  |           var formData = FormData.fromMap({ | ||||||
|  |             'deviceAssetId': entity.id, | ||||||
|  |             'deviceId': deviceId, | ||||||
|  |             'assetType': _getAssetType(entity.type), | ||||||
|  |             'createdAt': entity.createDateTime.toIso8601String(), | ||||||
|  |             'modifiedAt': entity.modifiedDateTime.toIso8601String(), | ||||||
|  |             'isFavorite': entity.isFavorite, | ||||||
|  |             'fileExtension': fileExtension, | ||||||
|  |             'lat': coordinate.latitude, | ||||||
|  |             'lon': coordinate.longitude, | ||||||
|  |             'files': [ | ||||||
|  |               await MultipartFile.fromFile( | ||||||
|  |                 file.path, | ||||||
|  |                 filename: fileNameWithoutPath, | ||||||
|  |                 contentType: MediaType( | ||||||
|  |                   mimeType["type"], | ||||||
|  |                   mimeType["subType"], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ] | ||||||
|  |           }); | ||||||
|  |  | ||||||
|  |           Response res = await dio.post( | ||||||
|  |             '$savedEndpoint/asset/upload', | ||||||
|  |             data: formData, | ||||||
|  |             cancelToken: cancelToken, | ||||||
|  |             onSendProgress: (sent, total) => uploadProgress(sent, total), | ||||||
|  |           ); | ||||||
|  |  | ||||||
|  |           if (res.statusCode == 201) { | ||||||
|  |             singleAssetDoneCb(); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } on DioError catch (e) { | ||||||
|  |         debugPrint("DioError backupAsset: ${e.response}"); | ||||||
|  |         break; | ||||||
|  |       } catch (e) { | ||||||
|  |         debugPrint("ERROR backupAsset: ${e.toString()}"); | ||||||
|  |         continue; | ||||||
|  |       } finally { | ||||||
|  |         if (Platform.isIOS) { | ||||||
|  |           file?.deleteSync(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String _getAssetType(AssetType assetType) { | ||||||
|  |     switch (assetType) { | ||||||
|  |       case AssetType.audio: | ||||||
|  |         return "AUDIO"; | ||||||
|  |       case AssetType.image: | ||||||
|  |         return "IMAGE"; | ||||||
|  |       case AssetType.video: | ||||||
|  |         return "VIDEO"; | ||||||
|  |       case AssetType.other: | ||||||
|  |         return "OTHER"; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<DeviceInfoRemote> setAutoBackup(bool status, String deviceId, String deviceType) async { | ||||||
|  |     var res = await _networkService.patchRequest(url: 'device-info', data: { | ||||||
|  |       "isAutoBackup": status, | ||||||
|  |       "deviceId": deviceId, | ||||||
|  |       "deviceType": deviceType, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return DeviceInfoRemote.fromJson(res.toString()); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								mobile/lib/shared/services/device_info.service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,30 @@ | |||||||
|  | import 'package:device_info_plus/device_info_plus.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  |  | ||||||
|  | class DeviceInfoService { | ||||||
|  |   Future<Map<String, dynamic>> getDeviceInfo() async { | ||||||
|  |     // Get device info | ||||||
|  |     DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); | ||||||
|  |     String? deviceId = ""; | ||||||
|  |     String deviceType = ""; | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; | ||||||
|  |       deviceId = androidInfo.androidId; | ||||||
|  |       deviceType = "ANDROID"; | ||||||
|  |     } catch (e) { | ||||||
|  |       debugPrint("Not an android device"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       IosDeviceInfo iosInfo = await deviceInfo.iosInfo; | ||||||
|  |       deviceId = iosInfo.identifierForVendor; | ||||||
|  |       deviceType = "IOS"; | ||||||
|  |       debugPrint("Device ID: $deviceId"); | ||||||
|  |     } catch (e) { | ||||||
|  |       debugPrint("Not an ios device"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return {"deviceId": deviceId, "deviceType": deviceType}; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								mobile/lib/shared/services/local_storage.service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | import 'package:hive/hive.dart'; | ||||||
|  | import 'package:immich_mobile/constants/hive_box.dart'; | ||||||
|  |  | ||||||
|  | class LocalStorageService { | ||||||
|  |   late Box _box; | ||||||
|  |  | ||||||
|  |   LocalStorageService() { | ||||||
|  |     _box = Hive.box(userInfoBox); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   T get<T>(String key) { | ||||||
|  |     return _box.get(key); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   put<T>(String key, T value) { | ||||||
|  |     return _box.put(key, value); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								mobile/lib/shared/services/network.service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,89 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:dio/dio.dart'; | ||||||
|  | import 'package:flutter/cupertino.dart'; | ||||||
|  | import 'package:hive/hive.dart'; | ||||||
|  | import 'package:immich_mobile/constants/hive_box.dart'; | ||||||
|  | import 'package:immich_mobile/utils/dio_http_interceptor.dart'; | ||||||
|  |  | ||||||
|  | class NetworkService { | ||||||
|  |   Future<dynamic> getRequest({required String url}) async { | ||||||
|  |     try { | ||||||
|  |       var dio = Dio(); | ||||||
|  |       dio.interceptors.add(AuthenticatedRequestInterceptor()); | ||||||
|  |  | ||||||
|  |       var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey); | ||||||
|  |       Response res = await dio.get('$savedEndpoint/$url'); | ||||||
|  |  | ||||||
|  |       if (res.statusCode == 200) { | ||||||
|  |         return res; | ||||||
|  |       } | ||||||
|  |     } on DioError catch (e) { | ||||||
|  |       debugPrint("DioError: ${e.response}"); | ||||||
|  |     } catch (e) { | ||||||
|  |       debugPrint("ERROR getRequest: ${e.toString()}"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<dynamic> postRequest({required String url, dynamic data}) async { | ||||||
|  |     try { | ||||||
|  |       var dio = Dio(); | ||||||
|  |       dio.interceptors.add(AuthenticatedRequestInterceptor()); | ||||||
|  |  | ||||||
|  |       var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey); | ||||||
|  |       String validUrl = Uri.parse('$savedEndpoint/$url').toString(); | ||||||
|  |       Response res = await dio.post(validUrl, data: data); | ||||||
|  |  | ||||||
|  |       return res; | ||||||
|  |     } on DioError catch (e) { | ||||||
|  |       debugPrint("DioError: ${e.response}"); | ||||||
|  |       return false; | ||||||
|  |     } catch (e) { | ||||||
|  |       debugPrint("ERROR BackupService: $e"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<dynamic> patchRequest({required String url, dynamic data}) async { | ||||||
|  |     try { | ||||||
|  |       var dio = Dio(); | ||||||
|  |       dio.interceptors.add(AuthenticatedRequestInterceptor()); | ||||||
|  |  | ||||||
|  |       var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey); | ||||||
|  |  | ||||||
|  |       String validUrl = Uri.parse('$savedEndpoint/$url').toString(); | ||||||
|  |       Response res = await dio.patch(validUrl, data: data); | ||||||
|  |  | ||||||
|  |       return res; | ||||||
|  |     } on DioError catch (e) { | ||||||
|  |       debugPrint("DioError: ${e.response}"); | ||||||
|  |     } catch (e) { | ||||||
|  |       debugPrint("ERROR BackupService: $e"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<bool> pingServer() async { | ||||||
|  |     try { | ||||||
|  |       var dio = Dio(); | ||||||
|  |  | ||||||
|  |       var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey); | ||||||
|  |  | ||||||
|  |       String validUrl = Uri.parse('$savedEndpoint/server-info/ping').toString(); | ||||||
|  |  | ||||||
|  |       debugPrint("pint server at url $validUrl"); | ||||||
|  |       Response res = await dio.get(validUrl); | ||||||
|  |       var jsonRespsonse = jsonDecode(res.toString()); | ||||||
|  |  | ||||||
|  |       if (jsonRespsonse["res"] == "pong") { | ||||||
|  |         return true; | ||||||
|  |       } else { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     } on DioError catch (e) { | ||||||
|  |       debugPrint("[PING SERVER] DioError: ${e.response} - $e"); | ||||||
|  |       return false; | ||||||
|  |     } catch (e) { | ||||||
|  |       debugPrint("ERROR BackupService: $e"); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||