Monorepo initial commit

master
Auri 2021-09-12 18:49:08 -07:00
commit f95466275e
89 changed files with 19607 additions and 0 deletions

View File

@ -0,0 +1,6 @@
{
"e997a5256149a4b76e6bfd6cbf519c5e5a0f1d278a3d8fa1253022b03c90473b": true,
"af683c96e0ffd2cf81287651c9433fa44debc1220ca7cb431fe482747f34a505": true,
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
}

73
companion/.gitignore vendored Normal file
View File

@ -0,0 +1,73 @@
node_modules/
.expo/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS
.DS_Store
# @generated expo-cli sync-2138f1e3e130677ea10ea873f6d498e3890e677b
# The following patterns were generated by expo-cli
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
!debug.keystore
# Bundle artifacts
*.jsbundle
# CocoaPods
/ios/Pods/
# Expo
.expo/
web-build/
# @end expo-cli

View File

@ -0,0 +1,55 @@
# To learn about Buck see [Docs](https://buckbuild.com/).
# To run your application with Buck:
# - install Buck
# - `npm start` - to start the packager
# - `cd android`
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
# - `buck install -r android/app` - compile, install and run application
#
load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
lib_deps = []
create_aar_targets(glob(["libs/*.aar"]))
create_jar_targets(glob(["libs/*.jar"]))
android_library(
name = "all-libs",
exported_deps = lib_deps,
)
android_library(
name = "app-code",
srcs = glob([
"src/main/java/**/*.java",
]),
deps = [
":all-libs",
":build_config",
":res",
],
)
android_build_config(
name = "build_config",
package = "com.aurailus.focuscompanion",
)
android_resource(
name = "res",
package = "com.aurailus.focuscompanion",
res = "src/main/res",
)
android_binary(
name = "app",
keystore = "//android/keystores:debug",
manifest = "src/main/AndroidManifest.xml",
package_type = "debug",
deps = [
":app-code",
],
)

View File

@ -0,0 +1,227 @@
apply plugin: "com.android.application"
apply plugin: "kotlin-android"
apply plugin: "kotlin-android-extensions"
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation. If none specified and
* // "index.android.js" exists, it will be used. Otherwise "index.js" is
* // default. Can be overridden with ENTRY_FILE environment variable.
* entryFile: "index.android.js",
*
* // https://reactnative.dev/docs/performance#enable-the-ram-format
* bundleCommand: "ram-bundle",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // whether to disable dev mode in custom build variants (by default only disabled in release)
* // for example: to disable dev mode in the staging build type (if configured)
* devDisabledInStaging: true,
* // The configuration property can be in the following formats
* // 'devDisabledIn${productFlavor}${buildType}'
* // 'devDisabledIn${buildType}'
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"],
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
project.ext.react = [
enableHermes: (findProperty('expo.jsEngine') ?: "jsc") == "hermes",
]
apply from: '../../node_modules/react-native-unimodules/gradle.groovy'
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/expo-constants/scripts/get-app-config-android.gradle"
apply from: "../../node_modules/expo-updates/scripts/create-manifest-android.gradle"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore.
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
/**
* Whether to enable the Hermes VM.
*
* This should be set on project.ext.react and mirrored here. If it is not set
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
* and the benefits of using Hermes will therefore be sharply reduced.
*/
def enableHermes = project.ext.react.get("enableHermes", false);
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId 'com.aurailus.focuscompanion'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.0"
ndk {
abiFilters 'armeabi-v7a'
}
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation "com.android.support:appcompat-v7:$supportLibVersion"
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
addUnimodulesDependencies()
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile into 'libs'
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

View File

@ -0,0 +1,19 @@
"""Helper definitions to glob .aar and .jar targets"""
def create_aar_targets(aarfiles):
for aarfile in aarfiles:
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
lib_deps.append(":" + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
def create_jar_targets(jarfiles):
for jarfile in jarfiles:
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
lib_deps.append(":" + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)

Binary file not shown.

View File

@ -0,0 +1,10 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
</manifest>

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.aurailus.focuscompanion;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new ReactFlipperPlugin());
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}

View File

@ -0,0 +1,29 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.aurailus.focuscompanion">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:usesCleartextTraffic="true">
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="42.0.0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@anonymous/FocusCompanion"/>
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="myapp"/>
<data android:scheme="com.aurailus.focuscompanion"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
</application>
</manifest>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,61 @@
package com.aurailus.focuscompanion
import android.content.Context
import android.database.Cursor
import android.provider.CalendarContract
import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
class CalendarModule(var ctx: ReactApplicationContext): ReactContextBaseJavaModule(ctx) {
fun getEnabled(): Set<String> {
val prefs = ctx.getSharedPreferences(
ctx.getString(R.string.preferences_key), Context.MODE_PRIVATE)
return prefs.getStringSet("calendars_enabled", HashSet())!!
}
@ReactMethod fun getCalendars(promise: Promise) {
val cur: Cursor = ctx.contentResolver?.query(
CalendarContract.Calendars.CONTENT_URI, arrayOf(
CalendarContract.Calendars._ID,
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
CalendarContract.Calendars.CALENDAR_COLOR
),
null, null, null
) ?: return promise.reject("Failed to get calendars.")
val enabled = getEnabled()
val arr = Arguments.createArray()
while (cur.moveToNext()) {
val data = Arguments.createMap()
data.putString("id", cur.getString(0))
data.putString("title", cur.getString(1))
val colorInt = cur.getInt(2)
val color = String.format("#%06X", 0xFFFFFF and colorInt)
data.putString("color", color)
data.putBoolean("enabled", enabled.contains(data.getString("id")))
arr.pushMap(data)
}
cur.close()
promise.resolve(arr)
}
@ReactMethod fun setCalendars(enabledArr: ReadableArray, promise: Promise) {
val enabled = HashSet<String>()
for (i in 0 until enabledArr.size())
enabledArr.getString(i)?.let { enabled.add(it) }
val prefs = ctx.getSharedPreferences(
ctx.getString(R.string.preferences_key), Context.MODE_PRIVATE)
prefs.edit().putStringSet("calendars_enabled", enabled).apply()
promise.resolve(null)
}
override fun getName(): String {
return "CalendarModule"
}
}

View File

@ -0,0 +1,42 @@
package com.aurailus.focuscompanion
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.ReactRootView
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView
import expo.modules.splashscreen.SplashScreenImageResizeMode
import expo.modules.splashscreen.singletons.SplashScreen.show
class MainActivity : ReactActivity() {
// Added automatically by Expo Config
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val intent = Intent("onConfigurationChanged")
intent.putExtra("newConfig", newConfig)
sendBroadcast(intent)
}
override fun onCreate(savedInstanceState: Bundle?) {
// Set the theme to AppTheme BEFORE onCreate to support
// coloring the background, status bar, and navigation bar.
// This is required for expo-splash-screen.
// setTheme(R.style.AppTheme)
super.onCreate(null)
// @generated begin expo-splash-screen-mainActivity-onCreate-show-splash - expo prebuild (DO NOT MODIFY) sync-8915a20732e7fda227585f9b6ef0d38bef4fbbbe
show(this, SplashScreenImageResizeMode.CONTAIN, ReactRootView::class.java, false)
// @generated end expo-splash-screen-mainActivity-onCreate-show-splash
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
}
override fun createReactActivityDelegate(): ReactActivityDelegate {
return object : ReactActivityDelegate(this, "main") {
override fun createRootView(): ReactRootView {
return RNGestureHandlerEnabledRootView(this@MainActivity)
}
}
}
}

View File

@ -0,0 +1,109 @@
package com.aurailus.focuscompanion
import android.app.Application
import android.content.Context
import com.aurailus.focuscompanion.generated.BasePackageList
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactInstanceManager
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.JSIModulePackage
import com.facebook.soloader.SoLoader
import com.swmansion.reanimated.ReanimatedJSIModulePackage
import expo.modules.updates.UpdatesController
import java.lang.reflect.InvocationTargetException
import org.unimodules.adapters.react.ModuleRegistryAdapter
import org.unimodules.adapters.react.ReactModuleRegistryProvider
class MainApplication : Application(), ReactApplication {
private val mModuleRegistryProvider = ReactModuleRegistryProvider(
BasePackageList().packageList
)
private val mReactNativeHost: ReactNativeHost = object : ReactNativeHost(this) {
override fun getUseDeveloperSupport(): Boolean {
return BuildConfig.DEBUG
}
override fun getPackages(): List<ReactPackage> {
val packages: MutableList<ReactPackage> = PackageList(this).packages
packages.add(ModuleRegistryAdapter(mModuleRegistryProvider))
packages.add(MyAppPackage())
return packages
}
override fun getJSMainModuleName(): String {
return "index"
}
override fun getJSIModulePackage(): JSIModulePackage {
return ReanimatedJSIModulePackage()
}
override fun getJSBundleFile(): String? {
return if (BuildConfig.DEBUG) {
super.getJSBundleFile()
}
else {
UpdatesController.getInstance().launchAssetFile
}
}
override fun getBundleAssetName(): String? {
return if (BuildConfig.DEBUG) {
super.getBundleAssetName()
}
else {
UpdatesController.getInstance().bundleAssetName
}
}
}
override fun getReactNativeHost(): ReactNativeHost {
return mReactNativeHost
}
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)
if (!BuildConfig.DEBUG) UpdatesController.initialize(this)
initializeFlipper(this, reactNativeHost.reactInstanceManager)
}
companion object {
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private fun initializeFlipper(
context: Context, reactInstanceManager: ReactInstanceManager
) {
if (BuildConfig.DEBUG) {
try {
/*
* We use reflection here to pick up the class that initializes Flipper,
* since Flipper library is not available in release mode
*/
val aClass = Class.forName("com.aurailus.focuscompanion.ReactNativeFlipper")
aClass.getMethod(
"initializeFlipper",
Context::class.java,
ReactInstanceManager::class.java
).invoke(null, context, reactInstanceManager)
} catch (e: ClassNotFoundException) {
e.printStackTrace()
} catch (e: NoSuchMethodException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
}
}
}
}
}

View File

@ -0,0 +1,21 @@
package com.aurailus.focuscompanion
import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
import java.util.*
class MyAppPackage: ReactPackage {
override fun createViewManagers(reactContext: ReactApplicationContext): MutableList<ViewManager<View, ReactShadowNode<*>>> {
return Collections.emptyList()
}
override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> {
val modules = ArrayList<NativeModule>()
modules.add(CalendarModule(reactContext))
return modules
}
}

View File

@ -0,0 +1,22 @@
package com.aurailus.focuscompanion.generated;
import java.util.Arrays;
import java.util.List;
import org.unimodules.core.interfaces.Package;
public class BasePackageList {
public List<Package> getPackageList() {
return Arrays.<Package>asList(
new expo.modules.application.ApplicationPackage(),
new expo.modules.constants.ConstantsPackage(),
new expo.modules.errorrecovery.ErrorRecoveryPackage(),
new expo.modules.filesystem.FileSystemPackage(),
new expo.modules.font.FontLoaderPackage(),
new expo.modules.imageloader.ImageLoaderPackage(),
new expo.modules.keepawake.KeepAwakePackage(),
new expo.modules.splashscreen.SplashScreenPackage(),
new expo.modules.updates.UpdatesPackage(),
new expo.modules.webbrowser.WebBrowserPackage()
);
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<!-- <background android:drawable="#000000"/>-->
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<!-- <background android:drawable="#000000"/>-->
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<string name="app_name">FocusCompanion</string>
<string name="preferences_key">com.aurailus.focuscompanion.PREFERENCE_FILE_KEY</string>
</resources>

View File

@ -0,0 +1,42 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = "29.0.3"
minSdkVersion = 21
compileSdkVersion = 30
targetSdkVersion = 30
kotlin_version = "1.5.30"
supportLibVersion = "25.3.0"
}
repositories {
google()
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:4.1.0")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
google()
jcenter()
maven { url 'https://www.jitpack.io' }
}
}

View File

@ -0,0 +1,33 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.54.0
# The hosted JavaScript engine
# Supported values: expo.jsEngine = "hermes" | "jsc"
expo.jsEngine=jsc

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Sat Sep 11 23:20:59 PDT 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

183
companion/android/gradlew vendored Executable file
View File

@ -0,0 +1,183 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

103
companion/android/gradlew.bat vendored Normal file
View File

@ -0,0 +1,103 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,9 @@
rootProject.name = 'FocusCompanion'
apply from: '../node_modules/react-native-unimodules/gradle.groovy'
includeUnimodulesProjects()
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
applyNativeModulesSettingsGradle(settings)
include ':app'

36
companion/app.json Normal file
View File

@ -0,0 +1,36 @@
{
"expo": {
"name": "FocusCompanion",
"slug": "FocusCompanion",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "myapp",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.aurailus.focuscompanion"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.aurailus.focuscompanion"
},
"web": {
"favicon": "./assets/images/favicon.png"
}
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,7 @@
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'],
};
};

9
companion/index.js Normal file
View File

@ -0,0 +1,9 @@
import 'react-native-gesture-handler';
import { registerRootComponent } from 'expo';
import App from './src/App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

View File

@ -0,0 +1,4 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');
module.exports = getDefaultConfig(__dirname);

15753
companion/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
companion/package.json Normal file
View File

@ -0,0 +1,43 @@
{
"scripts": {
"start": "react-native start",
"android": "react-native run-android",
"android-prebuild": "npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/"
},
"dependencies": {
"@expo/vector-icons": "^12.0.0",
"@react-navigation/bottom-tabs": "^6.0.5",
"@react-navigation/material-bottom-tabs": "^6.0.5",
"@react-navigation/native": "^6.0.2",
"@react-navigation/native-stack": "^6.1.0",
"expo": "~42.0.1",
"expo-asset": "~8.3.2",
"expo-constants": "~11.0.1",
"expo-font": "~9.2.1",
"expo-linking": "~2.3.1",
"expo-splash-screen": "~0.11.2",
"expo-status-bar": "~1.0.4",
"expo-web-browser": "~9.2.0",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "~0.63.4",
"react-native-gesture-handler": "~1.10.2",
"react-native-paper": "^4.9.2",
"react-native-reanimated": "~2.2.0",
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "~3.4.0",
"react-native-svg": "^12.1.1",
"react-native-web": "~0.13.12",
"expo-updates": "~0.8.1",
"react-native-unimodules": "~0.14.5"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@types/react": "~16.9.35",
"@types/react-native": "~0.63.2",
"typescript": "~4.0.0"
},
"private": true,
"name": "focuscompanion",
"version": "0.0.1"
}

58
companion/src/App.tsx Normal file
View File

@ -0,0 +1,58 @@
import React, { useEffect } from 'react';
import { StatusBar } from 'expo-status-bar';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { View, Text, Image, StyleSheet, } from 'react-native';
import Navigation from './Navigation';
export default function App() {
return (
<SafeAreaProvider style={{ backgroundColor: '#0c1014' }}>
<StatusBar style='light'/>
<View style={styles.top}>
<View style={styles.topImageWrap}>
<Image source={require('../assets/images/watch.png')} style={styles.topImage}/>
</View>
<View style={styles.titleWrap}>
<Text style={styles.title}>Focus Watchface</Text>
<Text style={styles.subtitle}>Companion App</Text>
</View>
</View>
{/* <View style={{ borderRadius: 12, flexGrow: 1, overflow: 'hidden' }}> */}
<Navigation/>
{/* </View> */}
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
titleWrap: {
padding: 24,
justifyContent: 'center'
},
title: {
color: '#cde',
fontSize: 22,
},
subtitle: {
color: '#789',
fontSize: 18
},
top: {
paddingLeft: 16,
paddingTop: 48,
paddingBottom: 16,
flexDirection: 'row'
},
topImageWrap: {
borderRadius: 9999,
backgroundColor: '#181f24',
padding: 4,
flexGrow: 0
},
topImage: {
width: 128,
height: 128,
borderRadius: 9999
}
});

View File

@ -0,0 +1,80 @@
// import * as WebBrowser from 'expo-web-browser';
// import React from 'react';
// import { StyleSheet, TouchableOpacity } from 'react-native';
// import Colors from '../constants/Colors';
// import { MonoText } from './StyledText';
// import { Text, View } from './Themed';
// export default function EditScreenInfo({ path }: { path: string }) {
// return (
// <View>
// <View style={styles.getStartedContainer}>
// <Text
// style={styles.getStartedText}
// lightColor="rgba(0,0,0,0.8)"
// darkColor="rgba(255,255,255,0.8)">
// Open up the code for this screen:
// </Text>
// <View
// style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
// darkColor="rgba(255,255,255,0.05)"
// lightColor="rgba(0,0,0,0.05)">
// <MonoText>{path}</MonoText>
// </View>
// <Text
// style={styles.getStartedText}
// lightColor="rgba(0,0,0,0.8)"
// darkColor="rgba(255,255,255,0.8)">
// Change any of the text, save the file, and your app will automatically update.
// </Text>
// </View>
// <View style={styles.helpContainer}>
// <TouchableOpacity onPress={handleHelpPress} style={styles.helpLink}>
// <Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
// Tap here if your app doesn't automatically update after making changes
// </Text>
// </TouchableOpacity>
// </View>
// </View>
// );
// }
// function handleHelpPress() {
// WebBrowser.openBrowserAsync(
// 'https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet'
// );
// }
// const styles = StyleSheet.create({
// getStartedContainer: {
// alignItems: 'center',
// marginHorizontal: 50,
// },
// homeScreenFilename: {
// marginVertical: 7,
// },
// codeHighlightContainer: {
// borderRadius: 3,
// paddingHorizontal: 4,
// },
// getStartedText: {
// fontSize: 17,
// lineHeight: 24,
// textAlign: 'center',
// },
// helpContainer: {
// marginTop: 15,
// marginHorizontal: 20,
// alignItems: 'center',
// },
// helpLink: {
// paddingVertical: 15,
// },
// helpLinkText: {
// textAlign: 'center',
// },
// });

79
companion/src/Icon.tsx Normal file
View File

@ -0,0 +1,79 @@
import * as React from 'react';
import Svg, { Path, Circle } from 'react-native-svg';
/** The default icon size, if none is specified. */
export const DEFAULT_ICON_SIZE = 24;
/** The valid icon names that can be provided to <Icon/> */
export type IconName = 'calendar' | 'theme' | 'heart';
interface IconProps {
width?: number | string;
height?: number | string;
color?: string | undefined;
colorPrimary?: string | undefined;
colorSecondary?: string | undefined;
}
/** Gets the derived values from an IconProps. */
function useIconDerivedProps(props: IconProps) {
const width = props.width ?? DEFAULT_ICON_SIZE;
const height = props.height ?? width;
const colorSecondary = props.color ?? props.colorPrimary ?? '#fff';
const colorPrimary = props.colorSecondary ??
(colorSecondary.length === 4 ? colorSecondary + '9' : colorSecondary + '99');
return { width, height, colorPrimary, colorSecondary };
}
/* Below are components to render specific icons. */
export function Calendar(props: IconProps) {
const { width, height, colorPrimary, colorSecondary } = useIconDerivedProps(props);
return (
<Svg viewBox='0 0 24 24' width={width} height={height}>
<Path fill={colorPrimary} d='M5 4h14a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6c0-1.1.9-2 2-2zm0 5v10h14V9H5z'/>
<Path fill={colorSecondary} d='M13 13h3v3h-3v-3zM7 2a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V3a1 1 0 0 1 1-1zm10 0a1 1 0 0 1 1 1v3a1 1 0 0 1-2 0V3a1 1 0 0 1 1-1z'/>
</Svg>
);
}
export function Theme(props: IconProps) {
const { width, height, colorPrimary, colorSecondary } = useIconDerivedProps(props);
return (
<Svg viewBox='0 0 24 24' width={width} height={height}>
<Path fill={colorPrimary} d='M9 22c.19-.14.37-.3.54-.46L17.07 14H20a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H9zM4 2h4a2 2 0 0 1 2 2v14a4 4 0 1 1-8 0V4c0-1.1.9-2 2-2zm2 17.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z'/>
<Path fill={colorSecondary} d='M11 18.66V7.34l2.07-2.07a2 2 0 0 1 2.83 0l2.83 2.83a2 2 0 0 1 0 2.83L11 18.66z'/>
</Svg>
);
}
export function Heart(props: IconProps) {
const { width, height, colorPrimary, colorSecondary } = useIconDerivedProps(props);
return (
<Svg viewBox='0 0 24 24' width={width} height={height}>
<Circle fill={colorPrimary} cx="12" cy="12" r="10"/>
<Path fill={colorSecondary} d="M12.88 8.88a3 3 0 1 1 4.24 4.24l-4.41 4.42a1 1 0 0 1-1.42 0l-4.41-4.42a3 3 0 1 1 4.24-4.24l.88.88.88-.88z"/>
</Svg>
);
}
/** A mapping of icon name to component. */
const ICON_COMPONENTS: Record<IconName, React.FunctionComponent<IconProps>> = {
'calendar': Calendar,
'theme': Theme,
'heart': Heart
};
interface Props extends IconProps {
icon: IconName;
}
/**
* Renders whatever icon is provided in the `icon` prop.
*/
export default function Icon(props: Props) {
const Component = ICON_COMPONENTS[props.icon];
return <Component {...props}/>;
}

View File

@ -0,0 +1,167 @@
import * as React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { NavigationContainer, DarkTheme } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import Icon, { IconName } from './Icon';
import { RootStackParamList, RootTabParamList } from '../types';
import { ModalScreen, CalendarsScreen, ThemesScreen, AboutScreen } from './screens';
import Animated, { withTiming, useAnimatedStyle, Easing } from 'react-native-reanimated';
import { LinkingOptions } from '@react-navigation/native';
import * as Linking from 'expo-linking';
/**
* The linking for the different screens.
*/
const LINKING: LinkingOptions<RootStackParamList> = {
prefixes: [Linking.makeUrl('/')],
config: {
screens: {
Root: {
screens: {
Calendars: {
screens: {
CalendarsScreen: 'calendar',
},
},
Theme: {
screens: {
ThemeScreen: 'theme',
},
},
About: {
screens: {
AboutScreen: 'about',
},
}
},
},
Modal: 'modal',
NotFound: '*',
},
},
};
/** Displays the active screen based on the linking. */
const Stack = createNativeStackNavigator<RootStackParamList>();
const NAVIGATION_THEME = {
dark: true,
colors: {
primary: 'white',
background: 'transparent',
card: '#0c1014',
text: 'white',
border: 'transparent',
notification: 'red'
}
};
/**
* Renders the navigation and active screen.
*/
export default function Navigation() {
return (
<NavigationContainer linking={LINKING} theme={NAVIGATION_THEME}>
<Stack.Navigator>
<Stack.Screen name='Root' component={BottomTabNavigator} options={{ headerShown: false }} />
<Stack.Group screenOptions={{ presentation: 'modal' }}>
<Stack.Screen name='Modal' component={ModalScreen} />
</Stack.Group>
</Stack.Navigator>
</NavigationContainer>
);
}
/** Displays a tab bar at the bottom of the screen. */
const BottomTab = createBottomTabNavigator<RootTabParamList>();
/**
* Renders the bottom navigation.
*/
function BottomTabNavigator() {
return (
<BottomTab.Navigator
initialRouteName='Calendars'
screenOptions={{
headerShown: false,
tabBarStyle: { height: 64 }
}}>
<BottomTab.Screen
name='Calendars'
component={CalendarsScreen}
options={{
title: 'Calendars',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} icon='calendar'/>,
tabBarLabel: ({ focused }) => <TabBarLabel focused={focused} title='Calendars'/>
}}
/>
<BottomTab.Screen
name='Theme'
component={ThemesScreen}
options={{
title: 'Theme',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} icon='theme'/>,
tabBarLabel: ({ focused }) => <TabBarLabel focused={focused} title='Theme'/>
}}
/>
<BottomTab.Screen
name='About'
component={AboutScreen}
options={{
title: 'About',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} icon='heart'/>,
tabBarLabel: ({ focused }) => <TabBarLabel focused={focused} title='About'/>
}}
/>
</BottomTab.Navigator>
);
}
/** Handles animating tab bar labels. */
const ANIM_CONFIG = { duration: 100, easing: Easing.inOut(Easing.ease) };
interface TabBarIconProps {
icon: IconName;
focused: boolean;
}
/**
* Renders the icon for a tab in the tab bar.
*/
function TabBarIcon(props: TabBarIconProps) {
return (
<Icon icon={props.icon} width={28} height={28}
color={props.focused ? '#ffc9fb' : '#789'}
colorSecondary={props.focused ? '#c46abd' : '#345'}/>
);
}
interface TabBarLabelProps {
title: string;
focused: boolean;
}
/**
* Renders the label for a tab in the tab bar.
*/
function TabBarLabel(props: TabBarLabelProps) {
const style = useAnimatedStyle(() => ({
opacity: withTiming(props.focused ? 1 : 0, ANIM_CONFIG),
fontSize: withTiming(props.focused ? 11 : 8, ANIM_CONFIG),
marginTop: withTiming(props.focused ? -6 : -20, ANIM_CONFIG),
marginBottom: withTiming(props.focused ? 10 : 6, ANIM_CONFIG),
}), [ props.focused ]);
return (
<Animated.Text style={[{ color: '#ffe8fd', }, style]}>
{props.title}
</Animated.Text>
);
}

View File

@ -0,0 +1,7 @@
// import * as React from 'react';
// import { Text, TextProps } from './Themed';
// export function MonoText(props: TextProps) {
// return <Text {...props} style={[props.style, { fontFamily: 'space-mono' }]} />;
// }

46
companion/src/Themed.tsx Normal file
View File

@ -0,0 +1,46 @@
// /**
// * Learn more about Light and Dark modes:
// * https://docs.expo.io/guides/color-schemes/
// */
// import * as React from 'react';
// import { Text as DefaultText, View as DefaultView } from 'react-native';
// import Colors from '../constants/Colors';
// import useColorScheme from '../hooks/useColorScheme';
// export function useThemeColor(
// props: { light?: string; dark?: string },
// colorName: keyof typeof Colors.light & keyof typeof Colors.dark
// ) {
// const theme = useColorScheme();
// const colorFromProps = props[theme];
// if (colorFromProps) {
// return colorFromProps;
// } else {
// return Colors[theme][colorName];
// }
// }
// type ThemeProps = {
// lightColor?: string;
// darkColor?: string;
// };
// export type TextProps = ThemeProps & DefaultText['props'];
// export type ViewProps = ThemeProps & DefaultView['props'];
// export function Text(props: TextProps) {
// const { style, lightColor, darkColor, ...otherProps } = props;
// const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
// return <DefaultText style={[{ color }, style]} {...otherProps} />;
// }
// export function View(props: ViewProps) {
// const { style, lightColor, darkColor, ...otherProps } = props;
// const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
// return <DefaultView style={[{ backgroundColor }, style]} {...otherProps} />;
// }

View File

@ -0,0 +1,19 @@
// const tintColorLight = '#2f95dc';
// const tintColorDark = '#f00';
// export default {
// light: {
// text: '#000',
// background: '#fff',
// tint: tintColorLight,
// tabIconDefault: '#ccc',
// tabIconSelected: tintColorLight,
// },
// dark: {
// text: '#fff',
// background: '#000',
// tint: tintColorDark,
// tabIconDefault: '#ccc',
// tabIconSelected: tintColorDark,
// }
// };

View File

@ -0,0 +1,12 @@
import { Dimensions } from 'react-native';
const width = Dimensions.get('window').width;
const height = Dimensions.get('window').height;
export default {
window: {
width,
height,
},
isSmallDevice: width < 375,
};

View File

@ -0,0 +1,17 @@
import { NativeModules } from 'react-native';
const { CalendarModule } = NativeModules;
export interface Calendar {
id: string;
color: string;
title: string;
enabled: boolean;
}
export function getCalendars(): Promise<Calendar[]> {
return CalendarModule.getCalendars();
}
export function setCalendars(enabled: string[]): Promise<void> {
return CalendarModule.setCalendars(enabled);
}

View File

@ -0,0 +1,39 @@
import * as React from 'react';
import { StyleSheet, View, Text, Linking } from 'react-native';
import { Heart } from '../Icon';
export default function AboutScreen() {
return (
<View style={styles.container}>
<Heart width={96} colorPrimary='#ff91f7' colorSecondary='#542459'/>
<Text style={styles.title}>Like Focus?</Text>
<Text style={styles.description}>Check out my other projects on my{' '}
<Text style={styles.link} onPress={() => Linking.openURL('https://aurail.us/discord')}>Discord</Text>.</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
borderRadius: 16,
overflow: 'hidden',
backgroundColor: 'black',
alignItems: 'center',
justifyContent: 'center'
},
description: {
color: '#789',
marginBottom: 36
},
title: {
color: 'white',
fontSize: 20,
fontWeight: 'bold',
marginVertical: 12
},
link: {
color: '#c46abd',
fontWeight: '700'
}
});

View File

@ -0,0 +1,115 @@
import * as React from 'react';
import { StyleSheet, View, Pressable, Text, ScrollView, Switch, FlatList } from 'react-native';
import * as Calendars from '../native/Calendars';
interface CalendarItemProps {
color: string;
title: string;
enabled: boolean;
onSwitch: () => void;
}
function getSwitchColors(enabled: boolean) {
return {
background: enabled ? '#c46abd' : '#123',
foreground: enabled ? '#ffc9fb' : '#789'
};
}
function CalendarItem(props: CalendarItemProps) {
const [ switchColors, setSwitchColors ] =
React.useState<{ background: string; foreground: string }>(getSwitchColors(props.enabled));
React.useEffect(() => {
const controller = new AbortController();
setTimeout(() => {
if (controller.signal.aborted) return;
setSwitchColors(getSwitchColors(props.enabled));
}, 100);
return () => controller.abort();
}, [ props.enabled ]);
return (
<View style={styles.item}>
<Pressable android_ripple={{ color: '#14181f' }} style={styles.itemPressable} onPress={props.onSwitch}>
<View style={[ styles.itemDot, { backgroundColor: props.color }]}/>
<Text style={[ styles.itemTitle, { color: props.enabled ? '#c0d8dd' : '#567' }]}>{props.title}</Text>
<Switch
trackColor={{ false: switchColors.background, true: switchColors.background }}
thumbColor={switchColors.foreground}
value={props.enabled} onValueChange={props.onSwitch}/>
</Pressable>
</View>
);
};
export default function CalendarsScreen() {
const [ calendars, setCalendars ] = React.useState<Calendars.Calendar[]>([]);
const refreshCalendars = async () => {
setCalendars((await Calendars.getCalendars()).sort((a, b) => a.title.localeCompare(b.title)));
};
React.useEffect(() => void(refreshCalendars()), []);
const handleToggle = async (ind: number) => {
const newCalendars: Calendars.Calendar[] = JSON.parse(JSON.stringify(calendars));
newCalendars[ind].enabled = !newCalendars[ind].enabled;
setCalendars(newCalendars);
Calendars.setCalendars(newCalendars.filter(c => c.enabled).map(c => c.id));
};
return (
<View style={styles.container}>
<FlatList
data={calendars}
contentContainerStyle={styles.scrollViewInner}
renderItem={({ item, index }) => <CalendarItem color={item.color} title={item.title}
enabled={item.enabled} onSwitch={() => handleToggle(index)}/>}
ItemSeparatorComponent={() => <View style={styles.separator}/>}
// onRefresh={() => void(refreshCalendars)}
// refreshing={false}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
borderRadius: 16,
overflow: 'hidden',
backgroundColor: 'black'
},
scrollViewInner: {
padding: 8
},
separator: {
height: 1,
marginVertical: 8,
backgroundColor: '#11161f'
},
item: {
borderRadius: 8,
overflow: 'hidden',
height: 52,
},
itemPressable: {
flexDirection: 'row',
alignItems: 'center',
paddingStart: 12,
paddingRight: 16,
flex: 1
},
itemDot: {
width: 12,
height: 12,
borderRadius: 9999,
marginRight: 20,
marginLeft: 8
},
itemTitle: {
fontSize: 16,
flex: 1
},
});

View File

@ -0,0 +1,30 @@
import * as React from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';
// import EditScreenInfo from '../components/EditScreenInfo';
// import { Text, View } from '../components/Themed';
export default function ModalScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Modal</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

View File

@ -0,0 +1,31 @@
import * as React from 'react';
import { StyleSheet, View, ScrollView, Text } from 'react-native';
export default function ThemesScreen() {
return (
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.scrollViewInner}>
<Text style={styles.title}>Themes coming soon :)</Text>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
borderRadius: 16,
overflow: 'hidden',
backgroundColor: 'black'
},
scrollViewInner: {
padding: 12,
alignItems: 'center',
},
title: {
color: '#567',
fontSize: 20,
marginTop: 32,
// fontWeight: 'bold',
}
});

View File

@ -0,0 +1,4 @@
export { default as ModalScreen } from './ModalScreen';
export { default as CalendarsScreen } from './CalendarsScreen';
export { default as ThemesScreen } from './ThemesScreen';
export { default as AboutScreen } from './AboutScreen';

6
companion/tsconfig.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true
}
}

36
companion/types.tsx Normal file
View File

@ -0,0 +1,36 @@
/**
* Learn more about using TypeScript with React Navigation:
* https://reactnavigation.org/docs/typescript/
*/
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import { CompositeScreenProps, NavigatorScreenParams } from '@react-navigation/native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
export type RootStackParamList = {
Root: NavigatorScreenParams<RootTabParamList> | undefined;
Modal: undefined;
NotFound: undefined;
};
export type RootStackScreenProps<Screen extends keyof RootStackParamList> = NativeStackScreenProps<
RootStackParamList,
Screen
>;
export type RootTabParamList = {
Calendars: undefined;
Theme: undefined;
About: undefined;
};
export type RootTabScreenProps<Screen extends keyof RootTabParamList> = CompositeScreenProps<
BottomTabScreenProps<RootTabParamList, Screen>,
NativeStackScreenProps<RootStackParamList>
>;

24
watch/.project Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Focus</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>json.validation.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.tizen.web.project.builder.WebBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>json.validation.nature</nature>
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
<nature>org.tizen.web.project.builder.WebNature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="**/bower_components/*|**/node_modules/*|**/*.min.js" kind="src" path="">
<attributes>
<attribute name="provider" value="org.eclipse.wst.jsdt.web.core.internal.project.ModuleSourcePathProvider"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.WebProject">
<attributes>
<attribute name="hide" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
<classpathentry kind="con" path="org.tizen.web.project.initializer.WebLibraryInitializer"/>
<classpathentry kind="con" path="org.eclipselabs.jsdt.jquery.core.CoflictLibrary_2.0"/>
<classpathentry kind="con" path="org.tizen.web.project.initializer.TizenLibraryInitializer"/>
<classpathentry kind="con" path="org.tizen.web.project.initializer.HTML5LibraryInitializer"/>
<classpathentry kind="con" path="org.tizen.web.project.initializer.W3CLibraryInitializer"/>
<classpathentry kind="con" path="org.tizen.web.project.initializer.ext.RESTSupport"/>
<classpathentry kind="output" path=""/>
</classpath>

View File

@ -0,0 +1,2 @@
css-profile/<project>=org.eclipse.wst.css.core.cssprofile.css3
eclipse.preferences.version=1

View File

@ -0,0 +1 @@
org.eclipse.wst.jsdt.launching.baseBrowserLibrary

View File

@ -0,0 +1 @@
Window

24
watch/.sign/.manifest.tmp Normal file
View File

@ -0,0 +1,24 @@
.vscode/tasks.json__DEL__z3Xirp4bBkVYOuLkeQRNUOUf0hU42upD11qVzov1carXdqFx4D+Fhxpcw1sSPddPpfAujQcSpGII
fNCXLTArdg==
LICENSE.Flora__DEL__DmG3CnrC92doof8/csn15OLKg6OO1WLYfQ9uEQQ/n/VN17C/uS3lvfaTnU0OZ+jz8fLIRG9Zqo75
upc4DKODmg==
NOTICE__DEL__fT2i6Z4ODh+DqtCjQyeK5uN4x+niPkI2udRx96CtU2imYHAMYXHhKFZjjQp3fmpZ6frYv4E3mbBz
6d2rvCgAxA==
config.xml__DEL__wEoe6rrKdUhpgIG6jEzvr4aXCSyObOwI03rTf6Oh+MRp4JCMN5B7fbMxFT4xdOlNxiU7vzRmdaHa
Uo/N0yXsQw==
css/style.css__DEL__pGxu4RkFZ31ePUZ9DZfg8oC27yh0KG+WeQvtJ3OqL94w3NK4B2KCXgxcwV3u0K8l3iLjL0mPqawJ
55eCkvo4Pw==
icon.png__DEL__V19vQSoQmeA+0SAe2zZ5n7ZfhLXwzW1huc0SL0x93gfRFoZJZJYHHE389ozH0pyeuXZsJ8C9miyp
Mq7Z/83Oag==
index.html__DEL__Qv+ECIfSc7x0qgabjOTQ6x0THpMMgdmNFAltsBFUwpLyPwOERbOw7W/rFuJdbr+39Aa8lBZezQX4
/J2XvzwzgQ==
js/app.js__DEL__LW2jSMYKpzVwyQl6Ss6Ci9XaGattqwHZuKr1dYDZmZF0fg0GDB7PcDcDA2MF7Y8EGRD+UIBUp0ug
DwCMp6BWaQ==
res/glow.png__DEL__+raW+o3PLDMsOdZ46jMqZn56GunBtPfuBWnUSYSEsP6msyDfVULxjD/gOzx0sdpKbW3+IV1pFPfs
jelRSZ+DPQ==
res/icon.svg__DEL__PfH43OvgmRvOyHlhzqpEmScP2C13iRlgQdCRliuCLygGJPS3Kt5J9/ocavcqiOZw4lBFfHKm0N4A
QFaT2CDakg==
version.txt__DEL__jJtvGOhH+xRt5nb2nvtEt+HGe/PG0CnhcMjO6XKTjHZEjeAZ5yuYeNEv9jXlGeLwx2zOOKSyvcob
EviMmoUiyw==
author-signature.xml__DEL__aIlAcoz0mBFIGxlZX9W9GwzktPq3vT1cX84vEFNZ2Rwe3CQ8l5nQxXEFCZd8k+eUwnLWgE4X8CU0
emsvomRuZg==

View File

@ -0,0 +1,119 @@
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="AuthorSignature">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"></SignatureMethod>
<Reference URI=".vscode%2Ftasks.json">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>z3Xirp4bBkVYOuLkeQRNUOUf0hU42upD11qVzov1carXdqFx4D+Fhxpcw1sSPddPpfAujQcSpGII
fNCXLTArdg==</DigestValue>
</Reference>
<Reference URI="LICENSE.Flora">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>DmG3CnrC92doof8/csn15OLKg6OO1WLYfQ9uEQQ/n/VN17C/uS3lvfaTnU0OZ+jz8fLIRG9Zqo75
upc4DKODmg==</DigestValue>
</Reference>
<Reference URI="NOTICE">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>fT2i6Z4ODh+DqtCjQyeK5uN4x+niPkI2udRx96CtU2imYHAMYXHhKFZjjQp3fmpZ6frYv4E3mbBz
6d2rvCgAxA==</DigestValue>
</Reference>
<Reference URI="config.xml">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>wEoe6rrKdUhpgIG6jEzvr4aXCSyObOwI03rTf6Oh+MRp4JCMN5B7fbMxFT4xdOlNxiU7vzRmdaHa
Uo/N0yXsQw==</DigestValue>
</Reference>
<Reference URI="css%2Fstyle.css">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>pGxu4RkFZ31ePUZ9DZfg8oC27yh0KG+WeQvtJ3OqL94w3NK4B2KCXgxcwV3u0K8l3iLjL0mPqawJ
55eCkvo4Pw==</DigestValue>
</Reference>
<Reference URI="icon.png">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>V19vQSoQmeA+0SAe2zZ5n7ZfhLXwzW1huc0SL0x93gfRFoZJZJYHHE389ozH0pyeuXZsJ8C9miyp
Mq7Z/83Oag==</DigestValue>
</Reference>
<Reference URI="index.html">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>Qv+ECIfSc7x0qgabjOTQ6x0THpMMgdmNFAltsBFUwpLyPwOERbOw7W/rFuJdbr+39Aa8lBZezQX4
/J2XvzwzgQ==</DigestValue>
</Reference>
<Reference URI="js%2Fapp.js">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>LW2jSMYKpzVwyQl6Ss6Ci9XaGattqwHZuKr1dYDZmZF0fg0GDB7PcDcDA2MF7Y8EGRD+UIBUp0ug
DwCMp6BWaQ==</DigestValue>
</Reference>
<Reference URI="res%2Fglow.png">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>+raW+o3PLDMsOdZ46jMqZn56GunBtPfuBWnUSYSEsP6msyDfVULxjD/gOzx0sdpKbW3+IV1pFPfs
jelRSZ+DPQ==</DigestValue>
</Reference>
<Reference URI="res%2Ficon.svg">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>PfH43OvgmRvOyHlhzqpEmScP2C13iRlgQdCRliuCLygGJPS3Kt5J9/ocavcqiOZw4lBFfHKm0N4A
QFaT2CDakg==</DigestValue>
</Reference>
<Reference URI="version.txt">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>jJtvGOhH+xRt5nb2nvtEt+HGe/PG0CnhcMjO6XKTjHZEjeAZ5yuYeNEv9jXlGeLwx2zOOKSyvcob
EviMmoUiyw==</DigestValue>
</Reference>
<Reference URI="#prop">
<Transforms>
<Transform Algorithm="http://www.w3.org/2006/12/xml-c14n11"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>aXbSAVgmAz0GsBUeZ1UmNDRrxkWhDUVGb45dZcNRq429wX3X+x6kaXT3NdNDTSNVTU+ypkysPMGv
QY10fG1EWQ==</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>
aaef+oiqV9BcDcwmR65XSRr3diPD5fjpp3Cd4QGdN/7tP4xtF34fiwXI2zQb9Tw2V3d5w2JWyoU9
yXn3iknub56ohKuhDWPLQEVua/g9IVB1V3vWMg0a6Y7aVKw1Bm3ejM1WBAutI/iYDB4Pv90VKN5+
Pkd9hOc8jAZBRNIeqBpBsJHPqyIJHJSxIcfZNClE/0Xna0WKg9H0BPYeqwbyHCGL6iw+RI9wBRK9
wLsM1lg/U0gZfAfpNbHtztcMMLmLriLRtTPAAHzNPM2QqP9Q7gD5UjlXle9d1d9ihJ8K3VwlISso
w8rfJ35NtPbsQ5fOJv64ysknx4+KLPKZmYx4JQ==
</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>
MIIDwjCCAqqgAwIBAgIBATANBgkqhkiG9w0BAQ0FADCBsDELMAkGA1UEBhMCS1IxFDASBgNVBAgM
C1NvdXRoIEtvcmVhMQ4wDAYDVQQHDAVTdXdvbjEmMCQGA1UECgwdU2Ftc3VuZyBFbGVjdHJvbmlj
cyBDby4sIEx0ZC4xDzANBgNVBAsMBk1vYmlsZTEgMB4GA1UEAwwXU2Ftc3VuZyBBdXRob3IgQ0Eg
Q2xhc3MxIDAeBgkqhkiG9w0BCQEWEXRpemVuQHNhbXN1bmcuY29tMB4XDTIxMDkwOTIzMjMxMloX
DTIyMDkwOTIzMjMxMlowfDELMAkGA1UEBhMCQ0ExGTAXBgNVBAgTEEJyaXRpc2ggQ29sdW1iaWEx
FTATBgNVBAcTDFBvd2VsbCBSaXZlcjEYMBYGA1UEChMPQXVyYWlsdXMgRGVzaWduMQkwBwYDVQQL
EwAxFjAUBgNVBAMTDUF1cmkgQ29sbGluZ3MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCn8wFEN7HYZaOQBHUfQuFV1dEwpjYz/FrFJOIwXbVoV1e+s87ya6h6fOa/F2kyyRVbggcQybCk
IzPqFRIWUEFXq6WWA6sCEFYngcCKtbvR109jYXgTiq6KdslxL+aCBp7+cm4A42TI2FZ0554DDUCx
MTHbRS4+hTJfiLq/AN+zcllZFom7qJ35Oz+JxXvShnqjGcKYGy8s6H8hhWtmh8ajJIKcg2fBCXs2
yC0Qqn1OWhfD5nG23KzVAj4zJy4iB1hbQAvrx4jRE+bvNAiN8SjSxxqXqk6NTU6RTIir3auHdcDp
ZrrKBBvAxN22K/3T6sefpW0mT+nOPCguFoLBRx5tAgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0P
BAQDAgeAMA0GCSqGSIb3DQEBDQUAA4IBAQCdmUZNTNEpTrwqpMlTNtbt4R/SHE/N+ia8ACkUtM4F
IxByp86ZOu8LuQkNQFDu3dG8R1btyraoUkU3haQK7m74PSKO6na7NTS+sYHqaAnxj6LsTwmGMSc2
RZMnS5fM2/vdoIgM99M2dq1xUrQhzaS7DRas3awr7+6sRHAkRAgMl/89yEgZ7TKx2yHHM0alMXRR
otyUa2JiHdYDaTFECmqQQkSzn2+Ba2Blv5V7baHYLNxvnp5FaqZRKP06SjF5IAF5KGvsygvLpFxi
tzTQsc+v4SvbILQju4tVkIDnSLZD107l6HQnb+Y9NJXfPWkcPHexskoCQ28wfZBN/gDIXRhy
</X509Certificate>
<X509Certificate>
MIIDmzCCAoOgAwIBAgICYygwDQYJKoZIhvcNAQELBQAwXjEaMBgGA1UECgwRVGl6ZW4gQXNzb2Np
YXRpb24xGjAYBgNVBAsMEVRpemVuIEFzc29jaWF0aW9uMSQwIgYDVQQDDBtUaXplbiBEZXZlbG9w
ZXJzIFJvb3QgQ2xhc3MwHhcNMTMxMjMwMTUwNTU4WhcNMjgxMjI2MTUwNTU4WjCBsDELMAkGA1UE
BhMCS1IxFDASBgNVBAgMC1NvdXRoIEtvcmVhMQ4wDAYDVQQHDAVTdXdvbjEmMCQGA1UECgwdU2Ft
c3VuZyBFbGVjdHJvbmljcyBDby4sIEx0ZC4xDzANBgNVBAsMBk1vYmlsZTEgMB4GA1UEAwwXU2Ft
c3VuZyBBdXRob3IgQ0EgQ2xhc3MxIDAeBgkqhkiG9w0BCQEWEXRpemVuQHNhbXN1bmcuY29tMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs+tRBgnKJs8j7BFg8/UftqbqTCYBV3Jrg1vK
YvEuoTfntYz2uT2SO67raiCsZBAYvJnP54ExkdV++UgB7BDGniWz7bA1pYKak5kNK5jtLQt2DmZX
3qgaLjMyoAz+293CxrBQO4h8NaTQGsO/WLpeQq2Y1ZEnHsq+EUn90H6Vm0HNW+KUayGPYdey+QSW
iiv+L++TSuHrw0b16GYn83emiTnKTCmwpSOZ712Gy9kccl46/K4C8skEDVZjTk9s7r/MN9ZNZsqR
brT/3AYcrF4ao8ipwlHK91WJBXXaiQICvp/dNfCSDWpTWy7z4XmgP16pSLnfgZlwEwWfiaavHRNM
mwIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB/ZlNMTzlIHqC3mFSq
ptuQDZG96XnYqiWsbYkqGgNhcq6c/B3TQsg7Z8cxXY/eqJQDN5gbrIpiUugMRdSOpAEcxF3lwd5k
oOzVLn+3I7x1k6Q4pZdi1fJx+1XjCtrQgPqtvwM77urNqIA1MSG6HUPxYAKkRKjWPsg346E8S/c1
Hq4UVBYEFcDC467uvWtYjxjEVQTmNUaUcQLU9P6VEL4QW+t7V54IN6IJDr9HoOGSgApxIBDDU46b
MUwl+yK0GPvhrviwfVPkfmys1hn5N+gWectQVpBB1gbfy2KlLCCvW/Kl1VmtYz1kWwTyG8bwcjE0
GLkwKNN5lPod+FmMhuW9
</X509Certificate>
</X509Data>
</KeyInfo>
<Object Id="prop"><SignatureProperties xmlns:dsp="http://www.w3.org/2009/xmldsig-properties"><SignatureProperty Id="profile" Target="#AuthorSignature"><dsp:Profile URI="http://www.w3.org/ns/widgets-digsig#profile"></dsp:Profile></SignatureProperty><SignatureProperty Id="role" Target="#AuthorSignature"><dsp:Role URI="http://www.w3.org/ns/widgets-digsig#role-author"></dsp:Role></SignatureProperty><SignatureProperty Id="identifier" Target="#AuthorSignature"><dsp:Identifier></dsp:Identifier></SignatureProperty></SignatureProperties></Object>
</Signature>

125
watch/.sign/signature1.xml Normal file
View File

@ -0,0 +1,125 @@
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="DistributorSignature">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"></SignatureMethod>
<Reference URI=".vscode%2Ftasks.json">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>z3Xirp4bBkVYOuLkeQRNUOUf0hU42upD11qVzov1carXdqFx4D+Fhxpcw1sSPddPpfAujQcSpGII
fNCXLTArdg==</DigestValue>
</Reference>
<Reference URI="LICENSE.Flora">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>DmG3CnrC92doof8/csn15OLKg6OO1WLYfQ9uEQQ/n/VN17C/uS3lvfaTnU0OZ+jz8fLIRG9Zqo75
upc4DKODmg==</DigestValue>
</Reference>
<Reference URI="NOTICE">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>fT2i6Z4ODh+DqtCjQyeK5uN4x+niPkI2udRx96CtU2imYHAMYXHhKFZjjQp3fmpZ6frYv4E3mbBz
6d2rvCgAxA==</DigestValue>
</Reference>
<Reference URI="author-signature.xml">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>aIlAcoz0mBFIGxlZX9W9GwzktPq3vT1cX84vEFNZ2Rwe3CQ8l5nQxXEFCZd8k+eUwnLWgE4X8CU0
emsvomRuZg==</DigestValue>
</Reference>
<Reference URI="config.xml">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>wEoe6rrKdUhpgIG6jEzvr4aXCSyObOwI03rTf6Oh+MRp4JCMN5B7fbMxFT4xdOlNxiU7vzRmdaHa
Uo/N0yXsQw==</DigestValue>
</Reference>
<Reference URI="css%2Fstyle.css">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>pGxu4RkFZ31ePUZ9DZfg8oC27yh0KG+WeQvtJ3OqL94w3NK4B2KCXgxcwV3u0K8l3iLjL0mPqawJ
55eCkvo4Pw==</DigestValue>
</Reference>
<Reference URI="icon.png">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>V19vQSoQmeA+0SAe2zZ5n7ZfhLXwzW1huc0SL0x93gfRFoZJZJYHHE389ozH0pyeuXZsJ8C9miyp
Mq7Z/83Oag==</DigestValue>
</Reference>
<Reference URI="index.html">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>Qv+ECIfSc7x0qgabjOTQ6x0THpMMgdmNFAltsBFUwpLyPwOERbOw7W/rFuJdbr+39Aa8lBZezQX4
/J2XvzwzgQ==</DigestValue>
</Reference>
<Reference URI="js%2Fapp.js">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>LW2jSMYKpzVwyQl6Ss6Ci9XaGattqwHZuKr1dYDZmZF0fg0GDB7PcDcDA2MF7Y8EGRD+UIBUp0ug
DwCMp6BWaQ==</DigestValue>
</Reference>
<Reference URI="res%2Fglow.png">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>+raW+o3PLDMsOdZ46jMqZn56GunBtPfuBWnUSYSEsP6msyDfVULxjD/gOzx0sdpKbW3+IV1pFPfs
jelRSZ+DPQ==</DigestValue>
</Reference>
<Reference URI="res%2Ficon.svg">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>PfH43OvgmRvOyHlhzqpEmScP2C13iRlgQdCRliuCLygGJPS3Kt5J9/ocavcqiOZw4lBFfHKm0N4A
QFaT2CDakg==</DigestValue>
</Reference>
<Reference URI="version.txt">
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>jJtvGOhH+xRt5nb2nvtEt+HGe/PG0CnhcMjO6XKTjHZEjeAZ5yuYeNEv9jXlGeLwx2zOOKSyvcob
EviMmoUiyw==</DigestValue>
</Reference>
<Reference URI="#prop">
<Transforms>
<Transform Algorithm="http://www.w3.org/2006/12/xml-c14n11"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"></DigestMethod>
<DigestValue>/r5npk2VVA46QFJnejgONBEh4BWtjrtu9x/IFeLksjWyGmB/cMWKSJWQl7aU3YRQRZ3AesG8gF7q
GyvKX9Snig==</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>
b5PqbrJOZSnLXSUnNj/0NTTNFwjblrGQuH4RUF8ZGkyLuktkEvJtVX/1HTQhcNjv4iJ7Eb82casB
JdeKq3Ml3qIL5UlRJZRcltdcPmU0Fl1aXlrDztBTotaS22BD012HEwRuEI8hBCwlOlNnPB2Eq6IC
ZLhSRPAjK8nrbnnyhkOqvaFNY/VU6jhchZ8fNvD+6LU/GhNqeleWD8Db65YvFR/qpAQnTPSKcDXv
sAnBBfzhwqp8N1teMYNpAnReWWZ/b/N+KeTUhjCrJjMKFXdNtFPxPlB/jTEdlJv6tnGFU1Uv0hTD
80CslwCx9ZA768tpYyTrsf53eFuAK/4FXRo37g==
</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>
MIID2DCCAsCgAwIBAgIBZTANBgkqhkiG9w0BAQ0FADCBnjELMAkGA1UEBhMCS1IxFDASBgNVBAgM
C1NvdXRoIEtvcmVhMQ4wDAYDVQQHDAVTdXdvbjEmMCQGA1UECgwdU2Ftc3VuZyBFbGVjdHJvbmlj
cyBDby4sIEx0ZC4xDzANBgNVBAsMBk1vYmlsZTEwMC4GA1UEAwwnU2Ftc3VuZyBUaXplbiBERVZF
TE9QRVIgUHVibGljIENBIENsYXNzMB4XDTIxMDkwOTIzMjMzN1oXDTIyMDkwOTIzMjMzN1owZjER
MA8GA1UEAwwIVGl6ZW5TREsxCTAHBgNVBAsMADEJMAcGA1UECgwAMQkwBwYDVQQHDAAxCTAHBgNV
BAgMADEJMAcGA1UEBhMAMRowGAYJKoZIhvcNAQkBFgttZUBhdXJpLnh5ejCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAIdyIjaEkrlgDNOBl0AD/sAg1MEVkIgTQYzWk/F1Cb/nB4ALQ0p+
JsYpuHoDySy1ioFqjz5r3dULmnOF1vH1xrk4ypTznYgJtccFknY3Ii6dvss/LCW3NHt7KY6j6UR7
xFbqiM/7sryK3qlXZEIJ5QW06pD+pYS3aMpREmBuLhuFqsOKcasST+osu+ES3sFGqN9lv714Kema
eneBvuw9FaHEeCx0xWpA/wtWoBSSjckxft03+zzgiOCLN+q39OGJIZDBe9IKY63CsTvbBEIRDxhZ
5B7hIbm7XaerRl/FGNPtvo2mA0oFAMYu4stEG/Kygk2J82y/VFmOo9qFStzk280CAwEAAaNYMFYw
VAYDVR0RBE0wS4YUVVJOOnRpemVuOnBhY2thZ2VpZD2GM1VSTjp0aXplbjpkZXZpY2VpZD0yLjAj
TFRaaDFEUkNocWxGdlp6RUhPd0V2TWtmT29jPTANBgkqhkiG9w0BAQ0FAAOCAQEAS+VYNYT/r/tn
l0zMGjoy3lREd3Zdbt8V488IzXT3WC84VAXcr6bl7k/XIzW4XSsB6Yqv7eB36DoPNaCT43pFCvP6
ReSYjYeQ/jnmcymQ5D8r+8VHNVI3xeSZzDaJovpBBVM3nmaB4UbeFSEvddmzIuVJX6ZTDIsKDt1l
JhM+a8GekvrkZhhbk+4XDy4rFK3t7IF0+CKAEQlxPbOE/iyqgLGAOdQR23HNpEMWK5MAV2lBUkjw
QzfbLiXNJ4mv4aeAyqD361Rp2pilNGPMv7l7N9Z05X4mJ5bDsurDKz7GjqYEEYQpOW8pTJdNURuK
Z7Fqo6RFiNUNP0CzKraVnXa6Tw==
</X509Certificate>
<X509Certificate>
MIIDuzCCAqOgAwIBAgICL+cwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAktSMRQwEgYDVQQI
DAtTb3V0aCBLb3JlYTEOMAwGA1UEBwwFU3V3b24xJjAkBgNVBAoMHVNhbXN1bmcgRWxlY3Ryb25p
Y3MgQ28uLCBMdGQuMTIwMAYDVQQDDClTYW1zdW5nIFRpemVuIERFVkVMT1BFUiBQdWJsaWMgUm9v
dCBDbGFzczAeFw0xMzEyMzAxNTAxNDdaFw0yODEyMjYxNTAxNDdaMIGeMQswCQYDVQQGEwJLUjEU
MBIGA1UECAwLU291dGggS29yZWExDjAMBgNVBAcMBVN1d29uMSYwJAYDVQQKDB1TYW1zdW5nIEVs
ZWN0cm9uaWNzIENvLiwgTHRkLjEPMA0GA1UECwwGTW9iaWxlMTAwLgYDVQQDDCdTYW1zdW5nIFRp
emVuIERFVkVMT1BFUiBQdWJsaWMgQ0EgQ2xhc3MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCkqW9d0zO5NFOc7u164DKe9Yx+yEgUnsbhnJasqHaqT71qaMxyCOjyysZi7gGycDcgmLcU
tr2wSMTGWPibK8SrJ8bV/J1cy9nTpljM3s+lbPIVVxZeufhJkU79tXImHLolERd0vui+rj0Xpd9O
zlyNTRt0+PWVT1taWcbfHL7pUD25hMkTc8C3bC+dUoT1/RhCkXgmUvIor3EnnU0rBtAl4qNtg5y1
7RLT8dyicieAHCcW923YC/ngMZxnBjbL2Ht1RBUvrL6K3X5+l0VeF4qC3g7TKm/a/SGjq12ZD4sc
7rglzjSNlTVSGag6/2WkqxE6HheEBWaloYOdauzwydFNAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8w
DQYJKoZIhvcNAQELBQADggEBAG88wKjKeTbj5I6wMEvRihd+gWrESJX3bXUSlHWjUfYKcvg3GzM9
cdooscqJdYgN8ezQuYiJQRk7BaPHSlPGkchsPUjvSM6WvIn9IT+VsEuVBrZNbZMSv6lM6wNig3TE
h2OxFTW6Bt8mCZB74EP2wb+39Fr5aJwWvmkxQjct3/O+GYPQhe0Lu1qpbbMhzafYYybSmO+om02V
cTWyJ/vtLUNyGfN7aHyBm6PFE5piAfpBaO9Az+zWil1HOsnzu1tGGnyV6IzQ/UiIo5P2sD8I5lEO
Xdf9Eicy9y5z2Ci2zrmDcAGWL8o5wOWrCqML2ijximFzyQ/swlMC9xBIX9RCFws=
</X509Certificate>
</X509Data>
</KeyInfo>
<Object Id="prop"><SignatureProperties xmlns:dsp="http://www.w3.org/2009/xmldsig-properties"><SignatureProperty Id="profile" Target="#DistributorSignature"><dsp:Profile URI="http://www.w3.org/ns/widgets-digsig#profile"></dsp:Profile></SignatureProperty><SignatureProperty Id="role" Target="#DistributorSignature"><dsp:Role URI="http://www.w3.org/ns/widgets-digsig#role-distributor"></dsp:Role></SignatureProperty><SignatureProperty Id="identifier" Target="#DistributorSignature"><dsp:Identifier></dsp:Identifier></SignatureProperty></SignatureProperties></Object>
</Signature>

12
watch/.tproject Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tproject xmlns="http://www.tizen.org/tproject">
<platforms>
<platform>
<name>wearable-4.0</name>
</platform>
</platforms>
<package>
<blacklist/>
<resFallback autoGen="true"/>
</package>
</tproject>

BIN
watch/Focus.wgt Normal file

Binary file not shown.

206
watch/LICENSE.Flora Normal file
View File

@ -0,0 +1,206 @@
Flora License
Version 1.1, April, 2013
http://floralicense.org/license/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and
all other entities that control, are controlled by, or are
under common control with that entity. For the purposes of
this definition, "control" means (i) the power, direct or indirect,
to cause the direction or management of such entity,
whether by contract or otherwise, or (ii) ownership of fifty percent (50%)
or more of the outstanding shares, or (iii) beneficial ownership of
such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source,
and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form,
made available under the License, as indicated by a copyright notice
that is included in or attached to the work (an example is provided
in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form,
that is based on (or derived from) the Work and for which the editorial
revisions, annotations, elaborations, or other modifications represent,
as a whole, an original work of authorship. For the purposes of this License,
Derivative Works shall not include works that remain separable from,
or merely link (or bind by name) to the interfaces of, the Work and
Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original
version of the Work and any modifications or additions to that Work or
Derivative Works thereof, that is intentionally submitted to Licensor
for inclusion in the Work by the copyright owner or by an individual or
Legal Entity authorized to submit on behalf of the copyright owner.
For the purposes of this definition, "submitted" means any form of
electronic, verbal, or written communication sent to the Licensor or
its representatives, including but not limited to communication on
electronic mailing lists, source code control systems, and issue
tracking systems that are managed by, or on behalf of, the Licensor
for the purpose of discussing and improving the Work, but excluding
communication that is conspicuously marked or otherwise designated
in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
"Tizen Certified Platform" shall mean a software platform that complies
with the standards set forth in the Tizen Compliance Specification
and passes the Tizen Compliance Tests as defined from time to time
by the Tizen Technical Steering Group and certified by the Tizen
Association or its designated agent.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work
solely as incorporated into a Tizen Certified Platform, where such
license applies only to those patent claims licensable by such
Contributor that are necessarily infringed by their Contribution(s)
alone or by combination of their Contribution(s) with the Work solely
as incorporated into a Tizen Certified Platform to which such
Contribution(s) was submitted. If You institute patent litigation
against any entity (including a cross-claim or counterclaim
in a lawsuit) alleging that the Work or a Contribution incorporated
within the Work constitutes direct or contributory patent infringement,
then any patent licenses granted to You under this License for that
Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof pursuant to the copyright license
above, in any medium, with or without modifications, and in Source or
Object form, provided that You meet the following conditions:
1. You must give any other recipients of the Work or Derivative Works
a copy of this License; and
2. You must cause any modified files to carry prominent notices stating
that You changed the files; and
3. You must retain, in the Source form of any Derivative Works that
You distribute, all copyright, patent, trademark, and attribution
notices from the Source form of the Work, excluding those notices
that do not pertain to any part of the Derivative Works; and
4. If the Work includes a "NOTICE" text file as part of its distribution,
then any Derivative Works that You distribute must include a readable
copy of the attribution notices contained within such NOTICE file,
excluding those notices that do not pertain to any part of
the Derivative Works, in at least one of the following places:
within a NOTICE text file distributed as part of the Derivative Works;
within the Source form or documentation, if provided along with the
Derivative Works; or, within a display generated by the Derivative Works,
if and wherever such third-party notices normally appear.
The contents of the NOTICE file are for informational purposes only
and do not modify the License. You may add Your own attribution notices
within Derivative Works that You distribute, alongside or as an addendum
to the NOTICE text from the Work, provided that such additional attribution
notices cannot be construed as modifying the License. You may add Your own
copyright statement to Your modifications and may provide additional or
different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works
as a whole, provided Your use, reproduction, and distribution of
the Work otherwise complies with the conditions stated in this License
and your own copyright statement or terms and conditions do not conflict
the conditions stated in the License including section 3.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Flora License to your work
To apply the Flora License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Samsung Electronics Co., Ltd.
Licensed under the Flora License, Version 1.1 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://floralicense.org/license/
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

3
watch/NOTICE Normal file
View File

@ -0,0 +1,3 @@
Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved.
Except as noted, this software is licensed under Flora License, Version 1.
Please, see the LICENSE.Flora file for Flora License, Version 1 terms and conditions.

15
watch/config.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version='1.0' encoding='UTF-8'?>
<widget xmlns:tizen='http://tizen.org/ns/widgets' xmlns='http://www.w3.org/ns/widgets' id='http://yourdomain/Focus' version='1.0.0' viewmodes='maximized'>
<tizen:application id='kFVD8WK8ro.Focus' package='kFVD8WK8ro' required_version='2.3.1' ambient_support='enable'/>
<tizen:category name='http://tizen.org/category/wearable_clock'/>
<feature name='http://tizen.org/feature/screen.shape.circle'/>
<feature name='http://tizen.org/feature/screen.size.all'/>
<feature name='http://tizen.org/feature/calendar'/>
<feature name='http://tizen.org/feature/calendar.read'/>
<feature name='http://tizen.org/feature/calendar.write'/>
<content src='index.html'/>
<icon src='icon.png'/>
<name>Focus</name>
<tizen:privilege name='http://tizen.org/privilege/alarm'/>
<tizen:profile name='wearable'/>
</widget>

24
watch/css/style.css Normal file
View File

@ -0,0 +1,24 @@
* {
margin: 0;
padding: 0
}
html, body {
width: 100%;
height: 100%;
color: #fff;
background: #000;
}
#container {
display: -webkit-flex;
-webkit-align-items: center;
width: 100%;
height: 100%;
}
#canvas {
width: 100%;
height: 100%;
margin: auto;
}

BIN
watch/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

17
watch/index.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<title>AmbientWatch</title>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<div id="container">
<canvas id="canvas"></canvas>
</div>
<script src="js/app.js"></script>
</body>
</html>

313
watch/js/app.js Normal file
View File

@ -0,0 +1,313 @@
'use strict';
(() => {
let theme = {
/** 0: none, 1: quarter, 2: hours, 3: hours + minute */
notches: 3,
/** False: don't display, True: display */
events: true,
/** False: don't show minute hand, True: show minute hand */
minutes: true,
/** False: don't show center glow, True: show center glow */
glow: true,
/** Center glow color. */
glow_a: '#4294ff',
/** Moving glow 1 color. */
glow_b: 'rgba(5, 124, 242, 0.6)',
/** Moving glow 2 color. */
glow_c: 'rgba(198, 0, 237, 1)',
// /** Center glow color. */
// glow_a: '#e838ff',
// /** Moving glow 1 color. */
// glow_b: 'rgba(255, 38, 56, 0.6)',
// /** Moving glow 2 color. */
// glow_c: 'rgba(179, 38, 255, 1)',
}
let image_loaded = false;
let window_loaded = false;
window.onload = () => {
window_loaded = true;
if (image_loaded) init();
};
const glow_image = document.createElement('img');
glow_image.src = 'res/glow.png';
glow_image.onload = () => {
image_loaded = true;
if (window_loaded) init();
};
const glow_canvas = document.createElement('canvas');
glow_canvas.width = 128;
glow_canvas.height = 128;
const glow_ctx = glow_canvas.getContext('2d');
function init() {
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
canvas.width = document.body.clientWidth;
canvas.height = canvas.width;
const clockRadius = document.body.clientWidth / 2;
let rot = 0;
let ambient = false;
const EVENT_WIDTH = 22;
let EVENT_BUFFER = theme.notches === 0 ? 2 : 14;
const TITLE_BUFFER = 2;
const TITLE_SIZE = 16;
const NOTCH_BUFFER = 2;
const DATE_SIZE = 20;
const COLOR_LIGHT_DIM = 'rgba(65, 199, 232, 0.15)';
const COLOR_LIGHT_OVERLAY = 'rgba(65, 199, 232, 0.33)';
const COLOR_MINUTE_HAND = 'rgba(65, 199, 232, 0.8)';
const degToRad = (deg) => deg * (Math.PI / 180);
const createDate = (hour, minute) => {
const date = new Date();
date.setHours(hour, minute, 0);
return date;
};
const events = [
{ start: createDate(9, 30), end: createDate(11, 20), color: '#33B679', title: 'PAAS' },
{ start: createDate(12, 30), end: createDate(1, 20), color: '#33B679', title: 'STAT' },
{ start: createDate(1, 30), end: createDate(2, 20), color: '#33B679', title: 'MATH' }
// { start: createDate(9, 30), end: createDate(11, 20), color: '#59acff', title: 'Work' },
// { start: createDate(12, 30), end: createDate(1, 20), color: '#59acff', title: 'Walk' },
// { start: createDate(1, 30), end: createDate(2, 20), color: '#59acff', title: 'Bike' }
];
function getTime() {
try { return tizen.time.getCurrentDateTime(); }
catch (e) { return new Date(); }
}
function drawCircle(angle, distance, width, color) {
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(angle)
ctx.beginPath();
ctx.arc(distance, 0, width / 2, 0, 2 * Math.PI, false);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
ctx.restore();
}
// function drawLine(startDistance, endDistance, angle, width, color) {
// ctx.save();
// ctx.translate(canvas.width / 2, canvas.height / 2);
// ctx.rotate(degToRad(angle + 90));
// ctx.beginPath();
// ctx.lineWidth = width;
// ctx.strokeStyle = color;
// ctx.moveTo(startDistance, 0);
// ctx.lineTo(endDistance, 0);
// ctx.stroke();
// ctx.closePath();
// ctx.restore();
// }
function drawRoundedRect(startDistance, endDistance, angle, width, fill, stroke, strokeWidth) {
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(-width / 2, startDistance);
ctx.lineTo(-width / 2, endDistance);
ctx.quadraticCurveTo(-width / 2, endDistance + width / 1.5, 0, endDistance + width / 1.5);
ctx.quadraticCurveTo(width / 2, endDistance + width / 1.5, width / 2, endDistance);
ctx.lineTo(width / 2, startDistance);
ctx.quadraticCurveTo(width / 2, startDistance - width / 1.5, 0, startDistance - width / 1.5);
ctx.quadraticCurveTo(-width / 2, startDistance - width / 1.5, -width / 2, startDistance);
ctx.closePath();
if (fill) {
ctx.fillStyle = fill;
ctx.fill();
}
if (stroke && strokeWidth) {
ctx.strokeStyle = stroke;
ctx.lineWidth = strokeWidth;
ctx.stroke();
}
ctx.restore()
}
function drawEvent(event) {
const startAngle = degToRad(((event.start.getHours() % 12) +
event.start.getMinutes() / 60) / 12 * 360 + EVENT_WIDTH / 6 - 90);
const endAngle = degToRad(((event.end.getHours() % 12) +
event.end.getMinutes() / 60) / 12 * 360 - EVENT_WIDTH / 8 - 90);
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.beginPath();
ctx.fillStyle = COLOR_LIGHT_DIM;
ctx.arc(0, 0, clockRadius - EVENT_BUFFER, startAngle, endAngle, false);
let controlPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
let controlPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
let endPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
let endPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
controlPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
controlPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
endPosX = Math.cos(endAngle) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
endPosY = Math.sin(endAngle) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
ctx.arc(0, 0, clockRadius - EVENT_BUFFER - EVENT_WIDTH, endAngle, startAngle, true);
controlPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
controlPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
endPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
endPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
controlPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
controlPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
endPosX = Math.cos(startAngle) * (clockRadius - EVENT_BUFFER);
endPosY = Math.sin(startAngle) * (clockRadius - EVENT_BUFFER);
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
ctx.fill();
ctx.closePath();
ctx.restore();
drawCircle(startAngle, clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2, EVENT_WIDTH - 8, event.color);
ctx.font = `900 ${TITLE_SIZE}px Arial sans-serif`;
ctx.fillStyle = '#fff';
ctx.textBaseline = 'top';
ctx.textAlign = 'center';
let currentAngle = startAngle + degToRad(96);
for (let i = 0; i < event.title.length; i++) {
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(currentAngle);
ctx.fillText(event.title[i], 0, -(clockRadius - EVENT_BUFFER - TITLE_BUFFER));
ctx.restore();
if (i < event.title.length - 1)
currentAngle += ctx.measureText(event.title[i]).width * 0.0065 / 2
+ ctx.measureText(event.title[i + 1]).width * 0.0065 / 2;
}
}
function drawGlow(offsetX, offsetY, scaleMult, color) {
glow_ctx.fillStyle = color;
glow_ctx.globalCompositeOperation = 'source-over';
glow_ctx.fillRect(0, 0, glow_canvas.width, glow_canvas.height);
glow_ctx.globalCompositeOperation = 'destination-in';
glow_ctx.drawImage(glow_image, 0, 0);
const scale = 240 * scaleMult;
ctx.drawImage(glow_canvas,
canvas.width / 2 + offsetX - scale / 2,
canvas.height / 2 + offsetY - scale / 2,
scale, scale);
}
function drawWatchFace() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const time = getTime();
ctx.font = `900 ${DATE_SIZE}px Arial sans-serif`;
ctx.fillStyle = '#aaa';
ctx.textBaseline = 'bottom';
ctx.textAlign = 'center';
ctx.fillText(`${['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'][time.getDay()]} ${time.getDate()}`,
clockRadius, clockRadius + clockRadius / 1.75);
ctx.fillStyle = '#666'
ctx.fillRect(clockRadius - 1 * DATE_SIZE, clockRadius + clockRadius / 1.75 + 2, 2 * DATE_SIZE, 2);
if (!ambient) {
rot = (rot + 1) % 360;
let scaleA = 1 + (Math.sin(degToRad(rot + 180)) / 6);
let scaleB = 1 + (Math.cos(degToRad(rot)) / 6);
let scaleC = 1.3 + (Math.cos(degToRad(rot - 90)) / 6);
let offsetX = Math.cos(degToRad(rot + 90)) * 50 + (1 - scaleA);
let offsetY = Math.sin(degToRad(rot + 90)) * 50 + (1 - scaleB);
if (theme.glow) {
ctx.globalCompositeOperation = 'lighter';
drawGlow(-offsetX, -offsetY, scaleA, theme.glow_b);
drawGlow(0, 0, scaleC, theme.glow_a);
drawGlow(offsetX, offsetY, scaleB, theme.glow_c);
ctx.globalCompositeOperation = 'source-over';
}
if (theme.notches) {
for (let i = 0; i < 12 * 5; i++) {
if (i % 15 === 0)
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 8, COLOR_MINUTE_HAND)
else if (i % 5 === 0 && theme.notches >= 2)
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 6, COLOR_LIGHT_OVERLAY)
else if (theme.notches >= 3)
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 4, COLOR_LIGHT_DIM)
}
}
}
if (theme.events) {
for (let event of events) {
drawEvent(event);
}
}
drawCircle(0, 0, 36, COLOR_LIGHT_OVERLAY);
drawCircle(0, 0, 20, '#fff');
if (theme.minutes) {
const minuteAngle = degToRad((time.getMinutes() + time.getSeconds() / 60) / 60 * 360 - 180);
drawRoundedRect(34, clockRadius - 64, minuteAngle, 16, COLOR_MINUTE_HAND);
}
const hourAngle = degToRad(((time.getHours() % 12) + time.getMinutes() / 60) / 12 * 360 - 180);
drawRoundedRect(36, clockRadius - 80, hourAngle, 16, 'rgba(0, 0, 0, 0.15)', '#fff', 4);
if (!ambient) setTimeout(() => window.requestAnimationFrame(drawWatchFace), 1000/10);
}
window.requestAnimationFrame(drawWatchFace);
window.addEventListener('timetick', drawWatchFace);
window.addEventListener('ambientmodechanged', (e) => {
ambient = e.detail.ambientMode;
drawWatchFace();
});
document.addEventListener('visibilitychange', () => {
if (!document.hidden) drawWatchFace();
});
}
})();

BIN
watch/res/glow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

1
watch/res/icon.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M6,17C6,15 10,13.9 12,13.9C14,13.9 18,15 18,17V18H6M15,9A3,3 0 0,1 12,12A3,3 0 0,1 9,9A3,3 0 0,1 12,6A3,3 0 0,1 15,9M3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5C3.89,3 3,3.9 3,5Z" /></svg>

After

Width:  |  Height:  |  Size: 502 B

1
watch/version.txt Normal file
View File

@ -0,0 +1 @@
1.0.5