chore: Remove the gemi_invoice Flutter project and all associated backend and platform-specific files.

This commit is contained in:
joe 2026-02-14 18:47:57 +09:00
parent 2f9c2983a9
commit 5e965b682a
166 changed files with 1347 additions and 17661 deletions

45
.gitignore vendored Normal file
View file

@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
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-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# 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

30
.metadata Normal file
View file

@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
- platform: android
create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
# 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'

View file

@ -8,8 +8,9 @@ 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)
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,

View file

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 544 B

View file

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 442 B

View file

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 721 B

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

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-8.14-all.zip

View file

@ -1,104 +0,0 @@
// app/src/main/java/com/example/mobilepos/data/api/SyncApiService.kt
package com.example.mobilepos.data.api
import retrofit2.Response
import retrofit2.http.*
import com.example.mobilepos.data.models.*
import java.util.UUID
interface SyncApiService {
@POST("/api/v1/sync")
suspend fun syncDocuments(
@Header("X-API-Key") apiKey: String,
@Body request: SyncRequestDto
): Response<SyncResponseDto>
@GET("/api/v1/customers")
suspend fun getCustomers(
@Header("X-API-Key") apiKey: String
): Response<CustomerListResponse>
@GET("/api/v1/documents/{id}")
suspend fun getDocument(
@Header("X-API-Key") apiKey: String,
@Path("id") documentId: Int
): Response<DocumentDetailResponse>
@POST("/api/v1/receipts/{invoiceId}")
suspend fun createReceipt(
@Header("X-API-Key") apiKey: String,
@Path("invoiceId") invoiceId: Int
): Response<ReceiptCreateResponse>
@GET("/api/v1/health")
suspend fun healthCheck(
@Header("X-API-Key") apiKey: String
): Response<HealthCheckResponse>
}
// ========== API Response Models ==========
data class CustomerListResponse(
val status: String,
val customers: List<CustomerDto>
)
data class DocumentDetailResponse(
val status: String,
val document: DocumentDto
)
data class ReceiptCreateResponse(
val status: String,
val receiptId: Int,
val message: String
)
data class HealthCheckResponse(
val status: String,
val timestamp: Long
)
// ========== Retrofit Factory ==========
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import com.google.gson.GsonBuilder
object ApiClient {
private var retrofit: Retrofit? = null
fun getClient(baseUrl: String): Retrofit {
if (retrofit == null) {
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.writeTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.build()
val gson = GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.create()
retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
return retrofit!!
}
fun getSyncApiService(baseUrl: String): SyncApiService {
return getClient(baseUrl).create(SyncApiService::class.java)
}
}

View file

@ -1,109 +0,0 @@
// app/src/main/java/com/example/mobilepos/data/dao/DocumentDao.kt
package com.example.mobilepos.data.dao
import androidx.room.*
import com.example.mobilepos.data.models.*
import kotlinx.coroutines.flow.Flow
@Dao
interface CustomerDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertCustomer(customer: CustomerEntity)
@Update
suspend fun updateCustomer(customer: CustomerEntity)
@Delete
suspend fun deleteCustomer(customer: CustomerEntity)
@Query("SELECT * FROM customers WHERE id = :id")
suspend fun getCustomerById(id: Int): CustomerEntity?
@Query("SELECT * FROM customers ORDER BY name ASC")
fun getAllCustomers(): Flow<List<CustomerEntity>>
@Query("SELECT * FROM customers WHERE synced = 0")
suspend fun getUnsyncedCustomers(): List<CustomerEntity>
@Query("UPDATE customers SET synced = 1 WHERE id = :id")
suspend fun markCustomerAsSynced(id: Int)
}
@Dao
interface DocumentDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertDocument(document: DocumentEntity): Long
@Update
suspend fun updateDocument(document: DocumentEntity)
@Delete
suspend fun deleteDocument(document: DocumentEntity)
@Query("SELECT * FROM documents WHERE id = :id")
suspend fun getDocumentById(id: Int): DocumentEntity?
@Query("SELECT * FROM documents WHERE customerId = :customerId ORDER BY documentDate DESC")
fun getDocumentsByCustomer(customerId: Int): Flow<List<DocumentEntity>>
@Query("SELECT * FROM documents WHERE docType = :docType ORDER BY documentDate DESC")
fun getDocumentsByType(docType: String): Flow<List<DocumentEntity>>
@Query("SELECT * FROM documents WHERE status = :status ORDER BY documentDate DESC")
fun getDocumentsByStatus(status: String): Flow<List<DocumentEntity>>
@Query("SELECT * FROM documents WHERE docType = 'invoice' AND paidDate IS NULL ORDER BY paymentDueDate ASC")
fun getUnpaidInvoices(): Flow<List<DocumentEntity>>
@Query("SELECT * FROM documents WHERE synced = 0")
suspend fun getUnsyncedDocuments(): List<DocumentEntity>
@Query("SELECT * FROM documents ORDER BY documentDate DESC")
fun getAllDocuments(): Flow<List<DocumentEntity>>
@Query("UPDATE documents SET synced = 1, syncTimestamp = :timestamp WHERE id = :id")
suspend fun markDocumentAsSynced(id: Int, timestamp: Long)
@Query("UPDATE documents SET status = :status, paidDate = :paidDate WHERE id = :id")
suspend fun updateDocumentStatus(id: Int, status: String, paidDate: Long?)
@Query("UPDATE documents SET paymentDueDate = :dueDate WHERE id = :id")
suspend fun updatePaymentDueDate(id: Int, dueDate: Long)
@Query("DELETE FROM documents WHERE docType = :docType")
suspend fun deleteDocumentsByType(docType: String)
}
@Dao
interface PaymentTermsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPaymentTerms(terms: PaymentTermsEntity)
@Query("SELECT * FROM payment_terms WHERE documentId = :documentId")
suspend fun getPaymentTermsByDocument(documentId: Int): PaymentTermsEntity?
@Query("SELECT * FROM payment_terms ORDER BY createdAt DESC")
suspend fun getAllPaymentTerms(): List<PaymentTermsEntity>
@Delete
suspend fun deletePaymentTerms(terms: PaymentTermsEntity)
}
@Dao
interface SyncLogDao {
@Insert
suspend fun insertSyncLog(log: SyncLogEntity)
@Query("SELECT * FROM sync_logs ORDER BY timestamp DESC LIMIT :limit")
suspend fun getRecentSyncLogs(limit: Int = 10): List<SyncLogEntity>
@Query("SELECT * FROM sync_logs WHERE deviceId = :deviceId ORDER BY timestamp DESC LIMIT :limit")
suspend fun getSyncLogsByDevice(deviceId: String, limit: Int = 10): List<SyncLogEntity>
@Query("SELECT MAX(timestamp) FROM sync_logs WHERE operation = 'sync' AND status = 'success'")
suspend fun getLastSuccessfulSyncTime(): Long?
@Query("DELETE FROM sync_logs WHERE timestamp < :olderThanMillis")
suspend fun deleteSyncLogsOlderThan(olderThanMillis: Long)
}

View file

@ -1,71 +0,0 @@
// app/src/main/java/com/example/mobilepos/data/AppDatabase.kt
package com.example.mobilepos.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.mobilepos.data.dao.*
import com.example.mobilepos.data.models.*
@Database(
entities = [
CustomerEntity::class,
DocumentEntity::class,
PaymentTermsEntity::class,
SyncLogEntity::class
],
version = 2,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
abstract fun customerDao(): CustomerDao
abstract fun documentDao(): DocumentDao
abstract fun paymentTermsDao(): PaymentTermsDao
abstract fun syncLogDao(): SyncLogDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"mobile_pos_database"
)
.addMigrations(MIGRATION_1_2)
.build()
INSTANCE = instance
instance
}
}
// マイグレーション: v1 -> v2
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// syncTimestamp カラムを追加
database.execSQL(
"ALTER TABLE documents ADD COLUMN syncTimestamp INTEGER"
)
// インデックスを追加(パフォーマンス向上)
database.execSQL(
"CREATE INDEX IF NOT EXISTS idx_documents_customerId ON documents(customerId)"
)
database.execSQL(
"CREATE INDEX IF NOT EXISTS idx_documents_docType ON documents(docType)"
)
database.execSQL(
"CREATE INDEX IF NOT EXISTS idx_documents_status ON documents(status)"
)
database.execSQL(
"CREATE INDEX IF NOT EXISTS idx_documents_synced ON documents(synced)"
)
}
}
}
}

View file

@ -1,170 +0,0 @@
// app/src/main/java/com/example/mobilepos/data/models/Models.kt
package com.example.mobilepos.data.models
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import java.util.Date
// ========== Room Database Entities ==========
@Entity(tableName = "customers")
data class CustomerEntity(
@PrimaryKey
val id: Int,
val odooCustomerId: Int? = null,
val name: String,
val address: String? = null,
val phone: String? = null,
val email: String? = null,
val createdAt: Long = System.currentTimeMillis(),
val updatedAt: Long = System.currentTimeMillis(),
val synced: Boolean = false
)
@Entity(tableName = "documents")
data class DocumentEntity(
@PrimaryKey
val id: Int = 0,
val odooId: Int? = null,
val docType: String, // quotation, delivery, invoice, receipt
val customerId: Int,
val documentDate: Long,
val items: String, // JSON string
val subtotal: Double,
val tax: Double,
val total: Double,
val status: String, // draft, sent, confirmed, paid
val billingDate: Long? = null,
val paymentDueDate: Long,
val paymentMethod: String? = null,
val paidDate: Long? = null,
val notes: String? = null,
val createdAt: Long = System.currentTimeMillis(),
val updatedAt: Long = System.currentTimeMillis(),
val synced: Boolean = false,
val syncTimestamp: Long? = null
)
@Entity(tableName = "payment_terms")
data class PaymentTermsEntity(
@PrimaryKey
val id: Int = 0,
val documentId: Int,
val billingDate: Long? = null,
val paymentDueDate: Long,
val paymentMethod: String,
val createdAt: Long = System.currentTimeMillis()
)
@Entity(tableName = "sync_logs")
data class SyncLogEntity(
@PrimaryKey
val id: Int = 0,
val deviceId: String,
val operation: String, // sync, upload, download
val documentCount: Int,
val status: String, // success, failure
val message: String? = null,
val timestamp: Long = System.currentTimeMillis()
)
// ========== Data Transfer Objects (DTOs) ==========
data class CustomerDto(
val id: Int,
val name: String,
val address: String? = null,
val phone: String? = null,
val email: String? = null
)
data class DocumentItemDto(
val productName: String,
val quantity: Double,
val unitPrice: Double,
val subtotal: Double
)
data class PaymentTermsDto(
val billingDate: Long? = null,
val paymentDueDate: Long,
val paymentMethod: String
)
data class DocumentDto(
val id: Int = 0,
val docType: String,
val customerId: Int,
val documentDate: Long,
val items: List<DocumentItemDto>,
val subtotal: Double,
val tax: Double,
val total: Double,
val paymentTerms: PaymentTermsDto,
val status: String = "draft",
val notes: String? = null
)
data class SyncRequestDto(
val deviceId: String,
val lastSyncTimestamp: Long? = null,
val documents: List<DocumentDto>
)
data class SyncResponseDto(
val status: String,
val message: String,
val syncedDocuments: Int,
val newDocuments: List<Map<String, Any>>? = null
)
// ========== UI State Models ==========
data class DocumentUIState(
val id: Int = 0,
val docType: String = "quotation",
val customer: CustomerDto? = null,
val customerId: Int = 0,
val documentDate: Long = System.currentTimeMillis(),
val items: List<DocumentItemDto> = emptyList(),
val subtotal: Double = 0.0,
val tax: Double = 0.0,
val total: Double = 0.0,
val billingDate: Long? = null,
val paymentDueDate: Long = System.currentTimeMillis(),
val paymentMethod: String = "bank_transfer",
val status: String = "draft",
val notes: String = "",
val isSaving: Boolean = false,
val isLoading: Boolean = false,
val errorMessage: String? = null,
val showPaymentDatePicker: Boolean = false
)
data class DocumentListUIState(
val documents: List<DocumentEntity> = emptyList(),
val isLoading: Boolean = false,
val errorMessage: String? = null,
val filter: String = "all" // all, quotation, delivery, invoice, receipt
)
data class SyncUIState(
val isSyncing: Boolean = false,
val syncProgress: Int = 0,
val lastSyncTime: Long? = null,
val syncedCount: Int = 0,
val totalCount: Int = 0,
val errorMessage: String? = null,
val status: String = "ready"
)
// ========== Payment Term Patterns ==========
enum class PaymentPattern(val displayName: String, val days: Int? = null) {
IMMEDIATE("即支払い", 0),
END_OF_MONTH("末締め翌月末", null),
THIRTY_DAYS("30日後", 30),
SIXTY_DAYS("60日後", 60),
CUSTOM("カスタム", null)
}

View file

@ -1,390 +0,0 @@
// app/src/main/java/com/example/mobilepos/util/PdfGenerator.kt
package com.example.mobilepos.util
import android.content.Context
import android.graphics.pdf.PdfDocument
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Color
import android.graphics.Typeface
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.*
import com.example.mobilepos.data.models.*
import com.google.gson.Gson
object PdfGenerator {
private const val PAGE_WIDTH = 595 // A4幅ポイント
private const val PAGE_HEIGHT = 842 // A4高さポイント
private const val MARGIN = 40
private const val CONTENT_WIDTH = PAGE_WIDTH - (MARGIN * 2)
fun generateQuotationPdf(
context: Context,
document: DocumentEntity,
customer: CustomerEntity,
fileName: String = "quotation_${document.id}.pdf"
): File? {
return try {
val pdfDocument = PdfDocument()
var pageHeight = PAGE_HEIGHT
var yPosition = MARGIN
val pageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, pageHeight, 1).create()
var page = pdfDocument.startPage(pageInfo)
var canvas = page.canvas
// ヘッダー
yPosition = drawHeader(canvas, yPosition, "見積書")
yPosition += 20
// 会社情報(左上)
yPosition = drawCompanyInfo(canvas, yPosition)
yPosition += 20
// 見積情報
canvas.drawText("見積日:${formatDate(document.documentDate)}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 20
canvas.drawText("見積番号QT-${document.id.toString().padStart(6, '0')}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 30
// 顧客情報
yPosition = drawCustomerInfo(canvas, yPosition, customer)
yPosition += 20
// 区切り線
canvas.drawLine(MARGIN.toFloat(), yPosition.toFloat(), (PAGE_WIDTH - MARGIN).toFloat(), yPosition.toFloat(), getPaint())
yPosition += 10
// 商品テーブルヘッダー
yPosition = drawTableHeader(canvas, yPosition)
yPosition += 5
// 商品行
val items = parseDocumentItems(document.items)
for (item in items) {
if (yPosition > PAGE_HEIGHT - 100) {
// 新しいページ
pdfDocument.finishPage(page)
val newPageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, pageHeight, pdfDocument.pages.size + 1).create()
page = pdfDocument.startPage(newPageInfo)
canvas = page.canvas
yPosition = MARGIN
}
yPosition = drawTableRow(canvas, yPosition, item)
}
yPosition += 10
canvas.drawLine(MARGIN.toFloat(), yPosition.toFloat(), (PAGE_WIDTH - MARGIN).toFloat(), yPosition.toFloat(), getPaint())
yPosition += 15
// 合計
yPosition = drawTotals(canvas, yPosition, document)
yPosition += 20
// 支払期限
val dueDate = formatDate(document.paymentDueDate)
canvas.drawText("お支払い期限:$dueDate", MARGIN.toFloat(), yPosition.toFloat(), getPaint(bold = true))
yPosition += 20
canvas.drawText("お支払い方法:${document.paymentMethod ?: "銀行振込"}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
// 備考
if (!document.notes.isNullOrEmpty()) {
yPosition += 20
canvas.drawText("備考:", MARGIN.toFloat(), yPosition.toFloat(), getPaint(bold = true))
yPosition += 15
val noteLines = document.notes!!.split("\n")
for (line in noteLines) {
canvas.drawText(line, MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 15
}
}
pdfDocument.finishPage(page)
// ファイル保存
val outputFile = File(context.getExternalFilesDir(null), fileName)
val fos = FileOutputStream(outputFile)
pdfDocument.writeTo(fos)
fos.close()
pdfDocument.close()
outputFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun generateInvoicePdf(
context: Context,
document: DocumentEntity,
customer: CustomerEntity,
fileName: String = "invoice_${document.id}.pdf"
): File? {
return try {
val pdfDocument = PdfDocument()
var pageHeight = PAGE_HEIGHT
var yPosition = MARGIN
val pageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, pageHeight, 1).create()
var page = pdfDocument.startPage(pageInfo)
var canvas = page.canvas
// ヘッダー
yPosition = drawHeader(canvas, yPosition, "請求書")
yPosition += 20
// 会社情報
yPosition = drawCompanyInfo(canvas, yPosition)
yPosition += 20
// 請求情報
canvas.drawText("請求日:${formatDate(document.documentDate)}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 20
canvas.drawText("請求番号INV-${document.id.toString().padStart(6, '0')}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 30
// 顧客情報
yPosition = drawCustomerInfo(canvas, yPosition, customer)
yPosition += 20
// 区切り線
canvas.drawLine(MARGIN.toFloat(), yPosition.toFloat(), (PAGE_WIDTH - MARGIN).toFloat(), yPosition.toFloat(), getPaint())
yPosition += 10
// 商品テーブル
yPosition = drawTableHeader(canvas, yPosition)
yPosition += 5
val items = parseDocumentItems(document.items)
for (item in items) {
if (yPosition > PAGE_HEIGHT - 150) {
pdfDocument.finishPage(page)
val newPageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, pageHeight, pdfDocument.pages.size + 1).create()
page = pdfDocument.startPage(newPageInfo)
canvas = page.canvas
yPosition = MARGIN
}
yPosition = drawTableRow(canvas, yPosition, item)
}
yPosition += 10
canvas.drawLine(MARGIN.toFloat(), yPosition.toFloat(), (PAGE_WIDTH - MARGIN).toFloat(), yPosition.toFloat(), getPaint())
yPosition += 15
// 合計
yPosition = drawTotals(canvas, yPosition, document)
yPosition += 20
// 支払期限・方法
val dueDate = formatDate(document.paymentDueDate)
canvas.drawText("お支払い期限:$dueDate", MARGIN.toFloat(), yPosition.toFloat(), getPaint(bold = true))
yPosition += 20
canvas.drawText("お支払い方法:${document.paymentMethod ?: "銀行振込"}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
pdfDocument.finishPage(page)
val outputFile = File(context.getExternalFilesDir(null), fileName)
val fos = FileOutputStream(outputFile)
pdfDocument.writeTo(fos)
fos.close()
pdfDocument.close()
outputFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun generateReceiptPdf(
context: Context,
document: DocumentEntity,
customer: CustomerEntity,
fileName: String = "receipt_${document.id}.pdf"
): File? {
return try {
val pdfDocument = PdfDocument()
var pageHeight = PAGE_HEIGHT
var yPosition = MARGIN
val pageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, pageHeight, 1).create()
val page = pdfDocument.startPage(pageInfo)
val canvas = page.canvas
// ヘッダー
yPosition = drawHeader(canvas, yPosition, "領収書")
yPosition += 20
// 会社情報
yPosition = drawCompanyInfo(canvas, yPosition)
yPosition += 20
// 領収情報
canvas.drawText("領収日:${formatDate(document.documentDate)}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 20
canvas.drawText("領収番号RCP-${document.id.toString().padStart(6, '0')}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 30
// 顧客情報
yPosition = drawCustomerInfo(canvas, yPosition, customer)
yPosition += 20
// 金額
canvas.drawText("お振込金額", MARGIN.toFloat(), yPosition.toFloat(), getPaint(bold = true))
yPosition += 20
val totalText = "¥${String.format("%,d", document.total.toLong())}"
val totalPaint = getPaint(bold = true, size = 36f)
canvas.drawText(totalText, MARGIN.toFloat(), yPosition.toFloat(), totalPaint)
yPosition += 40
// 摘要
canvas.drawText("摘要:商品・サービス提供代金", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 20
// 支払い日
if (document.paidDate != null) {
canvas.drawText("お支払い日:${formatDate(document.paidDate!!)}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
}
pdfDocument.finishPage(page)
val outputFile = File(context.getExternalFilesDir(null), fileName)
val fos = FileOutputStream(outputFile)
pdfDocument.writeTo(fos)
fos.close()
pdfDocument.close()
outputFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
// ========== ヘルパー関数 ==========
private fun drawHeader(canvas: Canvas, yPosition: Int, title: String): Int {
val paint = getPaint(bold = true, size = 28f)
canvas.drawText(title, MARGIN.toFloat(), (yPosition + 25).toFloat(), paint)
return yPosition + 40
}
private fun drawCompanyInfo(canvas: Canvas, yPosition: Int): Int {
val paint = getPaint(size = 10f)
var y = yPosition
canvas.drawText("株式会社 ○○○○", MARGIN.toFloat(), y.toFloat(), paint)
y += 12
canvas.drawText("住所〒000-0000 ○○県○○市○○町1-1", MARGIN.toFloat(), y.toFloat(), paint)
y += 12
canvas.drawText("電話09X-XXXX-XXXX", MARGIN.toFloat(), y.toFloat(), paint)
y += 12
canvas.drawText("メールinfo@example.com", MARGIN.toFloat(), y.toFloat(), paint)
return y
}
private fun drawCustomerInfo(canvas: Canvas, yPosition: Int, customer: CustomerEntity): Int {
val paint = getPaint()
var y = yPosition
canvas.drawText("ご購入者様", MARGIN.toFloat(), y.toFloat(), paint)
y += 20
canvas.drawText(customer.name, MARGIN.toFloat(), y.toFloat(), paint)
y += 15
if (!customer.address.isNullOrEmpty()) {
canvas.drawText("住所:${customer.address}", MARGIN.toFloat(), y.toFloat(), paint)
y += 15
}
if (!customer.phone.isNullOrEmpty()) {
canvas.drawText("電話:${customer.phone}", MARGIN.toFloat(), y.toFloat(), paint)
y += 15
}
return y
}
private fun drawTableHeader(canvas: Canvas, yPosition: Int): Int {
val paint = getPaint(bold = true)
val smallPaint = getPaint(size = 10f)
var x = MARGIN
canvas.drawText("品目", x.toFloat(), yPosition.toFloat(), paint)
x += 200
canvas.drawText("数量", x.toFloat(), yPosition.toFloat(), paint)
x += 80
canvas.drawText("単価", x.toFloat(), yPosition.toFloat(), paint)
x += 80
canvas.drawText("小計", x.toFloat(), yPosition.toFloat(), paint)
return yPosition + 15
}
private fun drawTableRow(canvas: Canvas, yPosition: Int, item: DocumentItemDto): Int {
val paint = getPaint(size = 11f)
val productText = item.productName
val quantityText = String.format("%.2f", item.quantity)
val unitPriceText = "¥${String.format("%,d", item.unitPrice.toLong())}"
val subtotalText = "¥${String.format("%,d", item.subtotal.toLong())}"
var x = MARGIN
canvas.drawText(productText, x.toFloat(), yPosition.toFloat(), paint)
x += 200
canvas.drawText(quantityText, x.toFloat(), yPosition.toFloat(), paint)
x += 80
canvas.drawText(unitPriceText, x.toFloat(), yPosition.toFloat(), paint)
x += 80
canvas.drawText(subtotalText, x.toFloat(), yPosition.toFloat(), paint)
return yPosition + 15
}
private fun drawTotals(canvas: Canvas, yPosition: Int, document: DocumentEntity): Int {
val paint = getPaint()
val boldPaint = getPaint(bold = true)
var y = yPosition
val rightX = (PAGE_WIDTH - MARGIN - 100).toFloat()
// 小計
canvas.drawText("小計:", rightX - 80, y.toFloat(), paint)
canvas.drawText("¥${String.format("%,d", document.subtotal.toLong())}", rightX.toFloat(), y.toFloat(), paint)
y += 20
// 税金
canvas.drawText("税金:", rightX - 80, y.toFloat(), paint)
canvas.drawText("¥${String.format("%,d", document.tax.toLong())}", rightX.toFloat(), y.toFloat(), paint)
y += 20
// 合計
canvas.drawText("合計:", rightX - 80, y.toFloat(), boldPaint)
canvas.drawText("¥${String.format("%,d", document.total.toLong())}", rightX.toFloat(), y.toFloat(), boldPaint)
return y + 20
}
private fun getPaint(bold: Boolean = false, size: Float = 12f): Paint {
return Paint().apply {
this.typeface = if (bold) Typeface.create(Typeface.DEFAULT, Typeface.BOLD) else Typeface.DEFAULT
this.textSize = size
this.color = Color.BLACK
}
}
private fun formatDate(timestamp: Long): String {
val sdf = SimpleDateFormat("yyyy年MM月dd日", Locale.JAPAN)
return sdf.format(Date(timestamp))
}
private fun parseDocumentItems(itemsJson: String): List<DocumentItemDto> {
return try {
val gson = Gson()
gson.fromJson(itemsJson, Array<DocumentItemDto>::class.java).toList()
} catch (e: Exception) {
emptyList()
}
}
}

View file

@ -1,414 +0,0 @@
# モバイルPOS・見積/納品/請求/領収書システム
## 概要
Proxmox CT上で動作するオフラインファーストのスマートフォンアプリケーション。
営業現場で完全スタンドアロンで見積・納品・請求・領収書を作成・管理し、
ネットワーク接続時にOdooと同期します。
## アーキテクチャ
```
┌─────────────────┐
│ Android App │ ← スマホ(完全オフライン対応)
│ SQLite DB │
│ PDF生成 │
└────────┬────────┘
│ (ネットワーク接続時)
┌────▼─────────────────────────┐
│ REST API コンテナ │
│ (Python FastAPI) │
│ - 同期エンドポイント │
│ - Odoo連携 │
└────┬────────────────┬─────────┘
│ │
┌────▼──────┐ ┌─────▼─────┐
│ PostgreSQL│ │ Odoo │
│ DB │ │ (Sales) │
└───────────┘ └───────────┘
```
## ディレクトリ構成
```
project_root/
├── docker-compose.yml # Odoo + API + DB
├── api/
│ ├── main.py # FastAPI メイン
│ ├── requirements.txt
│ └── Dockerfile
├── addons/ # Odooカスタムモジュール
├── scheduler/ # 定期同期スクリプト
└── android/
├── Models.kt # データモデル
├── DAO.kt # Room DAO
├── Database.kt # Room DB
├── ApiService.kt # Retrofit API
└── PdfGenerator.kt # PDF生成
```
## セットアップ手順
### 1. Docker環境構築Proxmox CT
```bash
# リポジトリクローン
git clone <your-repo-url>
cd project_root
# 環境変数設定
cp .env.example .env
# .envを編集API_SECRET_KEY等
# コンテナ起動
docker-compose up -d
# ログ確認
docker-compose logs -f api
docker-compose logs -f odoo
```
### 2. Odooの初期セットアップ
```bash
# Odooにアクセス
# http://localhost:8069
# 以下のモジュールを有効化
# - Sales (見積・受注管理)
# - Invoicing (請求・領収書)
# - Accounting (会計・売掛金)
# API認証設定
# Admin > 設定 > API キーを生成
```
### 3. REST APIの初期化
```bash
# DB テーブル作成
docker-compose exec api python -c "from main import Base, engine; Base.metadata.create_all(bind=engine)"
# テスト
curl -X GET http://localhost:8000/api/v1/health \
-H "X-API-Key: your_secret_key"
```
### 4. Androidアプリ開発
#### 依存パッケージ (build.gradle)
```gradle
dependencies {
// Room
implementation "androidx.room:room-runtime:2.6.0"
implementation "androidx.room:room-ktx:2.6.0"
kapt "androidx.room:room-compiler:2.6.0"
// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.11.0"
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
// Jetpack Compose
implementation "androidx.compose.ui:ui:1.6.0"
implementation "androidx.compose.material3:material3:1.1.0"
// PDF (iText or Apache POI)
implementation "com.itextpdf:itext-core:8.0.0"
// Gson
implementation "com.google.code.gson:gson:2.10.1"
}
```
#### AndroidManifest.xml の設定
```xml
<!-- パーミッション -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
```
#### build.gradle 設定
```gradle
android {
compileSdk 34
defaultConfig {
applicationId "com.example.mobilepos"
minSdk 26
targetSdk 34
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.0"
}
}
```
## API エンドポイント
### 同期
**POST** `/api/v1/sync`
```bash
curl -X POST http://localhost:8000/api/v1/sync \
-H "X-API-Key: secret_key" \
-H "Content-Type: application/json" \
-d '{
"device_id": "device_001",
"last_sync_timestamp": null,
"documents": [
{
"doc_type": "quotation",
"customer_id": 1,
"document_date": "2026-01-31T10:00:00",
"items": [
{
"product_name": "商品A",
"quantity": 10,
"unit_price": 1000,
"subtotal": 10000
}
],
"subtotal": 10000,
"tax": 1000,
"total": 11000,
"payment_terms": {
"billing_date": "2026-01-31",
"payment_due_date": "2026-02-28",
"payment_method": "bank_transfer"
}
}
]
}'
```
### 顧客一覧
**GET** `/api/v1/customers`
```bash
curl http://localhost:8000/api/v1/customers \
-H "X-API-Key: secret_key"
```
### ドキュメント取得
**GET** `/api/v1/documents/{id}`
### 領収書自動生成
**POST** `/api/v1/receipts/{invoice_id}`
```bash
# 入金から1週間以内の請求書から領収書を生成
curl -X POST http://localhost:8000/api/v1/receipts/1 \
-H "X-API-Key: secret_key"
```
## Android実装ガイド
### 1. データベース初期化
```kotlin
val db = AppDatabase.getInstance(context)
val documentDao = db.documentDao()
val customerDao = db.customerDao()
```
### 2. ドキュメント作成・保存
```kotlin
val document = DocumentEntity(
docType = "quotation",
customerId = 1,
documentDate = System.currentTimeMillis(),
items = Gson().toJson(listOf(
DocumentItemDto("商品A", 10.0, 1000.0, 10000.0)
)),
subtotal = 10000.0,
tax = 1000.0,
total = 11000.0,
paymentDueDate = calculateDueDate(PaymentPattern.END_OF_MONTH),
synced = false
)
documentDao.insertDocument(document)
```
### 3. PDF生成
```kotlin
val file = PdfGenerator.generateQuotationPdf(
context = context,
document = document,
customer = customer,
fileName = "quotation_${document.id}.pdf"
)
// ファイル共有
val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "application/pdf"
putExtra(Intent.EXTRA_STREAM, uri)
}
startActivity(Intent.createChooser(shareIntent, "PDFを共有"))
```
### 4. 同期処理
```kotlin
suspend fun syncDocuments(context: Context) {
val apiKey = "your_secret_key"
val baseUrl = "http://your_api_server:8000"
val apiService = ApiClient.getSyncApiService(baseUrl)
val db = AppDatabase.getInstance(context)
val unsyncedDocs = db.documentDao().getUnsyncedDocuments()
val request = SyncRequestDto(
deviceId = getDeviceId(),
documents = unsyncedDocs.map { convertToDto(it) }
)
try {
val response = apiService.syncDocuments(apiKey, request)
if (response.isSuccessful && response.body()?.status == "success") {
response.body()?.newDocuments?.forEach { doc ->
db.documentDao().markDocumentAsSynced(doc["local_id"] as Int, System.currentTimeMillis())
}
}
} catch (e: Exception) {
Log.e("Sync", "Error: ${e.message}")
}
}
```
### 5. 支払期限の計算
```kotlin
fun calculatePaymentDueDate(billingDate: Long, pattern: PaymentPattern): Long {
val calendar = Calendar.getInstance().apply {
timeInMillis = billingDate
}
return when (pattern) {
PaymentPattern.IMMEDIATE -> calendar.timeInMillis
PaymentPattern.END_OF_MONTH -> {
calendar.set(Calendar.DAY_OF_MONTH, 1)
calendar.add(Calendar.MONTH, 1)
calendar.add(Calendar.DAY_OF_MONTH, -1)
calendar.timeInMillis
}
PaymentPattern.THIRTY_DAYS -> {
calendar.add(Calendar.DAY_OF_MONTH, 30)
calendar.timeInMillis
}
PaymentPattern.SIXTY_DAYS -> {
calendar.add(Calendar.DAY_OF_MONTH, 60)
calendar.timeInMillis
}
else -> calendar.timeInMillis
}
}
```
## 同期フロー
### オフライン時
1. スマホアプリで見積/納品/請求/領収書を作成
2. SQLiteに自動保存
3. PDF生成・送信メール等
### ネットワーク接続時
1. 未同期ドキュメントを検出
2. REST APIに送信
3. API が Odoo に登録
4. 同期完了後、ローカルの synced フラグを更新
5. Odoo で売掛金管理・レポート生成
## 支払条件パターン
| パターン | 説明 | 計算方式 |
|---------|------|--------|
| 即支払い | 当日支払い | 請求日 |
| 末締め翌月末 | 末締めで翌月末払い | 翌月末日 |
| 30日後 | 請求日から30日後 | 請求日 + 30日 |
| 60日後 | 請求日から60日後 | 請求日 + 60日 |
| カスタム | 任意設定 | ユーザーが指定 |
## セキュリティ
- API キーは環境変数で管理
- HTTPS通信を推奨本番環境
- トークン認証で API アクセス制限
- Odoo への認証も環境変数化
## トラブルシューティング
### API接続エラー
```bash
# ヘルスチェック
curl http://localhost:8000/api/v1/health -H "X-API-Key: your_key"
# ログ確認
docker-compose logs api
```
### Odoo連携エラー
```bash
# Odooログ
docker-compose logs odoo
# DBテーブル確認
docker-compose exec postgres psql -U odoo -d odoo -c "\dt"
```
### Android PDF生成エラー
- ストレージパーミッション確認
- 外部ストレージ空き容量確認
- iText/Apache POI の対応バージョン確認
## 今後の実装予定
- [ ] Odoo REST API 直接連携
- [ ] Nextcloud WebDAV バックアップ統合
- [ ] 複数ユーザー・デバイス同期
- [ ] オフライン時の競合解決
- [ ] 売掛金ダッシュボード
- [ ] Web UIPCから Odoo 管理用)
- [ ] カスタム領収書テンプレート
- [ ] 電子署名対応
## 開発者向け情報
### REST API テスト
```bash
# FastAPI ドキュメント
http://localhost:8000/docs
```
### DB マイグレーション
```bash
# 新しいテーブル追加時
# Android: Migrations クラスを追加
# API: SQLAlchemy モデルを追加 → DB再作成
```
## ライセンス
MIT License
## サポート
問題報告は Issues で。

View file

@ -1,374 +0,0 @@
"""
Mobile Sync API - Odoo連携
見積/納品/請求/領収書のモバイル同期エンドポイント
"""
from fastapi import FastAPI, HTTPException, Depends, Header, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from datetime import datetime, timedelta
from typing import Optional, List
import sqlalchemy as sa
from sqlalchemy import create_engine, Column, Integer, String, DateTime, JSON, Numeric, Boolean, ForeignKey
from sqlalchemy.orm import sessionmaker, declarative_base, Session
from sqlalchemy.ext.declarative import declarative_base
import os
import json
import requests
from dateutil.relativedelta import relativedelta
import logging
# ========== 設定 ==========
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://odoo:odoo_secure_password@localhost:5432/odoo")
ODOO_URL = os.getenv("ODOO_URL", "http://localhost:8069")
ODOO_USER = os.getenv("ODOO_USER", "admin")
ODOO_PASSWORD = os.getenv("ODOO_PASSWORD", "admin")
API_SECRET_KEY = os.getenv("API_SECRET_KEY", "your_secret_key_here")
# ========== ログ設定 ==========
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ========== DB設定 ==========
engine = create_engine(DATABASE_URL, echo=False)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# ========== SQLAlchemy モデル ==========
class Customer(Base):
__tablename__ = "mobile_customers"
id = Column(Integer, primary_key=True)
odoo_customer_id = Column(Integer, unique=True, nullable=True)
name = Column(String(255))
address = Column(String(500), nullable=True)
phone = Column(String(20), nullable=True)
email = Column(String(255), nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
synced = Column(Boolean, default=False)
class Document(Base):
__tablename__ = "mobile_documents"
id = Column(Integer, primary_key=True)
odoo_id = Column(Integer, unique=True, nullable=True)
doc_type = Column(String(50)) # quotation, delivery, invoice, receipt
customer_id = Column(Integer, ForeignKey("mobile_customers.id"))
document_date = Column(DateTime)
items = Column(JSON) # [{product_name, quantity, unit_price, subtotal}, ...]
subtotal = Column(Numeric(12, 2))
tax = Column(Numeric(12, 2))
total = Column(Numeric(12, 2))
status = Column(String(50)) # draft, sent, confirmed, paid, etc.
billing_date = Column(DateTime, nullable=True)
payment_due_date = Column(DateTime, nullable=True)
payment_method = Column(String(100), nullable=True)
paid_date = Column(DateTime, nullable=True)
notes = Column(String(1000), nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
synced = Column(Boolean, default=False)
sync_timestamp = Column(DateTime, nullable=True)
class SyncLog(Base):
__tablename__ = "sync_logs"
id = Column(Integer, primary_key=True)
device_id = Column(String(255))
operation = Column(String(50)) # sync, upload, download
document_count = Column(Integer)
status = Column(String(50)) # success, failure
message = Column(String(500), nullable=True)
timestamp = Column(DateTime, default=datetime.utcnow)
# ========== Pydantic モデル ==========
class ItemModel(BaseModel):
product_name: str
quantity: float
unit_price: float
subtotal: float
class PaymentTermsModel(BaseModel):
billing_date: Optional[datetime] = None
payment_due_date: datetime
payment_method: str # bank_transfer, cash, etc.
class DocumentModel(BaseModel):
doc_type: str # quotation, delivery, invoice, receipt
customer_id: int
document_date: datetime
items: List[ItemModel]
subtotal: float
tax: float
total: float
payment_terms: PaymentTermsModel
status: str = "draft"
notes: Optional[str] = None
class SyncRequest(BaseModel):
device_id: str
last_sync_timestamp: Optional[datetime] = None
documents: List[DocumentModel]
class SyncResponse(BaseModel):
status: str
message: str
synced_documents: int
new_documents: Optional[List[dict]] = None
# ========== FastAPI アプリ ==========
app = FastAPI(title="Mobile Sync API", version="1.0.0")
# ========== DB セッション依存性 ==========
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# ========== 認証 ==========
async def verify_api_key(x_api_key: str = Header(None)):
if x_api_key != API_SECRET_KEY:
raise HTTPException(status_code=401, detail="Invalid API key")
return x_api_key
# ========== ヘルパー関数 ==========
def calculate_payment_due_date(billing_date: datetime, pattern: str) -> datetime:
"""
支払期限を計算
patterns:
- "immediate": 即支払い当日
- "end_of_month": 末締め翌月末
- "30days": 30日後
- "60days": 60日後
"""
if pattern == "immediate":
return billing_date
elif pattern == "end_of_month":
# 翌月末
next_month = billing_date + relativedelta(months=1)
return next_month.replace(day=1) - timedelta(days=1)
elif pattern == "30days":
return billing_date + timedelta(days=30)
elif pattern == "60days":
return billing_date + timedelta(days=60)
else:
# デフォルト30日後
return billing_date + timedelta(days=30)
def sync_to_odoo(db: Session, document: Document) -> dict:
"""
ドキュメントを Odoo に同期
"""
try:
# Odoo XML-RPC または REST API を使用してデータを送信
# ここでは簡略版
logger.info(f"Syncing document {document.id} to Odoo")
# TODO: Odoo API呼び出し
# response = odoo_api.create_document(...)
document.synced = True
document.sync_timestamp = datetime.utcnow()
db.commit()
return {"status": "success", "odoo_id": document.odoo_id}
except Exception as e:
logger.error(f"Error syncing to Odoo: {str(e)}")
return {"status": "error", "message": str(e)}
# ========== エンドポイント ==========
@app.post("/api/v1/sync", response_model=SyncResponse, dependencies=[Depends(verify_api_key)])
async def sync_documents(request: SyncRequest, db: Session = Depends(get_db)):
"""
モバイルアプリからのドキュメント同期
"""
try:
synced_count = 0
new_documents = []
for doc in request.documents:
# 顧客を確認
customer = db.query(Customer).filter(Customer.id == doc.customer_id).first()
if not customer:
logger.warning(f"Customer {doc.customer_id} not found")
continue
# ドキュメント作成
db_doc = Document(
doc_type=doc.doc_type,
customer_id=doc.customer_id,
document_date=doc.document_date,
items=json.dumps([item.model_dump() for item in doc.items]),
subtotal=doc.subtotal,
tax=doc.tax,
total=doc.total,
status=doc.status,
billing_date=doc.payment_terms.billing_date,
payment_due_date=doc.payment_terms.payment_due_date,
payment_method=doc.payment_terms.payment_method,
notes=doc.notes,
synced=False
)
db.add(db_doc)
db.commit()
db.refresh(db_doc)
# Odoo に同期
sync_result = sync_to_odoo(db, db_doc)
synced_count += 1
new_documents.append({
"local_id": db_doc.id,
"odoo_id": db_doc.odoo_id,
"doc_type": db_doc.doc_type,
"status": sync_result["status"]
})
# ログ記録
sync_log = SyncLog(
device_id=request.device_id,
operation="sync",
document_count=synced_count,
status="success",
message=f"Synced {synced_count} documents"
)
db.add(sync_log)
db.commit()
return SyncResponse(
status="success",
message=f"Successfully synced {synced_count} documents",
synced_documents=synced_count,
new_documents=new_documents
)
except Exception as e:
logger.error(f"Sync error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/v1/customers", dependencies=[Depends(verify_api_key)])
async def get_customers(db: Session = Depends(get_db)):
"""
顧客一覧取得オンライン時にマスタ更新
"""
try:
customers = db.query(Customer).all()
return {
"status": "success",
"customers": [
{
"id": c.id,
"name": c.name,
"address": c.address,
"phone": c.phone,
"email": c.email
} for c in customers
]
}
except Exception as e:
logger.error(f"Error fetching customers: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/v1/documents/{doc_id}", dependencies=[Depends(verify_api_key)])
async def get_document(doc_id: int, db: Session = Depends(get_db)):
"""
特定ドキュメント取得
"""
try:
doc = db.query(Document).filter(Document.id == doc_id).first()
if not doc:
raise HTTPException(status_code=404, detail="Document not found")
return {
"status": "success",
"document": {
"id": doc.id,
"doc_type": doc.doc_type,
"customer_id": doc.customer_id,
"document_date": doc.document_date,
"items": json.loads(doc.items) if doc.items else [],
"total": float(doc.total),
"payment_due_date": doc.payment_due_date,
"status": doc.status
}
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error fetching document: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/v1/receipts/{invoice_id}", dependencies=[Depends(verify_api_key)])
async def create_receipt(invoice_id: int, db: Session = Depends(get_db)):
"""
請求書から領収書を自動生成
入金1週間以内の場合に発行可能
"""
try:
invoice = db.query(Document).filter(
Document.id == invoice_id,
Document.doc_type == "invoice"
).first()
if not invoice:
raise HTTPException(status_code=404, detail="Invoice not found")
if not invoice.paid_date:
raise HTTPException(status_code=400, detail="Invoice not yet paid")
# 入金1週間以内かチェック
days_since_payment = (datetime.utcnow() - invoice.paid_date).days
if days_since_payment > 7:
raise HTTPException(status_code=400, detail="Receipt cannot be issued (payment older than 7 days)")
# 領収書作成
receipt = Document(
doc_type="receipt",
customer_id=invoice.customer_id,
document_date=datetime.utcnow(),
items=invoice.items,
subtotal=invoice.subtotal,
tax=invoice.tax,
total=invoice.total,
status="issued",
paid_date=invoice.paid_date,
synced=False
)
db.add(receipt)
db.commit()
db.refresh(receipt)
return {
"status": "success",
"receipt_id": receipt.id,
"message": "Receipt created successfully"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error creating receipt: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/v1/health", dependencies=[Depends(verify_api_key)])
async def health_check():
"""
ヘルスチェック
"""
return {"status": "ok", "timestamp": datetime.utcnow()}
# ========== DB初期化 ==========
@app.on_event("startup")
async def startup():
Base.metadata.create_all(bind=engine)
logger.info("Database tables created")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

View file

@ -1,96 +0,0 @@
version: '3.8'
services:
# PostgreSQLOdoo用
postgres:
image: postgres:15-alpine
container_name: odoo_db
environment:
POSTGRES_DB: odoo
POSTGRES_USER: odoo
POSTGRES_PASSWORD: odoo_secure_password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- odoo_network
restart: unless-stopped
# Odoo
odoo:
image: odoo:17.0
container_name: odoo_app
depends_on:
- postgres
environment:
HOST: postgres
USER: odoo
PASSWORD: odoo_secure_password
DB_NAME: odoo
volumes:
- odoo_data:/var/lib/odoo
- ./addons:/mnt/extra-addons
ports:
- "8069:8069"
networks:
- odoo_network
restart: unless-stopped
# REST APIモバイル同期用
api:
build:
context: ./api
dockerfile: Dockerfile
container_name: mobile_sync_api
depends_on:
- postgres
- odoo
environment:
DATABASE_URL: postgresql://odoo:odoo_secure_password@postgres:5432/odoo
ODOO_URL: http://odoo:8069
ODOO_USER: admin
ODOO_PASSWORD: admin
API_SECRET_KEY: your_secret_key_here_change_me
ports:
- "8000:8000"
volumes:
- ./api:/app
networks:
- odoo_network
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
restart: unless-stopped
# 同期・バックアップスクリプト(定期実行)
sync_scheduler:
build:
context: ./scheduler
dockerfile: Dockerfile
container_name: sync_scheduler
depends_on:
- postgres
- odoo
- api
environment:
DATABASE_URL: postgresql://odoo:odoo_secure_password@postgres:5432/odoo
ODOO_URL: http://odoo:8069
ODOO_USER: admin
ODOO_PASSWORD: admin
NEXTCLOUD_URL: https://your_nextcloud_url
NEXTCLOUD_USER: your_nextcloud_user
NEXTCLOUD_PASSWORD: your_nextcloud_password
volumes:
- ./scheduler:/app
- sync_logs:/var/log/sync
networks:
- odoo_network
restart: unless-stopped
volumes:
postgres_data:
odoo_data:
sync_logs:
networks:
odoo_network:
driver: bridge

View file

@ -1,334 +0,0 @@
# api/odoo_sync.py
"""
Odoo連携モジュール
REST API が受け取ったドキュメントを Odoo に同期
"""
import requests
import logging
from typing import Dict, List, Optional
from datetime import datetime
import json
logger = logging.getLogger(__name__)
class OdooClient:
"""Odoo XML-RPC クライアント"""
def __init__(self, odoo_url: str, db: str, username: str, password: str):
self.odoo_url = odoo_url
self.db = db
self.username = username
self.password = password
self.uid = None
self.authenticate()
def authenticate(self):
"""Odoo 認証"""
try:
import xmlrpc.client
common = xmlrpc.client.ServerProxy(f'{self.odoo_url}/xmlrpc/2/common')
self.uid = common.authenticate(self.db, self.username, self.password, {})
logger.info(f"Odoo authenticated: uid={self.uid}")
except Exception as e:
logger.error(f"Odoo authentication failed: {str(e)}")
raise
def create_customer(self, name: str, address: str = "", phone: str = "", email: str = "") -> int:
"""顧客を Odoo に作成"""
try:
import xmlrpc.client
models = xmlrpc.client.ServerProxy(f'{self.odoo_url}/xmlrpc/2/object')
partner_data = {
'name': name,
'street': address,
'phone': phone,
'email': email,
'customer_rank': 1,
}
partner_id = models.execute_kw(
self.db, self.uid, self.password,
'res.partner', 'create', [partner_data]
)
logger.info(f"Created Odoo customer: {partner_id}")
return partner_id
except Exception as e:
logger.error(f"Error creating customer: {str(e)}")
return 0
def create_quotation(self, customer_id: int, items: List[Dict],
payment_due_date: str, notes: str = "") -> int:
"""見積を Odoo に作成"""
try:
import xmlrpc.client
models = xmlrpc.client.ServerProxy(f'{self.odoo_url}/xmlrpc/2/object')
# 見積ラインの準備
order_lines = []
for item in items:
# 商品をOdooから検索簡略版
product_search = models.execute_kw(
self.db, self.uid, self.password,
'product.product', 'search',
[[('name', '=', item['product_name'])]]
)
product_id = product_search[0] if product_search else 1
line_data = (0, 0, {
'product_id': product_id,
'product_qty': item['quantity'],
'price_unit': item['unit_price'],
})
order_lines.append(line_data)
# 見積作成
quotation_data = {
'partner_id': customer_id,
'order_line': order_lines,
'date_order': datetime.now().isoformat(),
'payment_term_id': self._get_payment_term_id(payment_due_date),
'note': notes,
}
quotation_id = models.execute_kw(
self.db, self.uid, self.password,
'sale.order', 'create', [quotation_data]
)
logger.info(f"Created Odoo quotation: {quotation_id}")
return quotation_id
except Exception as e:
logger.error(f"Error creating quotation: {str(e)}")
return 0
def create_invoice(self, customer_id: int, items: List[Dict],
payment_due_date: str, notes: str = "") -> int:
"""請求書を Odoo に作成"""
try:
import xmlrpc.client
models = xmlrpc.client.ServerProxy(f'{self.odoo_url}/xmlrpc/2/object')
invoice_lines = []
for item in items:
product_search = models.execute_kw(
self.db, self.uid, self.password,
'product.product', 'search',
[[('name', '=', item['product_name'])]]
)
product_id = product_search[0] if product_search else 1
line_data = (0, 0, {
'product_id': product_id,
'quantity': item['quantity'],
'price_unit': item['unit_price'],
})
invoice_lines.append(line_data)
invoice_data = {
'partner_id': customer_id,
'invoice_line_ids': invoice_lines,
'invoice_date': datetime.now().date().isoformat(),
'invoice_date_due': payment_due_date,
'note': notes,
}
invoice_id = models.execute_kw(
self.db, self.uid, self.password,
'account.move', 'create', [invoice_data]
)
logger.info(f"Created Odoo invoice: {invoice_id}")
return invoice_id
except Exception as e:
logger.error(f"Error creating invoice: {str(e)}")
return 0
def record_payment(self, invoice_id: int, amount: float, payment_date: str) -> int:
"""支払いを記録"""
try:
import xmlrpc.client
models = xmlrpc.client.ServerProxy(f'{self.odoo_url}/xmlrpc/2/object')
payment_data = {
'move_id': invoice_id,
'amount': amount,
'payment_date': payment_date,
}
payment_id = models.execute_kw(
self.db, self.uid, self.password,
'account.payment', 'create', [payment_data]
)
logger.info(f"Recorded payment: {payment_id}")
return payment_id
except Exception as e:
logger.error(f"Error recording payment: {str(e)}")
return 0
def get_customer_by_email(self, email: str) -> Optional[int]:
"""メールアドレスで顧客を検索"""
try:
import xmlrpc.client
models = xmlrpc.client.ServerProxy(f'{self.odoo_url}/xmlrpc/2/object')
result = models.execute_kw(
self.db, self.uid, self.password,
'res.partner', 'search',
[[('email', '=', email)]]
)
return result[0] if result else None
except Exception as e:
logger.error(f"Error searching customer: {str(e)}")
return None
def _get_payment_term_id(self, due_date: str) -> int:
"""支払い条件を Odoo から取得"""
try:
import xmlrpc.client
models = xmlrpc.client.ServerProxy(f'{self.odoo_url}/xmlrpc/2/object')
# 簡略版「30日」の支払い条件 ID を取得
result = models.execute_kw(
self.db, self.uid, self.password,
'account.payment.term', 'search',
[[('name', 'like', '30')]]
)
return result[0] if result else 1
except Exception as e:
logger.warning(f"Could not get payment term: {str(e)}")
return 1
class SyncService:
"""REST API と Odoo の同期サービス"""
def __init__(self, odoo_client: OdooClient):
self.odoo = odoo_client
def sync_document(self, db_session, document_entity) -> Dict:
"""ドキュメントを Odoo に同期"""
# 顧客情報を取得
customer = db_session.query(Customer).filter(
Customer.id == document_entity.customer_id
).first()
if not customer:
return {"status": "error", "message": "Customer not found"}
# Odoo 顧客 ID を確認・作成
odoo_customer_id = customer.odoo_customer_id
if not odoo_customer_id:
odoo_customer_id = self.odoo.create_customer(
name=customer.name,
address=customer.address or "",
phone=customer.phone or "",
email=customer.email or ""
)
customer.odoo_customer_id = odoo_customer_id
db_session.commit()
if not odoo_customer_id:
return {"status": "error", "message": "Could not create/find Odoo customer"}
# ドキュメントタイプ別処理
items = json.loads(document_entity.items)
try:
if document_entity.doc_type == "quotation":
odoo_id = self.odoo.create_quotation(
customer_id=odoo_customer_id,
items=items,
payment_due_date=self._format_date(document_entity.payment_due_date),
notes=document_entity.notes or ""
)
elif document_entity.doc_type == "invoice":
odoo_id = self.odoo.create_invoice(
customer_id=odoo_customer_id,
items=items,
payment_due_date=self._format_date(document_entity.payment_due_date),
notes=document_entity.notes or ""
)
else:
odoo_id = 0
if odoo_id:
document_entity.odoo_id = odoo_id
return {"status": "success", "odoo_id": odoo_id}
else:
return {"status": "error", "message": "Failed to create Odoo document"}
except Exception as e:
logger.error(f"Sync error: {str(e)}")
return {"status": "error", "message": str(e)}
@staticmethod
def _format_date(timestamp: int) -> str:
"""タイムスタンプを ISO 形式に変換"""
from datetime import datetime
return datetime.fromtimestamp(timestamp / 1000).isoformat()
# FastAPI メインに統合される部分
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
app = FastAPI()
# グローバル Odoo クライアント
odoo_client = OdooClient(
odoo_url=os.getenv("ODOO_URL", "http://localhost:8069"),
db="odoo",
username=os.getenv("ODOO_USER", "admin"),
password=os.getenv("ODOO_PASSWORD", "admin")
)
sync_service = SyncService(odoo_client)
@app.post("/api/v1/sync")
async def sync_documents(request: SyncRequest, db: Session = Depends(get_db)):
"""ドキュメント同期エンドポイント"""
synced_count = 0
new_documents = []
for doc in request.documents:
# ドキュメント作成DB
db_doc = Document(...)
db.add(db_doc)
db.commit()
# Odoo に同期
result = sync_service.sync_document(db, db_doc)
if result["status"] == "success":
synced_count += 1
new_documents.append({
"local_id": db_doc.id,
"odoo_id": result.get("odoo_id"),
"doc_type": db_doc.doc_type
})
return SyncResponse(
status="success",
message=f"Synced {synced_count} documents",
synced_documents=synced_count,
new_documents=new_documents
)

View file

@ -1,8 +0,0 @@
fastapi==0.104.1
uvicorn==0.24.0
pydantic==2.5.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
python-dateutil==2.8.2
requests==2.31.0
python-multipart==0.0.6

View file

@ -1,58 +0,0 @@
# General
*.log
.DS_Store
# system-specific
*~
*.swp
# IDE configurations
.idea/
.vscode/
# Flutter/Dart specific
.dart_tool/
.flutter-plugins-dependencies
# ios/Pods/ is often ignored, but sometimes specific projects include it
# ios/Pods/
# For macOS desktop builds
macos/Runner/Flutter/AppFrameworkInfo.plist
macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
# For Windows desktop builds
windows/flutter/ephemeral/
# Android specific
android/.gradle/
android/gradle/wrapper/gradle-wrapper.properties
android/app/build.gradle.kts # Usually generated, but can be ignored if specific configurations are managed elsewhere or to prevent accidental commits
android/app/build/ # Build artifacts
android/captures/ # For Android Studio captures
android/gradle.properties # Usually fine to commit, but depends on project setup
# iOS specific
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json # Example for specific asset files, usually not ignored unless generated
ios/Runner.xcworkspace/contents.xcworkspacedata
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
# Web specific
web/icons/
web/manifest.json
# Build output
build/
# Dependency caching
.pub-cache/
# OS-generated files
.DS_Store
Thumbs.db
# IDE settings (IntelliJ IDEA)
*.iml
# Temporary files
*.tmp

View file

@ -1,45 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "67323de285b00232883f53b84095eb72be97d35c"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 67323de285b00232883f53b84095eb72be97d35c
base_revision: 67323de285b00232883f53b84095eb72be97d35c
- platform: android
create_revision: 67323de285b00232883f53b84095eb72be97d35c
base_revision: 67323de285b00232883f53b84095eb72be97d35c
- platform: ios
create_revision: 67323de285b00232883f53b84095eb72be97d35c
base_revision: 67323de285b00232883f53b84095eb72be97d35c
- platform: linux
create_revision: 67323de285b00232883f53b84095eb72be97d35c
base_revision: 67323de285b00232883f53b84095eb72be97d35c
- platform: macos
create_revision: 67323de285b00232883f53b84095eb72be97d35c
base_revision: 67323de285b00232883f53b84095eb72be97d35c
- platform: web
create_revision: 67323de285b00232883f53b84095eb72be97d35c
base_revision: 67323de285b00232883f53b84095eb72be97d35c
- platform: windows
create_revision: 67323de285b00232883f53b84095eb72be97d35c
base_revision: 67323de285b00232883f53b84095eb72be97d35c
# 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'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

File diff suppressed because it is too large Load diff

View file

@ -1,28 +0,0 @@
# 依頼内容
Flutterプロジェクトのビルドエラーを解消し、顧客マスター管理GPSソート/電話帳連携)と請求書履歴一覧の機能を完成させてください。
# 現状の問題
1. `CustomerPickerModal` 内で DB の `Customer` 型とアプリ用 `Customer` モデルが衝突し、型エラーが発生している。
2. `pdf_list_screen.dart` で、DB から顧客情報付きの請求データを取得する `InvoiceWithCustomer` 型や `watchAllInvoices()` メソッドが見つからずエラーになっている。
3. `invoice_input_screen.dart``CustomerPickerModal` を呼び出す際、古い引数 `existingCustomers` を渡しておりエラーになっている。
# 実行ステップ
1. **lib/data/database.dart の更新**:
- `Invoices``Customers` を結合して取得するための `InvoiceWithCustomer` クラスを定義してください。
- `AppDatabase` クラスに、最新順でデータを流す `Stream<List<InvoiceWithCustomer>> watchAllInvoices()` メソッドを実装してください。
- その後、`flutter pub run build_runner build --delete-conflicting-outputs` を実行してコード生成を完了させてください。
2. **lib/screens/customer_picker_modal.dart の修正**:
- インポートで `../models/invoice_models.dart``app_model` として別名を付け、DBの `Customer` 型と明確に区別してください。
- GPS座標latitude/longitudeを使用して、現在地に近い順にリストをソートするロジックを実装してください。
- 電話帳flutter_contactsからの取り込みと、DBへの保存insertOnConflictUpdateが正常に動くようにしてください。
3. **lib/screens/pdf_list_screen.dart の修正**:
- `database.watchAllInvoices()` を使用して履歴一覧を表示するようにしてください。
- リストタップ時に、DBのモデルからアプリ用の `Invoice` モデルへ変換して詳細画面へ遷移させてください。
4. **lib/screens/invoice_input_screen.dart の修正**:
- `CustomerPickerModal` の呼び出し箇所から、存在しない引数 `existingCustomers` を削除してください。
# 完了条件
`flutter build apk --debug` がエラーなく通り、かつ顧客マスターのGPSソートが機能すること。

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

View file

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

View file

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

View file

@ -1,616 +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 */; };
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 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 */
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>"; };
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 */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
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 */,
);
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>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
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 = (
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 = {
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 */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
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";
};
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 */
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;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
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 = 13.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.gemiInvoice;
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;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.gemiInvoice.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;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.gemiInvoice.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;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.gemiInvoice.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
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 = 13.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;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
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 = 13.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.gemiInvoice;
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.gemiInvoice;
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,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>

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>

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>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View file

@ -1,13 +0,0 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View file

@ -1,122 +0,0 @@
{
"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.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,23 +0,0 @@
{
"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.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

View file

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

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

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

@ -1,49 +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>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Gemi Invoice</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>gemi_invoice</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>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View file

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

View file

@ -1,12 +0,0 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View file

@ -1,99 +0,0 @@
import '../models/invoice_models.dart';
///
/// Odoo IDodooId
class Product {
final String id; // ID
final int? odooId; // Odoo上の product.product ID (nullの場合は未同期)
final String name; //
final int defaultUnitPrice; //
final String? category; //
const Product({
required this.id,
this.odooId,
required this.name,
required this.defaultUnitPrice,
this.category,
});
/// InvoiceItem
InvoiceItem toInvoiceItem({int quantity = 1}) {
return InvoiceItem(
description: name,
quantity: quantity,
unitPrice: defaultUnitPrice,
);
}
///
Product copyWith({
String? id,
int? odooId,
String? name,
int? defaultUnitPrice,
String? category,
}) {
return Product(
id: id ?? this.id,
odooId: odooId ?? this.odooId,
name: name ?? this.name,
defaultUnitPrice: defaultUnitPrice ?? this.defaultUnitPrice,
category: category ?? this.category,
);
}
/// JSON変換 (Odoo同期用)
Map<String, dynamic> toJson() {
return {
'id': id,
'odoo_id': odooId,
'name': name,
'default_unit_price': defaultUnitPrice,
'category': category,
};
}
/// JSONからモデルを生成
factory Product.fromJson(Map<String, dynamic> json) {
return Product(
id: json['id'],
odooId: json['odoo_id'],
name: json['name'],
defaultUnitPrice: json['default_unit_price'],
category: json['category'],
);
}
}
///
class ProductMaster {
static const List<Product> products = [
Product(id: 'S001', name: 'システム開発費', defaultUnitPrice: 500000, category: '開発'),
Product(id: 'S002', name: '保守・メンテナンス費', defaultUnitPrice: 50000, category: '運用'),
Product(id: 'S003', name: '技術コンサルティング', defaultUnitPrice: 100000, category: '開発'),
Product(id: 'G001', name: 'ライセンス料 (Pro)', defaultUnitPrice: 15000, category: '製品'),
Product(id: 'G002', name: '初期導入セットアップ', defaultUnitPrice: 30000, category: '製品'),
Product(id: 'M001', name: 'ハードウェア一式', defaultUnitPrice: 250000, category: '物品'),
Product(id: 'Z001', name: '諸経費', defaultUnitPrice: 5000, category: 'その他'),
];
///
static List<String> get categories {
return products.map((p) => p.category ?? 'その他').toSet().toList();
}
///
static List<Product> getProductsByCategory(String category) {
return products.where((p) => (p.category ?? 'その他') == category).toList();
}
/// IDで検索
static List<Product> search(String query) {
final q = query.toLowerCase();
return products.where((p) =>
p.name.toLowerCase().contains(q) ||
p.id.toLowerCase().contains(q)
).toList();
}
}

View file

@ -1,87 +0,0 @@
import 'package:intl/intl.dart';
///
/// Odoo IDodooId
class Customer {
final String id; // ID
final int? odooId; // Odoo上の res.partner ID (nullの場合は未同期)
final String displayName; //
final String formalName; //
final String? zipCode; // 便
final String? address; //
final String? department; //
final String? title; // ()
final DateTime lastUpdatedAt; //
Customer({
required this.id,
this.odooId,
required this.displayName,
required this.formalName,
this.zipCode,
this.address,
this.department,
this.title = '御中',
DateTime? lastUpdatedAt,
}) : this.lastUpdatedAt = lastUpdatedAt ?? DateTime.now();
///
String get invoiceName => department != null && department!.isNotEmpty
? "$formalName\n$department $title"
: "$formalName $title";
///
Customer copyWith({
String? id,
int? odooId,
String? displayName,
String? formalName,
String? zipCode,
String? address,
String? department,
String? title,
DateTime? lastUpdatedAt,
}) {
return Customer(
id: id ?? this.id,
odooId: odooId ?? this.odooId,
displayName: displayName ?? this.displayName,
formalName: formalName ?? this.formalName,
zipCode: zipCode ?? this.zipCode,
address: address ?? this.address,
department: department ?? this.department,
title: title ?? this.title,
lastUpdatedAt: lastUpdatedAt ?? DateTime.now(),
);
}
/// JSON変換 (Odoo同期用)
Map<String, dynamic> toJson() {
return {
'id': id,
'odoo_id': odooId,
'display_name': displayName,
'formal_name': formalName,
'zip_code': zipCode,
'address': address,
'department': department,
'title': title,
'last_updated_at': lastUpdatedAt.toIso8601String(),
};
}
/// JSONからモデルを生成
factory Customer.fromJson(Map<String, dynamic> json) {
return Customer(
id: json['id'],
odooId: json['odoo_id'],
displayName: json['display_name'],
formalName: json['formal_name'],
zipCode: json['zip_code'],
address: json['address'],
department: json['department'],
title: json['title'] ?? '御中',
lastUpdatedAt: DateTime.parse(json['last_updated_at']),
);
}
}

View file

@ -1,145 +0,0 @@
import 'package:intl/intl.dart';
import 'customer_model.dart';
///
class InvoiceItem {
String description;
int quantity;
int unitPrice;
InvoiceItem({
required this.description,
required this.quantity,
required this.unitPrice,
});
// ( * )
int get subtotal => quantity * unitPrice;
//
InvoiceItem copyWith({
String? description,
int? quantity,
int? unitPrice,
}) {
return InvoiceItem(
description: description ?? this.description,
quantity: quantity ?? this.quantity,
unitPrice: unitPrice ?? this.unitPrice,
);
}
// JSON変換
Map<String, dynamic> toJson() {
return {
'description': description,
'quantity': quantity,
'unit_price': unitPrice,
};
}
// JSONから復元
factory InvoiceItem.fromJson(Map<String, dynamic> json) {
return InvoiceItem(
description: json['description'] as String,
quantity: json['quantity'] as int,
unitPrice: json['unit_price'] as int,
);
}
}
///
class Invoice {
Customer customer; //
DateTime date;
List<InvoiceItem> items;
String? filePath; // PDFのパス
String invoiceNumber; //
String? notes; //
Invoice({
required this.customer,
required this.date,
required this.items,
this.filePath,
String? invoiceNumber,
this.notes,
}) : invoiceNumber = invoiceNumber ?? DateFormat('yyyyMMdd-HHmm').format(date);
//
String get clientName => customer.formalName;
//
int get subtotal {
return items.fold(0, (sum, item) => sum + item.subtotal);
}
// (10%)
int get tax {
return (subtotal * 0.1).floor();
}
//
int get totalAmount {
return subtotal + tax;
}
//
Invoice copyWith({
Customer? customer,
DateTime? date,
List<InvoiceItem>? items,
String? filePath,
String? invoiceNumber,
String? notes,
}) {
return Invoice(
customer: customer ?? this.customer,
date: date ?? this.date,
items: items ?? this.items,
filePath: filePath ?? this.filePath,
invoiceNumber: invoiceNumber ?? this.invoiceNumber,
notes: notes ?? this.notes,
);
}
// CSV形式への変換
String toCsv() {
StringBuffer sb = StringBuffer();
sb.writeln("Customer,${customer.formalName}");
sb.writeln("Invoice Number,$invoiceNumber");
sb.writeln("Date,${DateFormat('yyyy/MM/dd').format(date)}");
sb.writeln("");
sb.writeln("Description,Quantity,UnitPrice,Subtotal");
for (var item in items) {
sb.writeln("${item.description},${item.quantity},${item.unitPrice},${item.subtotal}");
}
return sb.toString();
}
// JSON変換 ()
Map<String, dynamic> toJson() {
return {
'customer': customer.toJson(),
'date': date.toIso8601String(),
'items': items.map((item) => item.toJson()).toList(),
'file_path': filePath,
'invoice_number': invoiceNumber,
'notes': notes,
};
}
// JSONから復元 ()
factory Invoice.fromJson(Map<String, dynamic> json) {
return Invoice(
customer: Customer.fromJson(json['customer'] as Map<String, dynamic>),
date: DateTime.parse(json['date'] as String),
items: (json['items'] as List)
.map((i) => InvoiceItem.fromJson(i as Map<String, dynamic>))
.toList(),
filePath: json['file_path'] as String?,
invoiceNumber: json['invoice_number'] as String,
notes: json['notes'] as String?,
);
}
}

View file

@ -1,255 +0,0 @@
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import '../data/product_master.dart';
import '../models/invoice_models.dart';
///
class ProductPickerModal extends StatefulWidget {
final Function(InvoiceItem) onItemSelected;
const ProductPickerModal({
Key? key,
required this.onItemSelected,
}) : super(key: key);
@override
State<ProductPickerModal> createState() => _ProductPickerModalState();
}
class _ProductPickerModalState extends State<ProductPickerModal> {
String _searchQuery = "";
List<Product> _masterProducts = [];
List<Product> _filteredProducts = [];
String _selectedCategory = "すべて";
@override
void initState() {
super.initState();
// ProductMasterの初期データを使用
_masterProducts = List.from(ProductMaster.products);
_filterProducts();
}
void _filterProducts() {
setState(() {
_filteredProducts = _masterProducts.where((product) {
final matchesQuery = product.name.toLowerCase().contains(_searchQuery.toLowerCase()) ||
product.id.toLowerCase().contains(_searchQuery.toLowerCase());
final matchesCategory = _selectedCategory == "すべて" || (product.category == _selectedCategory);
return matchesQuery && matchesCategory;
}).toList();
});
}
///
void _showProductEditDialog({Product? existingProduct}) {
final idController = TextEditingController(text: existingProduct?.id ?? "");
final nameController = TextEditingController(text: existingProduct?.name ?? "");
final priceController = TextEditingController(text: existingProduct?.defaultUnitPrice.toString() ?? "");
final categoryController = TextEditingController(text: existingProduct?.category ?? "");
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(existingProduct == null ? "新規商品の登録" : "商品情報の編集"),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (existingProduct == null)
TextField(
controller: idController,
decoration: const InputDecoration(labelText: "商品コード (例: S001)", border: OutlineInputBorder()),
),
const SizedBox(height: 12),
TextField(
controller: nameController,
decoration: const InputDecoration(labelText: "商品名", border: OutlineInputBorder()),
),
const SizedBox(height: 12),
TextField(
controller: priceController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: "標準単価", border: OutlineInputBorder()),
),
const SizedBox(height: 12),
TextField(
controller: categoryController,
decoration: const InputDecoration(labelText: "カテゴリ (任意)", border: OutlineInputBorder()),
),
],
),
),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text("キャンセル")),
ElevatedButton(
onPressed: () {
final String name = nameController.text.trim();
final int price = int.tryParse(priceController.text) ?? 0;
if (name.isEmpty) return;
setState(() {
if (existingProduct != null) {
//
final index = _masterProducts.indexWhere((p) => p.id == existingProduct.id);
if (index != -1) {
_masterProducts[index] = existingProduct.copyWith(
name: name,
defaultUnitPrice: price,
category: categoryController.text.trim(),
);
}
} else {
//
_masterProducts.add(Product(
id: idController.text.isEmpty ? const Uuid().v4().substring(0, 8) : idController.text,
name: name,
defaultUnitPrice: price,
category: categoryController.text.trim(),
));
}
_filterProducts();
});
Navigator.pop(context);
},
child: const Text("保存"),
),
],
),
);
}
///
void _confirmDelete(Product product) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("商品の削除"),
content: Text("${product.name}」をマスターから削除しますか?"),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text("キャンセル")),
TextButton(
onPressed: () {
setState(() {
_masterProducts.removeWhere((p) => p.id == product.id);
_filterProducts();
});
Navigator.pop(context);
},
child: const Text("削除する", style: TextStyle(color: Colors.red)),
),
],
),
);
}
@override
Widget build(BuildContext context) {
//
final dynamicCategories = ["すべて", ..._masterProducts.map((p) => p.category ?? 'その他').toSet().toList()];
return Material(
color: Colors.white,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("商品マスター管理", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
IconButton(icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context)),
],
),
const SizedBox(height: 12),
TextField(
decoration: InputDecoration(
hintText: "商品名やコードで検索...",
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
filled: true,
fillColor: Colors.grey.shade50,
),
onChanged: (val) {
_searchQuery = val;
_filterProducts();
},
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: dynamicCategories.map((cat) {
final isSelected = _selectedCategory == cat;
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: ChoiceChip(
label: Text(cat),
selected: isSelected,
onSelected: (s) {
if (s) {
setState(() {
_selectedCategory = cat;
_filterProducts();
});
}
},
),
);
}).toList(),
),
),
),
const SizedBox(width: 8),
IconButton.filled(
onPressed: () => _showProductEditDialog(),
icon: const Icon(Icons.add),
tooltip: "新規商品を追加",
),
],
),
],
),
),
const Divider(height: 1),
Expanded(
child: _filteredProducts.isEmpty
? const Center(child: Text("該当する商品がありません"))
: ListView.separated(
itemCount: _filteredProducts.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) {
final product = _filteredProducts[index];
return ListTile(
leading: const Icon(Icons.inventory_2, color: Colors.blueGrey),
title: Text(product.name, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text("${product.id} | ¥${product.defaultUnitPrice}"),
onTap: () => widget.onItemSelected(product.toInvoiceItem()),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit_outlined, size: 20, color: Colors.blueGrey),
onPressed: () => _showProductEditDialog(existingProduct: product),
),
IconButton(
icon: const Icon(Icons.delete_outline, size: 20, color: Colors.redAccent),
onPressed: () => _confirmDelete(product),
),
],
),
);
},
),
),
],
),
);
}
}

View file

@ -1,107 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import '../models/invoice_models.dart';
/// DB
/// PDFファイルとデータの整合性を保つための機能を提供します
class InvoiceRepository {
static const String _dbFileName = 'invoices_db.json';
///
Future<File> _getDbFile() async {
final directory = await getApplicationDocumentsDirectory();
return File('${directory.path}/$_dbFileName');
}
///
Future<List<Invoice>> getAllInvoices() async {
try {
final file = await _getDbFile();
if (!await file.exists()) return [];
final String content = await file.readAsString();
final List<dynamic> jsonList = json.decode(content);
return jsonList.map((json) => Invoice.fromJson(json)).toList()
..sort((a, b) => b.date.compareTo(a.date)); //
} catch (e) {
print('DB Loading Error: $e');
return [];
}
}
///
Future<void> saveInvoice(Invoice invoice) async {
final List<Invoice> all = await getAllInvoices();
//
final index = all.indexWhere((i) => i.invoiceNumber == invoice.invoiceNumber);
if (index != -1) {
// PDFの掃除
final oldPath = all[index].filePath;
if (oldPath != null && oldPath != invoice.filePath) {
await _deletePhysicalFile(oldPath);
}
all[index] = invoice;
} else {
all.add(invoice);
}
final file = await _getDbFile();
await file.writeAsString(json.encode(all.map((i) => i.toJson()).toList()));
}
///
Future<void> deleteInvoice(Invoice invoice) async {
final List<Invoice> all = await getAllInvoices();
all.removeWhere((i) => i.invoiceNumber == invoice.invoiceNumber);
//
if (invoice.filePath != null) {
await _deletePhysicalFile(invoice.filePath!);
}
final file = await _getDbFile();
await file.writeAsString(json.encode(all.map((i) => i.toJson()).toList()));
}
/// PDFファイルをストレージから削除する
Future<void> _deletePhysicalFile(String path) async {
try {
final file = File(path);
if (await file.exists()) {
await file.delete();
print('Physical file deleted: $path');
}
} catch (e) {
print('File Deletion Error: $path, $e');
}
}
/// DBに登録されていないPDFファイル
Future<int> cleanupOrphanedPdfs() async {
final List<Invoice> all = await getAllInvoices();
final Set<String> registeredPaths = all
.where((i) => i.filePath != null)
.map((i) => i.filePath!)
.toSet();
final directory = await getExternalStorageDirectory();
if (directory == null) return 0;
int deletedCount = 0;
final List<FileSystemEntity> files = directory.listSync();
for (var entity in files) {
if (entity is File && entity.path.endsWith('.pdf')) {
// DBに登録されていないPDFは削除
if (!registeredPaths.contains(entity.path)) {
await entity.delete();
deletedCount++;
}
}
}
return deletedCount;
}
}

View file

@ -1 +0,0 @@
flutter/ephemeral

View file

@ -1,128 +0,0 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "gemi_invoice")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.gemi_invoice")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View file

@ -1,88 +0,0 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View file

@ -1,26 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the application ID.
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")

View file

@ -1,6 +0,0 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}

View file

@ -1,148 +0,0 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Called when first Flutter frame received.
static void first_frame_cb(MyApplication* self, FlView* view) {
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
}
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "gemi_invoice");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "gemi_invoice");
}
gtk_window_set_default_size(window, 1280, 720);
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(
project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
GdkRGBA background_color;
// Background defaults to black, override it here if necessary, e.g. #00000000
// for transparent.
gdk_rgba_parse(&background_color, "#000000");
fl_view_set_background_color(view, &background_color);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
// Show the window when Flutter renders.
// Requires the view to be realized so we can start rendering.
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
self);
gtk_widget_realize(GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application,
gchar*** arguments,
int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GApplication::startup.
static void my_application_startup(GApplication* application) {
// MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
// Implements GApplication::shutdown.
static void my_application_shutdown(GApplication* application) {
// MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line =
my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
// Set the program name to the application ID, which helps various systems
// like GTK and desktop environments map this running application to its
// corresponding .desktop file. This ensures better integration by allowing
// the application to be recognized beyond its binary name.
g_set_prgname(APPLICATION_ID);
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID, "flags",
G_APPLICATION_NON_UNIQUE, nullptr));
}

View file

@ -1,21 +0,0 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication,
my_application,
MY,
APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_

View file

@ -1,7 +0,0 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/dgph
**/xcuserdata/

View file

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

View file

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

View file

@ -1,705 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
buildPhases = (
33CC111E2044C6BF0003C045 /* ShellScript */,
);
dependencies = (
);
name = "Flutter Assemble";
productName = FLX;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
remoteInfo = Runner;
};
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
remoteInfo = FLX;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
33CC110E2044A8840003C045 /* Bundle Framework */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Bundle Framework";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* gemi_invoice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "gemi_invoice.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
331C80D2294CF70F00263BE5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
);
path = Configs;
sourceTree = "<group>";
};
33CC10E42044A3C60003C045 = {
isa = PBXGroup;
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
);
sourceTree = "<group>";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* gemi_invoice.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
33CC11242044D66E0003C045 /* Resources */ = {
isa = PBXGroup;
children = (
33CC10F22044A3C60003C045 /* Assets.xcassets */,
33CC10F42044A3C60003C045 /* MainMenu.xib */,
33CC10F72044A3C60003C045 /* Info.plist */,
);
name = Resources;
path = ..;
sourceTree = "<group>";
};
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
);
path = Flutter;
sourceTree = "<group>";
};
33FAB671232836740065AC1E /* Runner */ = {
isa = PBXGroup;
children = (
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
33E51914231749380026EE4D /* Release.entitlements */,
33CC11242044D66E0003C045 /* Resources */,
33BA886A226E78AF003329D5 /* Configs */,
);
path = Runner;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
33CC10EC2044A3C60003C045 /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
);
buildRules = (
);
dependencies = (
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* gemi_invoice.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 33CC10EC2044A3C60003C045;
};
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
};
33CC111A2044C6BA0003C045 = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Manual;
};
};
};
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 33CC10E42044A3C60003C045;
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
331C80D4294CF70F00263BE5 /* RunnerTests */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C80D3294CF70F00263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EB2044A3C60003C045 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
};
33CC111E2044C6BF0003C045 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C80D1294CF70F00263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10E92044A3C60003C045 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC10EC2044A3C60003C045 /* Runner */;
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
};
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
33CC10F52044A3C60003C045 /* Base */,
);
name = MainMenu.xib;
path = Runner;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.gemiInvoice.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/gemi_invoice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/gemi_invoice";
};
name = Debug;
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.gemiInvoice.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/gemi_invoice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/gemi_invoice";
};
name = Release;
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.gemiInvoice.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/gemi_invoice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/gemi_invoice";
};
name = Profile;
};
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Profile;
};
338D0CEA231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Profile;
};
338D0CEB231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
};
33CC10F92044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
33CC10FA2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
33CC10FC2044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
33CC10FD2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
};
33CC111C2044C6BA0003C045 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
33CC111D2044C6BA0003C045 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C80DB294CF71000263BE5 /* Debug */,
331C80DC294CF71000263BE5 /* Release */,
331C80DD294CF71000263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10F92044A3C60003C045 /* Debug */,
33CC10FA2044A3C60003C045 /* Release */,
338D0CE9231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10FC2044A3C60003C045 /* Debug */,
33CC10FD2044A3C60003C045 /* Release */,
338D0CEA231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC111C2044C6BA0003C045 /* Debug */,
33CC111D2044C6BA0003C045 /* Release */,
338D0CEB231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}

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>

View file

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

View file

@ -1,13 +0,0 @@
import Cocoa
import FlutterMacOS
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}

View file

@ -1,68 +0,0 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

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