mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Update
This commit is contained in:
BIN
core/assets/main/databases/template.db
Normal file
BIN
core/assets/main/databases/template.db
Normal file
Binary file not shown.
@@ -19,26 +19,27 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits
|
package org.isoron.uhabits
|
||||||
|
|
||||||
import org.isoron.uhabits.models.HabitList
|
import org.isoron.uhabits.models.*
|
||||||
import org.isoron.uhabits.utils.*
|
import org.isoron.uhabits.utils.*
|
||||||
|
|
||||||
class Backend(var databaseOpener: DatabaseOpener,
|
class Backend(var databaseOpener: DatabaseOpener,
|
||||||
var fileOpener: FileOpener,
|
var fileOpener: FileOpener,
|
||||||
var log: Log) {
|
var log: Log) {
|
||||||
|
|
||||||
var db: Database
|
var database: Database
|
||||||
|
var habitsRepository: HabitRepository
|
||||||
var habits: HabitList
|
var habits = mutableMapOf<Int, Habit>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val dbFile = fileOpener.openUserFile("uhabits.sqlite3")
|
val dbFile = fileOpener.openUserFile("uhabits.db")
|
||||||
db = databaseOpener.open(dbFile)
|
database = databaseOpener.open(dbFile)
|
||||||
db.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
|
database.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
|
||||||
habits = HabitList(db)
|
habitsRepository = HabitRepository(database)
|
||||||
|
habits = habitsRepository.findAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getHabitList(): List<Map<String, *>> {
|
fun getHabitList(): List<Map<String, *>> {
|
||||||
return habits.getActive().map { h ->
|
return habits.values.sortedBy { h -> h.position }.map { h ->
|
||||||
mapOf("key" to h.id.toString(),
|
mapOf("key" to h.id.toString(),
|
||||||
"name" to h.name,
|
"name" to h.name,
|
||||||
"color" to h.color.paletteIndex)
|
"color" to h.color.paletteIndex)
|
||||||
@@ -46,11 +47,30 @@ class Backend(var databaseOpener: DatabaseOpener,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createHabit(name: String) {
|
fun createHabit(name: String) {
|
||||||
|
val id = habitsRepository.nextId()
|
||||||
|
val habit = Habit(id = id,
|
||||||
|
name = name,
|
||||||
|
description = "",
|
||||||
|
frequency = Frequency(1, 1),
|
||||||
|
color = Color(3),
|
||||||
|
isArchived = false,
|
||||||
|
position = habits.size,
|
||||||
|
unit = "",
|
||||||
|
target = 0.0,
|
||||||
|
type = HabitType.YES_NO_HABIT)
|
||||||
|
habitsRepository.insert(habit)
|
||||||
|
habits[id] = habit
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteHabit(id: Int) {
|
fun deleteHabit(id: Int) {
|
||||||
|
val habit = habits[id]!!
|
||||||
|
habitsRepository.delete(habit)
|
||||||
|
habits.remove(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateHabit(id: Int, name: String) {
|
fun updateHabit(id: Int, name: String) {
|
||||||
|
val habit = habits[id]!!
|
||||||
|
habit.name = name
|
||||||
|
habitsRepository.update(habit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,4 +19,4 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models
|
package org.isoron.uhabits.models
|
||||||
|
|
||||||
class Color(val paletteIndex: Int)
|
data class Color(val paletteIndex: Int)
|
||||||
@@ -27,5 +27,5 @@ data class Habit(var id: Int,
|
|||||||
var isArchived: Boolean,
|
var isArchived: Boolean,
|
||||||
var position: Int,
|
var position: Int,
|
||||||
var unit: String,
|
var unit: String,
|
||||||
var target: Int,
|
var target: Double,
|
||||||
var type: HabitType)
|
var type: HabitType)
|
||||||
@@ -1,57 +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.uhabits.models
|
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.Database
|
|
||||||
import org.isoron.uhabits.utils.StepResult
|
|
||||||
|
|
||||||
class HabitList(var db: Database) {
|
|
||||||
var habits = mutableListOf<Habit>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
loadHabitsFromDatabase()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getActive(): List<Habit> {
|
|
||||||
return habits.filter { h -> !h.isArchived }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadHabitsFromDatabase() {
|
|
||||||
val stmt = db.prepareStatement(
|
|
||||||
"select id, name, description, freq_num, freq_den, color, " +
|
|
||||||
"archived, position, unit, target_value, type " +
|
|
||||||
"from habits")
|
|
||||||
|
|
||||||
while (stmt.step() == StepResult.ROW) {
|
|
||||||
habits.add(Habit(id = stmt.getInt(0),
|
|
||||||
name = stmt.getText(1),
|
|
||||||
description = stmt.getText(2),
|
|
||||||
frequency = Frequency(stmt.getInt(3),
|
|
||||||
stmt.getInt(4)),
|
|
||||||
color = Color(stmt.getInt(5)),
|
|
||||||
isArchived = (stmt.getInt(6) != 0),
|
|
||||||
position = stmt.getInt(7),
|
|
||||||
unit = stmt.getText(8),
|
|
||||||
target = 0,
|
|
||||||
type = HabitType.YES_NO_HABIT))
|
|
||||||
}
|
|
||||||
stmt.finalize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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.uhabits.models
|
||||||
|
|
||||||
|
import org.isoron.uhabits.utils.Database
|
||||||
|
import org.isoron.uhabits.utils.PreparedStatement
|
||||||
|
import org.isoron.uhabits.utils.StepResult
|
||||||
|
import org.isoron.uhabits.utils.nextId
|
||||||
|
|
||||||
|
class HabitRepository(var db: Database) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SELECT_COLUMNS = "id, name, description, freq_num, freq_den, color, archived, position, unit, target_value, type"
|
||||||
|
const val SELECT_PLACEHOLDERS = "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?"
|
||||||
|
const val UPDATE_COLUMNS = "id=?, name=?, description=?, freq_num=?, freq_den=?, color=?, archived=?, position=?, unit=?, target_value=?, type=?"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val findAllStatement = db.prepareStatement("select $SELECT_COLUMNS from habits order by position")
|
||||||
|
private val insertStatement = db.prepareStatement("insert into Habits($SELECT_COLUMNS) values ($SELECT_PLACEHOLDERS)")
|
||||||
|
private val updateStatement = db.prepareStatement("update Habits set $UPDATE_COLUMNS where id=?")
|
||||||
|
private val deleteStatement = db.prepareStatement("delete from Habits where id=?")
|
||||||
|
|
||||||
|
fun nextId(): Int {
|
||||||
|
return db.nextId("Habits")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findAll(): MutableMap<Int, Habit> {
|
||||||
|
val result = mutableMapOf<Int, Habit>()
|
||||||
|
while (findAllStatement.step() == StepResult.ROW) {
|
||||||
|
val habit = buildHabitFromStatement(findAllStatement)
|
||||||
|
result[habit.id] = habit
|
||||||
|
}
|
||||||
|
findAllStatement.reset()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insert(habit: Habit) {
|
||||||
|
bindHabitToStatement(habit, insertStatement)
|
||||||
|
insertStatement.step()
|
||||||
|
insertStatement.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(habit: Habit) {
|
||||||
|
bindHabitToStatement(habit, updateStatement)
|
||||||
|
updateStatement.bindInt(11, habit.id)
|
||||||
|
updateStatement.step()
|
||||||
|
updateStatement.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildHabitFromStatement(stmt: PreparedStatement): Habit {
|
||||||
|
return Habit(id = stmt.getInt(0),
|
||||||
|
name = stmt.getText(1),
|
||||||
|
description = stmt.getText(2),
|
||||||
|
frequency = Frequency(stmt.getInt(3), stmt.getInt(4)),
|
||||||
|
color = Color(stmt.getInt(5)),
|
||||||
|
isArchived = stmt.getInt(6) != 0,
|
||||||
|
position = stmt.getInt(7),
|
||||||
|
unit = stmt.getText(8),
|
||||||
|
target = stmt.getReal(9),
|
||||||
|
type = if (stmt.getInt(10) == 0) HabitType.YES_NO_HABIT else HabitType.NUMERICAL_HABIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindHabitToStatement(habit: Habit, statement: PreparedStatement) {
|
||||||
|
statement.bindInt(0, habit.id)
|
||||||
|
statement.bindText(1, habit.name)
|
||||||
|
statement.bindText(2, habit.description)
|
||||||
|
statement.bindInt(3, habit.frequency.numerator)
|
||||||
|
statement.bindInt(4, habit.frequency.denominator)
|
||||||
|
statement.bindInt(5, habit.color.paletteIndex)
|
||||||
|
statement.bindInt(6, if (habit.isArchived) 1 else 0)
|
||||||
|
statement.bindInt(7, habit.position)
|
||||||
|
statement.bindText(8, habit.unit)
|
||||||
|
statement.bindReal(9, habit.target)
|
||||||
|
statement.bindInt(10, habit.type.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(habit: Habit) {
|
||||||
|
deleteStatement.bindInt(0, habit.id)
|
||||||
|
deleteStatement.step()
|
||||||
|
deleteStatement.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models
|
package org.isoron.uhabits.models
|
||||||
|
|
||||||
enum class HabitType {
|
enum class HabitType(val code: Int) {
|
||||||
YES_NO_HABIT,
|
YES_NO_HABIT(0),
|
||||||
NUMERICAL_HABIT,
|
NUMERICAL_HABIT(1),
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,10 @@ interface PreparedStatement {
|
|||||||
fun finalize()
|
fun finalize()
|
||||||
fun getInt(index: Int): Int
|
fun getInt(index: Int): Int
|
||||||
fun getText(index: Int): String
|
fun getText(index: Int): String
|
||||||
|
fun getReal(index: Int): Double
|
||||||
fun bindInt(index: Int, value: Int)
|
fun bindInt(index: Int, value: Int)
|
||||||
fun bindText(index: Int, value: String)
|
fun bindText(index: Int, value: String)
|
||||||
|
fun bindReal(index: Int, value: Double)
|
||||||
fun reset()
|
fun reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,25 +59,38 @@ fun Database.queryInt(sql: String): Int {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Database.nextId(tableName: String): Int {
|
||||||
|
val stmt = prepareStatement("select seq from sqlite_sequence where name='$tableName'")
|
||||||
|
if(stmt.step() == StepResult.ROW) {
|
||||||
|
val result = stmt.getInt(0)
|
||||||
|
stmt.finalize()
|
||||||
|
return result + 1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Database.begin() = execute("begin")
|
fun Database.begin() = execute("begin")
|
||||||
|
|
||||||
fun Database.commit() = execute("commit")
|
fun Database.commit() = execute("commit")
|
||||||
fun Database.rollback() = execute("rollback")
|
|
||||||
fun Database.getVersion() = queryInt("pragma user_version")
|
fun Database.getVersion() = queryInt("pragma user_version")
|
||||||
|
|
||||||
fun Database.setVersion(v: Int) = execute("pragma user_version = $v")
|
fun Database.setVersion(v: Int) = execute("pragma user_version = $v")
|
||||||
|
|
||||||
fun Database.migrateTo(newVersion: Int, fileOpener: FileOpener, log: Log) {
|
fun Database.migrateTo(newVersion: Int, fileOpener: FileOpener, log: Log) {
|
||||||
val currentVersion = getVersion()
|
val currentVersion = getVersion()
|
||||||
log.debug("Current database version: $currentVersion")
|
log.debug("Database", "Current database version: $currentVersion")
|
||||||
|
|
||||||
if (currentVersion == newVersion) return
|
if (currentVersion == newVersion) return
|
||||||
log.debug("Upgrading to version: $newVersion")
|
log.debug("Database", "Upgrading to version: $newVersion")
|
||||||
|
|
||||||
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) {
|
||||||
log.debug("Running migration $v")
|
log.debug("Database", "Running migration $v")
|
||||||
val filename = sprintf("migrations/%03d.sql", v)
|
val filename = sprintf("migrations/%03d.sql", v)
|
||||||
val migrationFile = fileOpener.openResourceFile(filename)
|
val migrationFile = fileOpener.openResourceFile(filename)
|
||||||
for (line in migrationFile.readLines()) {
|
for (line in migrationFile.readLines()) {
|
||||||
|
|||||||
@@ -20,6 +20,20 @@
|
|||||||
package org.isoron.uhabits.utils
|
package org.isoron.uhabits.utils
|
||||||
|
|
||||||
interface Log {
|
interface Log {
|
||||||
fun info(msg: String)
|
fun info(tag: String, msg: String)
|
||||||
fun debug(msg: String)
|
fun debug(tag: String, msg: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Log that prints to the standard output.
|
||||||
|
*/
|
||||||
|
class StandardLog : Log {
|
||||||
|
override fun info(tag: String, msg: String) {
|
||||||
|
println(sprintf("I/%-20s %s", tag, msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun debug(tag: String, msg: String) {
|
||||||
|
println(sprintf("D/%-20s %s", tag, msg))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
55
core/src/iosMain/kotlin/org/isoron/uhabits/utils/IosFiles.kt
Normal file
55
core/src/iosMain/kotlin/org/isoron/uhabits/utils/IosFiles.kt
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.uhabits.utils
|
||||||
|
|
||||||
|
import platform.Foundation.*
|
||||||
|
|
||||||
|
class IosResourceFile(val path: String) : ResourceFile {
|
||||||
|
private val fileManager = NSFileManager.defaultManager()
|
||||||
|
override fun readLines(): List<String> {
|
||||||
|
val contents = NSString.stringWithContentsOfFile(path) as NSString
|
||||||
|
return contents.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) as List<String>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IosUserFile(val path: String) : UserFile {
|
||||||
|
override fun exists(): Boolean {
|
||||||
|
return NSFileManager.defaultManager().fileExistsAtPath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete() {
|
||||||
|
NSFileManager.defaultManager().removeItemAtPath(path, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IosFileOpener : FileOpener {
|
||||||
|
override fun openResourceFile(filename: String): ResourceFile {
|
||||||
|
val root = NSBundle.mainBundle.resourcePath!!
|
||||||
|
return IosResourceFile("$root/$filename")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openUserFile(filename: String): UserFile {
|
||||||
|
val manager = NSFileManager.defaultManager()
|
||||||
|
val root = manager.URLForDirectory(NSDocumentDirectory,
|
||||||
|
NSUserDomainMask,
|
||||||
|
null, false, null)!!.path
|
||||||
|
return IosUserFile("$root/$filename")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,17 +17,16 @@
|
|||||||
* 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.uhabits.database
|
package org.isoron.uhabits.utils
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.*
|
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.sql.DriverManager
|
import java.sql.DriverManager
|
||||||
import java.sql.PreparedStatement
|
import java.sql.PreparedStatement
|
||||||
import java.sql.ResultSet
|
import java.sql.ResultSet
|
||||||
|
|
||||||
class JavaPreparedStatement(private var stmt : PreparedStatement) : org.isoron.uhabits.utils.PreparedStatement {
|
class JavaPreparedStatement(private var stmt: PreparedStatement) : org.isoron.uhabits.utils.PreparedStatement {
|
||||||
|
|
||||||
private var rs: ResultSet? = null
|
private var rs: ResultSet? = null
|
||||||
|
|
||||||
private var hasExecuted = false
|
private var hasExecuted = false
|
||||||
|
|
||||||
override fun step(): StepResult {
|
override fun step(): StepResult {
|
||||||
@@ -40,7 +39,6 @@ class JavaPreparedStatement(private var stmt : PreparedStatement) : org.isoron.u
|
|||||||
if (rs == null || !rs!!.next()) return StepResult.DONE
|
if (rs == null || !rs!!.next()) return StepResult.DONE
|
||||||
return StepResult.ROW
|
return StepResult.ROW
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun finalize() {
|
override fun finalize() {
|
||||||
stmt.close()
|
stmt.close()
|
||||||
}
|
}
|
||||||
@@ -53,16 +51,25 @@ class JavaPreparedStatement(private var stmt : PreparedStatement) : org.isoron.u
|
|||||||
return rs!!.getString(index + 1)
|
return rs!!.getString(index + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getReal(index: Int): Double {
|
||||||
|
return rs!!.getDouble(index + 1)
|
||||||
|
}
|
||||||
|
|
||||||
override fun bindInt(index: Int, value: Int) {
|
override fun bindInt(index: Int, value: Int) {
|
||||||
stmt.setInt(index, value)
|
stmt.setInt(index + 1, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindText(index: Int, value: String) {
|
override fun bindText(index: Int, value: String) {
|
||||||
stmt.setString(index, value)
|
stmt.setString(index + 1, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindReal(index: Int, value: Double) {
|
||||||
|
stmt.setDouble(index + 1, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reset() {
|
override fun reset() {
|
||||||
stmt.clearParameters()
|
stmt.clearParameters()
|
||||||
|
hasExecuted = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +77,7 @@ class JavaDatabase(private var conn: Connection,
|
|||||||
private val log: Log) : Database {
|
private val log: Log) : Database {
|
||||||
|
|
||||||
override fun prepareStatement(sql: String): org.isoron.uhabits.utils.PreparedStatement {
|
override fun prepareStatement(sql: String): org.isoron.uhabits.utils.PreparedStatement {
|
||||||
log.debug("Running SQL: ${sql}")
|
log.debug("Database", "Preparing: $sql")
|
||||||
return JavaPreparedStatement(conn.prepareStatement(sql))
|
return JavaPreparedStatement(conn.prepareStatement(sql))
|
||||||
}
|
}
|
||||||
override fun close() {
|
override fun close() {
|
||||||
@@ -19,11 +19,21 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits
|
package org.isoron.uhabits
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.JavaFileOpener
|
import org.isoron.uhabits.models.HabitRepository
|
||||||
import org.isoron.uhabits.database.JavaDatabaseOpener
|
import org.isoron.uhabits.utils.*
|
||||||
import org.isoron.uhabits.utils.JavaLog
|
import org.junit.Before
|
||||||
|
|
||||||
open class BaseTest {
|
open class BaseTest {
|
||||||
val fileOpener = JavaFileOpener()
|
val fileOpener = JavaFileOpener()
|
||||||
val databaseOpener = JavaDatabaseOpener(JavaLog())
|
val log = StandardLog()
|
||||||
|
val databaseOpener = JavaDatabaseOpener(log)
|
||||||
|
lateinit var db: Database
|
||||||
|
|
||||||
|
@Before
|
||||||
|
open fun setUp() {
|
||||||
|
val dbFile = fileOpener.openUserFile("test.sqlite3")
|
||||||
|
if (dbFile.exists()) dbFile.delete()
|
||||||
|
db = databaseOpener.open(dbFile)
|
||||||
|
db.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,15 +26,6 @@ import org.junit.Test
|
|||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class JavaDatabaseTest : BaseTest() {
|
class JavaDatabaseTest : BaseTest() {
|
||||||
private lateinit var db: Database
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
val dbFile = fileOpener.openUserFile("test.sqlite3")
|
|
||||||
if (dbFile.exists()) dbFile.delete()
|
|
||||||
db = databaseOpener.open(dbFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testUsage() {
|
fun testUsage() {
|
||||||
db.setVersion(0)
|
db.setVersion(0)
|
||||||
@@ -51,14 +42,14 @@ class JavaDatabaseTest : BaseTest() {
|
|||||||
stmt.step()
|
stmt.step()
|
||||||
stmt.finalize()
|
stmt.finalize()
|
||||||
|
|
||||||
stmt = db.prepareStatement("insert into demo(key, value) values (?1, ?2)")
|
stmt = db.prepareStatement("insert into demo(key, value) values (?, ?)")
|
||||||
stmt.bindInt(1, 42)
|
stmt.bindInt(0, 42)
|
||||||
stmt.bindText(2, "Hello World")
|
stmt.bindText(1, "Hello World")
|
||||||
stmt.step()
|
stmt.step()
|
||||||
stmt.finalize()
|
stmt.finalize()
|
||||||
|
|
||||||
stmt = db.prepareStatement("select * from demo where key > ?1")
|
stmt = db.prepareStatement("select * from demo where key > ?")
|
||||||
stmt.bindInt(1, 10)
|
stmt.bindInt(0, 10)
|
||||||
|
|
||||||
var result = stmt.step()
|
var result = stmt.step()
|
||||||
assertEquals(result, StepResult.ROW)
|
assertEquals(result, StepResult.ROW)
|
||||||
@@ -71,12 +62,4 @@ class JavaDatabaseTest : BaseTest() {
|
|||||||
stmt.finalize()
|
stmt.finalize()
|
||||||
db.close()
|
db.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testMigrateTo() {
|
|
||||||
assertEquals(0, db.getVersion())
|
|
||||||
db.migrateTo(22, fileOpener)
|
|
||||||
assertEquals(22, db.getVersion())
|
|
||||||
db.execute("select * from habits")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.uhabits.models
|
||||||
|
|
||||||
|
import junit.framework.Assert.assertEquals
|
||||||
|
import org.isoron.uhabits.BaseTest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class HabitRepositoryTest : BaseTest() {
|
||||||
|
lateinit var repository: HabitRepository
|
||||||
|
|
||||||
|
lateinit private var original0: Habit
|
||||||
|
lateinit private var original1: Habit
|
||||||
|
lateinit private var original2: Habit
|
||||||
|
|
||||||
|
@Before
|
||||||
|
override fun setUp() {
|
||||||
|
super.setUp()
|
||||||
|
|
||||||
|
original0 = Habit(id = 0,
|
||||||
|
name = "Wake up early",
|
||||||
|
description = "Did you wake up before 6am?",
|
||||||
|
frequency = Frequency(1, 1),
|
||||||
|
color = Color(3),
|
||||||
|
isArchived = false,
|
||||||
|
position = 0,
|
||||||
|
unit = "",
|
||||||
|
target = 0.0,
|
||||||
|
type = HabitType.YES_NO_HABIT)
|
||||||
|
|
||||||
|
original1 = Habit(id = 1,
|
||||||
|
name = "Exercise",
|
||||||
|
description = "Did you exercise for at least 20 minutes?",
|
||||||
|
frequency = Frequency(1, 2),
|
||||||
|
color = Color(4),
|
||||||
|
isArchived = false,
|
||||||
|
position = 1,
|
||||||
|
unit = "",
|
||||||
|
target = 0.0,
|
||||||
|
type = HabitType.YES_NO_HABIT)
|
||||||
|
|
||||||
|
original2 = Habit(id = 2,
|
||||||
|
name = "Learn Japanese",
|
||||||
|
description = "Did you study Japanese today?",
|
||||||
|
frequency = Frequency(1, 1),
|
||||||
|
color = Color(3),
|
||||||
|
isArchived = false,
|
||||||
|
position = 2,
|
||||||
|
unit = "",
|
||||||
|
target = 0.0,
|
||||||
|
type = HabitType.YES_NO_HABIT)
|
||||||
|
|
||||||
|
repository = HabitRepository(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFindActive() {
|
||||||
|
var habits = repository.findAll()
|
||||||
|
assertEquals(0, repository.nextId())
|
||||||
|
assertEquals(0, habits.size)
|
||||||
|
|
||||||
|
repository.insert(original0)
|
||||||
|
repository.insert(original1)
|
||||||
|
repository.insert(original2)
|
||||||
|
habits = repository.findAll()
|
||||||
|
assertEquals(3, habits.size)
|
||||||
|
assertEquals(original0, habits[0])
|
||||||
|
assertEquals(original1, habits[1])
|
||||||
|
assertEquals(original2, habits[2])
|
||||||
|
|
||||||
|
assertEquals(3, repository.nextId())
|
||||||
|
|
||||||
|
original0.description = "New description"
|
||||||
|
repository.update(original0)
|
||||||
|
habits = repository.findAll()
|
||||||
|
assertEquals(original0, habits[0])
|
||||||
|
|
||||||
|
repository.delete(original0)
|
||||||
|
habits = repository.findAll()
|
||||||
|
assertEquals(2, habits.size)
|
||||||
|
assertEquals(original1, habits[1])
|
||||||
|
assertEquals(original2, habits[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,14 +17,23 @@
|
|||||||
* 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.uhabits.utils
|
import UIKit
|
||||||
|
|
||||||
class JavaLog : Log {
|
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
override fun info(msg: String) {
|
|
||||||
println("[I] $msg")
|
var window: UIWindow?
|
||||||
|
|
||||||
|
func application(_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
|
||||||
|
window = UIWindow(frame: UIScreen.main.bounds)
|
||||||
|
if let window = window {
|
||||||
|
let nav = UINavigationController()
|
||||||
|
nav.viewControllers = [ListHabitsController()]
|
||||||
|
window.backgroundColor = UIColor.white
|
||||||
|
window.rootViewController = nav
|
||||||
|
window.makeKeyAndVisible()
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
override fun debug(msg: String) {
|
|
||||||
println("[D] $msg")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "29x29",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "29x29",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "40x40",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "40x40",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "60x60",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "60x60",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "29x29",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "29x29",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "40x40",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "40x40",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "76x76",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "76x76",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
ios-native/Application/Assets.xcassets/Contents.json
Normal file
6
ios-native/Application/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
87
ios-native/Application/EditHabit/EditHabitController.swift
Normal file
87
ios-native/Application/EditHabit/EditHabitController.swift
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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 UIKit
|
||||||
|
|
||||||
|
class EditHabitTableViewController: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||||
|
func disclosure(title: String, subtitle: String) -> UITableViewCell {
|
||||||
|
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
|
||||||
|
cell.textLabel?.text = title
|
||||||
|
cell.detailTextLabel?.text = subtitle
|
||||||
|
cell.accessoryType = .disclosureIndicator
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func input(title: String) -> UITableViewCell {
|
||||||
|
let cell = UITableViewCell()
|
||||||
|
let field = UITextField(frame: cell.bounds.insetBy(dx: 20, dy: 0))
|
||||||
|
field.placeholder = title
|
||||||
|
field.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth,
|
||||||
|
UIView.AutoresizingMask.flexibleHeight]
|
||||||
|
cell.contentView.addSubview(field)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
var primary = [UITableViewCell]()
|
||||||
|
var secondary = [UITableViewCell]()
|
||||||
|
var parentController: EditHabitController
|
||||||
|
|
||||||
|
init(withParentController parentController: EditHabitController) {
|
||||||
|
self.parentController = parentController
|
||||||
|
super.init()
|
||||||
|
primary.append(input(title: "Name"))
|
||||||
|
primary.append(input(title: "Question (e.g. Did you wake up early today?)"))
|
||||||
|
secondary.append(disclosure(title: "Color", subtitle: "Blue"))
|
||||||
|
secondary.append(disclosure(title: "Repeat", subtitle: "Daily"))
|
||||||
|
secondary.append(disclosure(title: "Reminder", subtitle: "Disabled"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return section == 0 ? primary.count : secondary.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
return indexPath.section == 0 ? primary[indexPath.item] : secondary[indexPath.item]
|
||||||
|
}
|
||||||
|
|
||||||
|
// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
// let alert = UIAlertController(title: "Hello", message: "You selected something", preferredStyle: .alert)
|
||||||
|
// parentController.present(alert, animated: true)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditHabitController: UIViewController {
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
let bounds = UIScreen.main.bounds
|
||||||
|
let tableController = EditHabitTableViewController(withParentController: self)
|
||||||
|
let table = UITableView(frame: bounds, style: .grouped)
|
||||||
|
table.dataSource = tableController
|
||||||
|
table.delegate = tableController
|
||||||
|
self.view = table
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
self.title = "Edit Habit"
|
||||||
|
}
|
||||||
|
}
|
||||||
43
ios-native/Application/Info.plist
Normal file
43
ios-native/Application/Info.plist
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?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>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>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>armv7</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>Launch.storyboard</string>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
7
ios-native/Application/Launch.storyboard
Normal file
7
ios-native/Application/Launch.storyboard
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes/>
|
||||||
|
</document>
|
||||||
41
ios-native/Application/ListHabits/ListHabitsController.swift
Normal file
41
ios-native/Application/ListHabits/ListHabitsController.swift
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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 UIKit
|
||||||
|
|
||||||
|
class ListHabitsController: UIViewController {
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
self.title = "Habits"
|
||||||
|
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add,
|
||||||
|
target: self,
|
||||||
|
action: #selector(self.onCreateHabitClicked))
|
||||||
|
|
||||||
|
// let box = UIView(frame: view.bounds.insetBy(dx: 100, dy: 100))
|
||||||
|
// box.backgroundColor = .blue
|
||||||
|
//// box.translatesAutoresizingMaskIntoConstraints = true
|
||||||
|
// box.autoresizingMask = [UIView.AutoresizingMask.flexibleLeftMargin,
|
||||||
|
// UIView.AutoresizingMask.flexibleRightMargin]
|
||||||
|
// view.addSubview(box)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func onCreateHabitClicked() {
|
||||||
|
self.navigationController?.pushViewController(EditHabitController(), animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
22
ios-native/Tests/Info.plist
Normal file
22
ios-native/Tests/Info.plist
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?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>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>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>BNDL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
0021019C21F8AA3E00F9283D /* IosDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0021019B21F8AA3E00F9283D /* IosDatabase.swift */; };
|
0021019C21F8AA3E00F9283D /* IosDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0021019B21F8AA3E00F9283D /* IosDatabase.swift */; };
|
||||||
002101A421F936A300F9283D /* IosSqlDatabaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002101A321F936A300F9283D /* IosSqlDatabaseTest.swift */; };
|
002101A421F936A300F9283D /* IosSqlDatabaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002101A321F936A300F9283D /* IosSqlDatabaseTest.swift */; };
|
||||||
002101AC21F9428C00F9283D /* core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0008A5C021F16D25000DB3E7 /* core.framework */; };
|
002101AC21F9428C00F9283D /* core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0008A5C021F16D25000DB3E7 /* core.framework */; };
|
||||||
0091878521FD70B5001BDE6B /* IosLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0091878421FD70B5001BDE6B /* IosLog.swift */; };
|
00513C3B2200843F00702112 /* libthird-party.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DF0FFE32056DD460020B375 /* libthird-party.a */; };
|
||||||
00B2AC3D21FCA9D900CBEC8E /* IosFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B2AC3C21FCA9D900CBEC8E /* IosFiles.swift */; };
|
00513C3C2200843F00702112 /* libyoga.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3EA51DF850E9000B6D8A /* libyoga.a */; };
|
||||||
00B2AC6521FD1A4500CBEC8E /* migrations in Resources */ = {isa = PBXBuildFile; fileRef = 00B2AC6421FD1A4500CBEC8E /* migrations */; };
|
00B2AC6521FD1A4500CBEC8E /* migrations in Resources */ = {isa = PBXBuildFile; fileRef = 00B2AC6421FD1A4500CBEC8E /* migrations */; };
|
||||||
00B2AC6821FD1DA700CBEC8E /* IosFilesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B2AC6621FD1CEF00CBEC8E /* IosFilesTest.swift */; };
|
00B2AC6821FD1DA700CBEC8E /* IosFilesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B2AC6621FD1CEF00CBEC8E /* IosFilesTest.swift */; };
|
||||||
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
|
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
|
||||||
@@ -339,8 +339,6 @@
|
|||||||
002101A121F936A300F9283D /* uhabitsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = uhabitsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
002101A121F936A300F9283D /* uhabitsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = uhabitsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
002101A321F936A300F9283D /* IosSqlDatabaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosSqlDatabaseTest.swift; sourceTree = "<group>"; };
|
002101A321F936A300F9283D /* IosSqlDatabaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosSqlDatabaseTest.swift; sourceTree = "<group>"; };
|
||||||
002101A521F936A300F9283D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
002101A521F936A300F9283D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
0091878421FD70B5001BDE6B /* IosLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = IosLog.swift; path = uhabits/IosLog.swift; sourceTree = "<group>"; };
|
|
||||||
00B2AC3C21FCA9D900CBEC8E /* IosFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = IosFiles.swift; path = uhabits/IosFiles.swift; sourceTree = "<group>"; };
|
|
||||||
00B2AC6421FD1A4500CBEC8E /* migrations */ = {isa = PBXFileReference; lastKnownFileType = folder; name = migrations; path = ../core/assets/main/migrations; sourceTree = "<group>"; };
|
00B2AC6421FD1A4500CBEC8E /* migrations */ = {isa = PBXFileReference; lastKnownFileType = folder; name = migrations; path = ../core/assets/main/migrations; sourceTree = "<group>"; };
|
||||||
00B2AC6621FD1CEF00CBEC8E /* IosFilesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosFilesTest.swift; sourceTree = "<group>"; };
|
00B2AC6621FD1CEF00CBEC8E /* IosFilesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosFilesTest.swift; sourceTree = "<group>"; };
|
||||||
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = "<group>"; };
|
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = "<group>"; };
|
||||||
@@ -373,6 +371,8 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
00513C3B2200843F00702112 /* libthird-party.a in Frameworks */,
|
||||||
|
00513C3C2200843F00702112 /* libyoga.a in Frameworks */,
|
||||||
000BCE0521F6CB1100F4DA11 /* libRCTWebSocket.a in Frameworks */,
|
000BCE0521F6CB1100F4DA11 /* libRCTWebSocket.a in Frameworks */,
|
||||||
000C283821F51C9B00C5EC6D /* libRNSVG.a in Frameworks */,
|
000C283821F51C9B00C5EC6D /* libRNSVG.a in Frameworks */,
|
||||||
ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */,
|
ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */,
|
||||||
@@ -425,8 +425,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
002101A521F936A300F9283D /* Info.plist */,
|
002101A521F936A300F9283D /* Info.plist */,
|
||||||
002101A321F936A300F9283D /* IosSqlDatabaseTest.swift */,
|
|
||||||
00B2AC6621FD1CEF00CBEC8E /* IosFilesTest.swift */,
|
00B2AC6621FD1CEF00CBEC8E /* IosFilesTest.swift */,
|
||||||
|
002101A321F936A300F9283D /* IosSqlDatabaseTest.swift */,
|
||||||
);
|
);
|
||||||
name = "Unit Tests";
|
name = "Unit Tests";
|
||||||
path = uhabitsTest;
|
path = uhabitsTest;
|
||||||
@@ -478,16 +478,14 @@
|
|||||||
13B07FAE1A68108700A75B9A /* Application */ = {
|
13B07FAE1A68108700A75B9A /* Application */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
|
||||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
|
||||||
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
|
|
||||||
0008A5F721F17531000DB3E7 /* BridgingHeader.h */,
|
0008A5F721F17531000DB3E7 /* BridgingHeader.h */,
|
||||||
0008A62B21F2B755000DB3E7 /* CoreModuleBridge.m */,
|
0008A62B21F2B755000DB3E7 /* CoreModuleBridge.m */,
|
||||||
|
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||||
0008A5F521F17513000DB3E7 /* AppDelegate.swift */,
|
0008A5F521F17513000DB3E7 /* AppDelegate.swift */,
|
||||||
0008A62921F2B728000DB3E7 /* CoreModule.swift */,
|
0008A62921F2B728000DB3E7 /* CoreModule.swift */,
|
||||||
0021019B21F8AA3E00F9283D /* IosDatabase.swift */,
|
0021019B21F8AA3E00F9283D /* IosDatabase.swift */,
|
||||||
00B2AC3C21FCA9D900CBEC8E /* IosFiles.swift */,
|
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||||
0091878421FD70B5001BDE6B /* IosLog.swift */,
|
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
|
||||||
);
|
);
|
||||||
name = Application;
|
name = Application;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -545,11 +543,8 @@
|
|||||||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
000BCDF621F6CAFF00F4DA11 /* RCTWebSocket.xcodeproj */,
|
|
||||||
000C280A21F51C4E00C5EC6D /* RNSVG.xcodeproj */,
|
|
||||||
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */,
|
|
||||||
146833FF1AC3E56700842450 /* React.xcodeproj */,
|
|
||||||
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
|
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
|
||||||
|
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */,
|
||||||
ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */,
|
ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */,
|
||||||
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */,
|
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */,
|
||||||
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */,
|
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */,
|
||||||
@@ -557,6 +552,9 @@
|
|||||||
00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */,
|
00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */,
|
||||||
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */,
|
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */,
|
||||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */,
|
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */,
|
||||||
|
000BCDF621F6CAFF00F4DA11 /* RCTWebSocket.xcodeproj */,
|
||||||
|
146833FF1AC3E56700842450 /* React.xcodeproj */,
|
||||||
|
000C280A21F51C4E00C5EC6D /* RNSVG.xcodeproj */,
|
||||||
);
|
);
|
||||||
name = Libraries;
|
name = Libraries;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1055,9 +1053,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
00B2AC3D21FCA9D900CBEC8E /* IosFiles.swift in Sources */,
|
|
||||||
0008A62C21F2B755000DB3E7 /* CoreModuleBridge.m in Sources */,
|
0008A62C21F2B755000DB3E7 /* CoreModuleBridge.m in Sources */,
|
||||||
0091878521FD70B5001BDE6B /* IosLog.swift in Sources */,
|
|
||||||
0021019C21F8AA3E00F9283D /* IosDatabase.swift in Sources */,
|
0021019C21F8AA3E00F9283D /* IosDatabase.swift in Sources */,
|
||||||
0008A62A21F2B728000DB3E7 /* CoreModule.swift in Sources */,
|
0008A62A21F2B728000DB3E7 /* CoreModule.swift in Sources */,
|
||||||
0008A5F621F17513000DB3E7 /* AppDelegate.swift in Sources */,
|
0008A5F621F17513000DB3E7 /* AppDelegate.swift in Sources */,
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
var bridge: RCTBridge!
|
var bridge: RCTBridge!
|
||||||
|
|
||||||
static var backend = Backend(databaseOpener: IosDatabaseOpener(),
|
static var backend = Backend(databaseOpener: IosDatabaseOpener(withLog: StandardLog()),
|
||||||
fileOpener: IosFileOpener(),
|
fileOpener: IosFileOpener(),
|
||||||
log: IosLog())
|
log: StandardLog())
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
let jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index.ios", fallbackResource: nil)
|
let jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index.ios", fallbackResource: nil)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ internal let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self)
|
|||||||
internal let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
internal let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
||||||
|
|
||||||
class IosPreparedStatement : NSObject, PreparedStatement {
|
class IosPreparedStatement : NSObject, PreparedStatement {
|
||||||
|
|
||||||
var db: OpaquePointer
|
var db: OpaquePointer
|
||||||
var statement: OpaquePointer
|
var statement: OpaquePointer
|
||||||
|
|
||||||
@@ -53,12 +52,20 @@ class IosPreparedStatement : NSObject, PreparedStatement {
|
|||||||
return String(cString: sqlite3_column_text(statement, index))
|
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) {
|
func bindInt(index: Int32, value: Int32) {
|
||||||
sqlite3_bind_int(statement, index, value)
|
sqlite3_bind_int(statement, index + 1, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindText(index: Int32, value: String) {
|
func bindText(index: Int32, value: String) {
|
||||||
sqlite3_bind_text(statement, index, value, -1, SQLITE_TRANSIENT)
|
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() {
|
func reset() {
|
||||||
@@ -72,16 +79,18 @@ class IosPreparedStatement : NSObject, PreparedStatement {
|
|||||||
|
|
||||||
class IosDatabase : NSObject, Database {
|
class IosDatabase : NSObject, Database {
|
||||||
var db: OpaquePointer
|
var db: OpaquePointer
|
||||||
|
var log: Log
|
||||||
|
|
||||||
init(withDb db: OpaquePointer) {
|
init(withDb db: OpaquePointer, withLog log: Log) {
|
||||||
self.db = db
|
self.db = db
|
||||||
|
self.log = log
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareStatement(sql: String) -> PreparedStatement {
|
func prepareStatement(sql: String) -> PreparedStatement {
|
||||||
if sql.isEmpty {
|
if sql.isEmpty {
|
||||||
fatalError("Provided SQL query is empty")
|
fatalError("Provided SQL query is empty")
|
||||||
}
|
}
|
||||||
print("Running SQL: \(sql)")
|
log.debug(tag: "IosDatabase", msg: "Preparing: \(sql)")
|
||||||
var statement : OpaquePointer?
|
var statement : OpaquePointer?
|
||||||
let result = sqlite3_prepare_v2(db, sql, -1, &statement, nil)
|
let result = sqlite3_prepare_v2(db, sql, -1, &statement, nil)
|
||||||
if result == SQLITE_OK {
|
if result == SQLITE_OK {
|
||||||
@@ -98,16 +107,23 @@ class IosDatabase : NSObject, Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class IosDatabaseOpener : NSObject, DatabaseOpener {
|
class IosDatabaseOpener : NSObject, DatabaseOpener {
|
||||||
|
|
||||||
|
var log: Log
|
||||||
|
|
||||||
|
init(withLog log: Log) {
|
||||||
|
self.log = log
|
||||||
|
}
|
||||||
|
|
||||||
func open(file: UserFile) -> Database {
|
func open(file: UserFile) -> Database {
|
||||||
let dbPath = (file as! IosUserFile).path
|
let dbPath = (file as! IosUserFile).path
|
||||||
|
|
||||||
let version = String(cString: sqlite3_libversion())
|
let version = String(cString: sqlite3_libversion())
|
||||||
print("SQLite \(version)")
|
log.info(tag: "IosDatabaseOpener", msg: "SQLite \(version)")
|
||||||
print("Opening database: \(dbPath)")
|
log.info(tag: "IosDatabaseOpener", msg: "Opening database: \(dbPath)")
|
||||||
var db: OpaquePointer?
|
var db: OpaquePointer?
|
||||||
let result = sqlite3_open(dbPath, &db)
|
let result = sqlite3_open(dbPath, &db)
|
||||||
if result == SQLITE_OK {
|
if result == SQLITE_OK {
|
||||||
return IosDatabase(withDb: db!)
|
return IosDatabase(withDb: db!, withLog: log)
|
||||||
} else {
|
} else {
|
||||||
fatalError("Error opening database (code \(result))")
|
fatalError("Error opening database (code \(result))")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +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
|
|
||||||
|
|
||||||
class IosResourceFile : NSObject, ResourceFile {
|
|
||||||
|
|
||||||
var path: String
|
|
||||||
|
|
||||||
var fileManager = FileManager.default
|
|
||||||
|
|
||||||
init(forPath path: String) {
|
|
||||||
self.path = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func readLines() -> [String] {
|
|
||||||
do {
|
|
||||||
let contents = try String(contentsOfFile: self.path, encoding: .utf8)
|
|
||||||
return contents.components(separatedBy: CharacterSet.newlines)
|
|
||||||
} catch {
|
|
||||||
return [""]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IosUserFile : NSObject, UserFile {
|
|
||||||
|
|
||||||
var path: String
|
|
||||||
|
|
||||||
init(forPath path: String) {
|
|
||||||
self.path = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func delete() {
|
|
||||||
do {
|
|
||||||
try FileManager.default.removeItem(atPath: path)
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func exists() -> Bool {
|
|
||||||
return FileManager.default.fileExists(atPath: path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IosFileOpener : NSObject, FileOpener {
|
|
||||||
func openResourceFile(filename: String) -> ResourceFile {
|
|
||||||
let path = "\(Bundle.main.resourcePath!)/\(filename)"
|
|
||||||
return IosResourceFile(forPath: path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func openUserFile(filename: String) -> UserFile {
|
|
||||||
do {
|
|
||||||
let root = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).path
|
|
||||||
return IosUserFile(forPath: "\(root)/\(filename)")
|
|
||||||
} catch {
|
|
||||||
return IosUserFile(forPath: "invalid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,6 +32,7 @@ class IosFilesTest: XCTestCase {
|
|||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
let root = try fm.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).path
|
let root = try fm.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).path
|
||||||
let path = "\(root)/test.txt"
|
let path = "\(root)/test.txt"
|
||||||
|
print(path)
|
||||||
fm.createFile(atPath: path, contents: "Hello world\nThis is line 2".data(using: .utf8), attributes: nil)
|
fm.createFile(atPath: path, contents: "Hello world\nThis is line 2".data(using: .utf8), attributes: nil)
|
||||||
|
|
||||||
let fileOpener = IosFileOpener()
|
let fileOpener = IosFileOpener()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import XCTest
|
|||||||
|
|
||||||
class IosDatabaseTest: XCTestCase {
|
class IosDatabaseTest: XCTestCase {
|
||||||
func testUsage() {
|
func testUsage() {
|
||||||
let databaseOpener = IosDatabaseOpener()
|
let databaseOpener = IosDatabaseOpener(withLog: StandardLog())
|
||||||
let fileOpener = IosFileOpener()
|
let fileOpener = IosFileOpener()
|
||||||
|
|
||||||
let dbFile = fileOpener.openUserFile(filename: "test.sqlite3")
|
let dbFile = fileOpener.openUserFile(filename: "test.sqlite3")
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ module.exports = {
|
|||||||
"rules": {
|
"rules": {
|
||||||
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
|
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
|
||||||
"import/prefer-default-export": false,
|
"import/prefer-default-export": false,
|
||||||
|
"react/prefer-stateless-function": false,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,23 +21,45 @@ import React from 'react';
|
|||||||
import {
|
import {
|
||||||
AppRegistry,
|
AppRegistry,
|
||||||
NavigatorIOS,
|
NavigatorIOS,
|
||||||
NativeModules,
|
|
||||||
NativeEventEmitter,
|
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import ListHabitsScene from './src/components/ListHabits/index';
|
import ListHabitsScene from './src/components/ListHabits/index';
|
||||||
|
import EditHabitScene from './src/components/EditHabit/index';
|
||||||
|
|
||||||
function RootComponent() {
|
let navigator;
|
||||||
return (
|
|
||||||
<NavigatorIOS
|
const routes = {
|
||||||
translucent={false}
|
index: {
|
||||||
initialRoute={{
|
|
||||||
component: ListHabitsScene,
|
component: ListHabitsScene,
|
||||||
title: 'Habits',
|
title: 'Habits',
|
||||||
rightButtonSystemIcon: 'add',
|
rightButtonSystemIcon: 'add',
|
||||||
}}
|
onRightButtonPress: () => navigator.push(routes.newHabit),
|
||||||
|
passProps: {
|
||||||
|
onClickHabit: () => navigator.push(routes.newHabit),
|
||||||
|
onClickCheckmark: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
newHabit: {
|
||||||
|
component: EditHabitScene,
|
||||||
|
title: 'New Habit',
|
||||||
|
leftButtonTitle: 'Cancel',
|
||||||
|
rightButtonTitle: 'Save',
|
||||||
|
onLeftButtonPress: () => navigator.pop(),
|
||||||
|
onRightButtonPress: () => navigator.pop(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class RootComponent extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<NavigatorIOS
|
||||||
|
ref={(c) => { navigator = c; }}
|
||||||
|
translucent={false}
|
||||||
|
initialRoute={routes.index}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AppRegistry.registerComponent('LoopHabitTracker', () => RootComponent);
|
AppRegistry.registerComponent('LoopHabitTracker', () => RootComponent);
|
||||||
|
|||||||
32
react-native/package-lock.json
generated
32
react-native/package-lock.json
generated
@@ -4910,38 +4910,6 @@
|
|||||||
"color": "^2.0.1"
|
"color": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-native-vector-icons": {
|
|
||||||
"version": "6.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-6.1.0.tgz",
|
|
||||||
"integrity": "sha512-1GF5I4VWgwnzBtVfAKNgEiR5ziHi5QaKL381wwApMzuiFgIJMNt5XIChuKwKoaiB86s+P5iMcYWxYCyENL96lA==",
|
|
||||||
"requires": {
|
|
||||||
"lodash": "^4.0.0",
|
|
||||||
"prop-types": "^15.6.2",
|
|
||||||
"yargs": "^8.0.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"yargs": {
|
|
||||||
"version": "8.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz",
|
|
||||||
"integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=",
|
|
||||||
"requires": {
|
|
||||||
"camelcase": "^4.1.0",
|
|
||||||
"cliui": "^3.2.0",
|
|
||||||
"decamelize": "^1.1.1",
|
|
||||||
"get-caller-file": "^1.0.1",
|
|
||||||
"os-locale": "^2.0.0",
|
|
||||||
"read-pkg-up": "^2.0.0",
|
|
||||||
"require-directory": "^2.1.1",
|
|
||||||
"require-main-filename": "^1.0.1",
|
|
||||||
"set-blocking": "^2.0.0",
|
|
||||||
"string-width": "^2.0.0",
|
|
||||||
"which-module": "^2.0.0",
|
|
||||||
"y18n": "^3.2.1",
|
|
||||||
"yargs-parser": "^7.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-proxy": {
|
"react-proxy": {
|
||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.8.tgz",
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.6.3",
|
"react": "^16.6.3",
|
||||||
"react-native": "^0.57.8",
|
"react-native": "^0.57.8",
|
||||||
"react-native-svg": "^9.0.0",
|
"react-native-svg": "^9.0.0"
|
||||||
"react-native-vector-icons": "^6.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^5.12.1",
|
"eslint": "^5.12.1",
|
||||||
|
|||||||
128
react-native/src/components/EditHabit/index.js
vendored
Normal file
128
react-native/src/components/EditHabit/index.js
vendored
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import {
|
||||||
|
StyleSheet,
|
||||||
|
TextInput,
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
TouchableHighlight,
|
||||||
|
} from 'react-native';
|
||||||
|
import FontAwesome from '../../helpers/FontAwesome';
|
||||||
|
import { Colors } from '../../helpers/Colors';
|
||||||
|
import ColorCircle from '../common/ColorCircle';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
backgroundColor: Colors.appBackground,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
fontSize: 17,
|
||||||
|
paddingTop: 15,
|
||||||
|
paddingBottom: 15,
|
||||||
|
paddingRight: 15,
|
||||||
|
paddingLeft: 15,
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: 17,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
fontSize: 17,
|
||||||
|
},
|
||||||
|
multiline: {
|
||||||
|
},
|
||||||
|
middle: {
|
||||||
|
borderBottomColor: Colors.headerBorderColor,
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
backgroundColor: Colors.appBackground,
|
||||||
|
marginTop: 30,
|
||||||
|
borderTopColor: Colors.headerBorderColor,
|
||||||
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderBottomColor: Colors.headerBorderColor,
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
fontFamily: 'FontAwesome',
|
||||||
|
color: Colors.unchecked,
|
||||||
|
marginLeft: 10,
|
||||||
|
fontSize: 12,
|
||||||
|
paddingTop: 2,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 25,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default class EditHabitsScene extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ScrollView style={styles.container}>
|
||||||
|
<View style={styles.section}>
|
||||||
|
<TextInput
|
||||||
|
autoFocus
|
||||||
|
style={[styles.item, styles.middle, { color: Colors[1] }]}
|
||||||
|
placeholder="Name"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.item]}
|
||||||
|
placeholder="Question (e.g. Did you exercise today?)"
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.section}>
|
||||||
|
<TouchableHighlight onPress={() => {}}>
|
||||||
|
<View style={[styles.item, styles.middle]}>
|
||||||
|
<Text style={styles.label}>Color</Text>
|
||||||
|
<ColorCircle size={20} color={Colors[1]} />
|
||||||
|
<Text style={styles.icon}>{FontAwesome.chevronRight}</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableHighlight>
|
||||||
|
<TouchableHighlight onPress={() => {}}>
|
||||||
|
<View style={[styles.item, styles.middle]}>
|
||||||
|
<Text style={styles.label}>Repeat</Text>
|
||||||
|
<Text style={styles.value}>Every Day</Text>
|
||||||
|
<Text style={styles.icon}>{FontAwesome.chevronRight}</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableHighlight>
|
||||||
|
<TouchableHighlight onPress={() => {}}>
|
||||||
|
<View style={[styles.item, styles.middle]}>
|
||||||
|
<Text style={styles.label}>Reminder</Text>
|
||||||
|
<Text style={styles.value}>12:30</Text>
|
||||||
|
<Text style={styles.icon}>{FontAwesome.chevronRight}</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableHighlight>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,14 +29,14 @@ import { Colors } from '../../helpers/Colors';
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
checkmarkBox: {
|
checkmarkBox: {
|
||||||
width: 44,
|
width: 55,
|
||||||
height: 44,
|
height: 55,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
checkmark: {
|
checkmark: {
|
||||||
fontFamily: 'FontAwesome',
|
fontFamily: 'FontAwesome',
|
||||||
fontSize: 14,
|
fontSize: 17,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,15 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
FlatList,
|
FlatList,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
View,
|
View,
|
||||||
|
TouchableHighlight,
|
||||||
|
TouchableOpacity,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Colors } from '../../helpers/Colors';
|
import { Colors } from '../../helpers/Colors';
|
||||||
import { Emitter, Backend } from '../../helpers/Backend';
|
import { Emitter, Backend } from '../../helpers/Backend';
|
||||||
@@ -32,23 +35,19 @@ import CheckmarkButton from './CheckmarkButton';
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
item: {
|
item: {
|
||||||
backgroundColor: Colors.itemBackground,
|
backgroundColor: Colors.itemBackground,
|
||||||
padding: 1,
|
borderBottomColor: Colors.headerBorderColor,
|
||||||
marginTop: 0,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
marginBottom: 1,
|
|
||||||
marginLeft: 0,
|
|
||||||
marginRight: 0,
|
|
||||||
elevation: 0,
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'stretch',
|
alignItems: 'stretch',
|
||||||
},
|
},
|
||||||
ringContainer: {
|
ringContainer: {
|
||||||
width: 35,
|
width: 40,
|
||||||
height: 45,
|
height: 55,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
labelContainer: {
|
labelContainer: {
|
||||||
width: 44,
|
width: 1,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
@@ -69,11 +68,13 @@ export default class HabitList extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { habits } = this.state;
|
const { habits } = this.state;
|
||||||
|
const { onClickHabit, onClickCheckmark } = this.props;
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
style={styles.container}
|
style={styles.container}
|
||||||
data={habits}
|
data={habits}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
|
<TouchableHighlight onPress={() => onClickHabit(item.key)}>
|
||||||
<View style={styles.item}>
|
<View style={styles.item}>
|
||||||
<View style={styles.ringContainer}>
|
<View style={styles.ringContainer}>
|
||||||
<Ring
|
<Ring
|
||||||
@@ -87,20 +88,31 @@ export default class HabitList extends React.Component {
|
|||||||
<Text
|
<Text
|
||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
style={{
|
style={{
|
||||||
fontSize: 14,
|
fontSize: 17,
|
||||||
color: Colors[item.color],
|
color: Colors[item.color],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
<TouchableOpacity onPress={() => onClickCheckmark(item.key, 1)}>
|
||||||
<CheckmarkButton color={Colors[item.color]} />
|
<CheckmarkButton color={Colors[item.color]} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity onPress={() => onClickCheckmark(item.key, 2)}>
|
||||||
<CheckmarkButton color={Colors[item.color]} />
|
<CheckmarkButton color={Colors[item.color]} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity onPress={() => onClickCheckmark(item.key, 3)}>
|
||||||
<CheckmarkButton color={Colors[item.color]} />
|
<CheckmarkButton color={Colors[item.color]} />
|
||||||
<CheckmarkButton color={Colors[item.color]} />
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
</TouchableHighlight>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HabitList.propTypes = {
|
||||||
|
onClickHabit: PropTypes.func.isRequired,
|
||||||
|
onClickCheckmark: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { Colors } from '../../helpers/Colors';
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
height: 50,
|
height: 55,
|
||||||
paddingRight: 1,
|
paddingRight: 1,
|
||||||
backgroundColor: Colors.headerBackground,
|
backgroundColor: Colors.headerBackground,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -35,7 +35,7 @@ const styles = StyleSheet.create({
|
|||||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 44,
|
width: 55,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
@@ -43,7 +43,7 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
dayName: {
|
dayName: {
|
||||||
fontSize: 10,
|
fontSize: 12,
|
||||||
},
|
},
|
||||||
dayNumber: {
|
dayNumber: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@@ -81,11 +81,6 @@ export default class HabitListHeader extends React.Component {
|
|||||||
dayName: 'Thu',
|
dayName: 'Thu',
|
||||||
dayNumber: '3',
|
dayNumber: '3',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
dayName: 'Wed',
|
|
||||||
dayNumber: '2',
|
|
||||||
},
|
|
||||||
|
|
||||||
].map((day) => {
|
].map((day) => {
|
||||||
const { dayName, dayNumber } = day;
|
const { dayName, dayNumber } = day;
|
||||||
return HabitListHeader.renderColumn(dayName, dayNumber);
|
return HabitListHeader.renderColumn(dayName, dayNumber);
|
||||||
|
|||||||
16
react-native/src/components/ListHabits/index.js
vendored
16
react-native/src/components/ListHabits/index.js
vendored
@@ -17,6 +17,7 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import { Colors } from '../../helpers/Colors';
|
import { Colors } from '../../helpers/Colors';
|
||||||
@@ -30,11 +31,22 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function ListHabitsScene() {
|
export default class ListHabitsScene extends React.Component {
|
||||||
|
render() {
|
||||||
|
const { onClickHabit, onClickCheckmark } = this.props;
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<HabitListHeader />
|
<HabitListHeader />
|
||||||
<HabitList />
|
<HabitList
|
||||||
|
onClickHabit={onClickHabit}
|
||||||
|
onClickCheckmark={onClickCheckmark}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ListHabitsScene.propTypes = {
|
||||||
|
onClickHabit: PropTypes.func.isRequired,
|
||||||
|
onClickCheckmark: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|||||||
@@ -17,14 +17,22 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Svg, { Circle } from 'react-native-svg';
|
||||||
|
import { Colors } from '../../helpers/Colors';
|
||||||
|
|
||||||
class IosLog : NSObject, Log {
|
export default function ColorCircle(props) {
|
||||||
func info(msg: String) {
|
const { size, color } = props;
|
||||||
print("[I] \(msg)")
|
return (
|
||||||
}
|
<Svg height={size} width={size} viewBox="0 0 100 100">
|
||||||
|
<Circle cx={50} cy={50} r={50} fill={color} />
|
||||||
func debug(msg: String) {
|
<Circle cx={50} cy={50} r={30} fill={Colors.itemBackground} />
|
||||||
print("[D] \(msg)")
|
</Svg>
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColorCircle.propTypes = {
|
||||||
|
size: PropTypes.number.isRequired,
|
||||||
|
color: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
1
react-native/src/helpers/FontAwesome.js
vendored
1
react-native/src/helpers/FontAwesome.js
vendored
@@ -1,4 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
check: '\uf00c',
|
check: '\uf00c',
|
||||||
times: '\uf00d',
|
times: '\uf00d',
|
||||||
|
chevronRight: '\uf054',
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user