Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Apps/Clock/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# tactility-cmakelists-version: 1
cmake_minimum_required(VERSION 3.20)

if (NOT DEFINED ENV{TACTILITY_SDK_PATH})
message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set")
endif()

get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE)
include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake")

tactility_project_pre(clock)
project(clock)
tactility_project_post(clock)
636 changes: 636 additions & 0 deletions Apps/Clock/LICENSE

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions Apps/Clock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Clock

Digital and analog clock for Tactility, with automatic time synchronization from the system.

## Features

- Digital and analog clock faces
- Follows the system 12/24-hour and date-format settings
- Automatically uses the system time, including NTP synchronization when Wi-Fi is connected
- Responsive layout for small and large displays
- Prompts to open Wi-Fi management when the time has not been set

## Controls

- Tap the mode button in the toolbar to switch between digital and analog views
- Tap the close button to exit the app

## License

GPL-3.0-or-later. See [LICENSE](LICENSE).
13 changes: 13 additions & 0 deletions Apps/Clock/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake")

file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
tactility_component_register(SRCS ${SOURCE_FILES})

if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
target_link_options(${PROJECT_NAME} PRIVATE -Wl,-undefined,dynamic_lookup)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${PROJECT_NAME}>
${CMAKE_BINARY_DIR}/lib${PROJECT_NAME}.so
COMMENT "Publishing lib${PROJECT_NAME}.so for the posix app loader"
)
endif()
82 changes: 82 additions & 0 deletions Apps/Clock/main/Source/Clock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "ClockSettings.h"
#include "ClockUi.h"

#include <app/event.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <tactility/check.h>
#include <tactility/concurrent/task_event_group.h>
#include <tactility/freertos/freertos.h>
#include <tactility/freertos/task.h>

#include <lvgl.h>

namespace {

constexpr uint32_t REFRESH_PERIOD_MS = 1000;

void onRefresh(lv_timer_t* timer) {
auto* context = static_cast<clockapp::Context*>(lv_timer_get_user_data(timer));
clockapp::refreshClock(context);
}

void clockCreateWidgets(lv_obj_t* parent, void* userData) {
auto* context = static_cast<clockapp::Context*>(userData);
clockapp::buildUi(context, parent);
context->timer = lv_timer_create(onRefresh, REFRESH_PERIOD_MS, context);
}

void clockDestroyWidgets(void* userData) {
auto* context = static_cast<clockapp::Context*>(userData);
if (context->timer != nullptr) {
lv_timer_delete(context->timer);
context->timer = nullptr;
}
clockapp::destroyUi(context);
}

}

extern "C" int main(int argc, char* argv[]) {
(void)argc;
(void)argv;

AppInstanceId appInstanceId = app_scheduler_current_app_id();

clockapp::Context context {};
context.appInstanceId = appInstanceId;
context.analog = clockapp::loadAnalogMode();

TaskEventGroup eventGroup {};
task_event_group_construct(&eventGroup);

AppEventSubscription sub {};
check(app_event_subscribe(&sub, &eventGroup) == ERROR_NONE);

WindowId window = window_manager_create_ext(
appInstanceId,
clockCreateWidgets,
clockDestroyWidgets,
&context
);

bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&eventGroup, nullptr, portMAX_DELAY);

AppEvent event {};
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
shouldClose = true;
break;
}
}
}

window_manager_remove(window);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&eventGroup);

return 0;
}
137 changes: 137 additions & 0 deletions Apps/Clock/main/Source/ClockSettings.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "ClockSettings.h"

#include <app/paths.h>
#include <tactility/error.h>
#include <tactility/filesystem/file.h>
#include <tactility/paths.h>
#include <tactility/preferences.h>
#include <tactility/properties_file.h>

#include <cstddef>
#include <cstdio>
#include <cstring>

namespace clockapp {
namespace {

constexpr const char* APP_ID = "tactility.clock";
constexpr const char* MODE_FILE = "clock.properties";
constexpr const char* MODE_KEY = "analog";
constexpr const char* SYSTEM_PROPERTIES_SUFFIX = "/settings/system.properties";
constexpr const char* TIME_FORMAT_KEY = "timeFormat24h";
constexpr const char* DATE_FORMAT_KEY = "dateFormat";

void buildSystemPropertiesPath(char* out_path, size_t out_path_size) {
out_path[0] = '\0';

char data_path[FILE_MAX_PATH_STRING_LENGTH];
if (paths_get_data_path(data_path, sizeof(data_path)) != ERROR_NONE) {
return;
}

int length = snprintf(
out_path,
out_path_size,
"%s%s",
data_path,
SYSTEM_PROPERTIES_SUFFIX
);
if (length < 0 || static_cast<size_t>(length) >= out_path_size) {
out_path[0] = '\0';
}
}

DateFormat parseDateFormat(const char* value) {
if (strcmp(value, "DD/MM/YYYY") == 0) {
return DateFormat::DayMonthYear;
}
if (strcmp(value, "YYYY-MM-DD") == 0) {
return DateFormat::YearMonthDayDash;
}
if (strcmp(value, "YYYY/MM/DD") == 0) {
return DateFormat::YearMonthDaySlash;
}
return DateFormat::MonthDayYear;
}

bool openModePreferences(Preferences** out_preferences) {
char path[FILE_MAX_PATH_STRING_LENGTH];
if (app_paths_get_user_data_path(APP_ID, MODE_FILE, path, sizeof(path)) != ERROR_NONE) {
return false;
}

Preferences* preferences = preferences_open(path);
if (preferences == nullptr) {
return false;
}

*out_preferences = preferences;
return true;
}

}

SystemDisplaySettings loadSystemDisplaySettings() {
SystemDisplaySettings settings;

char path[FILE_MAX_PATH_STRING_LENGTH];
buildSystemPropertiesPath(path, sizeof(path));
if (path[0] == '\0' || !file_exists(path)) {
return settings;
}

PropertiesFile* file = properties_file_open(path);
if (file == nullptr) {
return settings;
}

char value[16];
if (properties_file_get(file, TIME_FORMAT_KEY, value, sizeof(value)) == ERROR_NONE) {
settings.format24h = strcmp(value, "true") == 0;
}
if (properties_file_get(file, DATE_FORMAT_KEY, value, sizeof(value)) == ERROR_NONE) {
settings.dateFormat = parseDateFormat(value);
}

properties_file_close(file);
return settings;
}

const char* dateFormatPattern(DateFormat format) {
switch (format) {
case DateFormat::DayMonthYear:
return "%d/%m/%Y";
case DateFormat::YearMonthDayDash:
return "%Y-%m-%d";
case DateFormat::YearMonthDaySlash:
return "%Y/%m/%d";
case DateFormat::MonthDayYear:
default:
return "%m/%d/%Y";
}
}

bool loadAnalogMode() {
Preferences* preferences = nullptr;
if (!openModePreferences(&preferences)) {
return false;
}

bool analog = false;
preferences_opt_bool(preferences, MODE_KEY, &analog);
preferences_close(preferences);
return analog;
}

void saveAnalogMode(bool analog) {
Preferences* preferences = nullptr;
if (!openModePreferences(&preferences)) {
return;
Comment thread
KenVanHoeylandt marked this conversation as resolved.
Resolved
}

preferences_put_bool(preferences, MODE_KEY, analog);
preferences_close(preferences);
}

}
23 changes: 23 additions & 0 deletions Apps/Clock/main/Source/ClockSettings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

namespace clockapp {

enum class DateFormat {
MonthDayYear,
DayMonthYear,
YearMonthDayDash,
YearMonthDaySlash,
};

struct SystemDisplaySettings {
bool format24h = true;
DateFormat dateFormat = DateFormat::MonthDayYear;
};

SystemDisplaySettings loadSystemDisplaySettings();
const char* dateFormatPattern(DateFormat format);
bool loadAnalogMode();
void saveAnalogMode(bool analog);

}
Loading
Loading