Account Management mostly implemented

This commit is contained in:
Alexander Laevens
2022-11-25 18:50:30 -07:00
parent 35fc396050
commit 839147e0b0
175 changed files with 7508 additions and 72 deletions

View File

@@ -22,6 +22,10 @@ class UserInline(nested_admin.NestedTabularInline):
def has_delete_permission(self, request, obj=None):
return False
class HomegroupInviteInline(nested_admin.NestedTabularInline):
model = HomegroupInvite
extra = 0
class RecipeInline(nested_admin.NestedTabularInline):
model = Recipe
inlines = (IngredientInline,)
@@ -35,9 +39,9 @@ class RecipeAdmin(nested_admin.NestedModelAdmin):
@admin.register(Homegroup)
class HomegroupAdmin(nested_admin.NestedModelAdmin):
list_display = ("id", "name")
inlines = (UserInline, RecipeInline, IngredientInline)
inlines = (UserInline, HomegroupInviteInline, RecipeInline, IngredientInline)
@admin.register(List)
class ListAdmin(nested_admin.NestedModelAdmin):
list_display = ("id",)
list_display = ("homegroup",)
inlines = (RecipeInline, IngredientInline)

View File

@@ -1,4 +1,4 @@
# Generated by Django 4.1.3 on 2022-11-22 20:42
# Generated by Django 4.1.3 on 2022-11-25 09:10
from django.db import migrations, models
import django.db.models.deletion
@@ -17,12 +17,13 @@ class Migration(migrations.Migration):
name='Homegroup',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='List',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('homegroup', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='api.homegroup')),
],
),
migrations.CreateModel(
@@ -31,7 +32,6 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('homegroup', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='recipes', to='api.homegroup')),
('list', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='recipes', to='api.list')),
],
),
migrations.CreateModel(
@@ -44,4 +44,11 @@ class Migration(migrations.Migration):
('content_type', models.ForeignKey(limit_choices_to=models.Q(models.Q(('app_label', 'api'), ('model', 'recipe')), models.Q(('app_label', 'api'), ('model', 'list')), _connector='OR'), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
],
),
migrations.CreateModel(
name='HomegroupInvite',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('homegroup', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='api.homegroup')),
],
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-22 21:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='homegroup',
name='name',
field=models.CharField(default="Alex's Kitchen", max_length=50),
preserve_default=False,
),
]

View File

@@ -0,0 +1,37 @@
# Generated by Django 4.1.3 on 2022-11-25 09:10
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('api', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='homegroupinvite',
name='user',
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='invites', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='homegroup',
name='invited_users',
field=models.ManyToManyField(related_name='homegroup_invites', through='api.HomegroupInvite', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='recipe',
name='list',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='recipes', to='api.list'),
),
migrations.AlterUniqueTogether(
name='homegroupinvite',
unique_together={('homegroup', 'user')},
),
]

View File

@@ -13,15 +13,29 @@ class Ingredient(models.Model):
name = models.CharField(max_length=50)
in_stock = models.BooleanField(default=False)
class List(models.Model):
# Foreign Key Recipe -> List [as recipes]
extra_ingredients = GenericRelation(Ingredient, related_query_name="extra_ingredients")
class HomegroupInvite(models.Model):
homegroup = models.ForeignKey("api.Homegroup", on_delete=models.CASCADE, related_name="invites", blank=True)
user = models.ForeignKey("users.User", on_delete=models.CASCADE, related_name="invites", blank=True)
class Meta:
unique_together = ("homegroup", "user")
class Homegroup(models.Model):
# Foreign Key Recipe -> Homegroup [as recipes]
# Foreign Key User -> Homegroup [as users]
name = models.CharField(max_length=50)
invited_users = models.ManyToManyField("users.User", related_name="homegroup_invites", through=HomegroupInvite)
def __repr__(self):
return f"{self.id}: {self.name}"
def __str__(self):
return f"{self.id}: {self.name}"
class List(models.Model):
# Foreign Key Recipe -> List [as recipes]
extra_ingredients = GenericRelation(Ingredient, related_query_name="extra_ingredients")
homegroup = models.OneToOneField(Homegroup, on_delete=models.CASCADE, primary_key=True)
class Recipe(models.Model):

View File

@@ -1,24 +1,42 @@
from rest_framework import serializers
from api.models import *
from users.serializers import UserSerializer
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = ["id", "name", "in_stock", "content_type", "object_id"]
read_only_fields = ["id"]
class RecipeSerializer(serializers.ModelSerializer):
ingredients = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
# ingredients = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
# ingredients = IngredientSerializer(many=True, read_only=True)
ingredients = serializers.SerializerMethodField()
class Meta:
model = Recipe
fields = ["id", "name", "ingredients"]
read_only_fields = ["id"]
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = ["id", "name", "in_stock"]
read_only_fields = ["id"]
def get_ingredients(self, instance):
ingredients = instance.ingredients.all().order_by("name")
return IngredientSerializer(ingredients, many=True).data
class HomegroupSerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
recipes = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
def create(self, validated_data):
homegroup = super().create(validated_data)
List.objects.get_or_create(homegroup=homegroup)
return homegroup
class Meta:
model = Homegroup
fields = ["id", "recipes", "users"]
fields = ["id", "name", "recipes", "users", "invites"]
read_only_fields = ["recipes", "users", "invites"]
class InviteSerializer(serializers.ModelSerializer):
class Meta:
model = HomegroupInvite
fields = ["id", "homegroup", "user"]

View File

@@ -6,6 +6,7 @@ router = routers.DefaultRouter()
router.register(r'recipes', views.RecipeView)
router.register(r'ingredients', views.IngredientView)
router.register(r'homegroups', views.HomegroupView)
router.register(r'groupinvites', views.HomegroupInviteView)
urlpatterns = [
path('', include(router.urls))

View File

@@ -14,3 +14,7 @@ class IngredientView(viewsets.ModelViewSet):
class HomegroupView(viewsets.ModelViewSet):
serializer_class = HomegroupSerializer
queryset = Homegroup.objects.all()
class HomegroupInviteView(viewsets.ModelViewSet):
serializer_class = InviteSerializer
queryset = HomegroupInvite.objects.all()

View File

@@ -14,6 +14,9 @@ from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_ROOT = BASE_DIR.joinpath("media")
STATIC_URL = "static/"
MEDIA_URL = "/media/"
# Quick-start development settings - unsuitable for production
@@ -27,7 +30,9 @@ AUTH_USER_MODEL = 'users.User'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
ALLOWED_HOSTS = ["192.168.0.16", "127.0.0.1", "localhost"]
# CORS_ALLOWED_ORIGINS = ["http://192.168.0.16:8000"]
CORS_ALLOW_ALL_ORIGINS = True
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
@@ -47,6 +52,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'rest_framework',
'rest_framework.authtoken',
'nested_admin'
@@ -55,6 +61,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -124,12 +131,6 @@ USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field

View File

@@ -15,9 +15,12 @@ Including another URLconf
"""
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include("api.urls")),
path('auth/', include("users.urls"))
]
path('auth/', include("users.urls")),
path('api-auth/', include('rest_framework.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -4,18 +4,18 @@ from .models import User
class CustomUserAdmin(UserAdmin):
fieldsets = (
(None, {"fields": ("username", "password", "homegroup")}),
("Personal info", {"fields": ("first_name", "last_name", "email")}),
(
"Permissions",
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
),
},
),
(None, {"fields": ("username", "password", "image", "homegroup")}),
("Personal info", {"fields": ("first_name", "last_name")}),
# (
# "Permissions",
# {
# "fields": (
# "is_active",
# "is_staff",
# "is_superuser",
# ),
# },
# ),
("Important dates", {"fields": ("last_login", "date_joined")}),
)
add_fieldsets = (

View File

@@ -1,10 +1,11 @@
# Generated by Django 4.1.3 on 2022-11-22 20:42
# Generated by Django 4.1.3 on 2022-11-25 09:10
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import users.models
class Migration(migrations.Migration):
@@ -12,8 +13,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('api', '0001_initial'),
('auth', '0012_alter_user_first_name_max_length'),
('api', '0001_initial'),
]
operations = [
@@ -31,6 +32,7 @@ class Migration(migrations.Migration):
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('image', models.ImageField(blank=True, null=True, upload_to=users.models.image_path)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('homegroup', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='users', to='api.homegroup')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),

View File

@@ -1,6 +1,13 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from api.models import Homegroup
from pathlib import Path
def image_path(instance, fname):
extension = Path(fname).suffix
return f"profile-images/{instance.id}{extension}"
class User(AbstractUser):
homegroup = models.ForeignKey(Homegroup, related_name="users", on_delete=models.SET_NULL, blank=True, null=True)
image = models.ImageField(upload_to=image_path, null=True, blank=True)

View File

@@ -23,6 +23,6 @@ class UserSerializer(serializers.ModelSerializer): # https://stackoverflow.com/
class Meta:
model = User
fields = ("id", "username", "email", "password")
fields = ("id", "username", "first_name", "last_name", "password", "image", "homegroup", "homegroup_invites")
write_only_fields = ("password",)
read_only_fields = ("id",)
read_only_fields = ("id", "homegroup_invites")

View File

@@ -1,11 +1,14 @@
from django.urls import path, include
from rest_framework.authtoken import views as authviews
from rest_framework import routers
from users import views
router = routers.DefaultRouter()
router.register(r'users', views.UsersListView)
urlpatterns = [
path('', include(router.urls)),
path('token', authviews.obtain_auth_token, name="api-token-auth"),
path('users', views.RegisterUserView.as_view()), # Exposes POST to everyone for registering
# path('users', views.RegisterUserView), # Exposes POST to everyone for registering
path('users/me', views.ModifyUserView.as_view()) # exposes GET / PUT / PATCH / DELETE for registered users for their user object
]

View File

@@ -1,7 +1,10 @@
from rest_framework import generics, permissions, views, status
from rest_framework import generics, permissions, viewsets, mixins, filters, pagination
from rest_framework.response import Response
from users import serializers, models
from users.models import *
from users.serializers import *
class Pagination(pagination.PageNumberPagination):
page_size = 4
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
@@ -12,10 +15,17 @@ class IsOwner(permissions.BasePermission):
# Anyone can register
class RegisterUserView(generics.CreateAPIView):
model = models.User
serializer_class = serializers.UserSerializer
class UsersListView(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
model = User
serializer_class = UserSerializer
permission_classes = [permissions.AllowAny]
queryset = User.objects.all().order_by("first_name", "last_name", "username")
filter_backends = [filters.SearchFilter]
search_fields = ["username", "first_name", "last_name"]
pagination_class = Pagination
def get_serializer(self, *args, **kwargs):
return super().get_serializer(*args, **kwargs)
# Allows user to modify their own data only
@@ -24,13 +34,13 @@ class ModifyUserView(generics.RetrieveUpdateDestroyAPIView):
permissions.IsAuthenticated,
IsOwner
]
model = models.User
serializer_class = serializers.UserSerializer
model = User
serializer_class = UserSerializer
def get_object(self):
return models.User.objects.get(pk=self.request.user.id)
return User.objects.get(pk=self.request.user.id)
def retrieve(self, request, *args, **kwargs):
user = models.User.objects.get(pk=request.user.id)
serializer = serializers.UserSerializer(user)
user = User.objects.get(pk=request.user.id)
serializer = UserSerializer(user, context={"request":request})
return Response(serializer.data)

44
grocery_helper/.gitignore vendored Normal file
View File

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

45
grocery_helper/.metadata Normal file
View File

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

3
grocery_helper/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"cmake.configureOnOpen": false
}

16
grocery_helper/README.md Normal file
View File

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

View 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
grocery_helper/android/.gitignore vendored Normal file
View 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

View File

@@ -0,0 +1,72 @@
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
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.grocery_helper"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 18
compileSdkVersion 33
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"
}

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
package com.example.grocery_helper
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@@ -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>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View 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
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View 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
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

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

View File

@@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.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
}

View File

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

View File

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

View 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"

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="512"
viewBox="0 0 512 512"
version="1.1"
id="svg5"
sodipodi:docname="android.svg"
inkscape:version="1.2.1 (8a69933317, 2022-10-28, custom)"
inkscape:export-filename="../f2688c88/icon.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="1.4142136"
inkscape:cx="185.96908"
inkscape:cy="224.85996"
inkscape:window-width="1920"
inkscape:window-height="1003"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid312"
spacingx="16"
spacingy="16"
empspacing="4" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2">
<rect
style="fill:#10599e;stroke:#e5e5e5;stroke-width:0;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:none;fill-opacity:1"
id="rect421"
width="512"
height="512"
x="0"
y="0"
ry="0" />
<path
style="fill:#020202;fill-opacity:0.22088718;stroke:#e5e5e5;stroke-width:0;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 112.42105,396.37248 192.86499,512 H 512 V 382.84435 L 432,229.63563 H 98.234154 Z"
id="path5195" />
</g>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="path5148"
style="fill:#e5e5e5;fill-opacity:1;stroke:#e5e5e5;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
inkscape:label="Basket"
d="M 89.263157,243.53037 112.42105,396.37248 H 279.1579 399.57894 l 23.15788,-152.8421 z m 13.894733,23.15789 h 13.89473 l 18.52632,115.78947 H 121.6842 Z m 32.42105,0 h 13.89474 l 13.89474,115.78947 h -13.89474 z m 32.42106,0 h 13.89474 l 1.11085,13.89473 h -13.89474 z m 32.42105,0 h 13.89473 l 0.55543,13.89473 h -13.89473 z m 32.42105,0 h 13.89473 v 13.89473 H 232.8421 Z m 32.42105,0 h 13.89475 v 13.89473 h -13.89475 z m 32.42106,0 h 13.89473 l -0.55542,13.89473 h -13.89474 z m 32.42104,0 h 13.89474 l -1.11085,13.89473 H 328.9944 Z m 32.42105,0 h 13.89475 L 362.5263,382.47773 h -13.89474 z m 32.42107,0 h 13.89473 l -18.52631,115.78947 h -13.89474 z m -225.09474,23.15789 h 13.89474 17.6 13.89474 l 2.77894,69.47369 h -13.89474 -14.82104 -13.89474 z m 62.98947,0 h 13.89473 l 18.52632,37.05262 v -37.05262 h 13.89475 v 69.47369 H 265.26315 L 246.73683,322.2672 v 37.05264 H 232.8421 Z m 63.91578,0 h 13.89474 28.71579 2.77895 l -1.11085,13.89473 h -13.89474 -0.27862 -16.76595 l -0.55543,13.89474 h 16.11646 l -1.11087,13.89474 h -15.56281 l -0.55543,13.89475 h 15.19194 0.18453 13.89473 l -1.11086,13.89473 h -1.85262 -26.86316 -13.89474 z m -111.89966,13.89473 3.33438,41.68423 h 15.37828 l -1.66808,-41.68423 z m -8.70592,64.84211 h 13.89474 l 1.11085,13.89474 h -13.89473 z m 28.3449,0 h 13.89474 l 0.55542,13.89474 h -13.89473 z m 28.3449,0 h 13.89473 v 13.89474 H 232.8421 Z m 32.42105,0 h 13.89475 v 13.89474 h -13.89475 z m 28.34491,0 h 13.89473 l -0.55543,13.89474 h -13.89473 z m 28.34489,0 h 13.89473 l -1.11085,13.89474 H 320.8421 Z" />
<path
style="fill:#111111;fill-opacity:1;stroke:#e5e5e5;stroke-width:18.5263;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 163.36843,238.89881 46.31578,-111.1579"
id="path10618"
inkscape:label="LHandle" />
<path
style="fill:#111111;fill-opacity:1;stroke:#e5e5e5;stroke-width:18.5263;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 348.63158,238.89881 302.3158,127.74091"
id="path10620"
inkscape:label="RHandle" />
<path
style="fill:#898989;fill-opacity:1;stroke:#233459;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 80,252.79353 v -23.1579 h 352 v 23.1579 z"
id="path8930"
sodipodi:nodetypes="ccccc"
inkscape:label="Rim" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="400"
height="400"
viewBox="0 0 105.83333 105.83333"
version="1.1"
id="svg5"
inkscape:version="1.2.1 (8a69933317, 2022-10-28, custom)"
sodipodi:docname="base.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="2.8284271"
inkscape:cx="111.01576"
inkscape:cy="189.50462"
inkscape:window-width="1920"
inkscape:window-height="1003"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid2501"
spacingx="1.3229166"
spacingy="1.3229166"
originx="0"
originy="0" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#292929;fill-opacity:1;stroke:#292929;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 59.531252,103.1875 34.395831,0 6.614587,-43.65625 H 5.2916667 L 11.90625,103.1875 Z"
id="path5148"
sodipodi:nodetypes="cccccc"
inkscape:label="Basket" />
<path
id="path10336"
style="display:inline;fill:#111111;fill-opacity:1;stroke:#111111;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 9.2604164 66.145831 L 14.552083 99.218747 L 18.520833 99.218747 L 13.229166 66.145831 L 9.2604164 66.145831 z M 18.520833 66.145831 L 22.489583 99.218747 L 26.458332 99.218747 L 22.489583 66.145831 L 18.520833 66.145831 z M 27.781249 66.145831 L 28.098542 70.114581 L 32.067292 70.114581 L 31.749999 66.145831 L 27.781249 66.145831 z M 37.041666 66.145831 L 37.200312 70.114581 L 41.169062 70.114581 L 41.010415 66.145831 L 37.041666 66.145831 z M 46.302082 66.145831 L 46.302082 70.114581 L 50.270832 70.114581 L 50.270832 66.145831 L 46.302082 66.145831 z M 55.562498 66.145831 L 55.562498 70.114581 L 59.531248 70.114581 L 59.531248 66.145831 L 55.562498 66.145831 z M 64.822915 66.145831 L 64.664268 70.114581 L 68.633018 70.114581 L 68.791664 66.145831 L 64.822915 66.145831 z M 74.083331 66.145831 L 73.766038 70.114581 L 77.734788 70.114581 L 78.052081 66.145831 L 74.083331 66.145831 z M 83.343747 66.145831 L 79.374997 99.218747 L 83.343747 99.218747 L 87.312497 66.145831 L 83.343747 66.145831 z M 92.604164 66.145831 L 87.312497 99.218747 L 91.281247 99.218747 L 96.572914 66.145831 L 92.604164 66.145831 z M 30.109789 95.249997 L 30.427082 99.218747 L 34.395832 99.218747 L 34.078539 95.249997 L 30.109789 95.249997 z M 38.205935 95.249997 L 38.364582 99.218747 L 42.333332 99.218747 L 42.174685 95.249997 L 38.205935 95.249997 z M 46.302082 95.249997 L 46.302082 99.218747 L 50.270832 99.218747 L 50.270832 95.249997 L 46.302082 95.249997 z M 55.562498 95.249997 L 55.562498 99.218747 L 59.531248 99.218747 L 59.531248 95.249997 L 55.562498 95.249997 z M 63.658645 95.249997 L 63.499998 99.218747 L 67.468748 99.218747 L 67.627395 95.249997 L 63.658645 95.249997 z M 71.754791 95.249997 L 71.437498 99.218747 L 75.406248 99.218747 L 75.723541 95.249997 L 71.754791 95.249997 z "
inkscape:label="extra holes" />
<path
id="path15900"
style="fill:#111111;fill-opacity:1;stroke:#111111;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 64.558331 72.760414 L 63.764581 92.604164 L 67.733331 92.604164 L 75.406248 92.604164 L 75.935414 92.604164 L 76.252708 88.635414 L 72.283958 88.635414 L 72.231248 88.635414 L 67.891978 88.635414 L 68.050624 84.666664 L 72.495831 84.666664 L 72.813124 80.697914 L 68.209788 80.697914 L 68.368435 76.729164 L 73.157289 76.729164 L 73.236871 76.729164 L 77.205621 76.729164 L 77.522914 72.760414 L 76.729164 72.760414 L 68.527081 72.760414 L 64.558331 72.760414 z "
inkscape:label="E" />
<path
style="fill:#111111;fill-opacity:1;stroke:#111111;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 46.302082,72.760414 v 19.84375 h 3.96875 V 82.020831 l 5.291666,10.583333 h 3.96875 v -19.84375 h -3.96875 V 83.343747 L 50.270832,72.760414 Z"
id="path15898"
inkscape:label="N" />
<path
style="fill:#111111;fill-opacity:1;stroke:#111111;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 28.310416,72.760414 1.5875,19.84375 h 3.96875 4.233333 3.96875 l -0.79375,-19.84375 h -3.96875 -5.027083 z m 4.286043,3.96875 h 4.868436 l 0.476457,11.90625 h -4.392496 z"
id="path15896"
inkscape:label="O" />
<path
style="fill:#111111;fill-opacity:1;stroke:#292929;stroke-width:5.2916665;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 26.458333,58.208335 39.6875,26.458333"
id="path10618"
inkscape:label="LHandle" />
<path
style="fill:#111111;fill-opacity:1;stroke:#292929;stroke-width:5.2916665;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 79.375002,58.208335 66.145835,26.458333"
id="path10620"
inkscape:label="RHandle" />
<path
style="fill:#111111;fill-opacity:1;stroke:#233459;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 2.6458333,62.177083 0,-6.614583 H 103.1875 v 6.614583 z"
id="path8930"
sodipodi:nodetypes="ccccc"
inkscape:label="Rim" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="400"
height="400"
viewBox="0 0 105.83333 105.83333"
version="1.1"
id="svg5"
sodipodi:docname="holes-dark.svg"
inkscape:version="1.2.1 (8a69933317, 2022-10-28, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="2"
inkscape:cx="205.75"
inkscape:cy="203"
inkscape:window-width="1920"
inkscape:window-height="1003"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid2501"
spacingx="1.3229166"
spacingy="1.3229166"
originx="0"
originy="0" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="path5148"
style="fill:#e5e5e5;fill-opacity:1;stroke:#292929;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
inkscape:label="Basket"
d="M 5.2916665,49.222674 11.90625,92.878926 H 59.531248 93.92708 l 6.61458,-43.656252 z m 3.9687499,6.614583 h 3.9687496 l 5.291667,33.072916 h -3.96875 z m 9.2604166,0 h 3.96875 l 3.968749,33.072916 h -3.968749 z m 9.260416,0 h 3.96875 l 0.317293,3.96875 h -3.96875 z m 9.260417,0 h 3.968749 l 0.158647,3.96875 h -3.96875 z m 9.260416,0 h 3.96875 v 3.96875 h -3.96875 z m 9.260416,0 h 3.96875 v 3.96875 h -3.96875 z m 9.260417,0 h 3.968749 l -0.158646,3.96875 h -3.96875 z m 9.260416,0 h 3.96875 l -0.317293,3.96875 h -3.96875 z m 9.260416,0 h 3.96875 l -3.96875,33.072916 h -3.96875 z m 9.260417,0 h 3.96875 l -5.291667,33.072916 h -3.96875 z M 28.310416,62.45184 h 3.96875 5.027083 3.96875 l 0.79375,19.84375 h -3.96875 -4.233333 -3.96875 z m 17.991666,0 h 3.96875 l 5.291666,10.583333 V 62.45184 h 3.96875 v 19.84375 h -3.96875 L 50.270832,71.712257 V 82.29559 h -3.96875 z m 18.256249,0 h 3.96875 8.202083 0.79375 l -0.317293,3.96875 h -3.96875 -0.07958 -4.788854 l -0.158647,3.96875 h 4.603336 l -0.317293,3.96875 h -4.445207 l -0.158646,3.96875 h 4.33927 0.05271 3.96875 l -0.317294,3.96875 h -0.529166 -7.672917 -3.96875 z m -31.961872,3.96875 0.952397,11.90625 h 4.392496 L 37.464895,66.42059 Z m -2.48667,18.520833 h 3.96875 l 0.317293,3.96875 h -3.96875 z m 8.096146,0 h 3.96875 l 0.158647,3.96875 h -3.96875 z m 8.096147,0 h 3.96875 v 3.96875 h -3.96875 z m 9.260416,0 h 3.96875 v 3.96875 h -3.96875 z m 8.096147,0 h 3.96875 l -0.158647,3.96875 h -3.96875 z m 8.096146,0 h 3.96875 l -0.317293,3.96875 h -3.96875 z" />
<path
style="fill:#111111;fill-opacity:1;stroke:#e5e5e5;stroke-width:5.2916665;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 26.458333,47.899761 39.6875,16.149759"
id="path10618"
inkscape:label="LHandle" />
<path
style="fill:#111111;fill-opacity:1;stroke:#e5e5e5;stroke-width:5.2916665;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 79.375002,47.899761 66.145835,16.149759"
id="path10620"
inkscape:label="RHandle" />
<path
style="fill:#898989;fill-opacity:1;stroke:#233459;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 2.6458333,51.868509 V 45.253926 H 103.1875 v 6.614583 z"
id="path8930"
sodipodi:nodetypes="ccccc"
inkscape:label="Rim" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="400"
height="400"
viewBox="0 0 105.83333 105.83333"
version="1.1"
id="svg5"
sodipodi:docname="holes.svg"
inkscape:version="1.2.1 (8a69933317, 2022-10-28, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="2"
inkscape:cx="221.5"
inkscape:cy="203"
inkscape:window-width="1920"
inkscape:window-height="1003"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid2501"
spacingx="1.3229166"
spacingy="1.3229166"
originx="0"
originy="0" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="path5148"
style="fill:#292929;fill-opacity:1;stroke:#292929;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
inkscape:label="Basket"
d="M 5.2916665,49.222674 11.90625,92.878926 H 59.531248 93.92708 l 6.61458,-43.656252 z m 3.9687499,6.614583 h 3.9687496 l 5.291667,33.072916 h -3.96875 z m 9.2604166,0 h 3.96875 l 3.968749,33.072916 h -3.968749 z m 9.260416,0 h 3.96875 l 0.317293,3.96875 h -3.96875 z m 9.260417,0 h 3.968749 l 0.158647,3.96875 h -3.96875 z m 9.260416,0 h 3.96875 v 3.96875 h -3.96875 z m 9.260416,0 h 3.96875 v 3.96875 h -3.96875 z m 9.260417,0 h 3.968749 l -0.158646,3.96875 h -3.96875 z m 9.260416,0 h 3.96875 l -0.317293,3.96875 h -3.96875 z m 9.260416,0 h 3.96875 l -3.96875,33.072916 h -3.96875 z m 9.260417,0 h 3.96875 l -5.291667,33.072916 h -3.96875 z M 28.310416,62.45184 h 3.96875 5.027083 3.96875 l 0.79375,19.84375 h -3.96875 -4.233333 -3.96875 z m 17.991666,0 h 3.96875 l 5.291666,10.583333 V 62.45184 h 3.96875 v 19.84375 h -3.96875 L 50.270832,71.712257 V 82.29559 h -3.96875 z m 18.256249,0 h 3.96875 8.202083 0.79375 l -0.317293,3.96875 h -3.96875 -0.07958 -4.788854 l -0.158647,3.96875 h 4.603336 l -0.317293,3.96875 h -4.445207 l -0.158646,3.96875 h 4.33927 0.05271 3.96875 l -0.317294,3.96875 h -0.529166 -7.672917 -3.96875 z m -31.961872,3.96875 0.952397,11.90625 h 4.392496 L 37.464895,66.42059 Z m -2.48667,18.520833 h 3.96875 l 0.317293,3.96875 h -3.96875 z m 8.096146,0 h 3.96875 l 0.158647,3.96875 h -3.96875 z m 8.096147,0 h 3.96875 v 3.96875 h -3.96875 z m 9.260416,0 h 3.96875 v 3.96875 h -3.96875 z m 8.096147,0 h 3.96875 l -0.158647,3.96875 h -3.96875 z m 8.096146,0 h 3.96875 l -0.317293,3.96875 h -3.96875 z" />
<path
style="fill:#111111;fill-opacity:1;stroke:#292929;stroke-width:5.29167;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 26.458333,47.899761 39.6875,16.149759"
id="path10618"
inkscape:label="LHandle" />
<path
style="fill:#111111;fill-opacity:1;stroke:#292929;stroke-width:5.29167;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 79.375002,47.899761 66.145835,16.149759"
id="path10620"
inkscape:label="RHandle" />
<path
style="fill:#111111;fill-opacity:1;stroke:#233459;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 2.6458333,51.868509 V 45.253926 H 103.1875 v 6.614583 z"
id="path8930"
sodipodi:nodetypes="ccccc"
inkscape:label="Rim" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M24 23.95q-3.3 0-5.4-2.1-2.1-2.1-2.1-5.4 0-3.3 2.1-5.4 2.1-2.1 5.4-2.1 3.3 0 5.4 2.1 2.1 2.1 2.1 5.4 0 3.3-2.1 5.4-2.1 2.1-5.4 2.1ZM8 40v-4.7q0-1.9.95-3.25T11.4 30q3.35-1.5 6.425-2.25Q20.9 27 24 27q3.1 0 6.15.775 3.05.775 6.4 2.225 1.55.7 2.5 2.05.95 1.35.95 3.25V40Zm3-3h26v-1.7q0-.8-.475-1.525-.475-.725-1.175-1.075-3.2-1.55-5.85-2.125Q26.85 30 24 30t-5.55.575q-2.7.575-5.85 2.125-.7.35-1.15 1.075Q11 34.5 11 35.3Zm13-16.05q1.95 0 3.225-1.275Q28.5 18.4 28.5 16.45q0-1.95-1.275-3.225Q25.95 11.95 24 11.95q-1.95 0-3.225 1.275Q19.5 14.5 19.5 16.45q0 1.95 1.275 3.225Q22.05 20.95 24 20.95Zm0-4.5ZM24 37Z"/></svg>

After

Width:  |  Height:  |  Size: 682 B

34
grocery_helper/ios/.gitignore vendored Normal file
View 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

View 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>11.0</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1,483 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
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 */; };
/* 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>"; };
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>"; };
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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
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 = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
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;
alwaysOutOfDate = 1;
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";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.groceryHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.groceryHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.groceryHelper;
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 */;
}

View File

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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

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

View File

@@ -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>

View File

@@ -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>

View 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)
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View 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.

View 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>

View 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>

View File

@@ -0,0 +1,54 @@
<?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>One Trip</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>grocery_helper</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>So you may take a photo for your profile picture</string>
<string>
</dict>
</plist>

View File

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

View File

@@ -0,0 +1,86 @@
import 'dart:convert';
import 'consts.dart';
import 'package:http/http.dart' as http;
class TokenSingleton {
static final TokenSingleton _instance = TokenSingleton._internal();
String token = "";
factory TokenSingleton() {
return _instance;
}
void setToken(String tok) {
token = tok;
}
String getToken() {
return token;
}
TokenSingleton._internal();
}
Future<String> getToken(String username, String password) async {
const String requestURL = "$baseURL/auth/token";
final http.Response response = await http.post(Uri.parse(requestURL),
body: {"username": username, "password": password});
if (response.statusCode == 200) {
Map<String, dynamic> json = jsonDecode(response.body);
final TokenSingleton s = TokenSingleton();
s.setToken(json["token"]);
return json["token"];
} else {
return "";
}
}
Future<bool> testToken(String token) async {
const String requestURL = "$baseURL/auth/users/me";
final http.Response response = await http.get(
Uri.parse(requestURL),
headers: {
"Authorization": "Token $token",
},
);
return response.statusCode == 200;
}
Future<String> signup(
String firstName,
String lastName,
String username,
String password,
) async {
const String requestURL = "$baseURL/auth/users/";
final http.Response response = await http.post(
Uri.parse(requestURL),
body: {
"first_name": firstName,
"last_name": lastName,
"username": username,
"password": password,
},
);
if (response.statusCode == 201) {
return "";
}
Map<String, dynamic> errorBody = jsonDecode(response.body);
List<String> errors = [];
errorBody.forEach((key, value) {
errors.add("$key: ${value[0]}");
});
return errors.join(", ");
}

View File

@@ -0,0 +1,3 @@
const String baseURL = "http://192.168.0.16:8000";
const int resultsPerPage = 4;

View File

@@ -0,0 +1,72 @@
import 'dart:convert';
import 'package:grocery_helper/api/auth.dart';
import 'package:grocery_helper/api/consts.dart';
import 'package:http/http.dart' as http;
class Homegroup {
int id;
String name;
List<int> recipes;
List<int> users;
List<int> inviteIDs;
Homegroup({
required this.id,
required this.name,
required this.recipes,
required this.users,
required this.inviteIDs,
});
factory Homegroup.fromJson(Map<String, dynamic> json) {
List<int> recipes =
(json["recipes"] as List<dynamic>).map(((e) => e as int)).toList();
List<int> users =
(json["users"] as List<dynamic>).map(((e) => e as int)).toList();
List<int> inviteIDs =
(json["invites"] as List<dynamic>).map(((e) => e as int)).toList();
return Homegroup(
id: json["id"] as int,
name: json["name"] as String,
recipes: recipes,
users: users,
inviteIDs: inviteIDs,
);
}
static Future<Homegroup?> fetchHomegroup(int id) async {
String requestURL = "$baseURL/api/homegroups/$id/";
String token = TokenSingleton().getToken();
final http.Response response = await http.get(
Uri.parse(requestURL),
headers: {"Authorization": "Token $token"},
);
if (response.statusCode == 200) {
Homegroup hg = Homegroup.fromJson(jsonDecode(response.body));
return hg;
} else {
return null;
}
}
static Future<Homegroup?> createHomegroup(String title) async {
String requestURL = "$baseURL/api/homegroups/";
String token = TokenSingleton().getToken();
final http.Response response = await http.post(
Uri.parse(requestURL),
headers: {"Authorization": "Token $token"},
body: {"name": title},
);
if (response.statusCode == 201) {
Homegroup hg = Homegroup.fromJson(jsonDecode(response.body));
return hg;
}
return null;
}
}

View File

@@ -0,0 +1,71 @@
import 'dart:convert';
import 'package:grocery_helper/api/auth.dart';
import 'package:grocery_helper/api/consts.dart';
import 'package:http/http.dart' as http;
class HomegroupInvite {
int id;
int homegroupID;
int userID;
HomegroupInvite({
required this.id,
required this.homegroupID,
required this.userID,
});
factory HomegroupInvite.fromJson(Map<String, dynamic> json) {
return HomegroupInvite(
id: json["id"] as int,
homegroupID: json["homegroup"] as int,
userID: json["user"] as int,
);
}
static Future<HomegroupInvite?> get(int id) async {
String requestURL = "$baseURL/api/groupinvites/$id/";
String token = TokenSingleton().getToken();
final http.Response response = await http.get(
Uri.parse(requestURL),
headers: {"Authorization": "Token $token"},
);
if (response.statusCode == 200) {
return HomegroupInvite.fromJson(jsonDecode(response.body));
}
return null;
}
static Future<HomegroupInvite?> createInvite(
int homegroupID, int userID) async {
String requestURL = "$baseURL/api/groupinvites/";
String token = TokenSingleton().getToken();
final http.Response response = await http.post(
Uri.parse(requestURL),
headers: {"Authorization": "Token $token"},
body: {"homegroup": "$homegroupID", "user": "$userID"},
);
if (response.statusCode == 201) {
return HomegroupInvite.fromJson(jsonDecode(response.body));
}
return null;
}
Future<bool> deleteInvite() async {
String requestURL = "$baseURL/api/groupinvites/$id/";
String token = TokenSingleton().getToken();
final http.Response response = await http.delete(
Uri.parse(requestURL),
headers: {"Authorization": "Token $token"},
);
if (response.statusCode == 204) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,28 @@
import 'package:grocery_helper/api/auth.dart';
import 'package:grocery_helper/api/consts.dart';
import 'package:http/http.dart' as http;
class Ingredient {
int id;
String name;
bool inStock;
int contentType;
int objectID;
Ingredient({
required this.id,
required this.name,
required this.inStock,
required this.contentType,
required this.objectID,
});
factory Ingredient.fromJson(Map<String, dynamic> json) {
return Ingredient(
id: json["id"] as int,
name: json["name"] as String,
inStock: json["in_stock"] as bool,
contentType: json["content_type"] as int,
objectID: json["object_id"] as int);
}
}

View File

@@ -0,0 +1,59 @@
import 'dart:convert';
import 'package:grocery_helper/api/auth.dart';
import 'package:grocery_helper/api/consts.dart';
import 'package:grocery_helper/api/models/homegroup.dart';
import 'package:grocery_helper/api/models/ingredient.dart';
import 'package:http/http.dart' as http;
class Recipe {
int id;
String name;
List<Ingredient> ingredients;
Recipe({required this.id, required this.name, required this.ingredients});
factory Recipe.fromJson(Map<String, dynamic> json) {
List<Ingredient> ingredients = [];
for (dynamic ingredient in json["ingredients"]) {
ingredients.add(Ingredient.fromJson(ingredient));
}
return Recipe(
id: json["id"] as int,
name: json["name"] as String,
ingredients: ingredients);
}
static Future<Recipe?> fetch(int id) async {
String requestURL = "$baseURL/api/recipes/$id/";
String token = TokenSingleton().getToken();
final http.Response response = await http.get(
Uri.parse(requestURL),
headers: {"Authorization": "Token $token"},
);
if (response.statusCode == 200) {
return Recipe.fromJson(jsonDecode(response.body));
}
return null;
}
static Future<List<Recipe>> fetchList(int groupID) async {
Homegroup? group = await Homegroup.fetchHomegroup(groupID);
if (group == null) {
return [];
}
List<Recipe> recipes = [];
for (int recipeID in group.recipes) {
Recipe? recipe = await Recipe.fetch(recipeID);
if (recipe != null) {
recipes.add(recipe);
}
}
return recipes;
}
}

View File

@@ -0,0 +1,89 @@
import 'dart:convert';
import 'package:grocery_helper/api/auth.dart';
import 'package:grocery_helper/api/consts.dart';
import 'package:http/http.dart' as http;
class SearchResult {
List<SimpleUser> users;
String? next;
SearchResult({required this.users, required this.next});
}
class SimpleUser {
int id;
String username;
String firstName;
String lastName;
String? imageUrl;
int? invite;
SimpleUser({
required this.id,
required this.username,
required this.firstName,
required this.lastName,
this.imageUrl,
});
factory SimpleUser.fromJson(Map<String, dynamic> json) {
return SimpleUser(
id: json["id"] as int,
username: json["username"] as String,
firstName: json["first_name"] as String,
lastName: json["last_name"] as String,
imageUrl: json["image"] as String?,
);
}
static Future<SimpleUser?> fetchUser({int? id}) async {
String requestURL = "$baseURL/auth/users/${id ?? 'me'}";
String token = TokenSingleton().getToken();
final http.Response response = await http.get(
Uri.parse(requestURL),
headers: {"Authorization": "Token $token"},
);
if (response.statusCode == 200) {
SimpleUser u = SimpleUser.fromJson(jsonDecode(response.body));
return u;
} else {
return null;
}
}
static Future<SearchResult> search(String query, int page) async {
// String requestURL = "";
// if (url != null) {
// requestURL = url;
// } else if (query != null) {
// requestURL = "$baseURL/auth/users/?search=$query";
// } else {
// return SearchResult(users: [], next: null);
// }
String requestURL = "$baseURL/auth/users/?page=$page&search=$query";
requestURL = requestURL.replaceAll(RegExp(r"\s+"), "+");
String token = TokenSingleton().getToken();
final http.Response response = await http.get(
Uri.parse(requestURL),
headers: {"Authorization": "Token $token"},
);
if (response.statusCode == 200) {
Map<String, dynamic> json = jsonDecode(response.body);
List<SimpleUser> users = [];
for (var userObject in json["results"]) {
SimpleUser u = SimpleUser.fromJson(userObject);
users.add(u);
}
return SearchResult(users: users, next: json["next"] as String?);
}
return SearchResult(users: [], next: null);
}
}

View File

@@ -0,0 +1,115 @@
import 'dart:convert';
import 'package:grocery_helper/api/auth.dart';
import 'package:grocery_helper/api/consts.dart';
import 'package:http/http.dart' as http;
import 'dart:io' show File;
class User {
int id;
String username;
String firstName;
String lastName;
List<int> homegroupInvites;
int? homegroup;
String? imageUrl;
User({
required this.id,
required this.username,
required this.firstName,
required this.lastName,
required this.homegroupInvites,
this.homegroup,
this.imageUrl,
});
factory User.fromJson(Map<String, dynamic> json) {
List<dynamic> invitesDynamic = json["homegroup_invites"];
List<int> invites = invitesDynamic.map((e) => e as int).toList();
return User(
id: json["id"] as int,
username: json["username"] as String,
firstName: json["first_name"] as String,
lastName: json["last_name"] as String,
homegroup: json["homegroup"] as int?,
imageUrl: json["image"] as String?,
homegroupInvites: invites,
);
}
static Future<User?> fetchUser() async {
String requestURL = "$baseURL/auth/users/me";
String token = TokenSingleton().getToken();
final http.Response response =
await http.get(Uri.parse(requestURL), headers: {
"Authorization": "Token $token",
});
if (response.statusCode == 200) {
User u = User.fromJson(jsonDecode(response.body));
return u;
} else {
return null;
}
}
Future<User?> patch({
String? firstName,
String? lastName,
int? homegroup,
}) async {
String requestURL = "$baseURL/auth/users/me";
Map<String, String> body = {};
if (firstName != null) {
body["first_name"] = firstName;
}
if (lastName != null) {
body["last_name"] = lastName;
}
if (homegroup != null) {
body["homegroup"] = "$homegroup";
}
String token = TokenSingleton().getToken();
final http.Response response = await http.patch(
Uri.parse(requestURL),
headers: {
"Authorization": "Token $token",
},
body: body,
);
if (response.statusCode == 200) {
return User.fromJson(jsonDecode(response.body));
}
return null;
}
Future<User?> uploadImage(File file) async {
String requestURL = "$baseURL/auth/users/me";
String token = TokenSingleton().getToken();
http.MultipartRequest request =
http.MultipartRequest("PATCH", Uri.parse(requestURL));
request.headers.addAll({"Authorization": "Token $token"});
request.files.add(http.MultipartFile.fromBytes(
"image", file.readAsBytesSync(),
filename: file.path));
var multiresponse = await request.send();
http.Response response = await http.Response.fromStream(multiresponse);
if (response.statusCode == 200) {
return User.fromJson(jsonDecode(response.body));
}
return null;
}
}

View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:grocery_helper/screens/home_screen.dart';
import 'package:grocery_helper/screens/login_screen.dart';
import 'package:grocery_helper/theme.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Grocery Helper',
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ThemeMode.system,
debugShowCheckedModeBanner: false,
initialRoute: "/login",
routes: {
"/login": (context) => const LoginScreen(),
"/home": (context) => ScrollConfiguration(
behavior: MyBehavior(), child: const HomeScreen())
},
);
}
}

View File

@@ -0,0 +1,165 @@
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:grocery_helper/api/models/homegroup.dart';
import 'package:grocery_helper/widgets/text_entry_dialog.dart';
import 'package:grocery_helper/pages/profile_page/widgets/homegroup_card_widget.dart';
import 'package:grocery_helper/pages/profile_page/widgets/profile_card_widget.dart';
import 'package:grocery_helper/api/models/user.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io' show Platform, File;
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
late User _userInfo;
late Future<bool> _isLoaded;
Future<bool> _loadProfile() async {
User? userInfo = await User.fetchUser();
if (userInfo != null) {
_userInfo = userInfo;
return true;
}
return false;
}
void showError(String text) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Theme.of(context).colorScheme.error,
content: Text(text),
action: SnackBarAction(
textColor: Theme.of(context).colorScheme.onError,
label: "Dismiss",
onPressed: () => ScaffoldMessenger.of(context).hideCurrentSnackBar(),
),
),
);
}
@override
void initState() {
super.initState();
_isLoaded = _loadProfile();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _isLoaded,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
return Scrollbar(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ProfileCard(
userInfo: _userInfo,
onTapPhoto: () async {
if (Platform.isAndroid) {
final ImagePicker picker = ImagePicker();
final XFile? photo = await picker.pickImage(
source: ImageSource.camera,
imageQuality: 70,
maxWidth: 400,
maxHeight: 400);
if (photo == null) {
return;
}
File file = File(photo.path);
User? response = await _userInfo.uploadImage(file);
if (response != null) {
setState(() {
_userInfo = response;
});
}
} else {
showError(
"This feature is only available on Android");
}
},
onLogout: () async {
const storage = FlutterSecureStorage();
await storage.delete(key: "token");
if (mounted) {
Navigator.pushReplacementNamed(context, "/login");
}
},
),
const SizedBox(height: 12),
_userInfo.homegroup == null
? CreateJoinHomegroup(
invites: _userInfo.homegroupInvites,
onJoin: (id) async {
User? response =
await _userInfo.patch(homegroup: id);
if (response != null) {
setState(() {
_userInfo = response;
});
}
},
onCreate: () async {
String? name = await createHomegroupDialog(
context,
"Create Homegroup",
"Homegroup Name",
defaultValue: "${_userInfo.username}'s Kitchen",
);
if (name == null) {
return;
}
if (name == "") {
showError("Homegroup name must not be empty");
return;
}
Homegroup? hg =
await Homegroup.createHomegroup(name);
if (hg == null) {
return;
}
User? response =
await _userInfo.patch(homegroup: hg.id);
if (response != null) {
setState(() {
_userInfo = response;
});
}
},
)
: EditHomegroup(
homegroupID: _userInfo.homegroup!,
)
],
),
),
),
);
} else {
return const CircularProgressIndicator();
}
},
);
}
}

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