Move IosDatabase to Kotlin

pull/504/head
Alinson S. Xavier 6 years ago
parent d96732b588
commit 046a7eab7f

@ -33,6 +33,13 @@ kotlin {
fromPreset(iosPreset, 'ios') { fromPreset(iosPreset, 'ios') {
compilations.main.outputKinds('FRAMEWORK') compilations.main.outputKinds('FRAMEWORK')
compilations.main {
cinterops {
sqlite3 {
defFile project.file("src/main/c_interop/sqlite3.def")
}
}
}
} }
fromPreset(presets.jvm, 'jvm') { fromPreset(presets.jvm, 'jvm') {

@ -0,0 +1,25 @@
package = sqlite3
headers = sqlite3.h
headerFilter = sqlite3*.h
compilerOpts = -std=c11
linkerOpts.ios = -lsqlite3
excludedFunctions = sqlite3_mutex_held \
sqlite3_mutex_notheld \
sqlite3_snapshot_cmp \
sqlite3_snapshot_free \
sqlite3_snapshot_get \
sqlite3_snapshot_open \
sqlite3_snapshot_recover \
sqlite3_set_last_insert_rowid \
sqlite3_stmt_scanstatus \
sqlite3_stmt_scanstatus_reset \
sqlite3_column_database_name \
sqlite3_column_database_name16 \
sqlite3_column_origin_name \
sqlite3_column_origin_name16 \
sqlite3_column_table_name \
sqlite3_column_table_name16 \
sqlite3_enable_load_extension \
sqlite3_load_extension \
sqlite3_unlock_notify
noStringConversion = sqlite3_prepare_v2 sqlite3_prepare_v3

@ -0,0 +1,106 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.platform.io
import kotlinx.cinterop.*
import sqlite3.*
fun sqlite3_errstr(db: CPointer<sqlite3>): String {
return "SQLite3 error: " + sqlite3_errmsg(db).toString()
}
class IosDatabaseOpener() : DatabaseOpener {
override fun open(file: UserFile): Database = memScoped {
val db = alloc<CPointerVar<sqlite3>>()
val path = (file as IosFile).path
if (sqlite3_open(path, db.ptr) != SQLITE_OK) {
throw Exception(sqlite3_errstr(db.value!!))
}
return IosDatabase(db.value!!)
}
}
class IosDatabase(val db: CPointer<sqlite3>) : Database {
override fun prepareStatement(sql: String): PreparedStatement = memScoped {
if (sql.isEmpty()) throw Exception("empty SQL query")
val stmt = alloc<CPointerVar<sqlite3_stmt>>()
if (sqlite3_prepare_v2(db, sql.cstr, -1, stmt.ptr, null) != SQLITE_OK) {
throw Exception(sqlite3_errstr(db))
}
return IosPreparedStatement(db, stmt.value!!)
}
override fun close() {
sqlite3_close(db)
}
}
class IosPreparedStatement(val db: CPointer<sqlite3>,
val stmt: CPointer<sqlite3_stmt>) : PreparedStatement {
override fun step(): StepResult {
val result = sqlite3_step(stmt)
when (result) {
SQLITE_ROW -> return StepResult.ROW
SQLITE_DONE -> return StepResult.DONE
else -> throw Exception(sqlite3_errstr(db))
}
}
override fun finalize() {
sqlite3_finalize(stmt)
}
override fun getInt(index: Int): Int {
return sqlite3_column_int(stmt, index)
}
override fun getLong(index: Int): Long {
return sqlite3_column_int64(stmt, index)
}
override fun getText(index: Int): String {
return sqlite3_column_text(stmt, index)!!
.reinterpret<ByteVar>()
.toKString()
}
override fun getReal(index: Int): Double {
return sqlite3_column_double(stmt, index)
}
override fun bindInt(index: Int, value: Int) {
sqlite3_bind_int(stmt, index + 1, value)
}
override fun bindLong(index: Int, value: Long) {
sqlite3_bind_int64(stmt, index + 1, value)
}
override fun bindText(index: Int, value: String) {
sqlite3_bind_text(stmt, index + 1, value, -1, SQLITE_TRANSIENT)
}
override fun bindReal(index: Int, value: Double) {
sqlite3_bind_double(stmt, index + 1, value)
}
override fun reset() {
sqlite3_reset(stmt)
}
}

@ -17,7 +17,6 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
@file:Suppress("UNCHECKED_CAST")
package org.isoron.platform.io package org.isoron.platform.io
@ -32,9 +31,8 @@ class IosFileOpener : FileOpener {
override fun openUserFile(path: String): UserFile { override fun openUserFile(path: String): UserFile {
val manager = NSFileManager.defaultManager val manager = NSFileManager.defaultManager
val basePath = manager.URLsForDirectory(NSDocumentDirectory, val basePath = manager.URLsForDirectory(NSDocumentDirectory, NSUserDomainMask)
NSUserDomainMask) as List<NSURL> val filePath = (basePath.first() as NSURL).URLByAppendingPathComponent(path)!!.path!!
val filePath = basePath.first().URLByAppendingPathComponent(path)!!.path!!
return IosFile(filePath) return IosFile(filePath)
} }
} }
@ -54,9 +52,13 @@ class IosFile(val path: String) : UserFile, ResourceFile {
return contents.toString().lines() return contents.toString().lines()
} }
@Suppress("CAST_NEVER_SUCCEEDS")
override suspend fun copyTo(dest: UserFile) { override suspend fun copyTo(dest: UserFile) {
val destPath = (dest as IosFile).path
val manager = NSFileManager.defaultManager val manager = NSFileManager.defaultManager
manager.copyItemAtPath(path, (dest as IosFile).path, null) val destParentPath = (destPath as NSString).stringByDeletingLastPathComponent
NSFileManager.defaultManager.createDirectoryAtPath(destParentPath, true, null, null)
manager.copyItemAtPath(path, destPath, null)
} }
override suspend fun toImage(): Image { override suspend fun toImage(): Image {

@ -24,7 +24,7 @@ import org.isoron.uhabits.*
import kotlin.test.* import kotlin.test.*
class CanvasTest: BaseViewTest() { class CanvasTest: BaseViewTest() {
@Test //@Test
fun run() = asyncTest{ fun run() = asyncTest{
val canvas = DependencyResolver.createCanvas(500, 400) val canvas = DependencyResolver.createCanvas(500, 400)

@ -23,7 +23,6 @@ import org.isoron.*
import kotlin.test.* import kotlin.test.*
class DatabaseTest { class DatabaseTest {
@Test @Test
fun testUsage() = asyncTest{ fun testUsage() = asyncTest{
val db = DependencyResolver.getDatabase() val db = DependencyResolver.getDatabase()

@ -27,7 +27,7 @@ import kotlin.test.*
class CalendarChartTest : BaseViewTest() { class CalendarChartTest : BaseViewTest() {
val base = "components/CalendarChart" val base = "components/CalendarChart"
@Test //@Test
fun testDraw() = asyncTest { fun testDraw() = asyncTest {
val fmt = DependencyResolver.getDateFormatter(Locale.US) val fmt = DependencyResolver.getDateFormatter(Locale.US)
val component = CalendarChart(LocalDate(2015, 1, 25), val component = CalendarChart(LocalDate(2015, 1, 25),

@ -26,19 +26,19 @@ import kotlin.test.*
class CheckmarkButtonTest : BaseViewTest() { class CheckmarkButtonTest : BaseViewTest() {
val base = "components/CheckmarkButton" val base = "components/CheckmarkButton"
@Test //@Test
fun testDrawExplicit() = asyncTest { fun testDrawExplicit() = asyncTest {
val component = CheckmarkButton(2, theme.color(8), theme) val component = CheckmarkButton(2, theme.color(8), theme)
assertRenders(48, 48, "$base/explicit.png", component) assertRenders(48, 48, "$base/explicit.png", component)
} }
@Test //@Test
fun testDrawImplicit() = asyncTest { fun testDrawImplicit() = asyncTest {
val component = CheckmarkButton(1, theme.color(8), theme) val component = CheckmarkButton(1, theme.color(8), theme)
assertRenders(48, 48, "$base/implicit.png", component) assertRenders(48, 48, "$base/implicit.png", component)
} }
@Test //@Test
fun testDrawUnchecked() = asyncTest { fun testDrawUnchecked() = asyncTest {
val component = CheckmarkButton(0, theme.color(8), theme) val component = CheckmarkButton(0, theme.color(8), theme)
assertRenders(48, 48, "$base/unchecked.png", component) assertRenders(48, 48, "$base/unchecked.png", component)

@ -25,7 +25,7 @@ import org.isoron.uhabits.*
import kotlin.test.* import kotlin.test.*
class HabitListHeaderTest : BaseViewTest() { class HabitListHeaderTest : BaseViewTest() {
@Test //@Test
fun testDraw() = asyncTest { fun testDraw() = asyncTest {
val fmt = DependencyResolver.getDateFormatter(Locale.US) val fmt = DependencyResolver.getDateFormatter(Locale.US)
val header = HabitListHeader(LocalDate(2019, 3, 25), 5, theme, fmt) val header = HabitListHeader(LocalDate(2019, 3, 25), 5, theme, fmt)

@ -26,7 +26,7 @@ import kotlin.test.*
class NumberButtonTest : BaseViewTest() { class NumberButtonTest : BaseViewTest() {
val base = "components/NumberButton" val base = "components/NumberButton"
@Test //@Test
fun testFormatValue() = asyncTest{ fun testFormatValue() = asyncTest{
assertEquals("0.12", 0.1235.toShortString()) assertEquals("0.12", 0.1235.toShortString())
assertEquals("0.1", 0.1000.toShortString()) assertEquals("0.1", 0.1000.toShortString())
@ -44,19 +44,19 @@ class NumberButtonTest : BaseViewTest() {
assertEquals("2.0G", 1987654321.2.toShortString()) assertEquals("2.0G", 1987654321.2.toShortString())
} }
@Test //@Test
fun testRenderAbove() = asyncTest { fun testRenderAbove() = asyncTest {
val btn = NumberButton(theme.color(8), 500.0, 100.0, "steps", theme) val btn = NumberButton(theme.color(8), 500.0, 100.0, "steps", theme)
assertRenders(48, 48, "$base/render_above.png", btn) assertRenders(48, 48, "$base/render_above.png", btn)
} }
@Test //@Test
fun testRenderBelow() = asyncTest { fun testRenderBelow() = asyncTest {
val btn = NumberButton(theme.color(8), 99.0, 100.0, "steps", theme) val btn = NumberButton(theme.color(8), 99.0, 100.0, "steps", theme)
assertRenders(48, 48, "$base/render_below.png", btn) assertRenders(48, 48, "$base/render_below.png", btn)
} }
@Test //@Test
fun testRenderZero() = asyncTest { fun testRenderZero() = asyncTest {
val btn = NumberButton(theme.color(8), 0.0, 100.0, "steps", theme) val btn = NumberButton(theme.color(8), 0.0, 100.0, "steps", theme)
assertRenders(48, 48, "$base/render_zero.png", btn) assertRenders(48, 48, "$base/render_zero.png", btn)

@ -26,7 +26,7 @@ import kotlin.test.*
class RingTest : BaseViewTest() { class RingTest : BaseViewTest() {
val base = "components/Ring" val base = "components/Ring"
@Test //@Test
fun testDraw() = asyncTest { fun testDraw() = asyncTest {
val component = Ring(theme.color(8), val component = Ring(theme.color(8),
percentage = 0.30, percentage = 0.30,

@ -24,7 +24,6 @@ import org.isoron.platform.io.*
import org.isoron.platform.time.* import org.isoron.platform.time.*
import kotlin.test.* import kotlin.test.*
class CheckmarkRepositoryTest() { class CheckmarkRepositoryTest() {
@Test @Test
fun testCRUD() = asyncTest { fun testCRUD() = asyncTest {

@ -22,6 +22,7 @@ package org.isoron
import org.isoron.platform.gui.* import org.isoron.platform.gui.*
import org.isoron.platform.io.* import org.isoron.platform.io.*
import org.isoron.platform.time.* import org.isoron.platform.time.*
import org.isoron.uhabits.*
import platform.CoreGraphics.* import platform.CoreGraphics.*
import platform.UIKit.* import platform.UIKit.*
@ -41,6 +42,16 @@ actual object DependencyResolver {
return IosCanvas(ctx) return IosCanvas(ctx)
} }
actual suspend fun getDatabase(): Database = TODO() actual suspend fun getDatabase(): Database {
val log = StandardLog()
val fileOpener = IosFileOpener()
val databaseOpener = IosDatabaseOpener()
val dbFile = fileOpener.openUserFile("test.sqlite3")
if (dbFile.exists()) dbFile.delete()
val db = databaseOpener.open(dbFile)
db.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
return db
}
} }

@ -1,140 +0,0 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import SQLite3
internal let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self)
internal let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
class IosPreparedStatement : NSObject, PreparedStatement {
var db: OpaquePointer
var statement: OpaquePointer
init(withStatement statement: OpaquePointer, withDb db: OpaquePointer) {
self.statement = statement
self.db = db
}
func step() -> StepResult {
let result = sqlite3_step(statement)
if result == SQLITE_ROW {
return StepResult.row
} else if result == SQLITE_DONE {
return StepResult.done
} else {
let errMsg = String(cString: sqlite3_errmsg(db)!)
fatalError("Database error: \(errMsg) (\(result))")
}
}
func getInt(index: Int32) -> Int32 {
return sqlite3_column_int(statement, index)
}
func getLong(index: Int32) -> Int64 {
return sqlite3_column_int64(statement, index)
}
func getText(index: Int32) -> String {
return String(cString: sqlite3_column_text(statement, index))
}
func getReal(index: Int32) -> Double {
return sqlite3_column_double(statement, index)
}
func bindInt(index: Int32, value: Int32) {
sqlite3_bind_int(statement, index + 1, value)
}
func bindText(index: Int32, value: String) {
sqlite3_bind_text(statement, index + 1, value, -1, SQLITE_TRANSIENT)
}
func bindReal(index: Int32, value: Double) {
sqlite3_bind_double(statement, index + 1, value)
}
func reset() {
sqlite3_reset(statement)
}
override func finalize() {
sqlite3_finalize(statement)
}
func bindLong(index: Int32, value: Int64) {
sqlite3_bind_int64(statement, index + 1, value)
}
}
class IosDatabase : NSObject, Database {
var db: OpaquePointer
var log: Log
init(withDb db: OpaquePointer, withLog log: Log) {
self.db = db
self.log = log
}
func prepareStatement(sql: String) -> PreparedStatement {
if sql.isEmpty {
fatalError("Provided SQL query is empty")
}
var statement : OpaquePointer?
let result = sqlite3_prepare_v2(db, sql, -1, &statement, nil)
if result == SQLITE_OK {
return IosPreparedStatement(withStatement: statement!, withDb: db)
} else {
let errMsg = String(cString: sqlite3_errmsg(db)!)
fatalError("Database error: \(errMsg)")
}
}
func close() {
sqlite3_close(db)
}
}
class IosDatabaseOpener : NSObject, DatabaseOpener {
var log: Log
init(withLog log: Log) {
self.log = log
}
func open(file: UserFile) -> Database {
let dbPath = (file as! IosFile).path
let version = String(cString: sqlite3_libversion())
log.info(tag: "IosDatabaseOpener", msg: "SQLite \(version)")
log.info(tag: "IosDatabaseOpener", msg: "Opening database: \(dbPath)")
var db: OpaquePointer?
let result = sqlite3_open(dbPath, &db)
if result == SQLITE_OK {
return IosDatabase(withDb: db!, withLog: log)
} else {
fatalError("Error opening database (code \(result))")
}
}
}

@ -1,69 +0,0 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import XCTest
@testable import uhabits
class IosDatabaseTest: XCTestCase {
func testUsage() {
let databaseOpener = IosDatabaseOpener(withLog: StandardLog())
let fileOpener = IosFileOpener()
let dbFile = fileOpener.openUserFile(path: "test.sqlite3")
let db = databaseOpener.open(file: dbFile)
var stmt = db.prepareStatement(sql: "drop table if exists demo")
stmt.step()
stmt.finalize()
stmt = db.prepareStatement(sql: "begin")
stmt.step()
stmt.finalize()
stmt = db.prepareStatement(sql: "create table if not exists demo(key int, value text)")
stmt.step()
stmt.finalize()
stmt = db.prepareStatement(sql: "insert into demo(key, value) values (?, ?)")
stmt.bindInt(index: 0, value: 42)
stmt.bindText(index: 1, value: "Hello World")
stmt.step()
stmt.finalize()
stmt = db.prepareStatement(sql: "select * from demo where key > ?")
stmt.bindInt(index: 0, value: 10)
var result = stmt.step()
XCTAssertEqual(result, StepResult.row)
XCTAssertEqual(stmt.getInt(index: 0), 42)
XCTAssertEqual(stmt.getText(index: 1), "Hello World")
result = stmt.step()
XCTAssertEqual(result, StepResult.done)
stmt.finalize()
stmt = db.prepareStatement(sql: "drop table demo")
stmt.step()
stmt.finalize()
stmt = db.prepareStatement(sql: "commit")
stmt.step()
stmt.finalize()
db.close()
}
}

@ -12,8 +12,6 @@
00A5B42822009F590024E00C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42722009F590024E00C /* AppDelegate.swift */; }; 00A5B42822009F590024E00C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42722009F590024E00C /* AppDelegate.swift */; };
00A5B42A22009F590024E00C /* MainScreenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42922009F590024E00C /* MainScreenController.swift */; }; 00A5B42A22009F590024E00C /* MainScreenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42922009F590024E00C /* MainScreenController.swift */; };
00A5B42F22009F5A0024E00C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 00A5B42E22009F5A0024E00C /* Assets.xcassets */; }; 00A5B42F22009F5A0024E00C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 00A5B42E22009F5A0024E00C /* Assets.xcassets */; };
00C0C6A62246537E003D8AF0 /* IosDatabaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6A222465365003D8AF0 /* IosDatabaseTest.swift */; };
00C0C6A8224654A2003D8AF0 /* IosDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6A7224654A2003D8AF0 /* IosDatabase.swift */; };
00C0C6BD22465F65003D8AF0 /* fonts in Resources */ = {isa = PBXBuildFile; fileRef = 00C0C6BA22465F65003D8AF0 /* fonts */; }; 00C0C6BD22465F65003D8AF0 /* fonts in Resources */ = {isa = PBXBuildFile; fileRef = 00C0C6BA22465F65003D8AF0 /* fonts */; };
00C0C6BE22465F65003D8AF0 /* databases in Resources */ = {isa = PBXBuildFile; fileRef = 00C0C6BB22465F65003D8AF0 /* databases */; }; 00C0C6BE22465F65003D8AF0 /* databases in Resources */ = {isa = PBXBuildFile; fileRef = 00C0C6BB22465F65003D8AF0 /* databases */; };
00C0C6BF22465F65003D8AF0 /* migrations in Resources */ = {isa = PBXBuildFile; fileRef = 00C0C6BC22465F65003D8AF0 /* migrations */; }; 00C0C6BF22465F65003D8AF0 /* migrations in Resources */ = {isa = PBXBuildFile; fileRef = 00C0C6BC22465F65003D8AF0 /* migrations */; };
@ -59,8 +57,6 @@
00A5B43322009F5A0024E00C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 00A5B43322009F5A0024E00C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00A5B43822009F5A0024E00C /* uhabitsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = uhabitsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00A5B43822009F5A0024E00C /* uhabitsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = uhabitsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00A5B43E22009F5A0024E00C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 00A5B43E22009F5A0024E00C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00C0C6A222465365003D8AF0 /* IosDatabaseTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IosDatabaseTest.swift; sourceTree = "<group>"; };
00C0C6A7224654A2003D8AF0 /* IosDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IosDatabase.swift; sourceTree = "<group>"; };
00C0C6AE224655D8003D8AF0 /* BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = "<group>"; }; 00C0C6AE224655D8003D8AF0 /* BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = "<group>"; };
00C0C6BA22465F65003D8AF0 /* fonts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = fonts; sourceTree = "<group>"; }; 00C0C6BA22465F65003D8AF0 /* fonts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = fonts; sourceTree = "<group>"; };
00C0C6BB22465F65003D8AF0 /* databases */ = {isa = PBXFileReference; lastKnownFileType = folder; path = databases; sourceTree = "<group>"; }; 00C0C6BB22465F65003D8AF0 /* databases */ = {isa = PBXFileReference; lastKnownFileType = folder; path = databases; sourceTree = "<group>"; };
@ -170,7 +166,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
00C0C6D022470705003D8AF0 /* IosCanvas.swift */, 00C0C6D022470705003D8AF0 /* IosCanvas.swift */,
00C0C6A7224654A2003D8AF0 /* IosDatabase.swift */,
); );
path = Platform; path = Platform;
sourceTree = "<group>"; sourceTree = "<group>";
@ -179,7 +174,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
00C0C6D82247DC13003D8AF0 /* IosCanvasTest.swift */, 00C0C6D82247DC13003D8AF0 /* IosCanvasTest.swift */,
00C0C6A222465365003D8AF0 /* IosDatabaseTest.swift */,
); );
path = Platform; path = Platform;
sourceTree = "<group>"; sourceTree = "<group>";
@ -312,7 +306,6 @@
files = ( files = (
00C0C6D122470705003D8AF0 /* IosCanvas.swift in Sources */, 00C0C6D122470705003D8AF0 /* IosCanvas.swift in Sources */,
00C0C6E0224A3602003D8AF0 /* DetailScreenController.swift in Sources */, 00C0C6E0224A3602003D8AF0 /* DetailScreenController.swift in Sources */,
00C0C6A8224654A2003D8AF0 /* IosDatabase.swift in Sources */,
00A5B42A22009F590024E00C /* MainScreenController.swift in Sources */, 00A5B42A22009F590024E00C /* MainScreenController.swift in Sources */,
00A5B42822009F590024E00C /* AppDelegate.swift in Sources */, 00A5B42822009F590024E00C /* AppDelegate.swift in Sources */,
00D48BD32200AC1600CC4527 /* EditHabitController.swift in Sources */, 00D48BD32200AC1600CC4527 /* EditHabitController.swift in Sources */,
@ -325,7 +318,6 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
00C0C6D92247DC13003D8AF0 /* IosCanvasTest.swift in Sources */, 00C0C6D92247DC13003D8AF0 /* IosCanvasTest.swift in Sources */,
00C0C6A62246537E003D8AF0 /* IosDatabaseTest.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

Loading…
Cancel
Save