This commit is contained in:
Dmitry 2025-10-25 23:35:04 +03:00
parent d73f6c2427
commit 757305d78f
313 changed files with 2421 additions and 19905 deletions

View file

@ -4,8 +4,8 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49" revision: "3e493a3e4d0a5c99fa7da51faae354e95a9a1abe"
channel: "stable" channel: "beta"
project_type: app project_type: app
@ -13,11 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 create_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 base_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
- platform: android - platform: web
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 create_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 base_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
# User provided section # User provided section

219
README.md
View file

@ -1,16 +1,215 @@
# mnemo_cards # Mnemo Cards - Мобильное приложение для изучения языков
Mnemo Полнофункциональная система для изучения языков, включающая мобильное приложение, веб-сервер, телеграм-бот и веб-игры.
## 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: ### 📱 Мобильное приложение (`mnemo_cards/`)
Основное Flutter приложение для iOS и Android с функциями:
- Изучение языков через карточки
- Интеграция с Firebase (Firestore, Analytics, Crashlytics)
- Система покупок (YooKassa, In-App Purchase)
- Реклама (Yandex Mobile Ads)
- Локальные уведомления
- Интеграция с веб-играми через WebView
- Поддержка Telegram Web App
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) **Технологии:** Flutter, BLoC, Auto Route, Firebase, WebView
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the ### 🌐 Веб-приложение (`mnemo_cards_web/`)
[online documentation](https://docs.flutter.dev/), which offers tutorials, Веб-версия Flutter приложения с функциями:
samples, guidance on mobile development, and a full API reference. - Изучение языков через карточки в браузере
- Интеграция с Firebase (Firestore, Analytics, Crashlytics)
- Адаптивный дизайн для различных устройств
- Синхронизация данных с мобильным приложением
- Поддержка PWA (Progressive Web App)
- Интеграция с веб-играми
**Технологии:** Flutter Web, BLoC, Auto Route, Firebase, PWA
### 🔧 Общие компоненты (`mnemo_cards_common/`)
Общие DTO и утилиты для мобильного приложения:
- Модели данных
- Сетевые утилиты
- JSON сериализация
- Криптографические функции
### 🖥️ Backend сервер (`mnemo_cards_backend/`)
Dart сервер на базе Shelf framework:
- REST API для мобильного приложения
- База данных Isar
- Интеграция с Google APIs
- Система платежей YooKassa
- Периодические задачи
- Swagger документация
**Технологии:** Dart, Shelf, Isar, Google APIs
### 🔧 Backend общие компоненты (`mnemo_cards_common_backend/`)
Общие компоненты для backend:
- Модели данных
- Утилиты для работы с Isar
- Общие бизнес-логика
### 🤖 Telegram бот (`mnemo_cards_telegram_bot/`)
Telegram бот для управления приложением:
- Административные функции
- Распространение APK файлов
- Управление пользователями
- Интеграция с основным backend
**Технологии:** Dart, Teledart, Isar
### 🎮 Веб-игры и WebView Bridge (`games/`)
Система веб-игр с безопасной коммуникацией через WebView:
- **Host приложение** - Flutter приложение-контейнер
- **Web приложения** - Flutter Web игры (bus_word_game, web_app1)
- **Bridge Core** - система безопасной коммуникации
- **Payloads** - типизированные сообщения между приложениями
**Архитектура:**
- Изолированные payload'ы для каждого приложения
- JavaScript bridge для WebView коммуникации
- Типобезопасная обработка сообщений
- Поддержка сложных сценариев (авторизация, quiz, нативные диалоги)
**Технологии:** Flutter Web, WebView, JavaScript Bridge, Melos
## 🚀 Быстрый старт
### Мобильное приложение
```bash
cd mnemo_cards
flutter pub get
flutter run
```
### Веб-приложение
```bash
cd mnemo_cards_web
flutter pub get
flutter run -d chrome
```
### Backend сервер
```bash
cd mnemo_cards_backend
dart pub get
dart run
```
### Telegram бот
```bash
cd mnemo_cards_telegram_bot
dart pub get
dart run
```
### Веб-игры
```bash
cd games
melos bootstrap
cd apps/host_app
flutter run
```
## 🔗 Зависимости между компонентами
```
mnemo_cards
├── mnemo_cards_common (DTO)
├── games/mnemo_cards_web_bridge (WebView интеграция)
└── Firebase/Backend API
mnemo_cards_web
├── mnemo_cards_common (DTO)
├── mnemo_cards_frontend_common (общие компоненты)
├── games/payloads_shared (общие типы)
└── Firebase/Backend API
mnemo_cards_backend
├── mnemo_cards_common (общие DTO)
├── mnemo_cards_common_backend (backend утилиты)
└── Isar Database
mnemo_cards_telegram_bot
├── mnemo_cards_common_backend
└── Backend API
games/
├── bridge_core (WebView коммуникация)
├── payloads_shared (общие типы)
└── apps/ (веб-приложения)
```
## 📋 Основные функции
### Мобильное приложение
- 📚 Изучение языков через карточки
- 🎮 Интеграция с веб-играми
- 💳 Система покупок и подписок
- 📊 Аналитика и статистика
- 🔔 Push уведомления
- 🌐 Telegram Web App поддержка
### Веб-приложение
- 📚 Изучение языков через карточки в браузере
- 🎮 Интеграция с веб-играми
- 💳 Система покупок и подписок
- 📊 Аналитика и статистика
- 🔄 Синхронизация с мобильным приложением
- 📱 PWA поддержка для установки на устройство
### Backend
- 🔐 Аутентификация и авторизация
- 💾 Управление данными пользователей
- 💰 Обработка платежей
- 📈 Аналитика и метрики
- 🔄 Периодические задачи
- 📚 API для мобильного приложения
### Telegram бот
- 👨‍💼 Административные функции
- 📱 Распространение APK
- 👥 Управление пользователями
- 📊 Мониторинг системы
### Веб-игры
- 🎯 Интерактивные игры для изучения языков
- 🔗 Безопасная интеграция с мобильным приложением
- 📊 Отслеживание прогресса
- 🎨 Современный UI/UX
## 🛠️ Технологический стек
- **Frontend:** Flutter, Flutter Web
- **Backend:** Dart, Shelf, Isar
- **База данных:** Isar (локальная), Firestore (облачная)
- **Аутентификация:** Firebase Auth, Google Sign-In
- **Платежи:** YooKassa, In-App Purchase
- **Аналитика:** Firebase Analytics, Crashlytics
- **Реклама:** Yandex Mobile Ads
- **Уведомления:** Firebase Messaging, Local Notifications
- **Коммуникация:** WebView Bridge, REST API
- **Мониторинг:** Telegram Bot
## 📱 Поддерживаемые платформы
- **iOS** - нативное приложение
- **Android** - нативное приложение
- **Web** - Flutter Web приложение и игры
- **Telegram** - Web App интеграция
## 🔒 Безопасность
- Шифрование данных
- Безопасная WebView коммуникация
- Защита от root/jailbreak
- Валидация платежей
- Изоляция payload'ов в веб-играх
## 📄 Лицензия
Проект является приватным и не предназначен для публикации.

View file

@ -1,9 +0,0 @@
analyzer:
errors:
todo: info # чтобы отображать тудушки
# unused_import: error # Лишние импорты в проекте не нужны
# unused_local_variable: error # Неиспользуемые переменные в проекте не нужны
# missing_required_param: error # Не пропускаем обязательные параметры
# prefer_relative_imports: error # Относительные импорты в проекте
# directives_ordering: error # Следим за порядком импортов в проекте
unawaited_futures: error # Всегда резолвим Future

13
android/.gitignore vendored
View file

@ -1,13 +0,0 @@
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

@ -1,83 +0,0 @@
plugins {
id "com.android.application"
// START: FlutterFire Configuration
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
// END: FlutterFire Configuration
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = "1"
}
def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
flutterVersionName = "1.0"
}
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
namespace = "com.cinnabarflower.mnemo_cards"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId = "com.cinnabarflower.mnemo_cards"
// 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.
minSdk = 25
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
debug {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
debug {
signingConfig signingConfigs.release
}
}
}
flutter {
source = "../.."
}

View file

@ -1,70 +0,0 @@
{
"project_info": {
"project_number": "701767851968",
"project_id": "mnemo-cards",
"storage_bucket": "mnemo-cards.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:701767851968:android:6190df55346394732f7225",
"android_client_info": {
"package_name": "com.cinnabarflower.mnemo_cards"
}
},
"oauth_client": [
{
"client_id": "701767851968-3jgootslus3ie76t682j4v7glletloud.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.cinnabarflower.mnemo_cards",
"certificate_hash": "6df4f259b9c53ac01472c17df69e8c91494f3947"
}
},
{
"client_id": "701767851968-5e6v0b5humhj0iu22t21nb9dtgt4tjus.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.cinnabarflower.mnemo_cards",
"certificate_hash": "49bb5b6863c9e6d8db43e7eb4d54796a300d87fe"
}
},
{
"client_id": "701767851968-vqvjgf1u79jg924inm25bfuv4dg41t2u.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.cinnabarflower.mnemo_cards",
"certificate_hash": "19f45c4abc59f7ce5f22e84376273ee1ee19bdc2"
}
},
{
"client_id": "701767851968-bud97rud1d9qtqju96addn31nhm0oofu.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBGn7PVVDX-o7WipivtuBjdoH5nYEPsHms"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "701767851968-bud97rud1d9qtqju96addn31nhm0oofu.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "701767851968-8dqcmk706p08gujqbl2m9s4sq1aljibs.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.cinnabarflower.mnemoCards"
}
}
]
}
}
}
],
"configuration_version": "1"
}

View file

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 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

@ -1,58 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"
android:label="Mnemo cards">
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" />
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
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>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mnemocards" />
</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>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>

View file

@ -1,5 +0,0 @@
package com.cinnabarflower.mnemo_cards
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,21 +0,0 @@
<?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.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#ffffff</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</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

@ -1,22 +0,0 @@
<?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>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</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

@ -1,21 +0,0 @@
<?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">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#ffffff</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</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

@ -1,22 +0,0 @@
<?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>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</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

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<debug-overrides>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>

View file

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 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

@ -1,18 +0,0 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View file

@ -1,3 +0,0 @@
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View file

@ -1,4 +0,0 @@
storePassword=345234
keyPassword=345234
keyAlias=upload
storeFile=../../../../upload-keystore.jks

Binary file not shown.

View file

@ -1,43 +0,0 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
// For yoo kassa
buildscript {
repositories {
mavenCentral()
maven {
url = uri("https://storage.googleapis.com/r8-releases/raw")
}
}
dependencies {
classpath("com.android.tools:r8:8.3.37")
}
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
// START: FlutterFire Configuration
id "com.google.gms.google-services" version "4.3.15" apply false
id "com.google.firebase.crashlytics" version "2.9.9" apply false
// END: FlutterFire Configuration
id "org.jetbrains.kotlin.android" version "1.9.10" apply false
}
include ":app"

View file

@ -1,3 +0,0 @@
set -e -v
cp ~/mnemo_cards/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk ~/mnemo_cards_telegram_bot/apks/admin_app.apk

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

View file

@ -1,3 +0,0 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View file

@ -1 +0,0 @@
{"flutter":{"platforms":{"android":{"default":{"projectId":"mnemo-cards","appId":"1:701767851968:android:6190df55346394732f7225","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"mnemo-cards","appId":"1:701767851968:ios:5c9040634eac8c152f7225","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"mnemo-cards","configurations":{"android":"1:701767851968:android:6190df55346394732f7225","ios":"1:701767851968:ios:5c9040634eac8c152f7225"}}}}}}

Binary file not shown.

View file

@ -0,0 +1,2 @@
flutter build web --profile --dart-define=Dart2jsOptimization=O0

1
funny_letters/codegen.sh Normal file
View file

@ -0,0 +1 @@
flutter packages pub run build_runner build --delete-conflicting-outputs --verbose

View file

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
const Color borderGray = Color(0xffABABAB);
class Button extends StatelessWidget {
final String asset;
final VoidCallback onTap;
const Button(this.asset, this.onTap);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 110.w,
height: 60.h,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
border: Border.all(color: borderGray),
borderRadius: BorderRadius.circular(16.0),
),
alignment: Alignment.center,
child: Image(
image: AssetImage(asset),
width: 29.w,
color: Theme.of(context).colorScheme.primary,
fit: BoxFit.scaleDown,
),
),
);
}
}

1
funny_letters/run_web.sh Normal file
View file

@ -0,0 +1 @@
dhttpd --path build/web --host 0.0.0.0 --port 8888

View file

@ -0,0 +1,405 @@
<!DOCTYPE html>
<html>
<head>
<title>Vast-Player Example</title>
<script src="https://cdn.jsdelivr.net/npm/vast-player@latest/dist/vast-player.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
background: #000;
font-family: Arial, sans-serif;
}
#container {
width: 100%;
height: 100vh;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.loading {
color: white;
text-align: center;
}
.error {
color: #ff6b6b;
text-align: center;
padding: 20px;
}
.ad-info {
position: absolute;
top: 10px;
left: 10px;
color: white;
background: rgba(0,0,0,0.7);
padding: 10px;
border-radius: 5px;
font-size: 12px;
z-index: 1000;
}
.skip-button {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.8);
color: white;
border: 2px solid white;
border-radius: 20px;
padding: 8px 16px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
z-index: 1001;
display: none;
transition: all 0.3s ease;
}
.skip-button:hover {
background: rgba(255,255,255,0.2);
transform: scale(1.05);
}
.skip-button.visible {
display: block;
}
.skip-timer {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.8);
color: white;
border: 2px solid #ff6b6b;
border-radius: 20px;
padding: 8px 16px;
font-size: 14px;
font-weight: bold;
z-index: 1001;
display: none;
}
.skip-timer.visible {
display: block;
}
</style>
</head>
<body>
<div id="container">
<div class="loading">Загрузка рекламы...</div>
<button id="skipButton" class="skip-button" onclick="skipAd()">Пропустить рекламу</button>
<div id="skipTimer" class="skip-timer">Пропустить через: <span id="skipTime">5</span>с</div>
</div>
<script>
(function(VASTPlayer) {
'use strict';
var container = document.getElementById('container');
var player = new VASTPlayer(container);
// Элементы для кнопки пропуска
var skipButton = document.getElementById('skipButton');
var skipTimer = document.getElementById('skipTimer');
var skipTimeSpan = document.getElementById('skipTime');
var skipTimerInterval = null;
var skipOffset = 5; // секунд до возможности пропуска (по умолчанию)
var skipOffsetFromVast = null; // будет установлено из VAST данных
// Функция для отправки событий в Flutter
function sendToFlutter(eventName, data) {
console.log('Sending to Flutter:', eventName, data);
if (window.flutter_inappwebview) {
window.flutter_inappwebview.callHandler(eventName, data);
}
}
// Функция для парсинга времени из формата HH:MM:SS
function parseTimeToSeconds(timeString) {
if (!timeString) return 5; // по умолчанию 5 секунд
var parts = timeString.split(':');
if (parts.length === 3) {
var hours = parseInt(parts[0]) || 0;
var minutes = parseInt(parts[1]) || 0;
var seconds = parseInt(parts[2]) || 0;
return hours * 3600 + minutes * 60 + seconds;
}
return 5; // по умолчанию 5 секунд
}
// Функция для получения skipoffset из VAST данных
function getSkipOffsetFromVast() {
try {
// Пытаемся получить skipoffset из VAST данных
if (player && player.vastResponse && player.vastResponse.ads && player.vastResponse.ads.length > 0) {
var ad = player.vastResponse.ads[0];
if (ad.inLine && ad.inLine.creatives && ad.inLine.creatives.length > 0) {
var creative = ad.inLine.creatives[0];
if (creative.linear && creative.linear.skipoffset) {
return parseTimeToSeconds(creative.linear.skipoffset);
}
}
}
// Альтернативный способ - парсинг XML
if (player && player.vastResponse && player.vastResponse.xml) {
var xmlDoc = new DOMParser().parseFromString(player.vastResponse.xml, "text/xml");
var linearElement = xmlDoc.querySelector("Linear");
if (linearElement && linearElement.getAttribute("skipoffset")) {
var skipoffset = linearElement.getAttribute("skipoffset");
console.log('Found skipoffset in XML:', skipoffset);
return parseTimeToSeconds(skipoffset);
}
}
} catch (e) {
console.log('Error parsing VAST skipoffset:', e);
}
return skipOffset; // возвращаем значение по умолчанию
}
// Функция для пропуска рекламы
window.skipAd = function() {
console.log('Skipping ad');
player.skipAd();
hideSkipElements();
};
// Функция для показа кнопки пропуска
function showSkipButton() {
console.log('Showing skip button');
skipButton.classList.add('visible');
skipTimer.classList.remove('visible');
if (skipTimerInterval) {
clearInterval(skipTimerInterval);
skipTimerInterval = null;
}
}
// Функция для показа таймера пропуска
function showSkipTimer() {
console.log('Showing skip timer with offset:', skipOffsetFromVast);
skipTimer.classList.add('visible');
skipButton.classList.remove('visible');
var timeLeft = skipOffsetFromVast;
skipTimeSpan.textContent = timeLeft;
skipTimerInterval = setInterval(function() {
timeLeft--;
skipTimeSpan.textContent = timeLeft;
if (timeLeft <= 0) {
showSkipButton();
}
}, 1000);
}
// Функция для скрытия элементов пропуска
function hideSkipElements() {
console.log('Hiding skip elements');
skipButton.classList.remove('visible');
skipTimer.classList.remove('visible');
if (skipTimerInterval) {
clearInterval(skipTimerInterval);
skipTimerInterval = null;
}
}
// Функция для загрузки рекламы с URL
window.loadVastAd = function(vastUrl) {
console.log('Loading VAST ad from URL:', vastUrl);
container.innerHTML = '<div class="loading">Загрузка рекламы...</div>';
hideSkipElements(); // Скрываем элементы пропуска при загрузке
// Восстанавливаем элементы пропуска
container.innerHTML += '<button id="skipButton" class="skip-button" onclick="skipAd()">Пропустить рекламу</button>';
container.innerHTML += '<div id="skipTimer" class="skip-timer">Пропустить через: <span id="skipTime">5</span>с</div>';
// Обновляем ссылки на элементы
skipButton = document.getElementById('skipButton');
skipTimer = document.getElementById('skipTimer');
skipTimeSpan = document.getElementById('skipTime');
console.log('Skip elements restored:', {
skipButton: skipButton,
skipTimer: skipTimer,
skipTimeSpan: skipTimeSpan
});
player.load(vastUrl).then(function() {
console.log('VAST ad loaded successfully');
sendToFlutter('onAdLoaded');
return player.startAd();
}).catch(function(reason) {
console.error('Failed to load ad:', reason);
container.innerHTML = '<div class="error">Ошибка загрузки рекламы: ' + reason.message + '</div>';
sendToFlutter('onAdError', reason);
});
};
// Функция для загрузки пользовательского XML
window.loadCustomXml = function(xmlContent) {
console.log('Loading custom XML ad, content length:', xmlContent.length);
container.innerHTML = '<div class="loading">Загрузка пользовательской рекламы...</div>';
hideSkipElements(); // Скрываем элементы пропуска при загрузке
// Восстанавливаем элементы пропуска
container.innerHTML += '<button id="skipButton" class="skip-button" onclick="skipAd()">Пропустить рекламу</button>';
container.innerHTML += '<div id="skipTimer" class="skip-timer">Пропустить через: <span id="skipTime">5</span>с</div>';
// Обновляем ссылки на элементы
skipButton = document.getElementById('skipButton');
skipTimer = document.getElementById('skipTimer');
skipTimeSpan = document.getElementById('skipTime');
console.log('Skip elements restored for custom XML:', {
skipButton: skipButton,
skipTimer: skipTimer,
skipTimeSpan: skipTimeSpan
});
try {
// Создаем Blob URL для пользовательского XML
var blob = new Blob([xmlContent], {type: 'application/xml'});
var url = URL.createObjectURL(blob);
console.log('Custom XML blob URL created:', url);
player.load(url).then(function() {
console.log('Custom XML ad loaded successfully');
sendToFlutter('onAdLoaded');
return player.startAd();
}).catch(function(reason) {
console.error('Failed to load custom ad:', reason);
container.innerHTML = '<div class="error">Ошибка загрузки пользовательской рекламы: ' + reason.message + '</div>';
sendToFlutter('onAdError', reason);
});
} catch (error) {
console.error('Error creating blob URL for custom XML:', error);
container.innerHTML = '<div class="error">Ошибка создания пользовательской рекламы: ' + error.message + '</div>';
sendToFlutter('onAdError', error);
}
};
// Функция для остановки рекламы
window.stopAd = function() {
console.log('Stopping ad');
player.stopAd();
};
// Функция для паузы/возобновления рекламы
window.toggleAdPlayback = function() {
if (player.isPlaying()) {
player.pauseAd();
} else {
player.resumeAd();
}
};
// Обработчики всех событий
[
'AdLoaded',
'AdStarted',
'AdStopped',
'AdSkipped',
'AdSkippableStateChange',
'AdSizeChange',
'AdLinearChange',
'AdDurationChange',
'AdExpandedChange',
'AdRemainingTimeChange',
'AdVolumeChange',
'AdImpression',
'AdVideoStart',
'AdVideoFirstQuartile',
'AdVideoMidpoint',
'AdVideoThirdQuartile',
'AdVideoComplete',
'AdClickThru',
'AdInteraction',
'AdUserAcceptInvitation',
'AdUserMinimize',
'AdUserClose',
'AdPaused',
'AdPlaying',
'AdLog',
'AdError'
].forEach(function(type) {
player.on(type, function() {
var args = Array.prototype.slice.call(arguments);
console.log('EVENT: ' + type, args);
// Отправляем событие в Flutter
var eventName = 'on' + type;
sendToFlutter(eventName, args.length > 0 ? args[0] : null);
// Специальная обработка для завершения рекламы
if (type === 'AdStopped') {
sendToFlutter('onAdComplete');
hideSkipElements();
}
// Специальная обработка для пропуска рекламы
if (type === 'AdSkipped') {
sendToFlutter('onAdSkipped');
hideSkipElements();
}
// Специальная обработка для изменения состояния пропуска
if (type === 'AdSkippableStateChange') {
if (args.length > 0 && args[0]) {
// Получаем skipoffset из VAST данных
skipOffsetFromVast = getSkipOffsetFromVast();
console.log('Ad is skippable, skip offset from VAST:', skipOffsetFromVast);
showSkipTimer(); // Показываем таймер, если реклама стала пропускаемой
} else {
hideSkipElements(); // Скрываем, если реклама не пропускаемая
}
}
// Специальная обработка для начала рекламы
if (type === 'AdStarted') {
sendToFlutter('onAdStarted');
// Проверяем, пропускаемая ли реклама
if (player.isSkippable()) {
skipOffsetFromVast = getSkipOffsetFromVast();
console.log('Ad started and is skippable, skip offset:', skipOffsetFromVast);
showSkipTimer();
}
}
// Специальная обработка для загрузки рекламы
if (type === 'AdLoaded') {
sendToFlutter('onAdLoaded');
// Проверяем skipoffset сразу после загрузки
setTimeout(function() {
if (player.isSkippable()) {
skipOffsetFromVast = getSkipOffsetFromVast();
console.log('Ad loaded and is skippable, skip offset:', skipOffsetFromVast);
}
}, 100);
}
// Специальная обработка для кликов
if (type === 'AdClickThru') {
sendToFlutter('onAdClick');
// Пытаемся открыть ссылку
if (args.length > 0 && args[0]) {
console.log('Opening click URL:', args[0]);
try {
window.open(args[0], '_blank');
} catch (e) {
console.error('Error opening click URL:', e);
}
}
}
});
});
// Загружаем рекламу по умолчанию
loadVastAd('https://platform-staging.reelcontent.com/api/public/vast/2.0/tag?campaign=cam-e951792a909f17');
}(window.VASTPlayer));
</script>
</body>
</html>

View file

@ -0,0 +1,148 @@
# 📦 Flutter WebView Bridge — структура проекта
Проект разделён на независимые пакеты (монорепозиторий с Melos), чтобы:
- отделить зависимости между `host`, `web` и `payloads`;
- изолировать доступ к данным (web видит только нужное);
- упростить масштабирование и тестирование.
---
## 🗂️ Общая структура
```
├── games/
│ ├── bridge_core/ # Общие типы и сериализация
│ ├── payloads_shared/ # Payload'ы, общие для всех
│ ├── payloads_host/ # Только для host-приложения
│ ├── payloads_app1/ # Только для web_app1
│ ├── host_app/ # Flutter Host App (с WebView)
│ ├── web_app1/ # Flutter Web-приложение 1
├── melos.yaml # Конфигурация монорепозитория
```
---
## 📦 Описание пакетов
### `bridge_core/`
Содержит:
- `BridgeMessage` — модель сообщения
- `BridgePayload` — абстракция типа данных
- `PayloadRegistry` — карта `"type"` → `fromJson`
Используется **всеми** остальными пакетами.
---
### `payloads_shared/`
Содержит payload'ы, используемые и в `host`, и в web-приложениях.
Пример: `GetUserInfoPayload`, `PingPayload`.
Импортируется и в `host_app`, и в `web_app1`, `web_app2`.
---
### `payloads_host/`
Содержит payload'ы, специфичные для хост-приложения.
Пример: `SecretAdminCommand`, `ShowNativeDialogPayload`.
Импортируется **только в `host_app`**.
---
### `payloads_app1/`
Содержит payload'ы, специфичные для web-приложения 1.
Пример: `LoginRequestPayload`, `SubmitQuizPayload`.
Импортируется **только в `web_app1`** и `host_app`.
---
### `host_app/`
Flutter-приложение с WebView.
- Имеет доступ ко всем `payloads_*` пакетам
- Обрабатывает все события
- Реализует `BridgeWebViewController`
---
### `web_app1/`
Flutter Web-приложение, встроенное в WebView.
- Импортирует только нужные `payloads_*`
- Регистрирует только свои payload'ы
- Не видит лишнего
---
## 🔐 Разделение доступа
| Пакет | Видит payloads_shared | Видит payloads_host | Видит payloads_app1 |
|---------------|------------------------|----------------------|----------------------|
| host_app | ✅ | ✅ | ✅ |
| web_app1 | ✅ | ❌ | ✅ |
| web_app2 | ✅ | ❌ | ❌ |
---
## ⚙️ Melos
Файл `melos.yaml` в корне:
```yaml
name: flutter_webview_bridge_repo
packages:
- packages/**
```
Запуск:
```bash
melos bootstrap
```
---
## 📥 Регистрация payload'ов
Каждый payload-пакет содержит:
```dart
void registerPayloads() {
BridgePayload.register('type_name', (json) => MyPayload.fromJson(json));
}
```
В `main()` у приложения:
```dart
void main() {
registerPayloads();
runApp(MyApp());
}
```
---
## 📌 Пример использования
```dart
// Web-приложение
GetUserInfoPayload(fields: ['name']).toMessage().sendToHost();
// Хост-приложение
hostBridge.registerHandler('get_user_info', (msg) {
final payload = GetUserInfoPayload.fromJson(msg.data);
...
});
```
---

View file

@ -0,0 +1,25 @@
import 'package:mnemo_cards_game_api/mnemo_cards_game_api.dart';
import 'game_data.dart';
abstract class AppBinder<T extends GameData> {
/// Initialize the JavaScript environment
Future<void> init();
/// Dispose the JavaScript environment
Future<void> dispose();
Future<GameEventResponse?> sendEvent(GameEvent event);
Future<T> get getGameData;
set gameData(T settings);
Stream<T> get gameDataStream;
set gameSettings(GameSettings settings);
Future<GameSettings> get getGameSettings;
Stream<GameSettings> get gameSettingsStream;
}

View file

@ -0,0 +1,268 @@
// import 'dart:async';
// import 'dart:convert';
// import 'dart:convert';
// import 'dart:typed_data';
// import 'dart:js_interop' as js;
//
// import 'package:mnemo_cards_game_api/mnemo_cards_game_api.dart';
//
// import 'app_binder.dart';
//
// class AppBinderImpl implements AppBinder {
// final _messageHandlers = <String, Completer<dynamic>>{};
// bool _musicEnabled = true;
// bool _soundEnabled = true;
// bool _isDarkMode = false;
//
// @override
// void initializeJavaScript() {
// print('[AppBinder] Initializing JavaScript environment');
//
// // Set up the message handler for responses from the host app
// js.context['handleMessageFromHost'] = (String message) {
// print('[AppBinder] Received message from host: $message');
// try {
// final data = jsonDecode(message);
// final type = data['type'] as String;
// print('[AppBinder] Message type: $type');
//
// final completer = _messageHandlers[type];
// if (completer != null) {
// print('[AppBinder] Found handler for message type: $type');
// _messageHandlers.remove(type);
// completer.complete(data);
// } else {
// print(
// '[AppBinder] Warning: No handler found for message type: $type',
// );
// }
// } catch (e) {
// print('[AppBinder] Error handling message from host: $e');
// print('[AppBinder] Message was: $message');
// }
// };
//
// // Define the function that the host app will inject
// if (js.context['sendMessageToHost'] == null) {
// print('[AppBinder] Setting up default sendMessageToHost');
// js.context['sendMessageToHost'] = (String message) {
// print(
// '[AppBinder] Warning: Using default sendMessageToHost implementation',
// );
// print('[AppBinder] Message would have been: $message');
// };
// } else {
// print('[AppBinder] sendMessageToHost is already defined by host app');
// }
// }
//
// Future<dynamic> _sendMessage(String type, Map<String, dynamic> data) async {
// print('[AppBinder] Sending message type: $type');
// print('[AppBinder] Message data: $data');
//
// final completer = Completer<dynamic>();
// _messageHandlers[type] = completer;
//
// final request = jsonEncode({'type': type, 'data': data});
//
// print('[AppBinder] Sending request to host: $request');
//
// // Send message to host app
// js.context.callMethod('sendMessageToHost', [request]);
//
// print('[AppBinder] Waiting for response from host...');
// final response = await completer.future;
// print('[AppBinder] Received response: $response');
//
// return response;
// }
//
// @override
// Future<void> playWord(String word, {String? lang}) async {
// if (!_soundEnabled) {
// print('[AppBinder] Sound is disabled, skipping word playback');
// return;
// }
//
// print('[AppBinder] Playing word: $word, language: $lang');
// try {
// final Map<String, dynamic> args = {
// 'word': word,
// if (lang != null) 'lang': lang,
// };
// await _sendMessage('playWord', args);
// print('[AppBinder] Successfully played word: $word');
// } catch (e) {
// print('[AppBinder] Error playing word: $e');
// rethrow;
// }
// }
//
// @override
// Future<Uint8List> loadFileBytes(String path) async {
// print('[AppBinder] Loading file bytes from: $path');
// try {
// final result = await _sendMessage('loadFileBytes', {'path': path});
//
// if (result['error'] != null) {
// print('[AppBinder] Error loading file bytes: ${result['error']}');
// throw Exception(result['error']);
// }
//
// final bytes = base64Decode(result['data']);
// print('[AppBinder] Successfully loaded ${bytes.length} bytes');
// return bytes;
// } catch (e) {
// print('[AppBinder] Error loading file bytes: $e');
// rethrow;
// }
// }
//
// @override
// Future<String?> getCustomBackground(String word) async {
// print('[AppBinder] Getting custom background for word: $word');
// try {
// final result = await _sendMessage('getCustomBackground', {'word': word});
// final background = result['data'] as String?;
// print('[AppBinder] Custom background result: $background');
// return background;
// } catch (e) {
// print('[AppBinder] Error getting custom background: $e');
// rethrow;
// }
// }
//
// @override
// Future<GameSettings> loadSettings() async {
// print('[AppBinder] Loading settings');
// try {
// final result = await _sendMessage('loadSettings', {});
//
// if (result['error'] != null) {
// print('[AppBinder] Error in settings response: ${result['error']}');
// throw Exception(result['error']);
// }
//
// final data = result['data'] as Map<String, dynamic>;
// print('[AppBinder] Received settings: $data');
//
// // Update local states
// _musicEnabled = data['musicEnabled'] ?? true;
// _soundEnabled = data['soundEnabled'] ?? true;
// _isDarkMode = data['isDarkMode'] ?? false;
//
// return GameSettings.fromJson(data);
// } catch (e) {
// print('[AppBinder] Error loading settings: $e');
// rethrow;
// }
// }
//
// @override
// Future<List<WordWithTranslation>> loadWordsWithTranslations() async {
// print('[AppBinder] Loading words with translations');
// try {
// final result = await _sendMessage('loadWordsWithTranslations', {});
//
// if (result['error'] != null) {
// print('[AppBinder] Error in words response: ${result['error']}');
// throw Exception(result['error']);
// }
//
// final List<dynamic> data = result['data'] as List<dynamic>;
// print('[AppBinder] Received ${data.length} words');
// final words = data
// .map(
// (item) =>
// WordWithTranslation.fromJson(item as Map<String, dynamic>),
// )
// .toList();
// print('[AppBinder] Words loaded: ${words.map((w) => w.word).join(', ')}');
// return words;
// } catch (e) {
// print('[AppBinder] Error loading words with translations: $e');
// rethrow;
// }
// }
//
// @override
// Future<void> setMusicEnabled(bool enabled) async {
// print('[AppBinder] Setting music enabled: $enabled');
// try {
// await _sendMessage('setMusicEnabled', {'enabled': enabled});
// _musicEnabled = enabled;
// print('[AppBinder] Successfully set music enabled: $enabled');
// } catch (e) {
// print('[AppBinder] Error setting music enabled: $e');
// rethrow;
// }
// }
//
// @override
// Future<void> setSoundEnabled(bool enabled) async {
// print('[AppBinder] Setting sound enabled: $enabled');
// try {
// await _sendMessage('setSoundEnabled', {'enabled': enabled});
// _soundEnabled = enabled;
// print('[AppBinder] Successfully set sound enabled: $enabled');
// } catch (e) {
// print('[AppBinder] Error setting sound enabled: $e');
// rethrow;
// }
// }
//
// @override
// Future<bool> isMusicEnabled() async {
// print('[AppBinder] Getting music enabled state');
// try {
// final result = await _sendMessage('getMusicEnabled', {});
// _musicEnabled = result['data']['enabled'] as bool;
// print('[AppBinder] Music enabled: $_musicEnabled');
// return _musicEnabled;
// } catch (e) {
// print('[AppBinder] Error getting music enabled state: $e');
// return _musicEnabled; // Return cached value on error
// }
// }
//
// @override
// Future<bool> isSoundEnabled() async {
// print('[AppBinder] Getting sound enabled state');
// try {
// final result = await _sendMessage('getSoundEnabled', {});
// _soundEnabled = result['data']['enabled'] as bool;
// print('[AppBinder] Sound enabled: $_soundEnabled');
// return _soundEnabled;
// } catch (e) {
// print('[AppBinder] Error getting sound enabled state: $e');
// return _soundEnabled; // Return cached value on error
// }
// }
//
// @override
// Future<void> setDarkMode(bool enabled) async {
// print('[AppBinder] Setting dark mode: $enabled');
// try {
// await _sendMessage('setDarkMode', {'enabled': enabled});
// _isDarkMode = enabled;
// print('[AppBinder] Successfully set dark mode: $enabled');
// } catch (e) {
// print('[AppBinder] Error setting dark mode: $e');
// rethrow;
// }
// }
//
// @override
// Future<bool> isDarkMode() async {
// print('[AppBinder] Getting dark mode state');
// try {
// final result = await _sendMessage('getDarkMode', {});
// _isDarkMode = result['data']['enabled'] as bool;
// print('[AppBinder] Dark mode: $_isDarkMode');
// return _isDarkMode;
// } catch (e) {
// print('[AppBinder] Error getting dark mode state: $e');
// return _isDarkMode; // Return cached value on error
// }
// }
// }

View file

@ -0,0 +1,5 @@
import 'package:mnemo_cards_game_api/src/game_data.dart';
class FunnyLettersGameData implements GameData {
}

View file

@ -0,0 +1 @@
abstract class GameData {}

View file

@ -0,0 +1,154 @@
import 'dart:developer';
import 'package:flutter/material.dart';
// import 'package:flutter_screenutil/flutter_screenutil.dart';
// import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewPage extends StatefulWidget {
final String url;
final String? title;
final String? returnUrl;
final VoidCallback? onPop;
final bool safeArea;
final bool setupToken;
const WebViewPage({
required this.url,
this.onPop,
this.returnUrl,
this.title,
this.safeArea = false,
this.setupToken = false,
});
@override
State<WebViewPage> createState() => _WebViewPageState();
}
class _WebViewPageState extends State<WebViewPage> with WidgetsBindingObserver {
bool canPop = false;
late final WebViewController controller;
bool _isBlank = false;
void _onPop() {
if (widget.onPop != null) {
widget.onPop!();
}
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.white)
..setNavigationDelegate(
NavigationDelegate(
onNavigationRequest: (r) {
final u = Uri.parse(r.url);
if (u.scheme != 'http' && u.scheme != 'https') {
_handleDeeplink(u);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
onPageStarted: (u) async {
// если вернулись на returnUrl
if (widget.returnUrl != null && u.contains(widget.returnUrl!)) {
_popAndClose();
}
// подставляем токен
if (widget.setupToken) {
final token = await _userToken();
if (token != null) {
final uri = Uri.parse(widget.url);
final host = '${uri.scheme}://${uri.host}';
controller.runJavaScript('''
window.getUnityToken = ()=>'$token';
window.unityData={targetUrl:"$host"};
function ExitGame(){Flutter.postMessage('exitGame');}
''');
}
}
},
),
)
..loadRequest(Uri.parse(widget.url));
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if ((state == AppLifecycleState.paused || state == AppLifecycleState.inactive) && mounted) {
// при уходе в фон — грузим пустую страницу, чтобы сразу остановить звук
controller.loadRequest(Uri.parse('about:blank'));
_isBlank = true;
}
if (state == AppLifecycleState.resumed && mounted) {
// при возврате — восстанавливаем исходный URL
if (_isBlank) {
controller.loadRequest(Uri.parse(widget.url));
_isBlank = false;
}
}
}
Future<String?> _userToken() async {}
Future<void> _handleDeeplink(Uri uri) async {}
Future<void> _popAndClose() async {
if (!canPop) {
setState(() => canPop = true);
// очищаем WebView
await controller.loadRequest(Uri.parse('about:blank'));
await Future.wait([
controller.clearCache(),
controller.clearLocalStorage(),
controller.removeJavaScriptChannel('Flutter'),
]);
_onPop();
await Future.delayed(const Duration(milliseconds: 250));
// AutoRouter.of(context).parent()?.maybePop(true);
}
}
@override
void dispose() {
// на всякий случай — "убиваем" WebView перед уничтожением
controller.loadRequest(Uri.parse('about:blank'));
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: canPop,
onPopInvokedWithResult: (didPop, _) async {
if (!canPop) {
setState(() => canPop = true);
await controller.loadRequest(Uri.parse('about:blank'));
_onPop();
await Future.delayed(const Duration(milliseconds: 250));
// AutoRouter.of(context).back();
}
},
child: SafeArea(
child: Scaffold(
body: SafeArea(
top: widget.safeArea,
bottom: widget.safeArea,
child: WebViewWidget(controller: controller),
),
),
),
);
}
}

View file

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
class GameSettings {
final bool musicEnabled;
final bool soundEnabled;
final GameThemeData themeData;
GameSettings({
this.musicEnabled = true,
this.soundEnabled = true,
required this.themeData,
});
factory GameSettings.fromJson(Map<String, dynamic> json) {
return GameSettings(
musicEnabled: json['musicEnabled'] ?? true,
soundEnabled: json['soundEnabled'] ?? true,
themeData: GameThemeData(
brightness: json['isDarkMode'] == true
? Brightness.dark
: Brightness.light,
),
);
}
Map<String, dynamic> toJson() => {
'musicEnabled': musicEnabled,
'soundEnabled': soundEnabled,
'isDarkMode': themeData.brightness == Brightness.dark,
};
}
class GameThemeData {
final Brightness brightness;
const GameThemeData({required this.brightness});
}

View file

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:mnemo_cards_game_api/mnemo_cards_game_api.dart';
extension ThemeDataExtensions on GameThemeData {
ThemeData toMaterialTheme() {
return brightness == Brightness.light
? ThemeData.light().copyWith(
colorScheme: ColorScheme.fromSwatch(
backgroundColor: Colors.white,
brightness: Brightness.light,
),
)
: ThemeData.dark().copyWith(
colorScheme: ColorScheme.fromSwatch(
backgroundColor: Colors.white,
brightness: Brightness.light,
),
);
}
}

View file

@ -0,0 +1,16 @@
abstract interface class WebAppBridget {
/// Returns the current URL of the web application.
String getCurrentUrl();
/// Navigates to the specified URL.
void navigateTo(String url);
/// Reloads the current page.
void reloadPage();
/// Returns the title of the current page.
String getPageTitle();
/// Sets the title of the current page.
void setPageTitle(String title);
}

View file

@ -0,0 +1,77 @@
import 'package:bridge_core/bridge_core.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:copy_with_extension/copy_with_extension.dart';
part 'user_cards_payload.g.dart';
@CopyWith()
@JsonSerializable()
class UserCardsRequestPayload extends BridgePayload {
@JsonKey(name: 'pack_ids')
final List<String>? packIds;
@JsonKey(name: 'card_ids')
final List<String>? cardIds;
@JsonKey(name: 'original_words')
final List<String>? originalWords;
@JsonKey(name: 'translation_words')
final List<String>? translationWords;
const UserCardsRequestPayload({
this.packIds,
this.cardIds,
this.originalWords,
this.translationWords,
});
@override
Map<String, Object?> toJson() => _$UserCardsRequestPayloadToJson(this);
@override
String get type => 'user_cards_request_payload';
}
@CopyWith()
@JsonSerializable()
class UserCardsPayload extends BridgePayload {
@JsonKey(name: 'cards')
final List<GameCard> gameCards;
UserCardsPayload({required this.gameCards});
@override
Map<String, Object?> toJson() => _$UserCardsPayloadToJson(this);
@override
String get type => 'user_cards_payload';
}
@JsonSerializable()
@CopyWith()
class GameCard {
final int id;
final String? image;
final String? mnemo;
final String? original;
final String? translation;
final String? transcription;
final String? imageBack;
final String? transcriptionMnemo;
final String? back;
const GameCard({
required this.id,
required this.mnemo,
required this.original,
required this.translation,
required this.transcription,
this.transcriptionMnemo,
this.image,
this.imageBack,
this.back,
});
factory GameCard.fromJson(Map<String, dynamic> json) =>
_$GameCardFromJson(json);
Map<String, Object?> toJson() => _$GameCardToJson(this);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

View file

@ -1,2 +0,0 @@
dart run flutter_native_splash:create
dart run flutter_launcher_icons

34
ios/.gitignore vendored
View file

@ -1,34 +0,0 @@
**/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

@ -1,26 +0,0 @@
<?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>14.0</string>
</dict>
</plist>

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,766 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
88925E0304DA934B99CF07B5 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 039AEB256EC09C6DAD0D33F6 /* GoogleService-Info.plist */; };
92F786B29EF12520764C9B34 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88D363FEAADB3317B3E14A0A /* Pods_Runner.framework */; };
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 */; };
CF53E9FF8298D562D675E324 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F30B1603BBE3F803AFE2385E /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
039AEB256EC09C6DAD0D33F6 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
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>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4CC79C7D41E157D878D889F0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
5B971DEAC48A015D192A8EAB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
5DA2E070BDB961F389CD961D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
5EFDFD0076C43FC4B256A2F9 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<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>"; };
88D363FEAADB3317B3E14A0A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D56245B45AA4EC506A74FF4E /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
F30B1603BBE3F803AFE2385E /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F34F38B9F662A96F77B6E782 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
7F8493084A5D32479BB9EA15 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CF53E9FF8298D562D675E324 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
92F786B29EF12520764C9B34 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
70EAEEDC0F0D76C181949289 /* Frameworks */ = {
isa = PBXGroup;
children = (
88D363FEAADB3317B3E14A0A /* Pods_Runner.framework */,
F30B1603BBE3F803AFE2385E /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
9BC5FF8B20D1CEE670C66ACD /* Pods */,
70EAEEDC0F0D76C181949289 /* Frameworks */,
039AEB256EC09C6DAD0D33F6 /* GoogleService-Info.plist */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
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>";
};
9BC5FF8B20D1CEE670C66ACD /* Pods */ = {
isa = PBXGroup;
children = (
4CC79C7D41E157D878D889F0 /* Pods-Runner.debug.xcconfig */,
5B971DEAC48A015D192A8EAB /* Pods-Runner.release.xcconfig */,
5DA2E070BDB961F389CD961D /* Pods-Runner.profile.xcconfig */,
5EFDFD0076C43FC4B256A2F9 /* Pods-RunnerTests.debug.xcconfig */,
F34F38B9F662A96F77B6E782 /* Pods-RunnerTests.release.xcconfig */,
D56245B45AA4EC506A74FF4E /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
FD56395D2DB589B058B3AF52 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
7F8493084A5D32479BB9EA15 /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
11939B8737D0761B1D52BA09 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
10510DEBDDA8DED725BA2250 /* [CP] Copy Pods Resources */,
EAA1E3F7E6B26606E42D1048 /* [CP] Embed Pods Frameworks */,
8D11678BA08A0DD0DA55F454 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */,
);
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 = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
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 */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
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 */,
88925E0304DA934B99CF07B5 /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
10510DEBDDA8DED725BA2250 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
11939B8737D0761B1D52BA09 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
8D11678BA08A0DD0DA55F454 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\"";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\n#!/bin/bash\nPATH=${PATH}:$FLUTTER_ROOT/bin:$HOME/.pub-cache/bin\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=$PODS_ROOT/FirebaseCrashlytics/upload-symbols --platform=ios --apple-project-path=${SRCROOT} --env-platform-name=${PLATFORM_NAME} --env-configuration=${CONFIGURATION} --env-project-dir=${PROJECT_DIR} --env-built-products-dir=${BUILT_PRODUCTS_DIR} --env-dwarf-dsym-folder-path=${DWARF_DSYM_FOLDER_PATH} --env-dwarf-dsym-file-name=${DWARF_DSYM_FILE_NAME} --env-infoplist-path=${INFOPLIST_PATH} --default-config=default\n";
};
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";
};
EAA1E3F7E6B26606E42D1048 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
FD56395D2DB589B058B3AF52 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
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 = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6ZNY923QXG;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.cinnabarflower.mnemoCards;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5EFDFD0076C43FC4B256A2F9 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cinnabarflower.mnemoCards.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F34F38B9F662A96F77B6E782 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cinnabarflower.mnemoCards.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D56245B45AA4EC506A74FF4E /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cinnabarflower.mnemoCards.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
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 = 14.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 = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6ZNY923QXG;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.cinnabarflower.mnemoCards;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6ZNY923QXG;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.cinnabarflower.mnemoCards;
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 */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
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

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

View file

@ -1,8 +0,0 @@
<?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>

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