mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 01:08:50 -06:00
Restore Backend class; replace TaskRunner by Kotlin Coroutines
This commit is contained in:
@@ -1,67 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.platform.concurrency
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A TaskRunner provides the ability of running tasks in different queues. The
|
|
||||||
* class is also observable, and notifies listeners when new tasks are started
|
|
||||||
* or finished.
|
|
||||||
*
|
|
||||||
* Two queues are available: a foreground queue and a background queue. These
|
|
||||||
* two queues may run in parallel, depending on the hardware. Multiple tasks
|
|
||||||
* submitted to the same queue, however, always run sequentially, in the order
|
|
||||||
* they were enqueued.
|
|
||||||
*/
|
|
||||||
interface TaskRunner {
|
|
||||||
|
|
||||||
val listeners: MutableList<Listener>
|
|
||||||
|
|
||||||
val activeTaskCount: Int
|
|
||||||
|
|
||||||
fun runInBackground(task: () -> Unit)
|
|
||||||
|
|
||||||
fun runInForeground(task: () -> Unit)
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
fun onTaskStarted()
|
|
||||||
fun onTaskFinished()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sequential implementation of TaskRunner. Both background and foreground
|
|
||||||
* queues run in the same thread, so they block each other.
|
|
||||||
*/
|
|
||||||
class SequentialTaskRunner : TaskRunner {
|
|
||||||
|
|
||||||
override val listeners = mutableListOf<TaskRunner.Listener>()
|
|
||||||
|
|
||||||
override var activeTaskCount = 0
|
|
||||||
|
|
||||||
override fun runInBackground(task: () -> Unit) {
|
|
||||||
activeTaskCount += 1
|
|
||||||
for (l in listeners) l.onTaskStarted()
|
|
||||||
task()
|
|
||||||
activeTaskCount -= 1
|
|
||||||
for (l in listeners) l.onTaskFinished()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun runInForeground(task: () -> Unit) = runInBackground(task)
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
package org.isoron.platform.io
|
package org.isoron.platform.io
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*
|
||||||
|
|
||||||
interface PreparedStatement {
|
interface PreparedStatement {
|
||||||
fun step(): StepResult
|
fun step(): StepResult
|
||||||
fun finalize()
|
fun finalize()
|
||||||
@@ -63,7 +65,7 @@ fun Database.queryInt(sql: String): Int {
|
|||||||
|
|
||||||
fun Database.nextId(tableName: String): Int {
|
fun Database.nextId(tableName: String): Int {
|
||||||
val stmt = prepareStatement("select seq from sqlite_sequence where name='$tableName'")
|
val stmt = prepareStatement("select seq from sqlite_sequence where name='$tableName'")
|
||||||
if(stmt.step() == StepResult.ROW) {
|
if (stmt.step() == StepResult.ROW) {
|
||||||
val result = stmt.getInt(0)
|
val result = stmt.getInt(0)
|
||||||
stmt.finalize()
|
stmt.finalize()
|
||||||
return result + 1
|
return result + 1
|
||||||
@@ -80,7 +82,9 @@ fun Database.getVersion() = queryInt("pragma user_version")
|
|||||||
|
|
||||||
fun Database.setVersion(v: Int) = run("pragma user_version = $v")
|
fun Database.setVersion(v: Int) = run("pragma user_version = $v")
|
||||||
|
|
||||||
suspend fun Database.migrateTo(newVersion: Int, fileOpener: FileOpener, log: Log) {
|
suspend fun Database.migrateTo(newVersion: Int,
|
||||||
|
fileOpener: FileOpener,
|
||||||
|
log: Log) {
|
||||||
val currentVersion = getVersion()
|
val currentVersion = getVersion()
|
||||||
log.debug("Database", "Current database version: $currentVersion")
|
log.debug("Database", "Current database version: $currentVersion")
|
||||||
|
|
||||||
@@ -89,10 +93,10 @@ suspend fun Database.migrateTo(newVersion: Int, fileOpener: FileOpener, log: Log
|
|||||||
|
|
||||||
if (currentVersion > newVersion)
|
if (currentVersion > newVersion)
|
||||||
throw RuntimeException("database produced by future version of the application")
|
throw RuntimeException("database produced by future version of the application")
|
||||||
|
|
||||||
begin()
|
begin()
|
||||||
for (v in (currentVersion + 1)..newVersion) {
|
for (v in (currentVersion + 1)..newVersion) {
|
||||||
val sv = if(v < 10) "00$v" else if (v<100) "0$v" else "$v"
|
val sv = if (v < 10) "00$v" else if (v < 100) "0$v" else "$v"
|
||||||
val filename = "migrations/$sv.sql"
|
val filename = "migrations/$sv.sql"
|
||||||
val migrationFile = fileOpener.openResourceFile(filename)
|
val migrationFile = fileOpener.openResourceFile(filename)
|
||||||
for (line in migrationFile.lines()) {
|
for (line in migrationFile.lines()) {
|
||||||
|
|||||||
@@ -19,86 +19,118 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.backend
|
package org.isoron.uhabits.backend
|
||||||
|
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import org.isoron.platform.concurrency.*
|
import org.isoron.platform.concurrency.*
|
||||||
import org.isoron.platform.io.*
|
import org.isoron.platform.io.*
|
||||||
import org.isoron.uhabits.*
|
import org.isoron.uhabits.*
|
||||||
import org.isoron.uhabits.components.*
|
import org.isoron.uhabits.components.*
|
||||||
import org.isoron.uhabits.i18n.*
|
import org.isoron.uhabits.i18n.*
|
||||||
import org.isoron.uhabits.models.*
|
import org.isoron.uhabits.models.*
|
||||||
|
import kotlin.coroutines.*
|
||||||
|
|
||||||
class Backend(databaseName: String,
|
|
||||||
databaseOpener: DatabaseOpener,
|
open class BackendScope(private val ctx: CoroutineContext,
|
||||||
fileOpener: FileOpener,
|
private val log: Log) : CoroutineScope {
|
||||||
localeHelper: LocaleHelper,
|
|
||||||
|
private val job = Job()
|
||||||
|
|
||||||
|
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
|
log.info("Coroutine", throwable.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = ctx + job + exceptionHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
class Backend(private val databaseName: String,
|
||||||
|
private val databaseOpener: DatabaseOpener,
|
||||||
|
private val fileOpener: FileOpener,
|
||||||
|
private val localeHelper: LocaleHelper,
|
||||||
private val log: Log,
|
private val log: Log,
|
||||||
private val taskRunner: TaskRunner) {
|
private val crCtx: CoroutineContext
|
||||||
|
) : CoroutineScope by BackendScope(crCtx, log) {
|
||||||
|
|
||||||
// private val database: Database
|
|
||||||
//
|
|
||||||
// private val habitsRepository: HabitRepository
|
|
||||||
//
|
|
||||||
// private val checkmarkRepository: CheckmarkRepository
|
|
||||||
//
|
|
||||||
// private val habits = mutableMapOf<Int, Habit>()
|
|
||||||
//
|
|
||||||
// private val checkmarks = mutableMapOf<Habit, CheckmarkList>()
|
|
||||||
//
|
|
||||||
// private val scores = mutableMapOf<Habit, ScoreList>()
|
|
||||||
|
|
||||||
val mainScreenDataSource: MainScreenDataSource? = null
|
private lateinit var database: Database
|
||||||
val strings = localeHelper.getStringsForCurrentLocale()
|
private lateinit var habitsRepository: HabitRepository
|
||||||
val preferences: Preferences? = null
|
private lateinit var checkmarkRepository: CheckmarkRepository
|
||||||
|
lateinit var preferences: Preferences
|
||||||
|
|
||||||
|
lateinit var mainScreenDataSource: MainScreenDataSource
|
||||||
|
|
||||||
|
private val habits = mutableMapOf<Int, Habit>()
|
||||||
|
private val checkmarks = mutableMapOf<Habit, CheckmarkList>()
|
||||||
|
private val scores = mutableMapOf<Habit, ScoreList>()
|
||||||
|
|
||||||
|
var strings = localeHelper.getStringsForCurrentLocale()
|
||||||
var theme: Theme = LightTheme()
|
var theme: Theme = LightTheme()
|
||||||
|
|
||||||
init {
|
val observable = Observable<Listener>()
|
||||||
// val dbFile = fileOpener.openUserFile(databaseName)
|
|
||||||
// if (!dbFile.exists()) {
|
fun init() {
|
||||||
// val templateFile = fileOpener.openResourceFile("databases/template.db")
|
launch {
|
||||||
// templateFile.copyTo(dbFile)
|
initDatabase()
|
||||||
// }
|
initRepositories()
|
||||||
// database = databaseOpener.open(dbFile)
|
initDataSources()
|
||||||
// database.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
|
observable.notifyListeners { it.onReady() }
|
||||||
// preferences = Preferences(PreferencesRepository(database))
|
}
|
||||||
// habitsRepository = HabitRepository(database)
|
}
|
||||||
// checkmarkRepository = CheckmarkRepository(database)
|
|
||||||
// taskRunner.runInBackground {
|
private fun initRepositories() {
|
||||||
// habits.putAll(habitsRepository.findAll())
|
preferences = Preferences(PreferencesRepository(database))
|
||||||
// for ((key, habit) in habits) {
|
habitsRepository = HabitRepository(database)
|
||||||
// val checks = checkmarkRepository.findAll(key)
|
checkmarkRepository = CheckmarkRepository(database)
|
||||||
// checkmarks[habit] = CheckmarkList(habit.frequency, habit.type)
|
habits.putAll(habitsRepository.findAll())
|
||||||
// checkmarks[habit]?.setManualCheckmarks(checks)
|
log.info("Backend", "${habits.size} habits loaded")
|
||||||
// scores[habit] = ScoreList(checkmarks[habit]!!)
|
for ((key, habit) in habits) {
|
||||||
// }
|
val checks = checkmarkRepository.findAll(key)
|
||||||
// }
|
checkmarks[habit] = CheckmarkList(habit.frequency, habit.type)
|
||||||
// mainScreenDataSource = MainScreenDataSource(preferences,
|
checkmarks[habit]?.setManualCheckmarks(checks)
|
||||||
// habits,
|
scores[habit] = ScoreList(checkmarks[habit]!!)
|
||||||
// checkmarks,
|
}
|
||||||
// scores,
|
}
|
||||||
// taskRunner)
|
|
||||||
|
private fun initDataSources() {
|
||||||
|
mainScreenDataSource =
|
||||||
|
MainScreenDataSource(preferences, habits, checkmarks, scores)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun initDatabase() {
|
||||||
|
val dbFile = fileOpener.openUserFile(databaseName)
|
||||||
|
if (!dbFile.exists()) {
|
||||||
|
val templateFile = fileOpener.openResourceFile("databases/template.db")
|
||||||
|
templateFile.copyTo(dbFile)
|
||||||
|
}
|
||||||
|
database = databaseOpener.open(dbFile)
|
||||||
|
database.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createHabit(habit: Habit) {
|
fun createHabit(habit: Habit) {
|
||||||
// val id = habitsRepository.nextId()
|
val id = habitsRepository.nextId()
|
||||||
// habit.id = id
|
habit.id = id
|
||||||
// habit.position = habits.size
|
habit.position = habits.size
|
||||||
// habits[id] = habit
|
habits[id] = habit
|
||||||
// checkmarks[habit] = CheckmarkList(habit.frequency, habit.type)
|
checkmarks[habit] = CheckmarkList(habit.frequency, habit.type)
|
||||||
// habitsRepository.insert(habit)
|
habitsRepository.insert(habit)
|
||||||
// mainScreenDataSource.requestData()
|
mainScreenDataSource.requestData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteHabit(id: Int) {
|
fun deleteHabit(id: Int) {
|
||||||
// habits[id]?.let { habit ->
|
habits[id]?.let { habit ->
|
||||||
// habitsRepository.delete(habit)
|
habitsRepository.delete(habit)
|
||||||
// habits.remove(id)
|
habits.remove(id)
|
||||||
// mainScreenDataSource.requestData()
|
mainScreenDataSource.requestData()
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateHabit(modified: Habit) {
|
fun updateHabit(modified: Habit) {
|
||||||
// habits[modified.id]?.let { existing ->
|
habits[modified.id]?.let { existing ->
|
||||||
// modified.position = existing.position
|
modified.position = existing.position
|
||||||
// habitsRepository.update(modified)
|
habitsRepository.update(modified)
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onReady()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ import org.isoron.uhabits.models.Checkmark.Companion.UNCHECKED
|
|||||||
class MainScreenDataSource(val preferences: Preferences,
|
class MainScreenDataSource(val preferences: Preferences,
|
||||||
val habits: MutableMap<Int, Habit>,
|
val habits: MutableMap<Int, Habit>,
|
||||||
val checkmarks: MutableMap<Habit, CheckmarkList>,
|
val checkmarks: MutableMap<Habit, CheckmarkList>,
|
||||||
val scores: MutableMap<Habit, ScoreList>,
|
val scores: MutableMap<Habit, ScoreList>) {
|
||||||
val taskRunner: TaskRunner) {
|
|
||||||
|
|
||||||
val maxNumberOfButtons = 60
|
val maxNumberOfButtons = 60
|
||||||
private val today = LocalDate(2019, 3, 30) /* TODO */
|
private val today = LocalDate(2019, 3, 30) /* TODO */
|
||||||
@@ -44,36 +43,32 @@ class MainScreenDataSource(val preferences: Preferences,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun requestData() {
|
fun requestData() {
|
||||||
taskRunner.runInBackground {
|
var filtered = habits.values.toList()
|
||||||
var filtered = habits.values.toList()
|
|
||||||
|
|
||||||
if (!preferences.showArchived) {
|
if (!preferences.showArchived) {
|
||||||
filtered = filtered.filter { !it.isArchived }
|
filtered = filtered.filter { !it.isArchived }
|
||||||
}
|
}
|
||||||
|
|
||||||
val checkmarks = filtered.associate { habit ->
|
val checkmarks = filtered.associate { habit ->
|
||||||
val allValues = checkmarks.getValue(habit).getUntil(today)
|
val allValues = checkmarks.getValue(habit).getUntil(today)
|
||||||
if (allValues.size <= maxNumberOfButtons) habit to allValues
|
if (allValues.size <= maxNumberOfButtons) habit to allValues
|
||||||
else habit to allValues.subList(0, maxNumberOfButtons)
|
else habit to allValues.subList(0, maxNumberOfButtons)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preferences.showCompleted) {
|
if (!preferences.showCompleted) {
|
||||||
filtered = filtered.filter { habit ->
|
filtered = filtered.filter { habit ->
|
||||||
(habit.type == HabitType.BOOLEAN_HABIT && checkmarks.getValue(habit)[0].value == UNCHECKED) ||
|
(habit.type == HabitType.BOOLEAN_HABIT && checkmarks.getValue(habit)[0].value == UNCHECKED) ||
|
||||||
(habit.type == HabitType.NUMERICAL_HABIT && checkmarks.getValue(habit)[0].value * 1000 < habit.target)
|
(habit.type == HabitType.NUMERICAL_HABIT && checkmarks.getValue(habit)[0].value * 1000 < habit.target)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val scores = filtered.associate { habit ->
|
val scores = filtered.associate { habit ->
|
||||||
habit to scores[habit]!!.getAt(today)
|
habit to scores[habit]!!.getAt(today)
|
||||||
}
|
}
|
||||||
|
|
||||||
taskRunner.runInForeground {
|
observable.notifyListeners { listener ->
|
||||||
observable.notifyListeners { listener ->
|
val data = Data(filtered, scores, checkmarks)
|
||||||
val data = Data(filtered, scores, checkmarks)
|
listener.onDataChanged(data)
|
||||||
listener.onDataChanged(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,24 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron
|
|
||||||
|
|
||||||
val resolver = DependencyResolver()
|
|
||||||
|
|
||||||
open class BaseTest
|
|
||||||
@@ -19,14 +19,9 @@
|
|||||||
|
|
||||||
package org.isoron.platform.gui
|
package org.isoron.platform.gui
|
||||||
|
|
||||||
import org.isoron.*
|
class CanvasTest(val platform: Platform) {
|
||||||
import kotlin.test.*
|
fun run() {
|
||||||
|
val canvas = platform.createCanvas(500, 400)
|
||||||
class CanvasTest() : BaseTest() {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDrawing() {
|
|
||||||
val canvas = resolver.createCanvas(500, 400)
|
|
||||||
|
|
||||||
canvas.setColor(Color(0x303030))
|
canvas.setColor(Color(0x303030))
|
||||||
canvas.fillRect(0.0, 0.0, 500.0, 400.0)
|
canvas.fillRect(0.0, 0.0, 500.0, 400.0)
|
||||||
@@ -66,6 +61,11 @@ class CanvasTest() : BaseTest() {
|
|||||||
canvas.setFont(Font.FONT_AWESOME)
|
canvas.setFont(Font.FONT_AWESOME)
|
||||||
canvas.drawText(FontAwesome.CHECK, 250.0, 300.0)
|
canvas.drawText(FontAwesome.CHECK, 250.0, 300.0)
|
||||||
|
|
||||||
resolver.exportCanvas(canvas, "CanvasTest.png")
|
platform.exportCanvas(canvas, "CanvasTest.png")
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Platform {
|
||||||
|
fun createCanvas(width: Int, height: Int): Canvas
|
||||||
|
fun exportCanvas(canvas: Canvas, filename: String)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,12 +19,10 @@
|
|||||||
|
|
||||||
package org.isoron.platform.io
|
package org.isoron.platform.io
|
||||||
|
|
||||||
import org.isoron.*
|
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
class DatabaseTest() : BaseTest() {
|
class DatabaseTest(val db: Database) {
|
||||||
suspend fun testUsage() {
|
fun testUsage() {
|
||||||
val db = resolver.getDatabase()
|
|
||||||
db.setVersion(0)
|
db.setVersion(0)
|
||||||
assertEquals(0, db.getVersion())
|
assertEquals(0, db.getVersion())
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,8 @@ package org.isoron.platform.io
|
|||||||
import org.isoron.*
|
import org.isoron.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
class FilesTest() : BaseTest() {
|
class FilesTest(val fileOpener: FileOpener) {
|
||||||
suspend fun testLines() {
|
suspend fun testLines() {
|
||||||
val fileOpener = resolver.getFileOpener()
|
|
||||||
|
|
||||||
assertFalse(fileOpener.openUserFile("non-existing-usr.txt").exists(),
|
assertFalse(fileOpener.openUserFile("non-existing-usr.txt").exists(),
|
||||||
"non-existing-usr.txt shouldn't exist")
|
"non-existing-usr.txt shouldn't exist")
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,13 @@
|
|||||||
package org.isoron.uhabits.models
|
package org.isoron.uhabits.models
|
||||||
|
|
||||||
import org.isoron.*
|
import org.isoron.*
|
||||||
|
import org.isoron.platform.io.*
|
||||||
import org.isoron.platform.time.*
|
import org.isoron.platform.time.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
|
|
||||||
class CheckmarkRepositoryTest : BaseTest() {
|
class CheckmarkRepositoryTest(val db: Database) {
|
||||||
suspend fun testCRUD() {
|
fun testCRUD() {
|
||||||
val db = resolver.getDatabase()
|
|
||||||
val habitA = 10
|
val habitA = 10
|
||||||
var checkmarksA = listOf(Checkmark(LocalDate(2019, 1, 15), 100),
|
var checkmarksA = listOf(Checkmark(LocalDate(2019, 1, 15), 100),
|
||||||
Checkmark(LocalDate(2019, 1, 7), 500),
|
Checkmark(LocalDate(2019, 1, 7), 500),
|
||||||
|
|||||||
@@ -24,9 +24,8 @@ import org.isoron.platform.gui.*
|
|||||||
import org.isoron.platform.io.*
|
import org.isoron.platform.io.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
class HabitRepositoryTest() : BaseTest() {
|
class HabitRepositoryTest(val db: Database) {
|
||||||
suspend fun testCRUD() {
|
fun testCRUD() {
|
||||||
val db = resolver.getDatabase()
|
|
||||||
val original0 = Habit(id = 0,
|
val original0 = Habit(id = 0,
|
||||||
name = "Wake up early",
|
name = "Wake up early",
|
||||||
description = "Did you wake up before 6am?",
|
description = "Did you wake up before 6am?",
|
||||||
|
|||||||
@@ -19,14 +19,12 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models
|
package org.isoron.uhabits.models
|
||||||
|
|
||||||
import org.isoron.*
|
import org.isoron.platform.io.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
class PreferencesRepositoryTest : BaseTest() {
|
class PreferencesRepositoryTest(val db: Database) {
|
||||||
suspend fun testUsage() {
|
fun testUsage() {
|
||||||
val db = resolver.getDatabase()
|
|
||||||
val prefs = PreferencesRepository(db)
|
val prefs = PreferencesRepository(db)
|
||||||
|
|
||||||
assertEquals("default", prefs.getString("non_existing_key", "default"))
|
assertEquals("default", prefs.getString("non_existing_key", "default"))
|
||||||
prefs.putString("ringtone_path", "/tmp")
|
prefs.putString("ringtone_path", "/tmp")
|
||||||
assertEquals("/tmp", prefs.getString("ringtone_path", "none"))
|
assertEquals("/tmp", prefs.getString("ringtone_path", "none"))
|
||||||
|
|||||||
@@ -17,14 +17,17 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.isoron
|
package org.isoron.platform.concurrency
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
import kotlinx.coroutines.*
|
||||||
import org.isoron.platform.io.*
|
import platform.darwin.*
|
||||||
|
import kotlin.coroutines.*
|
||||||
|
|
||||||
expect class DependencyResolver() {
|
class UIDispatcher : CoroutineDispatcher() {
|
||||||
suspend fun getFileOpener(): FileOpener
|
override fun dispatch(context: CoroutineContext, block: Runnable) {
|
||||||
suspend fun getDatabase(): Database
|
val queue = dispatch_get_main_queue()
|
||||||
fun createCanvas(width: Int, height: Int): Canvas
|
dispatch_async(queue) {
|
||||||
fun exportCanvas(canvas: Canvas, filename: String)
|
block.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,50 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@file:Suppress("UNCHECKED_CAST")
|
|
||||||
|
|
||||||
package org.isoron
|
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
import org.isoron.platform.io.*
|
|
||||||
import platform.CoreGraphics.*
|
|
||||||
import platform.Foundation.*
|
|
||||||
import platform.UIKit.*
|
|
||||||
|
|
||||||
actual class DependencyResolver {
|
|
||||||
actual suspend fun getFileOpener(): FileOpener {
|
|
||||||
return IosFileOpener()
|
|
||||||
}
|
|
||||||
|
|
||||||
actual suspend fun getDatabase(): Database = TODO()
|
|
||||||
|
|
||||||
actual fun createCanvas(width: Int, height: Int): Canvas {
|
|
||||||
UIGraphicsBeginImageContext(CGSizeMake(width=500.0, height=600.0))
|
|
||||||
return IosCanvas()
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun exportCanvas(canvas: Canvas, filename: String): Unit {
|
|
||||||
val image = UIGraphicsGetImageFromCurrentImageContext()!!
|
|
||||||
val manager = NSFileManager.defaultManager
|
|
||||||
val paths = manager.URLsForDirectory(NSDocumentDirectory, NSUserDomainMask) as List<NSURL>
|
|
||||||
val filePath = paths.first().URLByAppendingPathComponent("IosCanvasTest.png")!!.path!!
|
|
||||||
val data = UIImagePNGRepresentation(image)!!
|
|
||||||
data.writeToFile(filePath, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,22 +21,11 @@ package org.isoron
|
|||||||
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.isoron.platform.io.*
|
import org.isoron.platform.io.*
|
||||||
import org.isoron.uhabits.models.*
|
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
class IosAsyncTests {
|
class IosAsyncTests {
|
||||||
@Test
|
@Test
|
||||||
fun testFiles() = runBlocking { FilesTest().testLines() }
|
fun testFiles() = runBlocking {
|
||||||
|
FilesTest(IosFileOpener()).testLines()
|
||||||
// @Test
|
}
|
||||||
// fun testDatabase() = runBlocking { DatabaseTest().testUsage() }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// fun testCheckmarkRepository() = runBlocking { CheckmarkRepositoryTest().testCRUD() }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// fun testHabitRepository() = runBlocking { HabitRepositoryTest().testCRUD() }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// fun testPreferencesRepository() = runBlocking { PreferencesRepositoryTest().testUsage() }
|
|
||||||
}
|
}
|
||||||
@@ -1,42 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import org.isoron.platform.io.*
|
|
||||||
import org.isoron.uhabits.models.*
|
|
||||||
import kotlin.test.*
|
|
||||||
|
|
||||||
class JsAsyncTests {
|
|
||||||
@Test
|
|
||||||
fun testFiles() = GlobalScope.promise { FilesTest().testLines() }
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDatabase() = GlobalScope.promise { DatabaseTest().testUsage() }
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckmarkRepository() = GlobalScope.promise { CheckmarkRepositoryTest().testCRUD() }
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testHabitRepository() = GlobalScope.promise { HabitRepositoryTest().testCRUD() }
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testPreferencesRepository() = GlobalScope.promise { PreferencesRepositoryTest().testUsage() }
|
|
||||||
}
|
|
||||||
70
core/src/jsTest/kotlin/org/isoron/platform/JsAsyncTests.kt
Normal file
70
core/src/jsTest/kotlin/org/isoron/platform/JsAsyncTests.kt
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.isoron.platform.io.*
|
||||||
|
import org.isoron.uhabits.*
|
||||||
|
import org.isoron.uhabits.models.*
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class JsAsyncTests {
|
||||||
|
var fs: JsFileStorage? = null
|
||||||
|
|
||||||
|
suspend fun getFileOpener(): FileOpener {
|
||||||
|
if (fs == null) {
|
||||||
|
fs = JsFileStorage()
|
||||||
|
fs?.init()
|
||||||
|
}
|
||||||
|
return JsFileOpener(fs!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getDatabase(): Database {
|
||||||
|
val nativeDB = eval("new SQL.Database()")
|
||||||
|
val db = JsDatabase(nativeDB)
|
||||||
|
db.migrateTo(LOOP_DATABASE_VERSION, getFileOpener(), StandardLog())
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFiles() = GlobalScope.promise {
|
||||||
|
FilesTest(getFileOpener()).testLines()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDatabase() = GlobalScope.promise {
|
||||||
|
DatabaseTest(getDatabase()).testUsage()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCheckmarkRepository() = GlobalScope.promise {
|
||||||
|
CheckmarkRepositoryTest(getDatabase()).testCRUD()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testHabitRepository() = GlobalScope.promise {
|
||||||
|
HabitRepositoryTest(getDatabase()).testCRUD()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPreferencesRepository() = GlobalScope.promise {
|
||||||
|
PreferencesRepositoryTest(getDatabase()).testUsage()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,41 +17,25 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.isoron
|
package org.isoron.platform
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
import org.isoron.platform.gui.*
|
||||||
import org.isoron.platform.io.*
|
|
||||||
import org.isoron.uhabits.*
|
|
||||||
import org.w3c.dom.*
|
import org.w3c.dom.*
|
||||||
import kotlin.browser.*
|
import kotlin.browser.*
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
actual class DependencyResolver {
|
class JsCanvasTest : CanvasTest.Platform {
|
||||||
|
override fun createCanvas(width: Int, height: Int): Canvas {
|
||||||
var fs: JsFileStorage? = null
|
|
||||||
|
|
||||||
actual suspend fun getFileOpener(): FileOpener {
|
|
||||||
if (fs == null) {
|
|
||||||
fs = JsFileStorage()
|
|
||||||
fs?.init()
|
|
||||||
}
|
|
||||||
return JsFileOpener(fs!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual suspend fun getDatabase(): Database {
|
|
||||||
val nativeDB = eval("new SQL.Database()")
|
|
||||||
val db = JsDatabase(nativeDB)
|
|
||||||
db.migrateTo(LOOP_DATABASE_VERSION, getFileOpener(), StandardLog())
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun createCanvas(width: Int, height: Int): Canvas {
|
|
||||||
val canvasElement = document.getElementById("canvas") as HTMLCanvasElement
|
val canvasElement = document.getElementById("canvas") as HTMLCanvasElement
|
||||||
canvasElement.style.width = "${width}px"
|
canvasElement.style.width = "${width}px"
|
||||||
canvasElement.style.height = "${height}px"
|
canvasElement.style.height = "${height}px"
|
||||||
return HtmlCanvas(canvasElement)
|
return HtmlCanvas(canvasElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun exportCanvas(canvas: Canvas, filename: String) {
|
override fun exportCanvas(canvas: Canvas, filename: String) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDrawing() = CanvasTest(this).run()
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ class JavaDatabase(private var conn: Connection,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class JavaDatabaseOpener(private val log: Log) : DatabaseOpener {
|
class JavaDatabaseOpener(val log: Log) : DatabaseOpener {
|
||||||
override fun open(file: UserFile): Database {
|
override fun open(file: UserFile): Database {
|
||||||
val platformFile = file as JavaUserFile
|
val platformFile = file as JavaUserFile
|
||||||
val conn = DriverManager.getConnection("jdbc:sqlite:${platformFile.path}")
|
val conn = DriverManager.getConnection("jdbc:sqlite:${platformFile.path}")
|
||||||
|
|||||||
@@ -21,22 +21,46 @@ package org.isoron
|
|||||||
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.isoron.platform.io.*
|
import org.isoron.platform.io.*
|
||||||
|
import org.isoron.uhabits.*
|
||||||
import org.isoron.uhabits.models.*
|
import org.isoron.uhabits.models.*
|
||||||
import org.junit.*
|
import org.junit.*
|
||||||
|
|
||||||
class JavaAsyncTests {
|
class JavaAsyncTests {
|
||||||
@Test
|
|
||||||
fun testFiles() = runBlocking { FilesTest().testLines() }
|
val log = StandardLog()
|
||||||
|
val fileOpener = JavaFileOpener()
|
||||||
|
val databaseOpener = JavaDatabaseOpener(log)
|
||||||
|
|
||||||
|
suspend fun getDatabase(): Database {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDatabase() = runBlocking { DatabaseTest().testUsage() }
|
fun testFiles() = runBlocking {
|
||||||
|
FilesTest(fileOpener).testLines()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCheckmarkRepository() = runBlocking { CheckmarkRepositoryTest().testCRUD() }
|
fun testDatabase() = runBlocking {
|
||||||
|
DatabaseTest(getDatabase()).testUsage()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testHabitRepository() = runBlocking { HabitRepositoryTest().testCRUD() }
|
fun testCheckmarkRepository() = runBlocking {
|
||||||
|
CheckmarkRepositoryTest(getDatabase()).testCRUD()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testPreferencesRepository() = runBlocking { PreferencesRepositoryTest().testUsage() }
|
fun testHabitRepository() = runBlocking {
|
||||||
|
HabitRepositoryTest(getDatabase()).testCRUD()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPreferencesRepository() = runBlocking {
|
||||||
|
PreferencesRepositoryTest(getDatabase()).testUsage()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -17,39 +17,25 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.isoron
|
package org.isoron.platform
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import org.isoron.platform.gui.*
|
import org.isoron.platform.gui.*
|
||||||
import org.isoron.platform.io.*
|
import org.junit.*
|
||||||
import org.isoron.uhabits.*
|
|
||||||
import java.awt.image.*
|
import java.awt.image.*
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import javax.imageio.*
|
import javax.imageio.*
|
||||||
|
|
||||||
actual class DependencyResolver actual constructor() {
|
class JavaCanvasTest : CanvasTest.Platform {
|
||||||
|
override fun createCanvas(width: Int, height: Int): Canvas {
|
||||||
val log = StandardLog()
|
|
||||||
val fileOpener = JavaFileOpener()
|
|
||||||
val databaseOpener = JavaDatabaseOpener(log)
|
|
||||||
|
|
||||||
actual suspend fun getFileOpener(): FileOpener = fileOpener
|
|
||||||
|
|
||||||
actual suspend fun getDatabase(): Database {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun createCanvas(width: Int, height: Int): Canvas {
|
|
||||||
val image = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
|
val image = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
|
||||||
return JavaCanvas(image, pixelScale = 1.0)
|
return JavaCanvas(image, pixelScale = 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun exportCanvas(canvas: Canvas, filename: String) {
|
override fun exportCanvas(canvas: Canvas, filename: String) {
|
||||||
val javaCanvas = canvas as JavaCanvas
|
val javaCanvas = canvas as JavaCanvas
|
||||||
ImageIO.write(javaCanvas.image, "png", File("/tmp/$filename"))
|
ImageIO.write(javaCanvas.image, "png", File("/tmp/$filename"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDrawing() = CanvasTest(this).run()
|
||||||
}
|
}
|
||||||
@@ -19,27 +19,36 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {
|
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, BackendListener {
|
||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
var backend = Backend(databaseName: "dev.db",
|
var nav: UINavigationController?
|
||||||
databaseOpener: IosDatabaseOpener(withLog: StandardLog()),
|
let log = StandardLog()
|
||||||
fileOpener: IosFileOpener(),
|
var backend: Backend?
|
||||||
localeHelper: IosLocaleHelper(NSLocale.preferredLanguages),
|
|
||||||
log: StandardLog(),
|
|
||||||
taskRunner: SequentialTaskRunner())
|
|
||||||
|
|
||||||
func application(_ application: UIApplication,
|
func application(_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
|
||||||
|
backend = Backend(databaseName: "uhabits.db",
|
||||||
|
databaseOpener: IosDatabaseOpener(withLog: log),
|
||||||
|
fileOpener: IosFileOpener(),
|
||||||
|
localeHelper: IosLocaleHelper(NSLocale.preferredLanguages),
|
||||||
|
log: log,
|
||||||
|
crCtx: UIDispatcher())
|
||||||
|
|
||||||
|
backend?.observable.addListener(listener: self)
|
||||||
|
backend?.doInit()
|
||||||
|
|
||||||
window = UIWindow(frame: UIScreen.main.bounds)
|
window = UIWindow(frame: UIScreen.main.bounds)
|
||||||
if let window = window {
|
nav = UINavigationController()
|
||||||
let nav = UINavigationController()
|
window?.backgroundColor = UIColor.white
|
||||||
nav.viewControllers = [MainScreenController(withBackend: backend)]
|
window?.rootViewController = nav
|
||||||
window.backgroundColor = UIColor.white
|
window?.makeKeyAndVisible()
|
||||||
window.rootViewController = nav
|
|
||||||
window.makeKeyAndVisible()
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func onReady() {
|
||||||
|
nav?.viewControllers = [MainScreenController(withBackend: backend!)]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class MainScreenController: UITableViewController, MainScreenDataSourceListener
|
|||||||
var preferences: Preferences
|
var preferences: Preferences
|
||||||
var theme: Theme
|
var theme: Theme
|
||||||
var nButtons = 3
|
var nButtons = 3
|
||||||
var strings: Strings
|
var strings = Strings()
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError()
|
fatalError()
|
||||||
@@ -109,9 +109,9 @@ class MainScreenController: UITableViewController, MainScreenDataSourceListener
|
|||||||
init(withBackend backend:Backend) {
|
init(withBackend backend:Backend) {
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
self.strings = backend.strings
|
self.strings = backend.strings
|
||||||
self.dataSource = backend.mainScreenDataSource!
|
self.dataSource = backend.mainScreenDataSource
|
||||||
self.theme = backend.theme
|
self.theme = backend.theme
|
||||||
self.preferences = backend.preferences!
|
self.preferences = backend.preferences
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
self.dataSource.observable.addListener(listener: self)
|
self.dataSource.observable.addListener(listener: self)
|
||||||
self.dataSource.requestData()
|
self.dataSource.requestData()
|
||||||
@@ -189,7 +189,7 @@ class MainScreenController: UITableViewController, MainScreenDataSourceListener
|
|||||||
|
|
||||||
@objc func onMoreActionsClicked() {
|
@objc func onMoreActionsClicked() {
|
||||||
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||||
|
|
||||||
if preferences.showArchived {
|
if preferences.showArchived {
|
||||||
alert.addAction(UIAlertAction(title: strings.hide_archived, style: .default) {
|
alert.addAction(UIAlertAction(title: strings.hide_archived, style: .default) {
|
||||||
(action: UIAlertAction) -> Void in
|
(action: UIAlertAction) -> Void in
|
||||||
@@ -203,7 +203,7 @@ class MainScreenController: UITableViewController, MainScreenDataSourceListener
|
|||||||
self.dataSource.requestData()
|
self.dataSource.requestData()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if preferences.showCompleted {
|
if preferences.showCompleted {
|
||||||
alert.addAction(UIAlertAction(title: strings.hide_completed, style: .default) {
|
alert.addAction(UIAlertAction(title: strings.hide_completed, style: .default) {
|
||||||
(action: UIAlertAction) -> Void in
|
(action: UIAlertAction) -> Void in
|
||||||
@@ -217,7 +217,7 @@ class MainScreenController: UITableViewController, MainScreenDataSourceListener
|
|||||||
self.dataSource.requestData()
|
self.dataSource.requestData()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if preferences.nightMode {
|
if preferences.nightMode {
|
||||||
alert.addAction(UIAlertAction(title: strings.day_mode, style: .default) {
|
alert.addAction(UIAlertAction(title: strings.day_mode, style: .default) {
|
||||||
(action: UIAlertAction) -> Void in
|
(action: UIAlertAction) -> Void in
|
||||||
@@ -229,7 +229,7 @@ class MainScreenController: UITableViewController, MainScreenDataSourceListener
|
|||||||
self.preferences.nightMode = true
|
self.preferences.nightMode = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
alert.addAction(UIAlertAction(title: strings.help, style: .default) {
|
alert.addAction(UIAlertAction(title: strings.help, style: .default) {
|
||||||
(action: UIAlertAction) -> Void in
|
(action: UIAlertAction) -> Void in
|
||||||
if let link = URL(string: "http://loophabits.org/faq") {
|
if let link = URL(string: "http://loophabits.org/faq") {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class IosDatabaseOpener : NSObject, DatabaseOpener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func open(file: UserFile) -> Database {
|
func open(file: UserFile) -> Database {
|
||||||
let dbPath = (file as! IosUserFile).path
|
let dbPath = (file as! IosFile).path
|
||||||
|
|
||||||
let version = String(cString: sqlite3_libversion())
|
let version = String(cString: sqlite3_libversion())
|
||||||
log.info(tag: "IosDatabaseOpener", msg: "SQLite \(version)")
|
log.info(tag: "IosDatabaseOpener", msg: "SQLite \(version)")
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ class IosDatabaseTest: XCTestCase {
|
|||||||
let fileOpener = IosFileOpener()
|
let fileOpener = IosFileOpener()
|
||||||
|
|
||||||
let dbFile = fileOpener.openUserFile(path: "test.sqlite3")
|
let dbFile = fileOpener.openUserFile(path: "test.sqlite3")
|
||||||
if dbFile.exists() {
|
|
||||||
dbFile.delete()
|
|
||||||
}
|
|
||||||
let db = databaseOpener.open(file: dbFile)
|
let db = databaseOpener.open(file: dbFile)
|
||||||
|
|
||||||
var stmt = db.prepareStatement(sql: "drop table if exists demo")
|
var stmt = db.prepareStatement(sql: "drop table if exists demo")
|
||||||
@@ -68,6 +65,5 @@ class IosDatabaseTest: XCTestCase {
|
|||||||
stmt.finalize()
|
stmt.finalize()
|
||||||
|
|
||||||
db.close()
|
db.close()
|
||||||
dbFile.delete()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
001592642260AE0F00D2814F /* main.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C0C6C92246E543003D8AF0 /* main.framework */; };
|
||||||
006EFE4E2252EA2B008464E0 /* IosLocale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006EFE4D2252EA2B008464E0 /* IosLocale.swift */; };
|
006EFE4E2252EA2B008464E0 /* IosLocale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006EFE4D2252EA2B008464E0 /* IosLocale.swift */; };
|
||||||
006EFE50225432B8008464E0 /* AboutScreenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006EFE4F225432B8008464E0 /* AboutScreenController.swift */; };
|
006EFE50225432B8008464E0 /* AboutScreenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006EFE4F225432B8008464E0 /* AboutScreenController.swift */; };
|
||||||
00A5B42822009F590024E00C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42722009F590024E00C /* AppDelegate.swift */; };
|
00A5B42822009F590024E00C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42722009F590024E00C /* AppDelegate.swift */; };
|
||||||
@@ -18,7 +19,6 @@
|
|||||||
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 */; };
|
||||||
00C0C6CA2246E543003D8AF0 /* main.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C0C6C92246E543003D8AF0 /* main.framework */; };
|
00C0C6CA2246E543003D8AF0 /* main.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C0C6C92246E543003D8AF0 /* main.framework */; };
|
||||||
00C0C6CB2246E543003D8AF0 /* main.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C0C6C92246E543003D8AF0 /* main.framework */; };
|
|
||||||
00C0C6CC2246E550003D8AF0 /* main.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 00C0C6C92246E543003D8AF0 /* main.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
00C0C6CC2246E550003D8AF0 /* main.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 00C0C6C92246E543003D8AF0 /* main.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
00C0C6CE2246EFB3003D8AF0 /* IosExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6CD2246EFB3003D8AF0 /* IosExtensions.swift */; };
|
00C0C6CE2246EFB3003D8AF0 /* IosExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6CD2246EFB3003D8AF0 /* IosExtensions.swift */; };
|
||||||
00C0C6D122470705003D8AF0 /* IosCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6D022470705003D8AF0 /* IosCanvas.swift */; };
|
00C0C6D122470705003D8AF0 /* IosCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6D022470705003D8AF0 /* IosCanvas.swift */; };
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
00C0C6CB2246E543003D8AF0 /* main.framework in Frameworks */,
|
001592642260AE0F00D2814F /* main.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -498,6 +498,7 @@
|
|||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = Application/BridgingHeader.h;
|
SWIFT_OBJC_BRIDGING_HEADER = Application/BridgingHeader.h;
|
||||||
|
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 4.2;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
@@ -525,6 +526,7 @@
|
|||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = Application/BridgingHeader.h;
|
SWIFT_OBJC_BRIDGING_HEADER = Application/BridgingHeader.h;
|
||||||
|
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 4.2;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
@@ -547,6 +549,8 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.isoron.uhabitsTests;
|
PRODUCT_BUNDLE_IDENTIFIER = org.isoron.uhabitsTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||||
|
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 4.2;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/uhabits.app/uhabits";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/uhabits.app/uhabits";
|
||||||
@@ -570,6 +574,8 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.isoron.uhabitsTests;
|
PRODUCT_BUNDLE_IDENTIFIER = org.isoron.uhabitsTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||||
|
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 4.2;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/uhabits.app/uhabits";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/uhabits.app/uhabits";
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<canvas id="canvas" width=500 height=400 style="display: none"></canvas>
|
||||||
<div id="mocha"></div>
|
<div id="mocha"></div>
|
||||||
<canvas id="canvas" style="width: 500px; height: 400px; display: none;"></canvas>
|
|
||||||
<script src="../lib/mocha.js"></script>
|
<script src="../lib/mocha.js"></script>
|
||||||
<script>mocha.setup('bdd')</script>
|
<script>mocha.setup('bdd')</script>
|
||||||
<script src="../test.js"></script>
|
<script src="../test.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user