Remove react-native; rewrite main screen in (native) swift

This commit is contained in:
2019-03-25 20:39:44 -05:00
parent a546f6de73
commit 8544c5dc8a
148 changed files with 2007 additions and 8899 deletions

28
ios/.gitignore vendored
View File

@@ -1,28 +0,0 @@
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
*.dSYM
*.dSYM.zip
*.hmap
*.ipa
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
*.xccheckout
*.xcscmblueprint
*.xcworkspace
.build/
Carthage/Build
DerivedData/
build/
fastlane/Preview.html
fastlane/report.xml
fastlane/screenshots/**/*.png
fastlane/test_output
iOSInjectionProject/
playground.xcworkspace
timeline.xctimeline
xcuserdata/

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var backend = Backend(databaseOpener: IosDatabaseOpener(withLog: StandardLog()),
fileOpener: IosFileOpener(),
log: StandardLog())
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
if let window = window {
let nav = UINavigationController()
nav.viewControllers = [ListHabitsController(withBackend: backend)]
window.backgroundColor = UIColor.white
window.rootViewController = nav
window.makeKeyAndVisible()
}
return true
}
}

View File

@@ -40,6 +40,51 @@
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",

View File

@@ -3,4 +3,4 @@
"version" : 1,
"author" : "xcode"
}
}
}

View File

@@ -17,8 +17,4 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
#import <core.h>

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
class EditHabitTableViewController: NSObject, UITableViewDataSource, UITableViewDelegate {
func disclosure(title: String, subtitle: String) -> UITableViewCell {
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
cell.textLabel?.text = title
cell.detailTextLabel?.text = subtitle
cell.accessoryType = .disclosureIndicator
return cell
}
func input(title: String) -> UITableViewCell {
let cell = UITableViewCell()
let field = UITextField(frame: cell.bounds.insetBy(dx: 20, dy: 0))
field.placeholder = title
field.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth,
UIView.AutoresizingMask.flexibleHeight]
cell.contentView.addSubview(field)
return cell
}
var primary = [UITableViewCell]()
var secondary = [UITableViewCell]()
var parentController: EditHabitController
init(withParentController parentController: EditHabitController) {
self.parentController = parentController
super.init()
primary.append(input(title: "Name"))
primary.append(input(title: "Question (e.g. Did you wake up early today?)"))
secondary.append(disclosure(title: "Color", subtitle: "Blue"))
secondary.append(disclosure(title: "Repeat", subtitle: "Daily"))
secondary.append(disclosure(title: "Reminder", subtitle: "Disabled"))
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? primary.count : secondary.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return indexPath.section == 0 ? primary[indexPath.item] : secondary[indexPath.item]
}
// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// let alert = UIAlertController(title: "Hello", message: "You selected something", preferredStyle: .alert)
// parentController.present(alert, animated: true)
// }
}
class EditHabitController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let bounds = UIScreen.main.bounds
let tableController = EditHabitTableViewController(withParentController: self)
let table = UITableView(frame: bounds, style: .grouped)
table.dataSource = tableController
table.delegate = tableController
self.view = table
}
override func viewDidLoad() {
self.title = "Edit Habit"
}
}

View File

@@ -4,12 +4,10 @@
<dict>
<key>UIAppFonts</key>
<array>
<string>FontAwesome Regular.ttf</string>
<string>fonts/FontAwesome.ttf</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>uhabits</string>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -22,16 +20,10 @@
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
@@ -42,20 +34,14 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>UILaunchStoryboardName</key>
<string>Launch.storyboard</string>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
</dependencies>
<scenes/>
</document>

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
class ListHabitsCell : UITableViewCell {
var ring: ComponentView
var label = UILabel()
var buttons: [ComponentView] = []
var theme = LightTheme()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
ring = ComponentView(frame: CGRect(), component: nil)
super.init(style: .default, reuseIdentifier: reuseIdentifier)
let size = CGFloat(theme.checkmarkButtonSize)
let stack = UIStackView(frame: contentView.frame)
stack.autoresizingMask = [.flexibleWidth, .flexibleHeight]
stack.backgroundColor = .red
stack.axis = .horizontal
stack.distribution = .fill
stack.alignment = .center
contentView.addSubview(stack)
ring.backgroundColor = .white
ring.widthAnchor.constraint(equalToConstant: size * 0.75).isActive = true
ring.heightAnchor.constraint(equalToConstant: size).isActive = true
stack.addArrangedSubview(ring)
label.backgroundColor = .white
label.heightAnchor.constraint(equalToConstant: size).isActive = true
stack.addArrangedSubview(label)
for _ in 1...4 {
let btn = ComponentView(frame: frame, component: nil)
btn.backgroundColor = .white
btn.widthAnchor.constraint(equalToConstant: size).isActive = true
btn.heightAnchor.constraint(equalToConstant: size).isActive = true
buttons.append(btn)
stack.addArrangedSubview(btn)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
func setColor(_ color: Color) {
label.textColor = color.uicolor
ring.component = Ring(color: color,
percentage: Double.random(in: 0...1),
thickness: 2.5,
radius: 7,
theme: theme,
label: false)
ring.setNeedsDisplay()
let isNumerical = Int.random(in: 1...4) == 1
for btn in buttons {
if isNumerical {
btn.component = NumberButton(color: color,
value: Double.random(in: 0...5000),
threshold: 2000,
units: "steps",
theme: theme)
} else {
btn.component = CheckmarkButton(value: Int32.random(in: 0...2),
color: color,
theme: theme)
}
btn.setNeedsDisplay()
}
}
}
class ListHabitsController: UITableViewController {
var backend: Backend
var habits: [[String: Any]]
var theme: Theme
required init?(coder aDecoder: NSCoder) {
fatalError()
}
init(withBackend backend:Backend) {
self.backend = backend
self.habits = backend.getHabitList()
self.theme = backend.theme
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
self.title = "Habits"
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add,
target: self,
action: #selector(self.onCreateHabitClicked))
tableView.register(ListHabitsCell.self, forCellReuseIdentifier: "cell")
tableView.backgroundColor = theme.headerBackgroundColor.uicolor
}
@objc func onCreateHabitClicked() {
self.navigationController?.pushViewController(EditHabitController(), animated: true)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return habits.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = indexPath.row
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ListHabitsCell
let color = theme.color(paletteIndex: habits[row]["color"] as! Int32)
cell.label.text = habits[row]["name"] as? String
cell.setColor(color)
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let component = HabitListHeader(today: LocalDate(year: 2019, month: 3, day: 24),
nButtons: 4,
theme: theme,
fmt: IosLocalDateFormatter(),
calc: IosLocalDateCalculator())
return ComponentView(frame: CGRect(x: 0, y: 0, width: 100, height: CGFloat(theme.checkmarkButtonSize)),
component: component)
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat(theme.checkmarkButtonSize)
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(theme.checkmarkButtonSize) + 1
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
class ComponentView : UIView {
var component: Component?
init(frame: CGRect, component: Component?) {
self.component = component
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
override func draw(_ rect: CGRect) {
let canvas = IosCanvas(withBounds: bounds)
component?.draw(canvas: canvas)
}
}
class IosCanvas : NSObject, Canvas {
func fillArc(centerX: Double, centerY: Double, radius: Double, startAngle: Double, swipeAngle: Double) {
let center = CGPoint(x: CGFloat(centerX), y: CGFloat(centerY))
let a1 = startAngle / 180 * .pi * (-1)
let a2 = a1 - swipeAngle / 180 * .pi
self.ctx.beginPath()
self.ctx.move(to: center)
self.ctx.addArc(center: center,
radius: CGFloat(radius),
startAngle: CGFloat(a1),
endAngle: CGFloat(a2),
clockwise: swipeAngle >= 0)
self.ctx.closePath()
self.ctx.fillPath()
}
func fillCircle(centerX: Double, centerY: Double, radius: Double) {
self.ctx.fillEllipse(in: CGRect(x: CGFloat(centerX - radius),
y: CGFloat(centerY - radius),
width: CGFloat(radius * 2),
height: CGFloat(radius * 2)))
}
var bounds: CGRect
var ctx: CGContext
var font = Font.regular
var textSize = CGFloat(12)
var textColor = UIColor.black
init(withBounds bounds: CGRect) {
self.bounds = bounds
self.ctx = UIGraphicsGetCurrentContext()!
}
func setColor(color: Color) {
self.ctx.setStrokeColor(color.cgcolor)
self.ctx.setFillColor(color.cgcolor)
textColor = color.uicolor
}
func drawLine(x1: Double, y1: Double, x2: Double, y2: Double) {
self.ctx.move(to: CGPoint(x: CGFloat(x1), y: CGFloat(y1)))
self.ctx.addLine(to: CGPoint(x: CGFloat(x2), y: CGFloat(y2)))
self.ctx.strokePath()
}
func drawText(text: String, x: Double, y: Double) {
let nsText = text as NSString
var uifont = UIFont.systemFont(ofSize: textSize)
if font == Font.bold {
uifont = UIFont.boldSystemFont(ofSize: textSize)
}
if font == Font.fontAwesome {
uifont = UIFont(name: "FontAwesome", size: textSize)!
}
let attrs = [NSAttributedString.Key.font: uifont,
NSAttributedString.Key.foregroundColor: textColor]
let size = nsText.size(withAttributes: attrs)
nsText.draw(at: CGPoint(x: CGFloat(x) - size.width / 2,
y : CGFloat(y) - size.height / 2),
withAttributes: attrs)
}
func drawRect(x: Double, y: Double, width: Double, height: Double) {
self.ctx.stroke(CGRect(x: CGFloat(x),
y: CGFloat(y),
width: CGFloat(width),
height: CGFloat(height)))
}
func fillRect(x: Double, y: Double, width: Double, height: Double) {
self.ctx.fill(CGRect(x: CGFloat(x),
y: CGFloat(y),
width: CGFloat(width),
height: CGFloat(height)))
}
func getHeight() -> Double {
return Double(bounds.height)
}
func getWidth() -> Double {
return Double(bounds.width)
}
func setTextSize(size: Double) {
self.textSize = CGFloat(size)
}
func setFont(font: Font) {
self.font = font
}
func setStrokeWidth(size: Double) {
self.ctx.setLineWidth(CGFloat(size))
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
class IosLocalDateFormatter : NSObject, LocalDateFormatter {
func shortWeekdayName(date: LocalDate) -> String {
let calendar = Calendar(identifier: .gregorian)
var dc = DateComponents()
dc.year = Int(date.year)
dc.month = Int(date.month)
dc.day = Int(date.day)
dc.hour = 13
dc.minute = 0
let d = calendar.date(from: dc)!
let fmt = DateFormatter()
fmt.dateFormat = "EEE"
return fmt.string(from: d)
}
}
class IosLocalDateCalculator : NSObject, LocalDateCalculator {
func plusDays(date: LocalDate, days: Int32) -> LocalDate {
let calendar = Calendar(identifier: .gregorian)
var dc = DateComponents()
dc.year = Int(date.year)
dc.month = Int(date.month)
dc.day = Int(date.day)
dc.hour = 13
dc.minute = 0
let d1 = calendar.date(from: dc)!
let d2 = d1.addingTimeInterval(24.0 * 60 * 60 * Double(days))
return LocalDate(year: Int32(calendar.component(.year, from: d2)),
month: Int32(calendar.component(.month, from: d2)),
day: Int32(calendar.component(.day, from: d2)))
}
func minusDays(date: LocalDate, days: Int32) -> LocalDate {
return plusDays(date: date, days: -days)
}
}

View File

@@ -17,13 +17,17 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
import UIKit
@interface RCT_EXTERN_MODULE(CoreModule, RCTEventEmitter)
RCT_EXTERN_METHOD(createHabit:(NSString)name)
//RCT_EXTERN_METHOD(updateHabit:(int)id (NSString)name)
//RCT_EXTERN_METHOD(deleteHabit:(int)id)
RCT_EXTERN_METHOD(requestHabitList)
@end
extension Color {
var uicolor: UIColor {
return UIColor(red: CGFloat(self.red),
green: CGFloat(self.green),
blue: CGFloat(self.blue),
alpha: 1.0)
}
var cgcolor : CGColor {
return uicolor.cgColor
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
class IosResourceFile : NSObject, ResourceFile {
var path: String
var fileManager = FileManager.default
init(forPath path: String) {
self.path = path
}
func readLines() -> [String] {
do {
let contents = try String(contentsOfFile: self.path, encoding: .utf8)
return contents.components(separatedBy: CharacterSet.newlines)
} catch {
return ["ERROR"]
}
}
}
class IosUserFile : NSObject, UserFile {
var path: String
init(forPath path: String) {
self.path = path
}
func delete() {
do {
try FileManager.default.removeItem(atPath: path)
} catch {
}
}
func exists() -> Bool {
return FileManager.default.fileExists(atPath: path)
}
}
class IosFileOpener : NSObject, FileOpener {
func openResourceFile(filename: String) -> ResourceFile {
let path = "\(Bundle.main.resourcePath!)/\(filename)"
return IosResourceFile(forPath: path)
}
func openUserFile(filename: String) -> UserFile {
do {
let root = try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false).path
return IosUserFile(forPath: "\(root)/\(filename)")
} catch {
return IosUserFile(forPath: "invalid")
}
}
}

Binary file not shown.

Binary file not shown.

View File

View File

View File

View File

View File

View File

View File

View File

View File

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

View File

@@ -0,0 +1,3 @@
delete from Score
delete from Streak
delete from Checkmarks

View File

@@ -0,0 +1 @@
alter table Habits add column reminder_days integer not null default 127

View File

@@ -0,0 +1,3 @@
delete from Score
delete from Streak
delete from Checkmarks

View File

@@ -0,0 +1,4 @@
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)

View File

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

View File

@@ -0,0 +1,3 @@
delete from Score
delete from Streak
delete from Checkmarks

View File

@@ -0,0 +1,2 @@
alter table Habits add column type integer not null default 0
alter table Repetitions add column value integer not null default 2

View File

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

View File

@@ -0,0 +1,3 @@
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 ""

View File

@@ -0,0 +1 @@
create table Events ( id integer primary key autoincrement, timestamp integer, message text, server_id integer )

View File

@@ -0,0 +1,3 @@
drop table checkmarks
drop table streak
drop table score

View File

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

View File

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

View File

@@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIAppFonts</key>
<array>
<string>fonts/FontAwesome.ttf</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import XCTest
import UIKit
@testable import uhabits
class IosCanvasTest : XCTestCase {
func testDraw() {
UIGraphicsBeginImageContext(CGSize(width: 500, height: 400))
let canvas = IosCanvas(withBounds: CGRect(x: 0, y: 0, width: 500, height: 400))
canvas.setColor(color: Color(argb: 0x303030))
canvas.fillRect(x: 0.0, y: 0.0, width: 500.0, height: 400.0)
canvas.setColor(color: Color(argb: 0x606060))
canvas.setStrokeWidth(size: 25.0)
canvas.drawRect(x: 100.0, y: 100.0, width: 300.0, height: 200.0)
canvas.setColor(color: Color(argb: 0xFFFF00))
canvas.setStrokeWidth(size: 1.0)
canvas.fillCircle(centerX: 50.0, centerY: 50.0, radius: 30.0)
canvas.fillArc(centerX: 50.0, centerY: 150.0, radius: 30.0, startAngle: 90.0, swipeAngle: 135.0)
canvas.fillArc(centerX: 50.0, centerY: 250.0, radius: 30.0, startAngle: 90.0, swipeAngle: -135.0)
canvas.fillArc(centerX: 50.0, centerY: 350.0, radius: 30.0, startAngle: 45.0, swipeAngle: 90.0)
canvas.drawRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
canvas.drawRect(x: 0.0, y: 100.0, width: 100.0, height: 100.0)
canvas.drawRect(x: 0.0, y: 200.0, width: 100.0, height: 100.0)
canvas.drawRect(x: 0.0, y: 300.0, width: 100.0, height: 100.0)
canvas.setColor(color: Color(argb: 0xFF0000))
canvas.setStrokeWidth(size: 2.0)
canvas.drawLine(x1: 0.0, y1: 0.0, x2: 500.0, y2: 400.0)
canvas.drawLine(x1: 500.0, y1: 0.0, x2: 0.0, y2: 400.0)
canvas.setTextSize(size: 50.0)
canvas.setColor(color: Color(argb: 0x00FF00))
canvas.drawText(text: "Test", x: 250.0, y: 200.0)
canvas.setFont(font: Font.bold)
canvas.drawText(text: "Test", x: 250.0, y: 100.0)
canvas.setFont(font: Font.fontAwesome)
canvas.drawText(text: FontAwesome.Companion().check, x: 250.0, y: 300.0)
let image = UIGraphicsGetImageFromCurrentImageContext()!
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let filePath = paths.first?.appendingPathComponent("IosCanvasTest.png")
try! image.pngData()!.write(to: filePath!, options: .atomic)
UIGraphicsEndImageContext()
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import XCTest
import UIKit
@testable import uhabits
class IosDateTimeTest : XCTestCase {
func testPlusMinusDay() {
let today = LocalDate(year: 2019, month: 3, day: 25)
let calc = IosLocalDateCalculator()
let d1 = calc.minusDays(date: today, days: 28)
let d2 = calc.plusDays(date: today, days: 7)
let d3 = calc.plusDays(date: today, days: 42)
XCTAssert(d1.isEqual(LocalDate(year: 2019, month: 2, day: 25)))
XCTAssert(d2.isEqual(LocalDate(year: 2019, month: 4, day: 1)))
XCTAssert(d3.isEqual(LocalDate(year: 2019, month: 5, day: 6)))
}
}
class IosDateFormatterTest : XCTestCase {
func testShortMonthName() {
let fmt = IosLocalDateFormatter()
let d1 = LocalDate(year: 2019, month: 3, day: 25)
let d2 = LocalDate(year: 2019, month: 4, day: 4)
let d3 = LocalDate(year: 2019, month: 5, day: 12)
XCTAssertEqual(fmt.shortWeekdayName(date: d1), "Mon")
XCTAssertEqual(fmt.shortWeekdayName(date: d2), "Thu")
XCTAssertEqual(fmt.shortWeekdayName(date: d3), "Sun")
}
}

View File

@@ -43,14 +43,14 @@ class IosDatabaseTest: XCTestCase {
stmt.step()
stmt.finalize()
stmt = db.prepareStatement(sql: "insert into demo(key, value) values (?1, ?2)")
stmt.bindInt(index: 1, value: 42)
stmt.bindText(index: 2, value: "Hello World")
stmt = db.prepareStatement(sql: "insert into demo(key, value) values (?, ?)")
stmt.bindInt(index: 0, value: 42)
stmt.bindText(index: 1, value: "Hello World")
stmt.step()
stmt.finalize()
stmt = db.prepareStatement(sql: "select * from demo where key > ?1")
stmt.bindInt(index: 1, value: 10)
stmt = db.prepareStatement(sql: "select * from demo where key > ?")
stmt.bindInt(index: 0, value: 10)
var result = stmt.step()
XCTAssertEqual(result, StepResult.row)
XCTAssertEqual(stmt.getInt(index: 0), 42)

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildLocationStyle</key>
<string>UseAppPreferences</string>
<key>CustomBuildLocationType</key>
<string>RelativeToDerivedData</string>
<key>DerivedDataLocationStyle</key>
<string>Default</string>
<key>EnabledFullIndexStoreVisibility</key>
<false/>
<key>IssueFilterStyle</key>
<string>ShowActiveSchemeOnly</string>
<key>LiveSourceIssuesEnabled</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict />
</plist>

View File

@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0940"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "NO"
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
@@ -14,21 +13,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "83CBBA2D1A601D0E00E9B192"
BuildableName = "libReact.a"
BlueprintName = "React"
ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BlueprintIdentifier = "00A5B42322009F590024E00C"
BuildableName = "uhabits.app"
BlueprintName = "uhabits"
ReferencedContainer = "container:uhabits.xcodeproj">
@@ -40,14 +25,13 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "002101A021F936A300F9283D"
BlueprintIdentifier = "00A5B43722009F5A0024E00C"
BuildableName = "uhabitsTests.xctest"
BlueprintName = "uhabitsTests"
ReferencedContainer = "container:uhabits.xcodeproj">
@@ -57,7 +41,7 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BlueprintIdentifier = "00A5B42322009F590024E00C"
BuildableName = "uhabits.app"
BlueprintName = "uhabits"
ReferencedContainer = "container:uhabits.xcodeproj">
@@ -80,21 +64,18 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BlueprintIdentifier = "00A5B42322009F590024E00C"
BuildableName = "uhabits.app"
BlueprintName = "uhabits"
ReferencedContainer = "container:uhabits.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "disable"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
<LocationScenarioReference
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
referenceType = "1">
</LocationScenarioReference>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -102,16 +83,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BlueprintIdentifier = "00A5B42322009F590024E00C"
BuildableName = "uhabits.app"
BlueprintName = "uhabits"
ReferencedContainer = "container:uhabits.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>uhabits.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>

View File

@@ -1,43 +0,0 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import SQLite3
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var bridge: RCTBridge!
static var backend = Backend(databaseOpener: IosDatabaseOpener(withLog: StandardLog()),
fileOpener: IosFileOpener(),
log: StandardLog())
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index.ios", fallbackResource: nil)
let rootView = RCTRootView(bundleURL: jsCodeLocation, moduleName: "LoopHabitTracker", initialProperties: nil, launchOptions: launchOptions)
rootView?.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
self.window = UIWindow(frame: UIScreen.main.bounds)
let rootViewController = UIViewController()
rootViewController.view = rootView
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
return true
}
}

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_0" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
</document>

View File

@@ -1,81 +0,0 @@
/*
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
extension String: Error {}
@objc(CoreModule)
class CoreModule: RCTEventEmitter {
func convert(_ obj: Any?) -> Any? {
if obj is KotlinInt {
return (obj as! KotlinInt).intValue
}
if obj is NSString {
return obj
}
if obj is Dictionary<String, Any> {
return (obj as! Dictionary<String, Any>).mapValues{ convert($0) }
}
if obj is Array<Any> {
return (obj as! Array<Any>).map { convert($0) }
}
return nil
}
@objc
open override func supportedEvents() -> [String] {
return ["onHabitList"]
}
@objc
func requestHabitList() {
DispatchQueue.main.async {
let result = AppDelegate.backend.getHabitList()
self.sendEvent(withName: "onHabitList", body: self.convert(result))
}
}
@objc
func createHabit(_ name: String) {
DispatchQueue.main.async {
AppDelegate.backend.createHabit(name: name)
}
}
@objc
func deleteHabit(_ id: Int32) {
DispatchQueue.main.async {
AppDelegate.backend.deleteHabit(id: id)
}
}
@objc
func updateHabit(_ id: Int32, _ name: String) {
DispatchQueue.main.async {
AppDelegate.backend.updateHabit(id: id, name: name)
}
}
@objc
override static func requiresMainQueueSetup() -> Bool {
return true
}
}