@ -1,30 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
input=$1
|
|
||||||
locale_name=$2
|
|
||||||
|
|
||||||
cat <<END
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
// THIS FILE WAS AUTOMATICALLY GENERATED
|
|
||||||
//
|
|
||||||
// Please do not submit pull request to modify it. Corrections to translations
|
|
||||||
// may be submitted at https://translate.loophabits.org/
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
package org.isoron.uhabits.i18n
|
|
||||||
|
|
||||||
END
|
|
||||||
|
|
||||||
prefix="override "
|
|
||||||
if [ "$locale_name" == "" ]; then
|
|
||||||
prefix="open "
|
|
||||||
echo "open class Strings() {"
|
|
||||||
else
|
|
||||||
echo "class Strings$locale_name : Strings() {"
|
|
||||||
fi
|
|
||||||
|
|
||||||
grep "<string name" "$1" | \
|
|
||||||
grep -v translatable | \
|
|
||||||
sed 's/&/\&/g' | \
|
|
||||||
sed 's/^.*name="\([^"]*\)">\([^<]*\)<.*/ '"$prefix"'val \1 = "\2"/'
|
|
||||||
|
|
||||||
echo "}"
|
|
@ -1,46 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
INPUT_DIR=../android/uhabits-android/src/main/res/
|
|
||||||
OUTPUT_DIR=../core/src/commonMain/kotlin/org/isoron/uhabits/i18n/
|
|
||||||
|
|
||||||
convert() {
|
|
||||||
./androidStringsToKt.sh $INPUT_DIR/$1/strings.xml "$2" > $OUTPUT_DIR/Strings$2.kt
|
|
||||||
}
|
|
||||||
|
|
||||||
#convert values ""
|
|
||||||
convert values-ar Arabic
|
|
||||||
convert values-bg Bulgarian
|
|
||||||
convert values-ca Catalan
|
|
||||||
convert values-cs Czech
|
|
||||||
convert values-da Danish
|
|
||||||
convert values-de German
|
|
||||||
convert values-el Greek
|
|
||||||
convert values-eo Esperanto
|
|
||||||
convert values-es Spanish
|
|
||||||
convert values-eu Basque
|
|
||||||
convert values-fa Persian
|
|
||||||
convert values-fi Finnish
|
|
||||||
convert values-fr French
|
|
||||||
convert values-hi Hindi
|
|
||||||
convert values-hr Croatian
|
|
||||||
convert values-hu Hungarian
|
|
||||||
convert values-in Indonesian
|
|
||||||
convert values-it Italian
|
|
||||||
convert values-iw Hebrew
|
|
||||||
convert values-ja Japanese
|
|
||||||
convert values-ko Korean
|
|
||||||
convert values-nl Dutch
|
|
||||||
convert values-no-rNO Norwegian
|
|
||||||
convert values-pl Polish
|
|
||||||
convert values-pt-rBR PortugueseBR
|
|
||||||
convert values-pt-rPT PortuguesePT
|
|
||||||
convert values-ro Romanian
|
|
||||||
convert values-ru Russian
|
|
||||||
convert values-sl Slovak
|
|
||||||
convert values-sr Serbian
|
|
||||||
convert values-sv Swedish
|
|
||||||
convert values-tr Turkish
|
|
||||||
convert values-uk Ukrainian
|
|
||||||
convert values-vi Vietnamese
|
|
||||||
convert values-zh-rCN ChineseCN
|
|
||||||
convert values-zh-rTW ChineseTW
|
|
@ -1,5 +0,0 @@
|
|||||||
create table Habits ( id integer primary key autoincrement, archived integer, color integer, description text, freq_den integer, freq_num integer, highlight integer, name text, position integer, reminder_hour integer, reminder_min integer )
|
|
||||||
create table Checkmarks ( id integer primary key autoincrement, habit integer references habits(id), timestamp integer, value integer )
|
|
||||||
create table Repetitions ( id integer primary key autoincrement, habit integer references habits(id), timestamp integer )
|
|
||||||
create table Streak ( id integer primary key autoincrement, end integer, habit integer references habits(id), length integer, start integer )
|
|
||||||
create table Score ( id integer primary key autoincrement, habit integer references habits(id), score integer, timestamp integer )
|
|
@ -1,3 +0,0 @@
|
|||||||
delete from Score
|
|
||||||
delete from Streak
|
|
||||||
delete from Checkmarks
|
|
@ -1 +0,0 @@
|
|||||||
alter table Habits add column reminder_days integer not null default 127
|
|
@ -1,3 +0,0 @@
|
|||||||
delete from Score
|
|
||||||
delete from Streak
|
|
||||||
delete from Checkmarks
|
|
@ -1,4 +0,0 @@
|
|||||||
create index idx_score_habit_timestamp on Score(habit, timestamp)
|
|
||||||
create index idx_checkmark_habit_timestamp on Checkmarks(habit, timestamp)
|
|
||||||
create index idx_repetitions_habit_timestamp on Repetitions(habit, timestamp)
|
|
||||||
create index idx_streak_habit_end on Streak(habit, end)
|
|
@ -1,14 +0,0 @@
|
|||||||
update habits set color=0 where color=-2937041
|
|
||||||
update habits set color=1 where color=-1684967
|
|
||||||
update habits set color=2 where color=-415707
|
|
||||||
update habits set color=3 where color=-5262293
|
|
||||||
update habits set color=4 where color=-13070788
|
|
||||||
update habits set color=5 where color=-16742021
|
|
||||||
update habits set color=6 where color=-16732991
|
|
||||||
update habits set color=7 where color=-16540699
|
|
||||||
update habits set color=8 where color=-10603087
|
|
||||||
update habits set color=9 where color=-7461718
|
|
||||||
update habits set color=10 where color=-2614432
|
|
||||||
update habits set color=11 where color=-13619152
|
|
||||||
update habits set color=12 where color=-5592406
|
|
||||||
update habits set color=0 where color<0 or color>12
|
|
@ -1,3 +0,0 @@
|
|||||||
delete from Score
|
|
||||||
delete from Streak
|
|
||||||
delete from Checkmarks
|
|
@ -1,2 +0,0 @@
|
|||||||
alter table Habits add column type integer not null default 0
|
|
||||||
alter table Repetitions add column value integer not null default 2
|
|
@ -1,5 +0,0 @@
|
|||||||
drop table Score
|
|
||||||
create table Score ( id integer primary key autoincrement, habit integer references habits(id), score real, timestamp integer)
|
|
||||||
create index idx_score_habit_timestamp on Score(habit, timestamp)
|
|
||||||
delete from streak
|
|
||||||
delete from checkmarks
|
|
@ -1,3 +0,0 @@
|
|||||||
alter table Habits add column target_type integer not null default 0
|
|
||||||
alter table Habits add column target_value real not null default 0
|
|
||||||
alter table Habits add column unit text not null default ""
|
|
@ -1 +0,0 @@
|
|||||||
create table Events ( id integer primary key autoincrement, timestamp integer, message text, server_id integer )
|
|
@ -1,3 +0,0 @@
|
|||||||
drop table checkmarks
|
|
||||||
drop table streak
|
|
||||||
drop table score
|
|
@ -1,12 +0,0 @@
|
|||||||
update habits set color=19 where color=12
|
|
||||||
update habits set color=17 where color=11
|
|
||||||
update habits set color=15 where color=10
|
|
||||||
update habits set color=14 where color=9
|
|
||||||
update habits set color=13 where color=8
|
|
||||||
update habits set color=10 where color=7
|
|
||||||
update habits set color=9 where color=6
|
|
||||||
update habits set color=8 where color=5
|
|
||||||
update habits set color=7 where color=4
|
|
||||||
update habits set color=5 where color=3
|
|
||||||
update habits set color=4 where color=2
|
|
||||||
update habits set color=0 where color<0 or color>19
|
|
@ -1,11 +0,0 @@
|
|||||||
delete from repetitions where habit not in (select id from habits)
|
|
||||||
delete from repetitions where timestamp is null
|
|
||||||
delete from repetitions where habit is null
|
|
||||||
delete from repetitions where rowid not in ( select min(rowid) from repetitions group by habit, timestamp )
|
|
||||||
alter table Repetitions rename to RepetitionsBak
|
|
||||||
create table Repetitions ( id integer primary key autoincrement, habit integer not null references habits(id), timestamp integer not null, value integer not null)
|
|
||||||
drop index if exists idx_repetitions_habit_timestamp
|
|
||||||
create unique index idx_repetitions_habit_timestamp on Repetitions( habit, timestamp)
|
|
||||||
insert into Repetitions select * from RepetitionsBak
|
|
||||||
drop table RepetitionsBak
|
|
||||||
pragma foreign_keys=ON
|
|
@ -1 +0,0 @@
|
|||||||
create table Preferences(key text unique not null, value text not null)
|
|
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 350 B |
Before Width: | Height: | Size: 345 B |
Before Width: | Height: | Size: 424 B |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.5 KiB |
@ -1,2 +0,0 @@
|
|||||||
Hello World!
|
|
||||||
This is a resource.
|
|
@ -1,136 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id 'kotlin-multiplatform' version "1.3.72"
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
targets {
|
|
||||||
def sdkName = System.getenv('SDK_NAME')
|
|
||||||
def isIphone = sdkName?.startsWith("iphoneos")
|
|
||||||
def iosPreset = isIphone ? presets.iosArm64 : presets.iosX64
|
|
||||||
|
|
||||||
fromPreset(iosPreset, 'ios') {
|
|
||||||
binaries {
|
|
||||||
framework {
|
|
||||||
baseName = "LoopHabitTracker"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compilations.main {
|
|
||||||
cinterops {
|
|
||||||
sqlite3 {
|
|
||||||
defFile project.file("src/main/c_interop/sqlite3.def")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fromPreset(presets.jvm, 'jvm') {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fromPreset(presets.js, 'js') {
|
|
||||||
compilations.main.kotlinOptions {
|
|
||||||
moduleKind = "commonjs"
|
|
||||||
}
|
|
||||||
compilations.test.kotlinOptions {
|
|
||||||
moduleKind = "commonjs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
commonMain {
|
|
||||||
kotlin { srcDir "src/main/common" }
|
|
||||||
dependencies {
|
|
||||||
implementation kotlin('stdlib-common')
|
|
||||||
implementation "$KX_COROUTINES-core-common:$KX_COROUTINES_VERSION"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commonTest {
|
|
||||||
kotlin { srcDir "src/test/common" }
|
|
||||||
dependencies {
|
|
||||||
implementation kotlin('test-common')
|
|
||||||
implementation kotlin('test-annotations-common')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jvmMain {
|
|
||||||
kotlin { srcDir "src/main/jvm" }
|
|
||||||
dependencies {
|
|
||||||
implementation kotlin('stdlib-jdk8')
|
|
||||||
implementation "$KX_COROUTINES-core:$KX_COROUTINES_VERSION"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jvmTest {
|
|
||||||
kotlin { srcDir "src/test/jvm" }
|
|
||||||
dependencies {
|
|
||||||
implementation kotlin('test')
|
|
||||||
implementation kotlin('test-junit')
|
|
||||||
implementation 'org.xerial:sqlite-jdbc:3.25.2'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jsMain {
|
|
||||||
kotlin { srcDir "src/main/js" }
|
|
||||||
dependencies {
|
|
||||||
implementation kotlin('stdlib-js')
|
|
||||||
implementation "$KX_COROUTINES-core-js:$KX_COROUTINES_VERSION"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jsTest {
|
|
||||||
kotlin { srcDir "src/test/js" }
|
|
||||||
dependencies {
|
|
||||||
implementation kotlin('test-js')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iosMain {
|
|
||||||
kotlin { srcDir "src/main/ios" }
|
|
||||||
dependencies {
|
|
||||||
implementation "$KX_COROUTINES-core-native:$KX_COROUTINES_VERSION"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iosTest {
|
|
||||||
kotlin { srcDir "src/test/ios" }
|
|
||||||
dependencies {
|
|
||||||
implementation "$KX_COROUTINES-core-native:$KX_COROUTINES_VERSION"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task iosTestCopyResources(type: Copy) {
|
|
||||||
from 'assets/test/'
|
|
||||||
from 'assets/main/'
|
|
||||||
into 'build/bin/ios/debugTest'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (project.tasks.findByName('iosTest')) {
|
|
||||||
iosTest.dependsOn(iosTestCopyResources)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
KX_COROUTINES_VERSION=1.3.6
|
|
||||||
KX_COROUTINES=org.jetbrains.kotlinx:kotlinx-coroutines
|
|
@ -1,9 +0,0 @@
|
|||||||
pluginManagement {
|
|
||||||
resolutionStrategy {
|
|
||||||
eachPlugin {
|
|
||||||
if (requested.id.id == "kotlin-multiplatform") {
|
|
||||||
useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package = sqlite3
|
|
||||||
headers = sqlite3.h
|
|
||||||
headerFilter = sqlite3*.h
|
|
||||||
compilerOpts = -std=c11
|
|
||||||
linkerOpts.ios = -lsqlite3
|
|
||||||
excludedFunctions = sqlite3_mutex_held \
|
|
||||||
sqlite3_mutex_notheld \
|
|
||||||
sqlite3_snapshot_cmp \
|
|
||||||
sqlite3_snapshot_free \
|
|
||||||
sqlite3_snapshot_get \
|
|
||||||
sqlite3_snapshot_open \
|
|
||||||
sqlite3_snapshot_recover \
|
|
||||||
sqlite3_set_last_insert_rowid \
|
|
||||||
sqlite3_stmt_scanstatus \
|
|
||||||
sqlite3_stmt_scanstatus_reset \
|
|
||||||
sqlite3_column_database_name \
|
|
||||||
sqlite3_column_database_name16 \
|
|
||||||
sqlite3_column_origin_name \
|
|
||||||
sqlite3_column_origin_name16 \
|
|
||||||
sqlite3_column_table_name \
|
|
||||||
sqlite3_column_table_name16 \
|
|
||||||
sqlite3_enable_load_extension \
|
|
||||||
sqlite3_load_extension \
|
|
||||||
sqlite3_unlock_notify
|
|
||||||
noStringConversion = sqlite3_prepare_v2 sqlite3_prepare_v3
|
|
@ -1,36 +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
|
|
||||||
|
|
||||||
class Observable<T> {
|
|
||||||
private val listeners = mutableListOf<T>()
|
|
||||||
|
|
||||||
fun addListener(listener: T) {
|
|
||||||
listeners.add(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun notifyListeners(action: (T) -> Unit) {
|
|
||||||
for (l in listeners) action.invoke(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeListener(listener: T) {
|
|
||||||
listeners.remove(listener)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +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.gui
|
|
||||||
|
|
||||||
enum class TextAlign {
|
|
||||||
LEFT, CENTER, RIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class Font {
|
|
||||||
REGULAR,
|
|
||||||
BOLD,
|
|
||||||
FONT_AWESOME
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Canvas {
|
|
||||||
fun setColor(color: Color)
|
|
||||||
fun drawLine(x1: Double, y1: Double, x2: Double, y2: Double)
|
|
||||||
fun drawText(text: String, x: Double, y: Double)
|
|
||||||
fun fillRect(x: Double, y: Double, width: Double, height: Double)
|
|
||||||
fun drawRect(x: Double, y: Double, width: Double, height: Double)
|
|
||||||
fun getHeight(): Double
|
|
||||||
fun getWidth(): Double
|
|
||||||
fun setFont(font: Font)
|
|
||||||
fun setFontSize(size: Double)
|
|
||||||
fun setStrokeWidth(size: Double)
|
|
||||||
fun fillArc(centerX: Double,
|
|
||||||
centerY: Double,
|
|
||||||
radius: Double,
|
|
||||||
startAngle: Double,
|
|
||||||
swipeAngle: Double)
|
|
||||||
fun fillCircle(centerX: Double, centerY: Double, radius: Double)
|
|
||||||
fun setTextAlign(align: TextAlign)
|
|
||||||
fun toImage(): Image
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +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.gui
|
|
||||||
|
|
||||||
data class PaletteColor(val index: Int)
|
|
||||||
|
|
||||||
data class Color(val red: Double,
|
|
||||||
val green: Double,
|
|
||||||
val blue: Double,
|
|
||||||
val alpha: Double) {
|
|
||||||
|
|
||||||
val luminosity: Double
|
|
||||||
get() {
|
|
||||||
return 0.21 * red + 0.72 * green + 0.07 * blue
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(rgb: Int) : this(((rgb shr 16) and 0xFF) / 255.0,
|
|
||||||
((rgb shr 8) and 0xFF) / 255.0,
|
|
||||||
((rgb shr 0) and 0xFF) / 255.0,
|
|
||||||
1.0)
|
|
||||||
|
|
||||||
fun blendWith(other: Color, weight: Double): Color {
|
|
||||||
return Color(red * (1 - weight) + other.red * weight,
|
|
||||||
green * (1 - weight) + other.green * weight,
|
|
||||||
blue * (1 - weight) + other.blue * weight,
|
|
||||||
alpha * (1 - weight) + other.alpha * weight)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.platform.gui
|
|
||||||
|
|
||||||
interface Component {
|
|
||||||
fun draw(canvas: Canvas)
|
|
||||||
}
|
|
@ -1,27 +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.gui
|
|
||||||
|
|
||||||
class FontAwesome {
|
|
||||||
companion object {
|
|
||||||
val CHECK = "\uf00c"
|
|
||||||
val TIMES = "\uf00d"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +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.gui
|
|
||||||
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
interface Image {
|
|
||||||
val width: Int
|
|
||||||
val height: Int
|
|
||||||
|
|
||||||
fun getPixel(x: Int, y: Int): Color
|
|
||||||
fun setPixel(x: Int, y: Int, color: Color)
|
|
||||||
|
|
||||||
suspend fun export(path: String)
|
|
||||||
|
|
||||||
fun diff(other: Image) {
|
|
||||||
if (width != other.width) error("Width must match: $width !== ${other.width}")
|
|
||||||
if (height != other.height) error("Height must match: $height !== ${other.height}")
|
|
||||||
|
|
||||||
for (x in 0 until width) {
|
|
||||||
for (y in 0 until height) {
|
|
||||||
val p1 = getPixel(x, y)
|
|
||||||
var l = 1.0
|
|
||||||
for (dx in -2..2) {
|
|
||||||
if (x + dx < 0 || x + dx >= width) continue
|
|
||||||
for (dy in -2..2) {
|
|
||||||
if (y + dy < 0 || y + dy >= height) continue
|
|
||||||
val p2 = other.getPixel(x + dx, y + dy)
|
|
||||||
l = min(l, abs(p1.luminosity - p2.luminosity))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setPixel(x, y, Color(l, l, l, 1.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val averageLuminosity: Double
|
|
||||||
get() {
|
|
||||||
var luminosity = 0.0
|
|
||||||
for (x in 0 until width) {
|
|
||||||
for (y in 0 until height) {
|
|
||||||
luminosity += getPixel(x, y).luminosity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return luminosity / (width * height)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,107 +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.io
|
|
||||||
|
|
||||||
interface PreparedStatement {
|
|
||||||
fun step(): StepResult
|
|
||||||
fun finalize()
|
|
||||||
fun getInt(index: Int): Int
|
|
||||||
fun getLong(index: Int): Long
|
|
||||||
fun getText(index: Int): String
|
|
||||||
fun getReal(index: Int): Double
|
|
||||||
fun bindInt(index: Int, value: Int)
|
|
||||||
fun bindLong(index: Int, value: Long)
|
|
||||||
fun bindText(index: Int, value: String)
|
|
||||||
fun bindReal(index: Int, value: Double)
|
|
||||||
fun reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class StepResult {
|
|
||||||
ROW,
|
|
||||||
DONE
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DatabaseOpener {
|
|
||||||
fun open(file: UserFile): Database
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Database {
|
|
||||||
fun prepareStatement(sql: String): PreparedStatement
|
|
||||||
fun close()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Database.run(sql: String) {
|
|
||||||
val stmt = prepareStatement(sql)
|
|
||||||
stmt.step()
|
|
||||||
stmt.finalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Database.queryInt(sql: String): Int {
|
|
||||||
val stmt = prepareStatement(sql)
|
|
||||||
stmt.step()
|
|
||||||
val result = stmt.getInt(0)
|
|
||||||
stmt.finalize()
|
|
||||||
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() = run("begin")
|
|
||||||
|
|
||||||
fun Database.commit() = run("commit")
|
|
||||||
|
|
||||||
fun Database.getVersion() = queryInt("pragma user_version")
|
|
||||||
|
|
||||||
fun Database.setVersion(v: Int) = run("pragma user_version = $v")
|
|
||||||
|
|
||||||
suspend fun Database.migrateTo(newVersion: Int,
|
|
||||||
fileOpener: FileOpener,
|
|
||||||
log: Log) {
|
|
||||||
val currentVersion = getVersion()
|
|
||||||
log.debug("Database", "Current database version: $currentVersion")
|
|
||||||
|
|
||||||
if (currentVersion == newVersion) return
|
|
||||||
log.debug("Database", "Upgrading to version: $newVersion")
|
|
||||||
|
|
||||||
if (currentVersion > newVersion)
|
|
||||||
throw RuntimeException("database produced by future version of the application")
|
|
||||||
|
|
||||||
begin()
|
|
||||||
for (v in (currentVersion + 1)..newVersion) {
|
|
||||||
val sv = if (v < 10) "00$v" else if (v < 100) "0$v" else "$v"
|
|
||||||
val filename = "migrations/$sv.sql"
|
|
||||||
val migrationFile = fileOpener.openResourceFile(filename)
|
|
||||||
for (line in migrationFile.lines()) {
|
|
||||||
if (line.isEmpty()) continue
|
|
||||||
run(line)
|
|
||||||
}
|
|
||||||
setVersion(v)
|
|
||||||
}
|
|
||||||
commit()
|
|
||||||
}
|
|
@ -1,98 +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.io
|
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
|
|
||||||
interface FileOpener {
|
|
||||||
/**
|
|
||||||
* Opens a file which was shipped bundled with the application, such as a
|
|
||||||
* migration file.
|
|
||||||
*
|
|
||||||
* The path is relative to the assets folder. For example, to open
|
|
||||||
* assets/main/migrations/09.sql you should provide migrations/09.sql
|
|
||||||
* as the path.
|
|
||||||
*
|
|
||||||
* This function always succeed, even if the file does not exist.
|
|
||||||
*/
|
|
||||||
fun openResourceFile(path: String): ResourceFile
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a file which was not shipped with the application, such as
|
|
||||||
* databases and logs.
|
|
||||||
*
|
|
||||||
* The path is relative to the user folder. For example, if the application
|
|
||||||
* stores the user data at /home/user/.loop/ and you wish to open the file
|
|
||||||
* /home/user/.loop/crash.log, you should provide crash.log as the path.
|
|
||||||
*
|
|
||||||
* This function always succeed, even if the file does not exist.
|
|
||||||
*/
|
|
||||||
fun openUserFile(path: String): UserFile
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a file that was created after the application was installed, as a
|
|
||||||
* result of some user action, such as databases and logs.
|
|
||||||
*/
|
|
||||||
interface UserFile {
|
|
||||||
/**
|
|
||||||
* Deletes the user file. If the file does not exist, nothing happens.
|
|
||||||
*/
|
|
||||||
suspend fun delete()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the file exists.
|
|
||||||
*/
|
|
||||||
suspend fun exists(): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the lines of the file. If the file does not exist, throws an
|
|
||||||
* exception.
|
|
||||||
*/
|
|
||||||
suspend fun lines(): List<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a file that was shipped with the application, such as migration
|
|
||||||
* files or database templates.
|
|
||||||
*/
|
|
||||||
interface ResourceFile {
|
|
||||||
/**
|
|
||||||
* Copies the resource file to the specified user file. If the user file
|
|
||||||
* already exists, it is replaced. If not, a new file is created.
|
|
||||||
*/
|
|
||||||
suspend fun copyTo(dest: UserFile)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the lines of the resource file. If the file does not exist,
|
|
||||||
* throws an exception.
|
|
||||||
*/
|
|
||||||
suspend fun lines(): List<String>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the file exists.
|
|
||||||
*/
|
|
||||||
suspend fun exists(): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads resource file as an image.
|
|
||||||
*/
|
|
||||||
suspend fun toImage(): Image
|
|
||||||
}
|
|
@ -1,46 +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.io
|
|
||||||
|
|
||||||
interface Log {
|
|
||||||
fun info(tag: String, msg: String)
|
|
||||||
fun debug(tag: String, msg: String)
|
|
||||||
fun warn(tag: String, msg: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Log that prints to the standard output.
|
|
||||||
*/
|
|
||||||
class StandardLog : Log {
|
|
||||||
override fun warn(tag: String, msg: String) {
|
|
||||||
val ftag = format("%-20s", tag)
|
|
||||||
println("W $ftag $msg")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun info(tag: String, msg: String) {
|
|
||||||
val ftag = format("%-20s", tag)
|
|
||||||
println("I $ftag $msg")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun debug(tag: String, msg: String) {
|
|
||||||
val ftag = format("%-20s", tag)
|
|
||||||
println("D $ftag $msg")
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.platform.io
|
|
||||||
|
|
||||||
expect fun format(format: String, arg: String): String
|
|
||||||
expect fun format(format: String, arg: Int): String
|
|
||||||
expect fun format(format: String, arg: Double): String
|
|
@ -1,173 +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.time
|
|
||||||
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
enum class DayOfWeek(val index: Int) {
|
|
||||||
SUNDAY(0),
|
|
||||||
MONDAY(1),
|
|
||||||
TUESDAY(2),
|
|
||||||
WEDNESDAY(3),
|
|
||||||
THURSDAY(4),
|
|
||||||
FRIDAY(5),
|
|
||||||
SATURDAY(6),
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Timestamp(val millisSince1970: Long) {
|
|
||||||
val localDate: LocalDate
|
|
||||||
get() {
|
|
||||||
val millisSince2000 = millisSince1970 - 946684800000
|
|
||||||
val daysSince2000 = millisSince2000 / 86400000
|
|
||||||
return LocalDate(daysSince2000.toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class LocalDate(val daysSince2000: Int) {
|
|
||||||
|
|
||||||
var yearCache = -1
|
|
||||||
var monthCache = -1
|
|
||||||
var dayCache = -1
|
|
||||||
|
|
||||||
// init {
|
|
||||||
// if (daysSince2000 < 0)
|
|
||||||
// throw IllegalArgumentException("$daysSince2000 < 0")
|
|
||||||
// }
|
|
||||||
|
|
||||||
constructor(year: Int, month: Int, day: Int) :
|
|
||||||
this(daysSince2000(year, month, day))
|
|
||||||
|
|
||||||
val dayOfWeek: DayOfWeek
|
|
||||||
get() {
|
|
||||||
return when (daysSince2000 % 7) {
|
|
||||||
0 -> DayOfWeek.SATURDAY
|
|
||||||
1 -> DayOfWeek.SUNDAY
|
|
||||||
2 -> DayOfWeek.MONDAY
|
|
||||||
3 -> DayOfWeek.TUESDAY
|
|
||||||
4 -> DayOfWeek.WEDNESDAY
|
|
||||||
5 -> DayOfWeek.THURSDAY
|
|
||||||
else -> DayOfWeek.FRIDAY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val timestamp: Timestamp
|
|
||||||
get() {
|
|
||||||
return Timestamp(946684800000 + daysSince2000.toLong() * 86400000)
|
|
||||||
}
|
|
||||||
|
|
||||||
val year: Int
|
|
||||||
get() {
|
|
||||||
if (yearCache < 0) updateYearMonthDayCache()
|
|
||||||
return yearCache
|
|
||||||
}
|
|
||||||
|
|
||||||
val month: Int
|
|
||||||
get() {
|
|
||||||
if (monthCache < 0) updateYearMonthDayCache()
|
|
||||||
return monthCache
|
|
||||||
}
|
|
||||||
|
|
||||||
val day: Int
|
|
||||||
get() {
|
|
||||||
if (dayCache < 0) updateYearMonthDayCache()
|
|
||||||
return dayCache
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateYearMonthDayCache() {
|
|
||||||
var currYear = 2000
|
|
||||||
var currDay = 0
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
val currYearLength = if (isLeapYear(currYear)) 366 else 365
|
|
||||||
if (daysSince2000 < currDay + currYearLength) {
|
|
||||||
yearCache = currYear
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
currYear++
|
|
||||||
currDay += currYearLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var currMonth = 1
|
|
||||||
val monthOffset = if (isLeapYear(currYear)) leapOffset else nonLeapOffset
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (daysSince2000 < currDay + monthOffset[currMonth]) {
|
|
||||||
monthCache = currMonth
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
currMonth++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currDay += monthOffset[currMonth - 1]
|
|
||||||
dayCache = daysSince2000 - currDay + 1
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isOlderThan(other: LocalDate): Boolean {
|
|
||||||
return daysSince2000 < other.daysSince2000
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isNewerThan(other: LocalDate): Boolean {
|
|
||||||
return daysSince2000 > other.daysSince2000
|
|
||||||
}
|
|
||||||
|
|
||||||
fun plus(days: Int): LocalDate {
|
|
||||||
return LocalDate(daysSince2000 + days)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun minus(days: Int): LocalDate {
|
|
||||||
return LocalDate(daysSince2000 - days)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun distanceTo(other: LocalDate): Int {
|
|
||||||
return abs(daysSince2000 - other.daysSince2000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LocalDateFormatter {
|
|
||||||
fun shortWeekdayName(date: LocalDate): String
|
|
||||||
fun shortMonthName(date: LocalDate): String
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isLeapYear(year: Int): Boolean {
|
|
||||||
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val leapOffset = arrayOf(0, 31, 60, 91, 121, 152, 182,
|
|
||||||
213, 244, 274, 305, 335, 366)
|
|
||||||
val nonLeapOffset = arrayOf(0, 31, 59, 90, 120, 151, 181,
|
|
||||||
212, 243, 273, 304, 334, 365)
|
|
||||||
|
|
||||||
private fun daysSince2000(year: Int, month: Int, day: Int): Int {
|
|
||||||
|
|
||||||
var result = 365 * (year - 2000)
|
|
||||||
result += ceil((year - 2000) / 4.0).toInt()
|
|
||||||
result -= ceil((year - 2000) / 100.0).toInt()
|
|
||||||
result += ceil((year - 2000) / 400.0).toInt()
|
|
||||||
if (isLeapYear(year)) {
|
|
||||||
result += leapOffset[month - 1]
|
|
||||||
} else {
|
|
||||||
result += nonLeapOffset[month - 1]
|
|
||||||
}
|
|
||||||
result += (day - 1)
|
|
||||||
return result
|
|
||||||
}
|
|
@ -1,22 +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
|
|
||||||
|
|
||||||
const val LOOP_DATABASE_VERSION = 23
|
|
@ -1,136 +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.backend
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import org.isoron.platform.concurrency.*
|
|
||||||
import org.isoron.platform.io.*
|
|
||||||
import org.isoron.uhabits.*
|
|
||||||
import org.isoron.uhabits.components.*
|
|
||||||
import org.isoron.uhabits.i18n.*
|
|
||||||
import org.isoron.uhabits.models.*
|
|
||||||
import kotlin.coroutines.*
|
|
||||||
|
|
||||||
|
|
||||||
open class BackendScope(private val ctx: CoroutineContext,
|
|
||||||
private val log: Log) : CoroutineScope {
|
|
||||||
|
|
||||||
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 scope: CoroutineContext
|
|
||||||
) : CoroutineScope by BackendScope(scope, log) {
|
|
||||||
|
|
||||||
|
|
||||||
private lateinit var database: Database
|
|
||||||
private lateinit var habitsRepository: HabitRepository
|
|
||||||
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()
|
|
||||||
|
|
||||||
val observable = Observable<Listener>()
|
|
||||||
|
|
||||||
fun init() {
|
|
||||||
launch {
|
|
||||||
initDatabase()
|
|
||||||
initRepositories()
|
|
||||||
initDataSources()
|
|
||||||
observable.notifyListeners { it.onReady() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initRepositories() {
|
|
||||||
preferences = Preferences(PreferencesRepository(database))
|
|
||||||
habitsRepository = HabitRepository(database)
|
|
||||||
checkmarkRepository = CheckmarkRepository(database)
|
|
||||||
habits.putAll(habitsRepository.findAll())
|
|
||||||
log.info("Backend", "${habits.size} habits loaded")
|
|
||||||
for ((key, habit) in habits) {
|
|
||||||
val checks = checkmarkRepository.findAll(key)
|
|
||||||
checkmarks[habit] = CheckmarkList(habit.frequency, habit.type)
|
|
||||||
checkmarks[habit]?.setManualCheckmarks(checks)
|
|
||||||
scores[habit] = ScoreList(checkmarks[habit]!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
val id = habitsRepository.nextId()
|
|
||||||
habit.id = id
|
|
||||||
habit.position = habits.size
|
|
||||||
habits[id] = habit
|
|
||||||
checkmarks[habit] = CheckmarkList(habit.frequency, habit.type)
|
|
||||||
habitsRepository.insert(habit)
|
|
||||||
mainScreenDataSource.requestData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteHabit(id: Int) {
|
|
||||||
habits[id]?.let { habit ->
|
|
||||||
habitsRepository.delete(habit)
|
|
||||||
habits.remove(id)
|
|
||||||
mainScreenDataSource.requestData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateHabit(modified: Habit) {
|
|
||||||
habits[modified.id]?.let { existing ->
|
|
||||||
modified.position = existing.position
|
|
||||||
habitsRepository.update(modified)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
fun onReady()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +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.backend
|
|
||||||
|
|
||||||
import org.isoron.platform.concurrency.*
|
|
||||||
import org.isoron.platform.time.*
|
|
||||||
import org.isoron.uhabits.models.*
|
|
||||||
import org.isoron.uhabits.models.Checkmark.Companion.UNCHECKED
|
|
||||||
|
|
||||||
class MainScreenDataSource(val preferences: Preferences,
|
|
||||||
val habits: MutableMap<Int, Habit>,
|
|
||||||
val checkmarks: MutableMap<Habit, CheckmarkList>,
|
|
||||||
val scores: MutableMap<Habit, ScoreList>) {
|
|
||||||
|
|
||||||
val maxNumberOfButtons = 60
|
|
||||||
private val today = LocalDate(2019, 3, 30) /* TODO */
|
|
||||||
|
|
||||||
data class Data(val habits: List<Habit>,
|
|
||||||
val scores: Map<Habit, Score>,
|
|
||||||
val checkmarks: Map<Habit, List<Checkmark>>)
|
|
||||||
|
|
||||||
val observable = Observable<Listener>()
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
fun onDataChanged(newData: Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requestData() {
|
|
||||||
var filtered = habits.values.toList()
|
|
||||||
|
|
||||||
if (!preferences.showArchived) {
|
|
||||||
filtered = filtered.filter { !it.isArchived }
|
|
||||||
}
|
|
||||||
|
|
||||||
val checkmarks = filtered.associate { habit ->
|
|
||||||
val allValues = checkmarks.getValue(habit).getUntil(today)
|
|
||||||
if (allValues.size <= maxNumberOfButtons) habit to allValues
|
|
||||||
else habit to allValues.subList(0, maxNumberOfButtons)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preferences.showCompleted) {
|
|
||||||
filtered = filtered.filter { habit ->
|
|
||||||
(habit.type == HabitType.BOOLEAN_HABIT && checkmarks.getValue(habit)[0].value == UNCHECKED) ||
|
|
||||||
(habit.type == HabitType.NUMERICAL_HABIT && checkmarks.getValue(habit)[0].value * 1000 < habit.target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val scores = filtered.associate { habit ->
|
|
||||||
habit to scores[habit]!!.getAt(today)
|
|
||||||
}
|
|
||||||
|
|
||||||
observable.notifyListeners { listener ->
|
|
||||||
val data = Data(filtered, scores, checkmarks)
|
|
||||||
listener.onDataChanged(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,164 +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.components
|
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
import org.isoron.platform.time.*
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
class BarChart(var theme: Theme,
|
|
||||||
var dateFormatter: LocalDateFormatter) : Component {
|
|
||||||
|
|
||||||
// Data
|
|
||||||
var series = mutableListOf<List<Double>>()
|
|
||||||
var colors = mutableListOf<Color>()
|
|
||||||
var axis = listOf<LocalDate>()
|
|
||||||
|
|
||||||
// Style
|
|
||||||
var paddingTop = 20.0
|
|
||||||
var paddingLeft = 5.0
|
|
||||||
var paddingRight = 5.0
|
|
||||||
var footerHeight = 40.0
|
|
||||||
var barGroupMargin = 4.0
|
|
||||||
var barMargin = 4.0
|
|
||||||
var barWidth = 20.0
|
|
||||||
var nGridlines = 6
|
|
||||||
var backgroundColor = theme.cardBackgroundColor
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
val width = canvas.getWidth()
|
|
||||||
val height = canvas.getHeight()
|
|
||||||
|
|
||||||
val n = series.size
|
|
||||||
val barGroupWidth = 2 * barGroupMargin + n * (barWidth + 2 * barMargin)
|
|
||||||
val safeWidth = width - paddingLeft - paddingRight
|
|
||||||
val nColumns = floor((safeWidth) / barGroupWidth).toInt()
|
|
||||||
val marginLeft = (safeWidth - nColumns * barGroupWidth) / 2
|
|
||||||
val maxBarHeight = height - footerHeight - paddingTop
|
|
||||||
var maxValue = series.map { it.max()!! }.max()!!
|
|
||||||
maxValue = max(maxValue, 1.0)
|
|
||||||
|
|
||||||
canvas.setColor(backgroundColor)
|
|
||||||
canvas.fillRect(0.0, 0.0, width, height)
|
|
||||||
|
|
||||||
fun barGroupOffset(c: Int) = marginLeft + paddingLeft +
|
|
||||||
(c) * barGroupWidth
|
|
||||||
|
|
||||||
fun barOffset(c: Int, s: Int) = barGroupOffset(c) +
|
|
||||||
barGroupMargin +
|
|
||||||
s * (barWidth + 2 * barMargin) +
|
|
||||||
barMargin
|
|
||||||
|
|
||||||
fun drawColumn(s: Int, c: Int) {
|
|
||||||
val value = if (c < series[s].size) series[s][c] else 0.0
|
|
||||||
val perc = value / maxValue
|
|
||||||
val barColorPerc = if (n > 1) 1.0 else round(perc / 0.20) * 0.20
|
|
||||||
val barColor = theme.lowContrastTextColor.blendWith(colors[s],
|
|
||||||
barColorPerc)
|
|
||||||
val barHeight = round(maxBarHeight * perc)
|
|
||||||
val x = barOffset(c, s)
|
|
||||||
val y = height - footerHeight - barHeight
|
|
||||||
canvas.setColor(barColor)
|
|
||||||
val r = round(barWidth * 0.33)
|
|
||||||
canvas.fillRect(x, y + r, barWidth, barHeight - r)
|
|
||||||
canvas.fillRect(x + r, y, barWidth - 2 * r, r)
|
|
||||||
canvas.fillCircle(x + r, y + r, r)
|
|
||||||
canvas.fillCircle(x + barWidth - r, y + r, r)
|
|
||||||
canvas.setFontSize(theme.smallTextSize)
|
|
||||||
canvas.setTextAlign(TextAlign.CENTER)
|
|
||||||
canvas.setColor(backgroundColor)
|
|
||||||
canvas.fillRect(x - barMargin,
|
|
||||||
y - theme.smallTextSize * 1.25,
|
|
||||||
barWidth + 2 * barMargin,
|
|
||||||
theme.smallTextSize * 1.0)
|
|
||||||
canvas.setColor(theme.mediumContrastTextColor)
|
|
||||||
canvas.drawText(value.toShortString(),
|
|
||||||
x + barWidth / 2,
|
|
||||||
y - theme.smallTextSize * 0.80)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun drawSeries(s: Int) {
|
|
||||||
for (c in 0 until nColumns) drawColumn(s, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun drawMajorGrid() {
|
|
||||||
canvas.setStrokeWidth(1.0)
|
|
||||||
if (n > 1) {
|
|
||||||
canvas.setColor(backgroundColor.blendWith(
|
|
||||||
theme.lowContrastTextColor,
|
|
||||||
0.5))
|
|
||||||
for (c in 0 until nColumns - 1) {
|
|
||||||
val x = barGroupOffset(c)
|
|
||||||
canvas.drawLine(x, paddingTop, x, paddingTop + maxBarHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (k in 1 until nGridlines) {
|
|
||||||
val pct = 1.0 - (k.toDouble() / (nGridlines - 1))
|
|
||||||
val y = paddingTop + maxBarHeight * pct
|
|
||||||
canvas.setColor(theme.lowContrastTextColor)
|
|
||||||
canvas.drawLine(0.0, y, width, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun drawFooter() {
|
|
||||||
val y = paddingTop + maxBarHeight
|
|
||||||
canvas.setColor(backgroundColor)
|
|
||||||
canvas.fillRect(0.0, y, width, height - y)
|
|
||||||
canvas.setColor(theme.lowContrastTextColor)
|
|
||||||
canvas.drawLine(0.0, y, width, y)
|
|
||||||
canvas.setColor(theme.mediumContrastTextColor)
|
|
||||||
canvas.setTextAlign(TextAlign.CENTER)
|
|
||||||
var prevMonth = -1
|
|
||||||
var prevYear = -1
|
|
||||||
val isLargeInterval = (axis[0].distanceTo(axis[1]) > 300)
|
|
||||||
|
|
||||||
for (c in 0 until nColumns) {
|
|
||||||
val x = barGroupOffset(c)
|
|
||||||
val date = axis[c]
|
|
||||||
if(isLargeInterval) {
|
|
||||||
canvas.drawText(date.year.toString(),
|
|
||||||
x + barGroupWidth / 2,
|
|
||||||
y + theme.smallTextSize * 1.0)
|
|
||||||
} else {
|
|
||||||
if (date.month != prevMonth) {
|
|
||||||
canvas.drawText(dateFormatter.shortMonthName(date),
|
|
||||||
x + barGroupWidth / 2,
|
|
||||||
y + theme.smallTextSize * 1.0)
|
|
||||||
} else {
|
|
||||||
canvas.drawText(date.day.toString(),
|
|
||||||
x + barGroupWidth / 2,
|
|
||||||
y + theme.smallTextSize * 1.0)
|
|
||||||
}
|
|
||||||
if (date.year != prevYear) {
|
|
||||||
canvas.drawText(date.year.toString(),
|
|
||||||
x + barGroupWidth / 2,
|
|
||||||
y + theme.smallTextSize * 2.3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevMonth = date.month
|
|
||||||
prevYear = date.year
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drawMajorGrid()
|
|
||||||
for (k in 0 until n) drawSeries(k)
|
|
||||||
drawFooter()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,125 +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.components
|
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
import org.isoron.platform.time.*
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
class CalendarChart(var today: LocalDate,
|
|
||||||
var color: Color,
|
|
||||||
var theme: Theme,
|
|
||||||
var dateFormatter: LocalDateFormatter) : Component {
|
|
||||||
|
|
||||||
var padding = 5.0
|
|
||||||
var backgroundColor = Color(0xFFFFFF)
|
|
||||||
var squareSpacing = 1.0
|
|
||||||
var series = listOf<Double>()
|
|
||||||
var scrollPosition = 0
|
|
||||||
private var squareSize = 0.0
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
val width = canvas.getWidth()
|
|
||||||
val height = canvas.getHeight()
|
|
||||||
canvas.setColor(backgroundColor)
|
|
||||||
canvas.fillRect(0.0, 0.0, width, height)
|
|
||||||
squareSize = round((height - 2 * padding) / 8.0)
|
|
||||||
canvas.setFontSize(height * 0.06)
|
|
||||||
|
|
||||||
val nColumns = floor((width - 2 * padding) / squareSize).toInt() - 2
|
|
||||||
val todayWeekday = today.dayOfWeek
|
|
||||||
val topLeftOffset = (nColumns - 1 + scrollPosition) * 7 + todayWeekday.index
|
|
||||||
val topLeftDate = today.minus(topLeftOffset)
|
|
||||||
|
|
||||||
repeat(nColumns) { column ->
|
|
||||||
val topOffset = topLeftOffset - 7 * column
|
|
||||||
val topDate = topLeftDate.plus(7 * column)
|
|
||||||
drawColumn(canvas, column, topDate, topOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.setColor(theme.mediumContrastTextColor)
|
|
||||||
repeat(7) { row ->
|
|
||||||
val date = topLeftDate.plus(row)
|
|
||||||
canvas.setTextAlign(TextAlign.LEFT)
|
|
||||||
canvas.drawText(dateFormatter.shortWeekdayName(date),
|
|
||||||
padding + nColumns * squareSize + padding,
|
|
||||||
padding + squareSize * (row+1) + squareSize / 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun drawColumn(canvas: Canvas,
|
|
||||||
column: Int,
|
|
||||||
topDate: LocalDate,
|
|
||||||
topOffset: Int) {
|
|
||||||
drawHeader(canvas, column, topDate)
|
|
||||||
repeat(7) { row ->
|
|
||||||
val offset = topOffset - row
|
|
||||||
val date = topDate.plus(row)
|
|
||||||
if (offset < 0) return
|
|
||||||
drawSquare(canvas,
|
|
||||||
padding + column * squareSize,
|
|
||||||
padding + (row + 1) * squareSize,
|
|
||||||
squareSize - squareSpacing,
|
|
||||||
squareSize - squareSpacing,
|
|
||||||
date,
|
|
||||||
offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun drawHeader(canvas: Canvas, column: Int, date: LocalDate) {
|
|
||||||
if (date.day >= 8) return
|
|
||||||
|
|
||||||
canvas.setColor(theme.mediumContrastTextColor)
|
|
||||||
if (date.month == 1) {
|
|
||||||
canvas.drawText(date.year.toString(),
|
|
||||||
padding + column * squareSize + squareSize / 2,
|
|
||||||
padding + squareSize / 2)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
canvas.drawText(dateFormatter.shortMonthName(date),
|
|
||||||
padding + column * squareSize + squareSize / 2,
|
|
||||||
padding + squareSize / 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun drawSquare(canvas: Canvas,
|
|
||||||
x: Double,
|
|
||||||
y: Double,
|
|
||||||
width: Double,
|
|
||||||
height: Double,
|
|
||||||
date: LocalDate,
|
|
||||||
offset: Int) {
|
|
||||||
|
|
||||||
var value = if (offset >= series.size) 0.0 else series[offset]
|
|
||||||
value = round(value * 5.0) / 5.0
|
|
||||||
|
|
||||||
var squareColor = color.blendWith(backgroundColor, 1 - value)
|
|
||||||
var textColor = backgroundColor
|
|
||||||
|
|
||||||
if (value == 0.0) squareColor = theme.lowContrastTextColor
|
|
||||||
if (squareColor.luminosity > 0.8)
|
|
||||||
textColor = squareColor.blendWith(theme.highContrastTextColor, 0.5)
|
|
||||||
|
|
||||||
canvas.setColor(squareColor)
|
|
||||||
canvas.fillRect(x, y, width, height)
|
|
||||||
canvas.setColor(textColor)
|
|
||||||
canvas.drawText(date.day.toString(), x + width / 2, y + width / 2)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +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.components
|
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
|
|
||||||
class CheckmarkButton(private val value: Int,
|
|
||||||
private val color: Color,
|
|
||||||
private val theme: Theme) : Component {
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
canvas.setFont(Font.FONT_AWESOME)
|
|
||||||
canvas.setFontSize(theme.smallTextSize * 1.5)
|
|
||||||
canvas.setColor(when (value) {
|
|
||||||
2 -> color
|
|
||||||
else -> theme.lowContrastTextColor
|
|
||||||
})
|
|
||||||
val text = when (value) {
|
|
||||||
0 -> FontAwesome.TIMES
|
|
||||||
else -> FontAwesome.CHECK
|
|
||||||
}
|
|
||||||
canvas.drawText(text, canvas.getWidth() / 2.0, canvas.getHeight() / 2.0)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +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.components
|
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
import org.isoron.platform.time.*
|
|
||||||
|
|
||||||
class HabitListHeader(private val today: LocalDate,
|
|
||||||
private val nButtons: Int,
|
|
||||||
private val theme: Theme,
|
|
||||||
private val fmt: LocalDateFormatter) : Component {
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
val width = canvas.getWidth()
|
|
||||||
val height = canvas.getHeight()
|
|
||||||
val buttonSize = theme.checkmarkButtonSize
|
|
||||||
canvas.setColor(theme.headerBackgroundColor)
|
|
||||||
canvas.fillRect(0.0, 0.0, width, height)
|
|
||||||
|
|
||||||
canvas.setColor(theme.headerBorderColor)
|
|
||||||
canvas.setStrokeWidth(0.5)
|
|
||||||
canvas.drawLine(0.0, height - 0.5, width, height - 0.5)
|
|
||||||
|
|
||||||
canvas.setColor(theme.headerTextColor)
|
|
||||||
canvas.setFont(Font.BOLD)
|
|
||||||
canvas.setFontSize(theme.smallTextSize)
|
|
||||||
|
|
||||||
repeat(nButtons) { index ->
|
|
||||||
val date = today.minus(nButtons - index - 1)
|
|
||||||
val name = fmt.shortWeekdayName(date).toUpperCase()
|
|
||||||
val number = date.day.toString()
|
|
||||||
|
|
||||||
val x = width - (index + 1) * buttonSize + buttonSize / 2
|
|
||||||
val y = height / 2
|
|
||||||
canvas.drawText(name, x, y - theme.smallTextSize * 0.6)
|
|
||||||
canvas.drawText(number, x, y + theme.smallTextSize * 0.6)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +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.components
|
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
import org.isoron.platform.io.*
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
fun Double.toShortString(): String = when {
|
|
||||||
this >= 1e9 -> format("%.1fG", this / 1e9)
|
|
||||||
this >= 1e8 -> format("%.0fM", this / 1e6)
|
|
||||||
this >= 1e7 -> format("%.1fM", this / 1e6)
|
|
||||||
this >= 1e6 -> format("%.1fM", this / 1e6)
|
|
||||||
this >= 1e5 -> format("%.0fk", this / 1e3)
|
|
||||||
this >= 1e4 -> format("%.1fk", this / 1e3)
|
|
||||||
this >= 1e3 -> format("%.1fk", this / 1e3)
|
|
||||||
this >= 1e2 -> format("%.0f", this)
|
|
||||||
this >= 1e1 -> when {
|
|
||||||
round(this) == this -> format("%.0f", this)
|
|
||||||
else -> format("%.1f", this)
|
|
||||||
}
|
|
||||||
else -> when {
|
|
||||||
round(this) == this -> format("%.0f", this)
|
|
||||||
round(this * 10) == this * 10 -> format("%.1f", this)
|
|
||||||
else -> format("%.2f", this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NumberButton(val color: Color,
|
|
||||||
val value: Double,
|
|
||||||
val threshold: Double,
|
|
||||||
val units: String,
|
|
||||||
val theme: Theme) : Component {
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
val width = canvas.getWidth()
|
|
||||||
val height = canvas.getHeight()
|
|
||||||
val em = theme.smallTextSize
|
|
||||||
|
|
||||||
canvas.setColor(when {
|
|
||||||
value >= threshold -> color
|
|
||||||
value >= 0.01 -> theme.mediumContrastTextColor
|
|
||||||
else -> theme.lowContrastTextColor
|
|
||||||
})
|
|
||||||
|
|
||||||
canvas.setFontSize(theme.regularTextSize)
|
|
||||||
canvas.setFont(Font.BOLD)
|
|
||||||
canvas.drawText(value.toShortString(), width / 2, height / 2 - 0.6 * em)
|
|
||||||
|
|
||||||
canvas.setFontSize(theme.smallTextSize)
|
|
||||||
canvas.setFont(Font.REGULAR)
|
|
||||||
canvas.drawText(units, width / 2, height / 2 + 0.6 * em)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +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.components
|
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
import org.isoron.platform.io.*
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
class Ring(val color: Color,
|
|
||||||
val percentage: Double,
|
|
||||||
val thickness: Double,
|
|
||||||
val radius: Double,
|
|
||||||
val theme: Theme,
|
|
||||||
val label: Boolean = false) : Component {
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
val width = canvas.getWidth()
|
|
||||||
val height = canvas.getHeight()
|
|
||||||
val angle = 360.0 * max(0.0, min(360.0, percentage))
|
|
||||||
|
|
||||||
canvas.setColor(theme.lowContrastTextColor)
|
|
||||||
canvas.fillCircle(width/2, height/2, radius)
|
|
||||||
|
|
||||||
canvas.setColor(color)
|
|
||||||
canvas.fillArc(width/2, height/2, radius, 90.0, -angle)
|
|
||||||
|
|
||||||
canvas.setColor(theme.cardBackgroundColor)
|
|
||||||
canvas.fillCircle(width/2, height/2, radius - thickness)
|
|
||||||
|
|
||||||
if(label) {
|
|
||||||
canvas.setColor(color)
|
|
||||||
canvas.setFontSize(radius * 0.4)
|
|
||||||
canvas.drawText(format("%.0f%%", percentage * 100), width / 2, height / 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +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.components
|
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
|
|
||||||
abstract class Theme {
|
|
||||||
val toolbarColor = Color(0xffffff)
|
|
||||||
|
|
||||||
val lowContrastTextColor = Color(0xe0e0e0)
|
|
||||||
val mediumContrastTextColor = Color(0x808080)
|
|
||||||
val highContrastTextColor = Color(0x202020)
|
|
||||||
|
|
||||||
val cardBackgroundColor = Color(0xFFFFFF)
|
|
||||||
val appBackgroundColor = Color(0xf4f4f4)
|
|
||||||
val toolbarBackgroundColor = Color(0xf4f4f4)
|
|
||||||
val statusBarBackgroundColor = Color(0x333333)
|
|
||||||
|
|
||||||
val headerBackgroundColor = Color(0xeeeeee)
|
|
||||||
val headerBorderColor = Color(0xcccccc)
|
|
||||||
val headerTextColor = mediumContrastTextColor
|
|
||||||
|
|
||||||
val itemBackgroundColor = Color(0xffffff)
|
|
||||||
|
|
||||||
fun color(paletteIndex: Int): Color {
|
|
||||||
return when (paletteIndex) {
|
|
||||||
0 -> Color(0xD32F2F)
|
|
||||||
1 -> Color(0x512DA8)
|
|
||||||
2 -> Color(0xF57C00)
|
|
||||||
3 -> Color(0xFF8F00)
|
|
||||||
4 -> Color(0xF9A825)
|
|
||||||
5 -> Color(0xAFB42B)
|
|
||||||
6 -> Color(0x7CB342)
|
|
||||||
7 -> Color(0x388E3C)
|
|
||||||
8 -> Color(0x00897B)
|
|
||||||
9 -> Color(0x00ACC1)
|
|
||||||
10 -> Color(0x039BE5)
|
|
||||||
11 -> Color(0x1976D2)
|
|
||||||
12 -> Color(0x303F9F)
|
|
||||||
13 -> Color(0x5E35B1)
|
|
||||||
14 -> Color(0x8E24AA)
|
|
||||||
15 -> Color(0xD81B60)
|
|
||||||
16 -> Color(0x5D4037)
|
|
||||||
else -> Color(0x000000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val checkmarkButtonSize = 48.0
|
|
||||||
val smallTextSize = 12.0
|
|
||||||
val regularTextSize = 17.0
|
|
||||||
}
|
|
||||||
|
|
||||||
class LightTheme : Theme()
|
|
@ -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.uhabits.i18n
|
|
||||||
|
|
||||||
interface LocaleHelper {
|
|
||||||
fun getStringsForCurrentLocale(): Strings
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
package org.isoron.uhabits.i18n
|
|
||||||
|
|
||||||
@Suppress("PropertyName", "unused")
|
|
||||||
open class Strings() {
|
|
||||||
open val about = "About"
|
|
||||||
open val action = "Action"
|
|
||||||
open val action_settings = "Settings"
|
|
||||||
open val add_habit = "Add habit"
|
|
||||||
open val all_time = "All time"
|
|
||||||
open val any_day = "Any day of the week"
|
|
||||||
open val any_weekday = "Monday to Friday"
|
|
||||||
open val app_name = "Loop Habit Tracker"
|
|
||||||
open val archive = "Archive"
|
|
||||||
open val behavior = "Behavior"
|
|
||||||
open val best_streaks = "Best streaks"
|
|
||||||
open val bug_report_failed = "Failed to generate bug report."
|
|
||||||
open val by_color = "By color"
|
|
||||||
open val by_name = "By name"
|
|
||||||
open val by_score = "By score"
|
|
||||||
open val calendar = "Calendar"
|
|
||||||
open val cancel = "Cancel"
|
|
||||||
open val change_value = "Change value"
|
|
||||||
open val check = "Check"
|
|
||||||
open val checkmark = "Checkmark"
|
|
||||||
open val checkmark_stack_widget = "Checkmark Stack Widget"
|
|
||||||
open val clear = "Clear"
|
|
||||||
open val clear_label = "Clear"
|
|
||||||
open val color_picker_default_title = "Change color"
|
|
||||||
open val could_not_export = "Failed to export data."
|
|
||||||
open val could_not_import = "Failed to import data."
|
|
||||||
open val count = "Count"
|
|
||||||
open val create_habit = "Create habit"
|
|
||||||
open val create_stackview_widget_button = "StackView Widget For All Habits"
|
|
||||||
open val current_streaks = "Current streak"
|
|
||||||
open val custom_frequency = "Custom …"
|
|
||||||
open val customize_notification = "Customize notifications"
|
|
||||||
open val customize_notification_summary = "Change sound, vibration, light and other notification settings"
|
|
||||||
open val database_repaired = "Database repaired."
|
|
||||||
open val day = "Day"
|
|
||||||
open val days = "days"
|
|
||||||
open val delete = "Delete"
|
|
||||||
open val delete_habits = "Delete Habits"
|
|
||||||
open val delete_habits_message = "The habits will be permanently deleted. This action cannot be undone."
|
|
||||||
open val description_hint = "Question (Did you … today?)"
|
|
||||||
open val developers = "Developers"
|
|
||||||
open val discard = "Discard"
|
|
||||||
open val done_label = "Done"
|
|
||||||
open val download = "Download"
|
|
||||||
open val edit = "Edit"
|
|
||||||
open val edit_habit = "Edit habit"
|
|
||||||
open val every_day = "Every day"
|
|
||||||
open val every_week = "Every week"
|
|
||||||
open val every_x_days = "Every %d days"
|
|
||||||
open val every_x_months = "Every %d months"
|
|
||||||
open val every_x_weeks = "Every %d weeks"
|
|
||||||
open val example_question_boolean = "e.g. Did you exercise today?"
|
|
||||||
open val example_question_numerical = "e.g. How many steps did you walk today?"
|
|
||||||
open val example_units = "e.g. steps"
|
|
||||||
open val export = "Export"
|
|
||||||
open val export_as_csv_summary = "Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc. This file cannot be imported back."
|
|
||||||
open val export_full_backup = "Export full backup"
|
|
||||||
open val export_full_backup_summary = "Generates a file that contains all your data. This file can be imported back."
|
|
||||||
open val export_to_csv = "Export as CSV"
|
|
||||||
open val file_not_recognized = "File not recognized."
|
|
||||||
open val filter = "Filter"
|
|
||||||
open val five_times_per_week = "5 times per week"
|
|
||||||
open val frequency = "Frequency"
|
|
||||||
open val frequency_stack_widget = "Frequency Stack Widget"
|
|
||||||
open val full_backup_success = "Full backup successfully exported."
|
|
||||||
open val generate_bug_report = "Generate bug report"
|
|
||||||
open val habit = "Habit"
|
|
||||||
open val habit_not_found = "Habit deleted / not found"
|
|
||||||
open val habit_strength = "Habit strength"
|
|
||||||
open val habits_imported = "Habits imported successfully."
|
|
||||||
open val help = "Help & FAQ"
|
|
||||||
open val help_translate = "Help translate this app"
|
|
||||||
open val hide_archived = "Hide archived"
|
|
||||||
open val hide_completed = "Hide completed"
|
|
||||||
open val hint_drag = "To rearrange the entries, press-and-hold on the name of the habit, then drag it to the correct place."
|
|
||||||
open val hint_landscape = "You can see more days by putting your phone in landscape mode."
|
|
||||||
open val hint_title = "Did you know?"
|
|
||||||
open val history = "History"
|
|
||||||
open val history_stack_widget = "History Stack Widget"
|
|
||||||
open val import_data = "Import data"
|
|
||||||
open val import_data_summary = "Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information."
|
|
||||||
open val interface_preferences = "Interface"
|
|
||||||
open val interval_15_minutes = "15 minutes"
|
|
||||||
open val interval_1_hour = "1 hour"
|
|
||||||
open val interval_24_hour = "24 hours"
|
|
||||||
open val interval_2_hour = "2 hours"
|
|
||||||
open val interval_30_minutes = "30 minutes"
|
|
||||||
open val interval_4_hour = "4 hours"
|
|
||||||
open val interval_8_hour = "8 hours"
|
|
||||||
open val interval_always_ask = "Always ask"
|
|
||||||
open val interval_custom = "Custom..."
|
|
||||||
open val intro_description_1 = "Loop Habit Tracker helps you create and maintain good habits."
|
|
||||||
open val intro_description_2 = "Every day, after performing your habit, put a checkmark on the app."
|
|
||||||
open val intro_description_3 = "Habits performed consistently for a long time will earn a full star."
|
|
||||||
open val intro_description_4 = "Detailed graphs show you how your habits improved over time."
|
|
||||||
open val intro_title_1 = "Welcome"
|
|
||||||
open val intro_title_2 = "Create some new habits"
|
|
||||||
open val intro_title_3 = "Keep doing it"
|
|
||||||
open val intro_title_4 = "Track your progress"
|
|
||||||
open val last_x_days = "Last %d days"
|
|
||||||
open val last_x_months = "Last %d months"
|
|
||||||
open val last_x_weeks = "Last %d weeks"
|
|
||||||
open val last_x_years = "Last %d years"
|
|
||||||
open val led_notifications = "Notification light"
|
|
||||||
open val led_notifications_description = "Shows a blinking light for reminders. Only available in phones with LED notification lights."
|
|
||||||
open val links = "Links"
|
|
||||||
open val long_press_to_edit = "Press-and-hold to change the value"
|
|
||||||
open val long_press_to_toggle = "Press-and-hold to check or uncheck"
|
|
||||||
open val main_activity_title = "Habits"
|
|
||||||
open val manually = "Manually"
|
|
||||||
open val month = "Month"
|
|
||||||
open val name = "Name"
|
|
||||||
open val night_mode = "Night mode"
|
|
||||||
open val no = "No"
|
|
||||||
open val no_habits_found = "You have no active habits"
|
|
||||||
open val none = "None"
|
|
||||||
open val number_of_repetitions = "Number of repetitions"
|
|
||||||
open val overview = "Overview"
|
|
||||||
open val pref_rate_this_app = "Rate this app on Google Play"
|
|
||||||
open val pref_send_feedback = "Send feedback to developer"
|
|
||||||
open val pref_snooze_interval_title = "Snooze interval on reminders"
|
|
||||||
open val pref_toggle_description = "Put checkmarks with a single tap instead of press-and-hold. More convenient, but might cause accidental toggles."
|
|
||||||
open val pref_toggle_title = "Toggle with short press"
|
|
||||||
open val pref_view_app_introduction = "View app introduction"
|
|
||||||
open val pref_view_source_code = "View source code at GitHub"
|
|
||||||
open val pure_black_description = "Replaces gray backgrounds with pure black in night mode. Reduces battery usage in phones with AMOLED display."
|
|
||||||
open val quarter = "Quarter"
|
|
||||||
open val question = "Question"
|
|
||||||
open val reminder = "Reminder"
|
|
||||||
open val reminder_off = "Off"
|
|
||||||
open val reminder_sound = "Reminder sound"
|
|
||||||
open val repair_database = "Repair database"
|
|
||||||
open val repeat = "Repeat"
|
|
||||||
open val reverse_days = "Reverse order of days"
|
|
||||||
open val reverse_days_description = "Show days in reverse order on the main screen."
|
|
||||||
open val save = "Save"
|
|
||||||
open val score = "Score"
|
|
||||||
open val score_stack_widget = "Score Stack Widget"
|
|
||||||
open val select_habit_requirement_prompt = "Please select at least one habit"
|
|
||||||
open val select_hours = "Select hours"
|
|
||||||
open val select_minutes = "Select minutes"
|
|
||||||
open val select_snooze_delay = "Select snooze delay"
|
|
||||||
open val select_weekdays = "Select days"
|
|
||||||
open val settings = "Settings"
|
|
||||||
open val show_archived = "Show archived"
|
|
||||||
open val show_completed = "Show completed"
|
|
||||||
open val snooze = "Later"
|
|
||||||
open val snooze_interval = "Snooze interval"
|
|
||||||
open val sort = "Sort"
|
|
||||||
open val sticky_notifications = "Make notifications sticky"
|
|
||||||
open val sticky_notifications_description = "Prevents notifications from being swiped away."
|
|
||||||
open val streaks = "Streaks"
|
|
||||||
open val streaks_stack_widget = "Streaks Stack Widget"
|
|
||||||
open val strength = "Strength"
|
|
||||||
open val target = "Target"
|
|
||||||
open val time_every = "time in"
|
|
||||||
open val times_every = "times in"
|
|
||||||
open val toast_habit_archived = "Habits archived"
|
|
||||||
open val toast_habit_changed = "Habit changed"
|
|
||||||
open val toast_habit_changed_back = "Habit changed back"
|
|
||||||
open val toast_habit_created = "Habit created"
|
|
||||||
open val toast_habit_deleted = "Habits deleted"
|
|
||||||
open val toast_habit_restored = "Habits restored"
|
|
||||||
open val toast_habit_unarchived = "Habits unarchived"
|
|
||||||
open val toast_nothing_to_redo = "Nothing to redo"
|
|
||||||
open val toast_nothing_to_undo = "Nothing to undo"
|
|
||||||
open val toggle = "Toggle"
|
|
||||||
open val total = "Total"
|
|
||||||
open val translators = "Translators"
|
|
||||||
open val troubleshooting = "Troubleshooting"
|
|
||||||
open val two_times_per_week = "2 times per week"
|
|
||||||
open val unarchive = "Unarchive"
|
|
||||||
open val uncheck = "Uncheck"
|
|
||||||
open val unit = "Unit"
|
|
||||||
open val use_pure_black = "Use pure black in night mode"
|
|
||||||
open val validation_at_most_one_rep_per_day = "You can have at most one repetition per day"
|
|
||||||
open val validation_name_should_not_be_blank = "Name cannot be blank."
|
|
||||||
open val validation_number_should_be_positive = "Number must be positive."
|
|
||||||
open val validation_show_not_be_blank = "This field should not be blank"
|
|
||||||
open val version_n = "Version %s"
|
|
||||||
open val week = "Week"
|
|
||||||
open val weekends = "Weekends"
|
|
||||||
open val year = "Year"
|
|
||||||
open val yes = "Yes"
|
|
||||||
open val day_mode = "Day mode"
|
|
||||||
}
|
|
@ -1,47 +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.platform.time.*
|
|
||||||
|
|
||||||
data class Checkmark(var date: LocalDate,
|
|
||||||
var value: Int) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Value assigned when the user has explicitly marked the habit as
|
|
||||||
* completed.
|
|
||||||
*/
|
|
||||||
const val CHECKED_MANUAL = 2
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value assigned when the user has not explicitly marked the habit as
|
|
||||||
* completed, however, due to the frequency of the habit, an automatic
|
|
||||||
* checkmark was added.
|
|
||||||
*/
|
|
||||||
const val CHECKED_AUTOMATIC = 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value assigned when the user has not completed the habit, and the app
|
|
||||||
* has not automatically a checkmark.
|
|
||||||
*/
|
|
||||||
const val UNCHECKED = 0
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,204 +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.platform.time.*
|
|
||||||
import org.isoron.uhabits.models.Checkmark.Companion.CHECKED_AUTOMATIC
|
|
||||||
import org.isoron.uhabits.models.Checkmark.Companion.CHECKED_MANUAL
|
|
||||||
import org.isoron.uhabits.models.Checkmark.Companion.UNCHECKED
|
|
||||||
|
|
||||||
class CheckmarkList(val frequency: Frequency,
|
|
||||||
val habitType: HabitType) {
|
|
||||||
|
|
||||||
private val manualCheckmarks = mutableListOf<Checkmark>()
|
|
||||||
private val computedCheckmarks = mutableListOf<Checkmark>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the entire list of manual checkmarks by the ones provided. The
|
|
||||||
* list of automatic checkmarks will be automatically updated.
|
|
||||||
*/
|
|
||||||
fun setManualCheckmarks(checks: List<Checkmark>) {
|
|
||||||
manualCheckmarks.clear()
|
|
||||||
computedCheckmarks.clear()
|
|
||||||
manualCheckmarks.addAll(checks)
|
|
||||||
if (habitType == HabitType.NUMERICAL_HABIT) {
|
|
||||||
computedCheckmarks.addAll(checks)
|
|
||||||
} else {
|
|
||||||
val computed = computeCheckmarks(checks, frequency)
|
|
||||||
computedCheckmarks.addAll(computed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns values of all checkmarks (manual and automatic) from the oldest
|
|
||||||
* entry until the date provided.
|
|
||||||
*
|
|
||||||
* The interval is inclusive, and the list is sorted from newest to oldest.
|
|
||||||
* That is, the first element of the returned list corresponds to the date
|
|
||||||
* provided.
|
|
||||||
*/
|
|
||||||
fun getUntil(date: LocalDate): List<Checkmark> {
|
|
||||||
if (computedCheckmarks.isEmpty()) return listOf()
|
|
||||||
|
|
||||||
val result = mutableListOf<Checkmark>()
|
|
||||||
val newest = computedCheckmarks.first().date
|
|
||||||
val distToNewest = newest.distanceTo(date)
|
|
||||||
|
|
||||||
var k = 0
|
|
||||||
var fromIndex = 0
|
|
||||||
val toIndex = computedCheckmarks.size
|
|
||||||
if (newest.isOlderThan(date)) {
|
|
||||||
repeat(distToNewest) { result.add(Checkmark(date.minus(k++), UNCHECKED)) }
|
|
||||||
} else {
|
|
||||||
fromIndex = distToNewest
|
|
||||||
}
|
|
||||||
val subList = computedCheckmarks.subList(fromIndex, toIndex)
|
|
||||||
result.addAll(subList.map { Checkmark(date.minus(k++), it.value) })
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Computes the list of automatic checkmarks a list of manual ones.
|
|
||||||
*/
|
|
||||||
fun computeCheckmarks(checks: List<Checkmark>,
|
|
||||||
frequency: Frequency
|
|
||||||
): MutableList<Checkmark> {
|
|
||||||
|
|
||||||
val intervals = buildIntervals(checks, frequency)
|
|
||||||
snapIntervalsTogether(intervals)
|
|
||||||
return buildCheckmarksFromIntervals(checks, intervals)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies the intervals so that gaps between intervals are eliminated.
|
|
||||||
*
|
|
||||||
* More specifically, this function shifts the beginning and the end of
|
|
||||||
* intervals so that they overlap the least as possible. The center of
|
|
||||||
* the interval, however, still falls within the interval. The length of
|
|
||||||
* the intervals are also not modified.
|
|
||||||
*/
|
|
||||||
fun snapIntervalsTogether(intervals: MutableList<Interval>) {
|
|
||||||
|
|
||||||
for (i in 1 until intervals.size) {
|
|
||||||
val (begin, center, end) = intervals[i]
|
|
||||||
val (_, _, prevEnd) = intervals[i - 1]
|
|
||||||
|
|
||||||
val gap = prevEnd.distanceTo(begin) - 1
|
|
||||||
if (gap <= 0 || end.minus(gap).isOlderThan(center)) continue
|
|
||||||
intervals[i] = Interval(begin.minus(gap),
|
|
||||||
center,
|
|
||||||
end.minus(gap))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a list of (manually checked) checkmarks and computed
|
|
||||||
* intervals into a list of unchecked, manually checked and
|
|
||||||
* automatically checked checkmarks.
|
|
||||||
*
|
|
||||||
* Manual checkmarks are simply copied over to the output list. Days
|
|
||||||
* that are an interval, but which do not have manual checkmarks receive
|
|
||||||
* automatic checkmarks. Days that fall in the gaps between intervals
|
|
||||||
* receive unchecked checkmarks.
|
|
||||||
*/
|
|
||||||
fun buildCheckmarksFromIntervals(checks: List<Checkmark>,
|
|
||||||
intervals: List<Interval>
|
|
||||||
): MutableList<Checkmark> {
|
|
||||||
|
|
||||||
if (checks.isEmpty()) throw IllegalArgumentException()
|
|
||||||
if (intervals.isEmpty()) throw IllegalArgumentException()
|
|
||||||
|
|
||||||
var oldest = intervals[0].begin
|
|
||||||
var newest = intervals[0].end
|
|
||||||
for (interval in intervals) {
|
|
||||||
if (interval.begin.isOlderThan(oldest)) oldest = interval.begin
|
|
||||||
if (interval.end.isNewerThan(newest)) newest = interval.end
|
|
||||||
}
|
|
||||||
for (check in checks) {
|
|
||||||
if (check.date.isOlderThan(oldest)) oldest = check.date
|
|
||||||
if (check.date.isNewerThan(newest)) newest = check.date
|
|
||||||
}
|
|
||||||
|
|
||||||
val distance = oldest.distanceTo(newest)
|
|
||||||
val checkmarks = mutableListOf<Checkmark>()
|
|
||||||
for (offset in 0..distance)
|
|
||||||
checkmarks.add(Checkmark(newest.minus(offset),
|
|
||||||
UNCHECKED))
|
|
||||||
|
|
||||||
for (interval in intervals) {
|
|
||||||
val beginOffset = newest.distanceTo(interval.begin)
|
|
||||||
val endOffset = newest.distanceTo(interval.end)
|
|
||||||
|
|
||||||
for (offset in endOffset..beginOffset) {
|
|
||||||
checkmarks.set(offset,
|
|
||||||
Checkmark(newest.minus(offset),
|
|
||||||
CHECKED_AUTOMATIC))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (check in checks) {
|
|
||||||
val offset = newest.distanceTo(check.date)
|
|
||||||
checkmarks.set(offset, Checkmark(check.date, CHECKED_MANUAL))
|
|
||||||
}
|
|
||||||
|
|
||||||
return checkmarks
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a list of intervals based on a list of (manual)
|
|
||||||
* checkmarks.
|
|
||||||
*/
|
|
||||||
fun buildIntervals(checks: List<Checkmark>,
|
|
||||||
frequency: Frequency): MutableList<Interval> {
|
|
||||||
|
|
||||||
val num = frequency.numerator
|
|
||||||
val den = frequency.denominator
|
|
||||||
|
|
||||||
val intervals = mutableListOf<Interval>()
|
|
||||||
for (i in 0..(checks.size - num)) {
|
|
||||||
val first = checks[i]
|
|
||||||
val last = checks[i + num - 1]
|
|
||||||
|
|
||||||
if (first.date.distanceTo(last.date) >= den) continue
|
|
||||||
|
|
||||||
val end = first.date.plus(den - 1)
|
|
||||||
intervals.add(Interval(first.date, last.date, end))
|
|
||||||
}
|
|
||||||
|
|
||||||
return intervals
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For non-daily habits, some groups of repetitions generate many
|
|
||||||
* automatic checkmarks. For weekly habits, each repetition generates
|
|
||||||
* seven checkmarks. For twice-a-week habits, two repetitions that are close
|
|
||||||
* enough together also generate seven checkmarks. This group of generated
|
|
||||||
* checkmarks is represented by an interval.
|
|
||||||
*
|
|
||||||
* The fields `begin` and `end` indicate the length of the interval, and are
|
|
||||||
* inclusive. The field `center` indicates the newest day within the interval
|
|
||||||
* that has a manual checkmark.
|
|
||||||
*/
|
|
||||||
data class Interval(val begin: LocalDate,
|
|
||||||
val center: LocalDate,
|
|
||||||
val end: LocalDate)
|
|
||||||
}
|
|
@ -1,59 +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.platform.io.*
|
|
||||||
import org.isoron.platform.time.*
|
|
||||||
|
|
||||||
class CheckmarkRepository(db: Database) {
|
|
||||||
|
|
||||||
private val findStatement = db.prepareStatement("select timestamp, value from Repetitions where habit = ? order by timestamp desc")
|
|
||||||
private val insertStatement = db.prepareStatement("insert into Repetitions(habit, timestamp, value) values (?, ?, ?)")
|
|
||||||
private val deleteStatement = db.prepareStatement("delete from Repetitions where habit=? and timestamp=?")
|
|
||||||
|
|
||||||
fun findAll(habitId: Int): List<Checkmark> {
|
|
||||||
findStatement.bindInt(0, habitId)
|
|
||||||
val result = mutableListOf<Checkmark>()
|
|
||||||
while (findStatement.step() == StepResult.ROW) {
|
|
||||||
val date = Timestamp(findStatement.getLong(0)).localDate
|
|
||||||
val value = findStatement.getInt(1)
|
|
||||||
result.add(Checkmark(date, value))
|
|
||||||
}
|
|
||||||
findStatement.reset()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun insert(habitId: Int, checkmark: Checkmark) {
|
|
||||||
val timestamp = checkmark.date.timestamp
|
|
||||||
insertStatement.bindInt(0, habitId)
|
|
||||||
insertStatement.bindLong(1, timestamp.millisSince1970)
|
|
||||||
insertStatement.bindInt(2, checkmark.value)
|
|
||||||
insertStatement.step()
|
|
||||||
insertStatement.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun delete(habitId: Int, date: LocalDate) {
|
|
||||||
val timestamp = date.timestamp
|
|
||||||
deleteStatement.bindInt(0, habitId)
|
|
||||||
deleteStatement.bindLong(1, timestamp.millisSince1970)
|
|
||||||
deleteStatement.step()
|
|
||||||
deleteStatement.reset()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +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
|
|
||||||
|
|
||||||
data class Frequency(val numerator: Int,
|
|
||||||
val denominator: Int) {
|
|
||||||
|
|
||||||
fun toDouble(): Double {
|
|
||||||
return numerator.toDouble() / denominator
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val WEEKLY = Frequency(1, 7)
|
|
||||||
val DAILY = Frequency(1, 1)
|
|
||||||
val TWO_TIMES_PER_WEEK = Frequency(2, 7)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +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.platform.gui.*
|
|
||||||
|
|
||||||
data class Habit(var id: Int,
|
|
||||||
var name: String,
|
|
||||||
var description: String,
|
|
||||||
var frequency: Frequency,
|
|
||||||
var color: PaletteColor,
|
|
||||||
var isArchived: Boolean,
|
|
||||||
var position: Int,
|
|
||||||
var unit: String,
|
|
||||||
var target: Double,
|
|
||||||
var type: HabitType)
|
|
@ -1,100 +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.platform.gui.*
|
|
||||||
import org.isoron.platform.io.Database
|
|
||||||
import org.isoron.platform.io.PreparedStatement
|
|
||||||
import org.isoron.platform.io.StepResult
|
|
||||||
import org.isoron.platform.io.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 = PaletteColor(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.BOOLEAN_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.index)
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +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
|
|
||||||
|
|
||||||
enum class HabitType(val code: Int) {
|
|
||||||
BOOLEAN_HABIT(0),
|
|
||||||
NUMERICAL_HABIT(1),
|
|
||||||
}
|
|
@ -1,40 +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
|
|
||||||
|
|
||||||
class Preferences(private val repository: PreferencesRepository) {
|
|
||||||
var showArchived = repository.getBoolean("show_archived", false)
|
|
||||||
set(value) {
|
|
||||||
repository.putBoolean("show_archived", value)
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
var showCompleted = repository.getBoolean("show_completed", true)
|
|
||||||
set(value) {
|
|
||||||
repository.putBoolean("show_completed", value)
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
var nightMode = repository.getBoolean("night_mode", false)
|
|
||||||
set(value) {
|
|
||||||
repository.putBoolean("night_mode", value)
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by the
|
|
||||||
* Free Software Foundation, either version 3 of the License, or (at your
|
|
||||||
* option) any later version.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.models
|
|
||||||
|
|
||||||
import org.isoron.platform.io.*
|
|
||||||
|
|
||||||
class PreferencesRepository(private val db: Database) {
|
|
||||||
|
|
||||||
private val insertStatement = db.prepareStatement("insert into Preferences(key, value) values (?, ?)")
|
|
||||||
private val deleteStatement = db.prepareStatement("delete from Preferences where key=?")
|
|
||||||
private val selectStatement = db.prepareStatement("select value from Preferences where key=?")
|
|
||||||
|
|
||||||
fun putBoolean(key: String, value: Boolean) {
|
|
||||||
putString(key, value.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getBoolean(key: String, default: Boolean): Boolean {
|
|
||||||
val value = getString(key, "NULL")
|
|
||||||
return if (value == "NULL") default else value.toBoolean()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putLong(key: String, value: Long) {
|
|
||||||
putString(key, value.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLong(key: String, default: Long): Long {
|
|
||||||
val value = getString(key, "NULL")
|
|
||||||
return if (value == "NULL") default else value.toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putString(key: String, value: String) {
|
|
||||||
deleteStatement.bindText(0, key)
|
|
||||||
deleteStatement.step()
|
|
||||||
deleteStatement.reset()
|
|
||||||
insertStatement.bindText(0, key)
|
|
||||||
insertStatement.bindText(1, value)
|
|
||||||
insertStatement.step()
|
|
||||||
insertStatement.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getString(key: String, default: String): String {
|
|
||||||
selectStatement.bindText(0, key)
|
|
||||||
if (selectStatement.step() == StepResult.DONE) {
|
|
||||||
selectStatement.reset()
|
|
||||||
return default
|
|
||||||
} else {
|
|
||||||
val value = selectStatement.getText(0)
|
|
||||||
selectStatement.reset()
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +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.platform.time.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Score is a number which indicates how strong the habit is at a given date.
|
|
||||||
*
|
|
||||||
* Scores are computed by taking an exponential moving average of the values of
|
|
||||||
* the checkmarks in preceding days. For boolean habits, when computing the
|
|
||||||
* average, each checked day (whether the check was manual or automatic) has
|
|
||||||
* value as 1, while days without checkmarks have value 0.
|
|
||||||
*
|
|
||||||
* For numerical habits, each day that exceeded the target has value 1, while
|
|
||||||
* days which failed to exceed the target receive a partial value, based on the
|
|
||||||
* proportion that was completed. For example, if the target is 100 units and
|
|
||||||
* the user completed 70 units, then the value for that day is 0.7 when
|
|
||||||
* computing the average.
|
|
||||||
*/
|
|
||||||
data class Score(val date: LocalDate,
|
|
||||||
val value: Double)
|
|
@ -1,70 +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.platform.time.*
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
class ScoreList(private val checkmarkList: CheckmarkList) {
|
|
||||||
/**
|
|
||||||
* Returns a list of all scores, from the beginning of the habit history
|
|
||||||
* until the specified date.
|
|
||||||
*
|
|
||||||
* The interval is inclusive, and the list is sorted from newest to oldest.
|
|
||||||
* That is, the first element of the returned list corresponds to the date
|
|
||||||
* provided.
|
|
||||||
*/
|
|
||||||
fun getUntil(date: LocalDate): List<Score> {
|
|
||||||
val frequency = checkmarkList.frequency
|
|
||||||
val checks = checkmarkList.getUntil(date)
|
|
||||||
val scores = mutableListOf<Score>()
|
|
||||||
val type = checkmarkList.habitType
|
|
||||||
|
|
||||||
var currentScore = 0.0
|
|
||||||
checks.reversed().forEach { check ->
|
|
||||||
val value = if (type == HabitType.BOOLEAN_HABIT) {
|
|
||||||
min(1, check.value)
|
|
||||||
} else {
|
|
||||||
check.value
|
|
||||||
}
|
|
||||||
currentScore = compute(frequency, currentScore, value)
|
|
||||||
scores.add(Score(check.date, currentScore))
|
|
||||||
}
|
|
||||||
return scores.reversed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAt(date: LocalDate): Score {
|
|
||||||
return getUntil(date)[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Given the frequency of the habit, the previous score, and the value of
|
|
||||||
* the current checkmark, computes the current score for the habit.
|
|
||||||
*/
|
|
||||||
fun compute(frequency: Frequency,
|
|
||||||
previousScore: Double,
|
|
||||||
checkmarkValue: Int): Double {
|
|
||||||
val multiplier = 0.5.pow(frequency.toDouble() / 13.0)
|
|
||||||
val score = previousScore * multiplier + checkmarkValue * (1 - multiplier)
|
|
||||||
return floor(score * 1e6) / 1e6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +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.platform.time.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A streak is an uninterrupted sequence of days where the habit was performed.
|
|
||||||
*
|
|
||||||
* For daily boolean habits, the definition is straightforward: a streak is a
|
|
||||||
* sequence of days that have checkmarks. For non-daily habits, note
|
|
||||||
* that automatic checkmarks (the ones added by the app) can also keep the
|
|
||||||
* streak going. For numerical habits, a streak is a sequence of days where the
|
|
||||||
* user has consistently exceeded the target for the habit.
|
|
||||||
*/
|
|
||||||
data class Streak(val start: LocalDate,
|
|
||||||
val end: LocalDate)
|
|
@ -1,34 +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
|
|
||||||
|
|
||||||
class StreakList(private val checkmarkList: CheckmarkList) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the longest streaks.
|
|
||||||
*
|
|
||||||
* The argument specifies the maximum number of streaks to find. The
|
|
||||||
* returned list is sorted by date (descending). That is, the first element
|
|
||||||
* corresponds to the most recent streak.
|
|
||||||
*/
|
|
||||||
fun getBest(limit: Int): List<Streak> {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +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
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import platform.darwin.*
|
|
||||||
import kotlin.coroutines.*
|
|
||||||
|
|
||||||
class UIDispatcher : CoroutineDispatcher() {
|
|
||||||
override fun dispatch(context: CoroutineContext, block: Runnable) {
|
|
||||||
val queue = dispatch_get_main_queue()
|
|
||||||
dispatch_async(queue) {
|
|
||||||
block.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,149 +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.gui
|
|
||||||
|
|
||||||
import kotlinx.cinterop.*
|
|
||||||
import platform.CoreGraphics.*
|
|
||||||
import platform.Foundation.*
|
|
||||||
import platform.UIKit.*
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
val Color.uicolor: UIColor
|
|
||||||
get() = UIColor.colorWithRed(this.red, this.green, this.blue, this.alpha)
|
|
||||||
|
|
||||||
val Color.cgcolor: CGColorRef?
|
|
||||||
get() = uicolor.CGColor
|
|
||||||
|
|
||||||
class IosCanvas(val width: Double,
|
|
||||||
val height: Double,
|
|
||||||
val scale: Double = 2.0
|
|
||||||
) : Canvas {
|
|
||||||
|
|
||||||
var textColor = UIColor.blackColor
|
|
||||||
var font = Font.REGULAR
|
|
||||||
var fontSize = 12.0
|
|
||||||
var textAlign = TextAlign.CENTER
|
|
||||||
val ctx = UIGraphicsGetCurrentContext()!!
|
|
||||||
|
|
||||||
override fun setColor(color: Color) {
|
|
||||||
CGContextSetStrokeColorWithColor(ctx, color.cgcolor)
|
|
||||||
CGContextSetFillColorWithColor(ctx, color.cgcolor)
|
|
||||||
textColor = color.uicolor
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drawLine(x1: Double, y1: Double, x2: Double, y2: Double) {
|
|
||||||
CGContextMoveToPoint(ctx, x1 * scale, y1 * scale)
|
|
||||||
CGContextAddLineToPoint(ctx, x2 * scale, y2 * scale)
|
|
||||||
CGContextStrokePath(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
|
||||||
override fun drawText(text: String, x: Double, y: Double) {
|
|
||||||
val sx = scale * x
|
|
||||||
val sy = scale * y
|
|
||||||
val nsText = (text as NSString)
|
|
||||||
val uiFont = when (font) {
|
|
||||||
Font.REGULAR -> UIFont.systemFontOfSize(fontSize)
|
|
||||||
Font.BOLD -> UIFont.boldSystemFontOfSize(fontSize)
|
|
||||||
Font.FONT_AWESOME -> UIFont.fontWithName("FontAwesome", fontSize)
|
|
||||||
}
|
|
||||||
val size = nsText.sizeWithFont(uiFont)
|
|
||||||
val width = size.useContents { width }
|
|
||||||
val height = size.useContents { height }
|
|
||||||
val origin = when (textAlign) {
|
|
||||||
TextAlign.CENTER -> CGPointMake(sx - width / 2, sy - height / 2)
|
|
||||||
TextAlign.LEFT -> CGPointMake(sx, sy - height / 2)
|
|
||||||
TextAlign.RIGHT -> CGPointMake(sx - width, sy - height / 2)
|
|
||||||
}
|
|
||||||
nsText.drawAtPoint(origin, uiFont)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fillRect(x: Double, y: Double, width: Double, height: Double) {
|
|
||||||
CGContextFillRect(ctx,
|
|
||||||
CGRectMake(x * scale,
|
|
||||||
y * scale,
|
|
||||||
width * scale,
|
|
||||||
height * scale))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drawRect(x: Double, y: Double, width: Double, height: Double) {
|
|
||||||
CGContextStrokeRect(ctx,
|
|
||||||
CGRectMake(x * scale,
|
|
||||||
y * scale,
|
|
||||||
width * scale,
|
|
||||||
height * scale))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHeight(): Double {
|
|
||||||
return height
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getWidth(): Double {
|
|
||||||
return width
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFont(font: Font) {
|
|
||||||
this.font = font
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFontSize(size: Double) {
|
|
||||||
this.fontSize = size * scale
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setStrokeWidth(size: Double) {
|
|
||||||
CGContextSetLineWidth(ctx, size * scale)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fillArc(centerX: Double,
|
|
||||||
centerY: Double,
|
|
||||||
radius: Double,
|
|
||||||
startAngle: Double,
|
|
||||||
swipeAngle: Double) {
|
|
||||||
val a1 = startAngle / 180 * PI * (-1)
|
|
||||||
val a2 = a1 - swipeAngle / 180 * PI
|
|
||||||
CGContextBeginPath(ctx)
|
|
||||||
CGContextMoveToPoint(ctx, centerX * scale, centerY * scale)
|
|
||||||
CGContextAddArc(ctx,
|
|
||||||
centerX * scale,
|
|
||||||
centerY * scale,
|
|
||||||
radius * scale,
|
|
||||||
a1,
|
|
||||||
a2,
|
|
||||||
if (swipeAngle > 0) 1 else 0)
|
|
||||||
CGContextClosePath(ctx)
|
|
||||||
CGContextFillPath(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fillCircle(centerX: Double, centerY: Double, radius: Double) {
|
|
||||||
val rect = CGRectMake(scale * (centerX - radius),
|
|
||||||
scale * (centerY - radius),
|
|
||||||
scale * radius * 2.0,
|
|
||||||
scale * radius * 2.0)
|
|
||||||
CGContextFillEllipseInRect(ctx, rect)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTextAlign(align: TextAlign) {
|
|
||||||
this.textAlign = align
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toImage(): Image {
|
|
||||||
return IosImage(UIGraphicsGetImageFromCurrentImageContext()!!)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +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.gui
|
|
||||||
|
|
||||||
import platform.UIKit.*
|
|
||||||
import platform.CoreGraphics.*
|
|
||||||
import platform.Foundation.*
|
|
||||||
|
|
||||||
class IosImage(val image: UIImage) : Image {
|
|
||||||
|
|
||||||
override val width: Int
|
|
||||||
get() {
|
|
||||||
return CGImageGetWidth(image.CGImage).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val height: Int
|
|
||||||
get() {
|
|
||||||
return CGImageGetHeight(image.CGImage).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPixel(x: Int, y: Int): Color {
|
|
||||||
return Color(1.0, 0.0, 0.0, 1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setPixel(x: Int, y: Int, color: Color) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
|
||||||
override suspend fun export(path: String) {
|
|
||||||
val tmpPath = "${NSTemporaryDirectory()}/$path"
|
|
||||||
val dir = (tmpPath as NSString).stringByDeletingLastPathComponent
|
|
||||||
NSFileManager.defaultManager.createDirectoryAtPath(dir, true, null, null)
|
|
||||||
val data = UIImagePNGRepresentation(image)!!
|
|
||||||
val success = data.writeToFile(tmpPath, true)
|
|
||||||
if (!success) throw RuntimeException("could not write to $tmpPath")
|
|
||||||
println(tmpPath)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,111 +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.io
|
|
||||||
|
|
||||||
import kotlinx.cinterop.*
|
|
||||||
import platform.Foundation.*
|
|
||||||
import sqlite3.*
|
|
||||||
|
|
||||||
fun sqlite3_errstr(db: CPointer<sqlite3>): String {
|
|
||||||
return "SQLite3 error: " + sqlite3_errmsg(db).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
|
||||||
class IosDatabaseOpener : DatabaseOpener {
|
|
||||||
override fun open(file: UserFile): Database = memScoped {
|
|
||||||
val path = (file as IosFile).path
|
|
||||||
val dirname = (path as NSString).stringByDeletingLastPathComponent
|
|
||||||
NSFileManager.defaultManager.createDirectoryAtPath(dirname, true, null, null)
|
|
||||||
|
|
||||||
val db = alloc<CPointerVar<sqlite3>>()
|
|
||||||
val result = sqlite3_open(path, db.ptr)
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
throw Exception("sqlite3_open failed (code $result)")
|
|
||||||
|
|
||||||
return IosDatabase(db.value!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IosDatabase(val db: CPointer<sqlite3>) : Database {
|
|
||||||
override fun prepareStatement(sql: String): PreparedStatement = memScoped {
|
|
||||||
if (sql.isEmpty()) throw Exception("empty SQL query")
|
|
||||||
val stmt = alloc<CPointerVar<sqlite3_stmt>>()
|
|
||||||
val result = sqlite3_prepare_v2(db, sql.cstr, -1, stmt.ptr, null)
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
throw Exception("sqlite3_prepare_v2 failed (code $result)")
|
|
||||||
return IosPreparedStatement(db, stmt.value!!)
|
|
||||||
}
|
|
||||||
override fun close() {
|
|
||||||
sqlite3_close(db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IosPreparedStatement(val db: CPointer<sqlite3>,
|
|
||||||
val stmt: CPointer<sqlite3_stmt>) : PreparedStatement {
|
|
||||||
override fun step(): StepResult {
|
|
||||||
when (sqlite3_step(stmt)) {
|
|
||||||
SQLITE_ROW -> return StepResult.ROW
|
|
||||||
SQLITE_DONE -> return StepResult.DONE
|
|
||||||
else -> throw Exception(sqlite3_errstr(db))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun finalize() {
|
|
||||||
sqlite3_finalize(stmt)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getInt(index: Int): Int {
|
|
||||||
return sqlite3_column_int(stmt, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLong(index: Int): Long {
|
|
||||||
return sqlite3_column_int64(stmt, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getText(index: Int): String {
|
|
||||||
return sqlite3_column_text(stmt, index)!!
|
|
||||||
.reinterpret<ByteVar>()
|
|
||||||
.toKString()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getReal(index: Int): Double {
|
|
||||||
return sqlite3_column_double(stmt, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindInt(index: Int, value: Int) {
|
|
||||||
sqlite3_bind_int(stmt, index + 1, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindLong(index: Int, value: Long) {
|
|
||||||
sqlite3_bind_int64(stmt, index + 1, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindText(index: Int, value: String) {
|
|
||||||
sqlite3_bind_text(stmt, index + 1, value, -1, SQLITE_TRANSIENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindReal(index: Int, value: Double) {
|
|
||||||
sqlite3_bind_double(stmt, index + 1, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun reset() {
|
|
||||||
sqlite3_reset(stmt)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +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.io
|
|
||||||
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
import platform.Foundation.*
|
|
||||||
import platform.UIKit.*
|
|
||||||
|
|
||||||
class IosFileOpener : FileOpener {
|
|
||||||
override fun openResourceFile(path: String): ResourceFile {
|
|
||||||
val resPath = NSBundle.mainBundle.resourcePath!!
|
|
||||||
return IosFile("$resPath/$path")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openUserFile(path: String): UserFile {
|
|
||||||
val manager = NSFileManager.defaultManager
|
|
||||||
val basePath = manager.URLsForDirectory(NSDocumentDirectory, NSUserDomainMask)
|
|
||||||
val filePath = (basePath.first() as NSURL).URLByAppendingPathComponent(path)!!.path!!
|
|
||||||
return IosFile(filePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IosFile(val path: String) : UserFile, ResourceFile {
|
|
||||||
override suspend fun delete() {
|
|
||||||
NSFileManager.defaultManager.removeItemAtPath(path, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun exists(): Boolean {
|
|
||||||
return NSFileManager.defaultManager.fileExistsAtPath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun lines(): List<String> {
|
|
||||||
if (!exists()) throw Exception("File not found: $path")
|
|
||||||
val contents = NSString.stringWithContentsOfFile(path)
|
|
||||||
return contents.toString().lines()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
|
||||||
override suspend fun copyTo(dest: UserFile) {
|
|
||||||
val destPath = (dest as IosFile).path
|
|
||||||
val manager = NSFileManager.defaultManager
|
|
||||||
val destParentPath = (destPath as NSString).stringByDeletingLastPathComponent
|
|
||||||
NSFileManager.defaultManager.createDirectoryAtPath(destParentPath, true, null, null)
|
|
||||||
manager.copyItemAtPath(path, destPath, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun toImage(): Image {
|
|
||||||
return IosImage(UIImage.imageWithContentsOfFile(path)!!)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +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.io
|
|
||||||
|
|
||||||
import kotlinx.cinterop.*
|
|
||||||
|
|
||||||
// Although the three following methods have exactly the same implementation,
|
|
||||||
// replacing them all by a single format(format: String, arg: Any) breaks
|
|
||||||
// everything, as of Kotlin/Native 1.3.72. Apparently, Kotlin/Native is not
|
|
||||||
// able to do proper type conversions for variables of type Any when calling
|
|
||||||
// C functions.
|
|
||||||
|
|
||||||
actual fun format(format: String, arg: String): String {
|
|
||||||
val buffer = ByteArray(1000)
|
|
||||||
buffer.usePinned { p -> platform.posix.sprintf(p.addressOf(0), format, arg) }
|
|
||||||
return buffer.toKString()
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun format(format: String, arg: Int): String {
|
|
||||||
val buffer = ByteArray(1000)
|
|
||||||
buffer.usePinned { p -> platform.posix.sprintf(p.addressOf(0), format, arg) }
|
|
||||||
return buffer.toKString()
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun format(format: String, arg: Double): String {
|
|
||||||
val buffer = ByteArray(1000)
|
|
||||||
buffer.usePinned { p -> platform.posix.sprintf(p.addressOf(0), format, arg) }
|
|
||||||
return buffer.toKString()
|
|
||||||
}
|
|
@ -1,55 +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.time
|
|
||||||
|
|
||||||
import platform.Foundation.*
|
|
||||||
|
|
||||||
fun LocalDate.toNsDate(): NSDate {
|
|
||||||
val calendar = NSCalendar.calendarWithIdentifier(NSCalendarIdentifierGregorian)!!
|
|
||||||
val dc = NSDateComponents()
|
|
||||||
dc.year = year.toLong()
|
|
||||||
dc.month = month.toLong()
|
|
||||||
dc.day = day.toLong()
|
|
||||||
dc.hour = 13
|
|
||||||
dc.minute = 0
|
|
||||||
return calendar.dateFromComponents(dc)!!
|
|
||||||
}
|
|
||||||
|
|
||||||
class IosLocalDateFormatter(val locale: String) : LocalDateFormatter {
|
|
||||||
|
|
||||||
constructor() : this(NSLocale.preferredLanguages[0] as String)
|
|
||||||
|
|
||||||
private val fmt = NSDateFormatter()
|
|
||||||
|
|
||||||
init {
|
|
||||||
fmt.setLocale(NSLocale.localeWithLocaleIdentifier(locale))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shortWeekdayName(date: LocalDate): String {
|
|
||||||
fmt.dateFormat = "EEE"
|
|
||||||
return fmt.stringFromDate(date.toNsDate())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shortMonthName(date: LocalDate): String {
|
|
||||||
fmt.dateFormat = "MMM"
|
|
||||||
return fmt.stringFromDate(date.toNsDate())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,37 +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.uhabits
|
|
||||||
|
|
||||||
import org.isoron.platform.io.*
|
|
||||||
import org.isoron.uhabits.i18n.*
|
|
||||||
import platform.Foundation.*
|
|
||||||
|
|
||||||
class IosLocaleHelper(private val log: Log) : LocaleHelper {
|
|
||||||
override fun getStringsForCurrentLocale(): Strings {
|
|
||||||
val pref = NSLocale.preferredLanguages as List<String>
|
|
||||||
val lang = if (pref.isEmpty()) "en-US" else pref[0]
|
|
||||||
log.info("IosLocaleHelper", lang)
|
|
||||||
return when {
|
|
||||||
else -> Strings()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,143 +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.gui
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import org.w3c.dom.*
|
|
||||||
import kotlin.js.*
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
class JsCanvas(val element: HTMLCanvasElement,
|
|
||||||
val pixelScale: Double) : Canvas {
|
|
||||||
|
|
||||||
|
|
||||||
val ctx = element.getContext("2d") as CanvasRenderingContext2D
|
|
||||||
var fontSize = 12.0
|
|
||||||
var fontFamily = "NotoRegular"
|
|
||||||
var align = CanvasTextAlign.CENTER
|
|
||||||
|
|
||||||
private fun toPixel(x: Double): Double {
|
|
||||||
return pixelScale * x
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toDp(x: Int): Double {
|
|
||||||
return x / pixelScale
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setColor(color: Color) {
|
|
||||||
val c = "rgb(${color.red * 255}, ${color.green * 255}, ${color.blue * 255})"
|
|
||||||
ctx.fillStyle = c;
|
|
||||||
ctx.strokeStyle = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drawLine(x1: Double, y1: Double, x2: Double, y2: Double) {
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.moveTo(toPixel(x1), toPixel(y1))
|
|
||||||
ctx.lineTo(toPixel(x2), toPixel(y2))
|
|
||||||
ctx.stroke()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drawText(text: String, x: Double, y: Double) {
|
|
||||||
ctx.font = "${fontSize}px ${fontFamily}"
|
|
||||||
ctx.textAlign = align
|
|
||||||
ctx.textBaseline = CanvasTextBaseline.MIDDLE
|
|
||||||
ctx.fillText(text, toPixel(x), toPixel(y + fontSize * 0.025))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fillRect(x: Double, y: Double, width: Double, height: Double) {
|
|
||||||
ctx.fillRect(toPixel(x),
|
|
||||||
toPixel(y),
|
|
||||||
toPixel(width),
|
|
||||||
toPixel(height))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drawRect(x: Double, y: Double, width: Double, height: Double) {
|
|
||||||
ctx.strokeRect(toPixel(x),
|
|
||||||
toPixel(y),
|
|
||||||
toPixel(width),
|
|
||||||
toPixel(height))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHeight(): Double {
|
|
||||||
return toDp(element.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getWidth(): Double {
|
|
||||||
return toDp(element.width)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFont(font: Font) {
|
|
||||||
fontFamily = when (font) {
|
|
||||||
Font.REGULAR -> "NotoRegular"
|
|
||||||
Font.BOLD -> "NotoBold"
|
|
||||||
Font.FONT_AWESOME -> "FontAwesome"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFontSize(size: Double) {
|
|
||||||
fontSize = size * pixelScale
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setStrokeWidth(size: Double) {
|
|
||||||
ctx.lineWidth = size * pixelScale
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fillArc(centerX: Double,
|
|
||||||
centerY: Double,
|
|
||||||
radius: Double,
|
|
||||||
startAngle: Double,
|
|
||||||
swipeAngle: Double) {
|
|
||||||
val x = toPixel(centerX)
|
|
||||||
val y = toPixel(centerY)
|
|
||||||
val from = startAngle / 180 * PI
|
|
||||||
val to = (startAngle + swipeAngle) / 180 * PI
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.moveTo(x, y)
|
|
||||||
ctx.arc(x, y, toPixel(radius), -from, -to, swipeAngle >= 0)
|
|
||||||
ctx.lineTo(x, y)
|
|
||||||
ctx.fill()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fillCircle(centerX: Double, centerY: Double, radius: Double) {
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.arc(toPixel(centerX),
|
|
||||||
toPixel(centerY),
|
|
||||||
toPixel(radius),
|
|
||||||
0.0,
|
|
||||||
2 * PI)
|
|
||||||
ctx.fill()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTextAlign(align: TextAlign) {
|
|
||||||
this.align = when (align) {
|
|
||||||
TextAlign.LEFT -> CanvasTextAlign.LEFT
|
|
||||||
TextAlign.CENTER -> CanvasTextAlign.CENTER
|
|
||||||
TextAlign.RIGHT -> CanvasTextAlign.RIGHT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toImage(): Image {
|
|
||||||
return JsImage(this,
|
|
||||||
ctx.getImageData(0.0,
|
|
||||||
0.0,
|
|
||||||
element.width.toDouble(),
|
|
||||||
element.height.toDouble()))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,72 +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.gui
|
|
||||||
|
|
||||||
import org.khronos.webgl.*
|
|
||||||
import org.w3c.dom.*
|
|
||||||
import kotlin.browser.*
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
class JsImage(val canvas: JsCanvas,
|
|
||||||
val imageData: ImageData) : Image {
|
|
||||||
|
|
||||||
override val width: Int
|
|
||||||
get() = imageData.width
|
|
||||||
|
|
||||||
override val height: Int
|
|
||||||
get() = imageData.height
|
|
||||||
|
|
||||||
val pixels = imageData.unsafeCast<Uint16Array>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
console.log(width, height, imageData.data.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun export(path: String) {
|
|
||||||
canvas.ctx.putImageData(imageData, 0.0, 0.0)
|
|
||||||
val container = document.createElement("div")
|
|
||||||
container.className = "export"
|
|
||||||
val title = document.createElement("div")
|
|
||||||
title.innerHTML = path
|
|
||||||
document.body?.appendChild(container)
|
|
||||||
container.appendChild(title)
|
|
||||||
container.appendChild(canvas.element)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPixel(x: Int, y: Int): Color {
|
|
||||||
val offset = 4 * (y * width + x)
|
|
||||||
return Color(imageData.data[offset + 0] / 255.0,
|
|
||||||
imageData.data[offset + 1] / 255.0,
|
|
||||||
imageData.data[offset + 2] / 255.0,
|
|
||||||
imageData.data[offset + 3] / 255.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setPixel(x: Int, y: Int, color: Color) {
|
|
||||||
val offset = 4 * (y * width + x)
|
|
||||||
inline fun map(x: Double): Byte {
|
|
||||||
return (x * 255).roundToInt().unsafeCast<Byte>()
|
|
||||||
}
|
|
||||||
imageData.data.set(offset + 0, map(color.red))
|
|
||||||
imageData.data.set(offset + 1, map(color.green))
|
|
||||||
imageData.data.set(offset + 2, map(color.blue))
|
|
||||||
imageData.data.set(offset + 3, map(color.alpha))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,80 +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.io
|
|
||||||
|
|
||||||
external fun require(module: String): dynamic
|
|
||||||
|
|
||||||
class JsPreparedStatement(val stmt: dynamic) : PreparedStatement {
|
|
||||||
|
|
||||||
override fun step(): StepResult {
|
|
||||||
val isRowAvailable = stmt.step() as Boolean
|
|
||||||
return if(isRowAvailable) StepResult.ROW else StepResult.DONE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun finalize() {
|
|
||||||
stmt.free()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getInt(index: Int): Int {
|
|
||||||
return (stmt.getNumber(index) as Double).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLong(index: Int): Long {
|
|
||||||
return (stmt.getNumber(index) as Double).toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getText(index: Int): String {
|
|
||||||
return stmt.getString(index) as String
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getReal(index: Int): Double {
|
|
||||||
return stmt.getNumber(index) as Double
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindInt(index: Int, value: Int) {
|
|
||||||
stmt.bindNumber(value, index + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindLong(index: Int, value: Long) {
|
|
||||||
stmt.bindNumber(value, index + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindText(index: Int, value: String) {
|
|
||||||
stmt.bindString(value, index + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindReal(index: Int, value: Double) {
|
|
||||||
stmt.bindNumber(value, index + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun reset() {
|
|
||||||
stmt.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class JsDatabase(val db: dynamic) : Database {
|
|
||||||
override fun prepareStatement(sql: String): PreparedStatement {
|
|
||||||
return JsPreparedStatement(db.prepare(sql))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,165 +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.io
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import org.isoron.platform.gui.*
|
|
||||||
import org.isoron.platform.gui.Image
|
|
||||||
import org.w3c.dom.*
|
|
||||||
import org.w3c.xhr.*
|
|
||||||
import kotlin.browser.*
|
|
||||||
import kotlin.js.*
|
|
||||||
|
|
||||||
class JsFileStorage {
|
|
||||||
private val TAG = "JsFileStorage"
|
|
||||||
private val log = StandardLog()
|
|
||||||
|
|
||||||
private val indexedDB = eval("indexedDB")
|
|
||||||
private var db: dynamic = null
|
|
||||||
|
|
||||||
private val DB_NAME = "Main"
|
|
||||||
private val OS_NAME = "Files"
|
|
||||||
|
|
||||||
suspend fun init() {
|
|
||||||
log.info(TAG, "Initializing")
|
|
||||||
Promise<Int> { resolve, reject ->
|
|
||||||
val req = indexedDB.open(DB_NAME, 2)
|
|
||||||
req.onerror = { reject(Exception("could not open IndexedDB")) }
|
|
||||||
req.onupgradeneeded = {
|
|
||||||
log.info(TAG, "Creating document store")
|
|
||||||
req.result.createObjectStore(OS_NAME)
|
|
||||||
}
|
|
||||||
req.onsuccess = {
|
|
||||||
log.info(TAG, "Ready")
|
|
||||||
db = req.result
|
|
||||||
resolve(0)
|
|
||||||
}
|
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun delete(path: String) {
|
|
||||||
Promise<Int> { resolve, reject ->
|
|
||||||
val transaction = db.transaction(OS_NAME, "readwrite")
|
|
||||||
val os = transaction.objectStore(OS_NAME)
|
|
||||||
val req = os.delete(path)
|
|
||||||
req.onerror = { reject(Exception("could not delete $path")) }
|
|
||||||
req.onsuccess = { resolve(0) }
|
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun put(path: String, content: String) {
|
|
||||||
Promise<Int> { resolve, reject ->
|
|
||||||
val transaction = db.transaction(OS_NAME, "readwrite")
|
|
||||||
val os = transaction.objectStore(OS_NAME)
|
|
||||||
val req = os.put(content, path)
|
|
||||||
req.onerror = { reject(Exception("could not put $path")) }
|
|
||||||
req.onsuccess = { resolve(0) }
|
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun get(path: String): String {
|
|
||||||
return Promise<String> { resolve, reject ->
|
|
||||||
val transaction = db.transaction(OS_NAME, "readonly")
|
|
||||||
val os = transaction.objectStore(OS_NAME)
|
|
||||||
val req = os.get(path)
|
|
||||||
req.onerror = { reject(Exception("could not get $path")) }
|
|
||||||
req.onsuccess = { resolve(req.result) }
|
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun exists(path: String): Boolean {
|
|
||||||
return Promise<Boolean> { resolve, reject ->
|
|
||||||
val transaction = db.transaction(OS_NAME, "readonly")
|
|
||||||
val os = transaction.objectStore(OS_NAME)
|
|
||||||
val req = os.count(path)
|
|
||||||
req.onerror = { reject(Exception("could not count $path")) }
|
|
||||||
req.onsuccess = { resolve(req.result > 0) }
|
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class JsFileOpener(val fileStorage: JsFileStorage) : FileOpener {
|
|
||||||
|
|
||||||
override fun openUserFile(path: String): UserFile {
|
|
||||||
return JsUserFile(fileStorage, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openResourceFile(path: String): ResourceFile {
|
|
||||||
return JsResourceFile(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class JsUserFile(val fs: JsFileStorage,
|
|
||||||
val filename: String) : UserFile {
|
|
||||||
override suspend fun lines(): List<String> {
|
|
||||||
return fs.get(filename).lines()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun delete() {
|
|
||||||
fs.delete(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun exists(): Boolean {
|
|
||||||
return fs.exists(filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class JsResourceFile(val filename: String) : ResourceFile {
|
|
||||||
override suspend fun exists(): Boolean {
|
|
||||||
return Promise<Boolean> { resolve, reject ->
|
|
||||||
val xhr = XMLHttpRequest()
|
|
||||||
xhr.open("GET", "/assets/$filename", true)
|
|
||||||
xhr.onload = { resolve(xhr.status.toInt() != 404) }
|
|
||||||
xhr.onerror = { reject(Exception()) }
|
|
||||||
xhr.send()
|
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun lines(): List<String> {
|
|
||||||
return Promise<List<String>> { resolve, reject ->
|
|
||||||
val xhr = XMLHttpRequest()
|
|
||||||
xhr.open("GET", "/assets/$filename", true)
|
|
||||||
xhr.onload = { resolve(xhr.responseText.lines()) }
|
|
||||||
xhr.onerror = { reject(Exception()) }
|
|
||||||
xhr.send()
|
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun copyTo(dest: UserFile) {
|
|
||||||
val fs = (dest as JsUserFile).fs
|
|
||||||
fs.put(dest.filename, lines().joinToString("\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun toImage(): Image {
|
|
||||||
return Promise<Image> { resolve, reject ->
|
|
||||||
val img = org.w3c.dom.Image()
|
|
||||||
img.onload = {
|
|
||||||
val canvas = JsCanvas(document.createElement("canvas") as HTMLCanvasElement, 1.0)
|
|
||||||
canvas.element.width = img.naturalWidth
|
|
||||||
canvas.element.height = img.naturalHeight
|
|
||||||
canvas.setColor(Color(0xffffff))
|
|
||||||
canvas.fillRect(0.0, 0.0, canvas.getWidth(), canvas.getHeight())
|
|
||||||
canvas.ctx.drawImage(img, 0.0, 0.0)
|
|
||||||
resolve(canvas.toImage())
|
|
||||||
}
|
|
||||||
img.src = "/assets/$filename"
|
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +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.io
|
|
||||||
|
|
||||||
actual fun format(format: String, arg: String): String {
|
|
||||||
return js("vsprintf")(format, arg) as String
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun format(format: String, arg: Int): String {
|
|
||||||
return js("vsprintf")(format, arg) as String
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun format(format: String, arg: Double): String {
|
|
||||||
return js("vsprintf")(format, arg) as String
|
|
||||||
}
|
|