Merge pull request #80 from Gimanh/feat/mobile-widgets

feat: mobile widgets
This commit is contained in:
Nikolai Giman
2026-07-11 10:59:14 +02:00
committed by GitHub
48 changed files with 1581 additions and 30 deletions
@@ -0,0 +1,6 @@
android/build/
android/.gradle/
android/local.properties
.swiftpm/
ios/.build/
DerivedData/
@@ -0,0 +1,24 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "CapacitorWidgetBridge",
platforms: [.iOS(.v15)],
products: [
.library(
name: "CapacitorWidgetBridge",
targets: ["WidgetBridgePlugin"])
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
],
targets: [
.target(
name: "WidgetBridgePlugin",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm")
],
path: "ios/Sources/WidgetBridgePlugin")
]
)
@@ -0,0 +1,51 @@
ext {
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
}
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.0'
}
}
apply plugin: 'com.android.library'
android {
namespace = "tech.taskview.plugins.widgetbridge"
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
defaultConfig {
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError = false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
}
repositories {
google()
mavenCentral()
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':capacitor-android')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
}
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,48 @@
package tech.taskview.plugins.widgetbridge;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
@CapacitorPlugin(name = "WidgetBridge")
public class WidgetBridgePlugin extends Plugin {
public static final String PREFS_NAME = "taskview_widget";
public static final String SNAPSHOT_KEY = "widgetSnapshot";
public static final String ACTION_WIDGET_UPDATE = "tech.taskview.widget.UPDATE";
@PluginMethod
public void setSnapshot(PluginCall call) {
String snapshot = call.getString("snapshot");
if (snapshot == null) {
call.reject("snapshot is required");
return;
}
prefs().edit().putString(SNAPSHOT_KEY, snapshot).apply();
notifyWidgets();
call.resolve();
}
@PluginMethod
public void clearSnapshot(PluginCall call) {
prefs().edit().remove(SNAPSHOT_KEY).apply();
notifyWidgets();
call.resolve();
}
private SharedPreferences prefs() {
return getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
}
private void notifyWidgets() {
Context context = getContext();
Intent intent = new Intent(ACTION_WIDGET_UPDATE);
intent.setPackage(context.getPackageName());
context.sendBroadcast(intent);
}
}
@@ -0,0 +1,44 @@
import Foundation
import Capacitor
import WidgetKit
@objc(WidgetBridgePlugin)
public class WidgetBridgePlugin: CAPPlugin, CAPBridgedPlugin {
public let identifier = "WidgetBridgePlugin"
public let jsName = "WidgetBridge"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "setSnapshot", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "clearSnapshot", returnType: CAPPluginReturnPromise)
]
static let snapshotKey = "widgetSnapshot"
private var sharedDefaults: UserDefaults? {
guard let appGroup = getConfig().getString("appGroup") else { return nil }
return UserDefaults(suiteName: appGroup)
}
@objc func setSnapshot(_ call: CAPPluginCall) {
guard let snapshot = call.getString("snapshot") else {
call.reject("snapshot is required")
return
}
guard let defaults = sharedDefaults else {
call.reject("WidgetBridge appGroup is not configured in capacitor.config")
return
}
defaults.set(snapshot, forKey: Self.snapshotKey)
WidgetCenter.shared.reloadAllTimelines()
call.resolve()
}
@objc func clearSnapshot(_ call: CAPPluginCall) {
guard let defaults = sharedDefaults else {
call.reject("WidgetBridge appGroup is not configured in capacitor.config")
return
}
defaults.removeObject(forKey: Self.snapshotKey)
WidgetCenter.shared.reloadAllTimelines()
call.resolve()
}
}
@@ -0,0 +1,41 @@
{
"name": "capacitor-widget-bridge",
"private": false,
"version": "0.1.0",
"type": "module",
"description": "Capacitor bridge that shares a data snapshot with native home-screen widgets (iOS WidgetKit / Android App Widgets)",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": [
"dist",
"ios",
"android",
"Package.swift"
],
"scripts": {
"build": "tsc",
"type-check": "tsc --noEmit"
},
"capacitor": {
"ios": {
"src": "ios"
},
"android": {
"src": "android"
}
},
"peerDependencies": {
"@capacitor/core": "^8.0.0"
},
"devDependencies": {
"@capacitor/core": "^8.1.0",
"typescript": "~5.8.3"
}
}
@@ -0,0 +1,32 @@
export type WidgetSnapshotTask = {
id: number
title: string
priority: 1 | 2 | 3
overdue: boolean
endTime: string | null
endDate: string | null
path: string
}
export type WidgetSnapshotMode = 'today' | 'upcoming'
export type WidgetSnapshot = {
v: 3
generatedAt: string
locale: string
orgSlug: string | null
mode: WidgetSnapshotMode
todayCount: number
overdueCount: number
upcomingCount: number
tasks: WidgetSnapshotTask[]
}
export type SetSnapshotOptions = {
snapshot: string
}
export type WidgetBridgePlugin = {
setSnapshot(options: SetSnapshotOptions): Promise<void>
clearSnapshot(): Promise<void>
}
@@ -0,0 +1,12 @@
import { registerPlugin } from '@capacitor/core'
import type { WidgetBridgePlugin } from './definitions'
export const WidgetBridge = registerPlugin<WidgetBridgePlugin>('WidgetBridge')
export type {
WidgetBridgePlugin,
WidgetSnapshot,
WidgetSnapshotMode,
WidgetSnapshotTask,
SetSnapshotOptions,
} from './definitions'
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": [
"ES2020"
],
"skipLibCheck": true,
"moduleResolution": "bundler",
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"noEmit": false,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src"
]
}
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "com.handscreamgnl.taskview.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 14801
versionName "1.48.1"
versionCode 14902
versionName "1.49.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+1
View File
@@ -17,6 +17,7 @@ dependencies {
implementation project(':capacitor-push-notifications')
implementation project(':capacitor-splash-screen')
implementation project(':capgo-capacitor-updater')
implementation project(':capacitor-widget-bridge')
}
@@ -29,6 +29,24 @@
</activity>
<receiver
android:name=".widget.TodayWidgetProvider"
android:exported="false"
android:label="@string/widget_today_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="tech.taskview.widget.UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_today_info" />
</receiver>
<service
android:name=".widget.TodayWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
@@ -0,0 +1,141 @@
package com.handscream.taskview.app.widget;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
import com.handscream.taskview.app.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import tech.taskview.plugins.widgetbridge.WidgetBridgePlugin;
public class TodayWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
private final Context context;
private final List<JSONObject> tasks = new ArrayList<>();
private boolean upcoming = false;
public TodayWidgetFactory(Context context) {
this.context = context;
}
@Override
public void onCreate() {
}
@Override
public void onDataSetChanged() {
tasks.clear();
upcoming = false;
String snapshot = context
.getSharedPreferences(WidgetBridgePlugin.PREFS_NAME, Context.MODE_PRIVATE)
.getString(WidgetBridgePlugin.SNAPSHOT_KEY, null);
if (snapshot == null) return;
try {
JSONObject parsed = new JSONObject(snapshot);
upcoming = "upcoming".equals(parsed.optString("mode"));
JSONArray items = parsed.optJSONArray("tasks");
if (items == null) return;
for (int i = 0; i < items.length(); i++) {
JSONObject task = items.optJSONObject(i);
if (task != null) tasks.add(task);
}
} catch (JSONException ignored) {
}
}
@Override
public void onDestroy() {
tasks.clear();
}
@Override
public int getCount() {
return tasks.size();
}
@Override
public RemoteViews getViewAt(int position) {
JSONObject task = tasks.get(position);
RemoteViews row = new RemoteViews(context.getPackageName(), R.layout.widget_today_item);
row.setTextViewText(R.id.widget_item_title, task.optString("title"));
row.setImageViewResource(R.id.widget_item_checkbox, priorityCheckbox(task.optInt("priority", 1)));
boolean overdue = !upcoming && task.optBoolean("overdue", false);
String meta = upcoming
? formatEndDate(task)
: (overdue ? context.getString(R.string.widget_overdue) : formatEndTime(task));
if (meta == null || meta.isEmpty()) {
row.setViewVisibility(R.id.widget_item_meta, View.GONE);
} else {
row.setViewVisibility(R.id.widget_item_meta, View.VISIBLE);
row.setTextViewText(R.id.widget_item_meta, meta);
row.setTextColor(
R.id.widget_item_meta,
context.getColor(overdue ? R.color.widget_overdue : R.color.widget_text_secondary));
}
Intent fillIn = new Intent();
String path = task.optString("path", "");
if (!path.isEmpty()) {
fillIn.setData(Uri.parse("taskview://open?path=" + Uri.encode(path)));
}
row.setOnClickFillInIntent(R.id.widget_item_root, fillIn);
return row;
}
private int priorityCheckbox(int priority) {
switch (priority) {
case 3:
return R.drawable.widget_checkbox_high;
case 2:
return R.drawable.widget_checkbox_medium;
default:
return R.drawable.widget_checkbox_low;
}
}
private String formatEndTime(JSONObject task) {
if (task.isNull("endTime")) return null;
String endTime = task.optString("endTime", "");
return endTime.length() >= 5 ? endTime.substring(0, 5) : endTime;
}
private String formatEndDate(JSONObject task) {
if (task.isNull("endDate")) return null;
String endDate = task.optString("endDate", "");
String[] parts = endDate.split("-");
return parts.length == 3 ? parts[2] + "." + parts[1] : endDate;
}
@Override
public RemoteViews getLoadingView() {
return null;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public long getItemId(int position) {
return tasks.get(position).optLong("id", position);
}
@Override
public boolean hasStableIds() {
return true;
}
}
@@ -0,0 +1,90 @@
package com.handscream.taskview.app.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.RemoteViews;
import com.handscream.taskview.app.MainActivity;
import com.handscream.taskview.app.R;
import org.json.JSONObject;
import tech.taskview.plugins.widgetbridge.WidgetBridgePlugin;
public class TodayWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager manager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
manager.updateAppWidget(appWidgetId, buildViews(context, appWidgetId));
}
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (WidgetBridgePlugin.ACTION_WIDGET_UPDATE.equals(intent.getAction())) {
AppWidgetManager manager = AppWidgetManager.getInstance(context);
int[] ids = manager.getAppWidgetIds(new ComponentName(context, TodayWidgetProvider.class));
if (ids.length == 0) return;
manager.notifyAppWidgetViewDataChanged(ids, R.id.widget_today_list);
onUpdate(context, manager, ids);
}
}
private RemoteViews buildViews(Context context, int appWidgetId) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_today);
JSONObject snapshot = readSnapshot(context);
boolean upcoming = snapshot != null && "upcoming".equals(snapshot.optString("mode"));
int count = snapshot == null
? 0
: (upcoming ? snapshot.optInt("upcomingCount", 0) : snapshot.optInt("todayCount", 0));
views.setTextViewText(
R.id.widget_today_title,
context.getString(upcoming ? R.string.widget_upcoming_title : R.string.widget_today_title));
views.setTextViewText(R.id.widget_today_count, String.valueOf(count));
Intent adapter = new Intent(context, TodayWidgetService.class);
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
adapter.setData(Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME)));
views.setRemoteAdapter(R.id.widget_today_list, adapter);
views.setEmptyView(R.id.widget_today_list, R.id.widget_today_empty);
PendingIntent openApp = PendingIntent.getActivity(
context,
0,
new Intent(context, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.widget_today_header, openApp);
views.setOnClickPendingIntent(R.id.widget_today_empty, openApp);
Intent template = new Intent(context, MainActivity.class);
template.setAction(Intent.ACTION_VIEW);
PendingIntent templateIntent = PendingIntent.getActivity(
context,
1,
template,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
views.setPendingIntentTemplate(R.id.widget_today_list, templateIntent);
return views;
}
private JSONObject readSnapshot(Context context) {
String snapshot = context
.getSharedPreferences(WidgetBridgePlugin.PREFS_NAME, Context.MODE_PRIVATE)
.getString(WidgetBridgePlugin.SNAPSHOT_KEY, null);
if (snapshot == null) return null;
try {
return new JSONObject(snapshot);
} catch (Exception e) {
return null;
}
}
}
@@ -0,0 +1,12 @@
package com.handscream.taskview.app.widget;
import android.content.Intent;
import android.widget.RemoteViewsService;
public class TodayWidgetService extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new TodayWidgetFactory(getApplicationContext());
}
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/widget_accent" />
<corners android:radius="999dp" />
</shape>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/widget_background" />
<corners android:radius="16dp" />
</shape>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/transparent" />
<stroke
android:width="1.5dp"
android:color="@color/widget_priority_high" />
</shape>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/transparent" />
<stroke
android:width="1.5dp"
android:color="@color/widget_priority_low" />
</shape>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/transparent" />
<stroke
android:width="1.5dp"
android:color="@color/widget_priority_medium" />
</shape>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/widget_header_background" />
<corners
android:topLeftRadius="16dp"
android:topRightRadius="16dp" />
</shape>
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/widget_bg">
<LinearLayout
android:id="@+id/widget_today_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@drawable/widget_header_bg"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<ImageView
android:id="@+id/widget_today_logo"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:src="@mipmap/ic_launcher_round"
android:importantForAccessibility="no" />
<TextView
android:id="@+id/widget_today_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/widget_today_title"
android:textColor="@color/widget_text_primary"
android:textSize="16sp"
android:textStyle="bold"
android:maxLines="1" />
<TextView
android:id="@+id/widget_today_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/widget_badge_bg"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<ListView
android:id="@+id/widget_today_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/widget_divider"
android:dividerHeight="0.5dp" />
<TextView
android:id="@+id/widget_today_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/widget_today_empty"
android:textColor="@color/widget_text_secondary"
android:textSize="13sp" />
</FrameLayout>
</LinearLayout>
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_item_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="8dp"
android:paddingEnd="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<ImageView
android:id="@+id/widget_item_checkbox"
android:layout_width="32dp"
android:layout_height="32dp"
android:padding="8dp"
android:src="@drawable/widget_checkbox_low"
android:importantForAccessibility="no" />
<TextView
android:id="@+id/widget_item_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="2dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/widget_text_primary"
android:textSize="14sp" />
<TextView
android:id="@+id/widget_item_meta"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="@color/widget_text_secondary"
android:textSize="11sp" />
</LinearLayout>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="widget_background">#18181B</color>
<color name="widget_header_background">#27272A</color>
<color name="widget_divider">#3F3F46</color>
<color name="widget_text_primary">#FAFAFA</color>
<color name="widget_text_secondary">#A1A1AA</color>
</resources>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="widget_today_title">Сегодня</string>
<string name="widget_upcoming_title">Ближайшие</string>
<string name="widget_today_empty">Нет задач на сегодня</string>
<string name="widget_overdue">Просрочено</string>
<string name="widget_today_description">Ваши задачи на сегодня</string>
</resources>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="widget_today_title">Today</string>
<string name="widget_upcoming_title">Upcoming</string>
<string name="widget_today_empty">No tasks for today</string>
<string name="widget_overdue">Overdue</string>
<string name="widget_today_description">Your tasks for today at a glance</string>
</resources>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="widget_background">#FFFFFF</color>
<color name="widget_header_background">#F4F4F5</color>
<color name="widget_divider">#E4E4E7</color>
<color name="widget_text_primary">#18181B</color>
<color name="widget_text_secondary">#71717A</color>
<color name="widget_accent">#16A34A</color>
<color name="widget_overdue">#FF1744</color>
<color name="widget_priority_low">#38D681</color>
<color name="widget_priority_medium">#FF9100</color>
<color name="widget_priority_high">#FF1744</color>
</resources>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="250dp"
android:minHeight="110dp"
android:minResizeWidth="110dp"
android:minResizeHeight="70dp"
android:targetCellWidth="4"
android:targetCellHeight="2"
android:updatePeriodMillis="1800000"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen"
android:initialLayout="@layout/widget_today"
android:previewLayout="@layout/widget_today"
android:description="@string/widget_today_description" />
+12 -9
View File
@@ -1,27 +1,30 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../../node_modules/.pnpm/@capacitor+android@8.1.0_@capacitor+core@8.1.0/node_modules/@capacitor/android/capacitor')
project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@8.1.0_@capacitor+core@8.1.0/node_modules/@capacitor/android/capacitor')
include ':capacitor-firebase-messaging'
project(':capacitor-firebase-messaging').projectDir = new File('../../node_modules/.pnpm/@capacitor-firebase+messaging@8.1.0_@capacitor+core@8.1.0_firebase@12.10.0/node_modules/@capacitor-firebase/messaging/android')
project(':capacitor-firebase-messaging').projectDir = new File('../../../node_modules/.pnpm/@capacitor-firebase+messaging@8.1.0_@capacitor+core@8.1.0_firebase@12.11.0/node_modules/@capacitor-firebase/messaging/android')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../../node_modules/.pnpm/@capacitor+app@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/app/android')
project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/app/android')
include ':capacitor-browser'
project(':capacitor-browser').projectDir = new File('../../node_modules/.pnpm/@capacitor+browser@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/browser/android')
project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/browser/android')
include ':capacitor-device'
project(':capacitor-device').projectDir = new File('../../node_modules/.pnpm/@capacitor+device@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/device/android')
project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/device/android')
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/preferences/android')
project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/preferences/android')
include ':capacitor-push-notifications'
project(':capacitor-push-notifications').projectDir = new File('../../node_modules/.pnpm/@capacitor+push-notifications@8.0.2_@capacitor+core@8.1.0/node_modules/@capacitor/push-notifications/android')
project(':capacitor-push-notifications').projectDir = new File('../../../node_modules/.pnpm/@capacitor+push-notifications@8.0.3_@capacitor+core@8.1.0/node_modules/@capacitor/push-notifications/android')
include ':capacitor-splash-screen'
project(':capacitor-splash-screen').projectDir = new File('../../node_modules/.pnpm/@capacitor+splash-screen@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/splash-screen/android')
project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/splash-screen/android')
include ':capgo-capacitor-updater'
project(':capgo-capacitor-updater').projectDir = new File('../../node_modules/.pnpm/@capgo+capacitor-updater@8.43.8_@capacitor+core@8.1.0/node_modules/@capgo/capacitor-updater/android')
project(':capgo-capacitor-updater').projectDir = new File('../../../node_modules/.pnpm/@capgo+capacitor-updater@8.43.2_@capacitor+core@8.1.0/node_modules/@capgo/capacitor-updater/android')
include ':capacitor-widget-bridge'
project(':capacitor-widget-bridge').projectDir = new File('../node_modules/capacitor-widget-bridge/android')
+3
View File
@@ -25,6 +25,9 @@ const config: CapacitorConfig = {
CapacitorHttp: {
enabled: true,
},
WidgetBridge: {
appGroup: 'group.com.handscream.taskview.app',
},
},
}
+212 -4
View File
@@ -8,6 +8,9 @@
/* Begin PBXBuildFile section */
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
30163AB74EEE183517D215DC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 41EECE9262690D5E2EF23CE9 /* Assets.xcassets */; };
30F5ABA9CC301D3E3BA985C7 /* TaskViewWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90258576D7FDAEEBD7298B /* TaskViewWidgetBundle.swift */; };
336711805E8F2928D054F35E /* TodayWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4822ABF5AC8CD3030A5CE020 /* TodayWidget.swift */; };
4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */ = {isa = PBXBuildFile; productRef = 4D22ABE82AF431CB00220026 /* CapApp-SPM */; };
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
@@ -15,11 +18,43 @@
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
7E638BA1BFCF4E185865F85D /* TaskViewWidget.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7F4057F5AC9B7A7AC7E70240 /* TaskViewWidget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
A71181DC51C8DA03879B72ED /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 029A385E0A8D9B1FCBD9CC75 /* Foundation.framework */; };
AAA632B22F6BB598007C705D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = AAA632B12F6BB598007C705D /* GoogleService-Info.plist */; };
ABED08BA434D23E174534E03 /* WidgetSnapshotModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873F85DE4FF5908A8CA6A467 /* WidgetSnapshotModel.swift */; };
AB10F00D00000000000000A2 /* OpenTaskIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB10F00D00000000000000A1 /* OpenTaskIntent.swift */; };
AB10F00D00000000000000A3 /* OpenTaskIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB10F00D00000000000000A1 /* OpenTaskIntent.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
8471300B26377F4336A38D36 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 504EC2FC1FED79650016851F /* Project object */;
proxyType = 1;
remoteGlobalIDString = 13D58B519FF779A139CB09EB;
remoteInfo = TaskViewWidget;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
E6DDB52E9A683D16F43B337C /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
7E638BA1BFCF4E185865F85D /* TaskViewWidget.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
029A385E0A8D9B1FCBD9CC75 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
41EECE9262690D5E2EF23CE9 /* Assets.xcassets */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
4822ABF5AC8CD3030A5CE020 /* TodayWidget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TodayWidget.swift; sourceTree = "<group>"; };
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -28,9 +63,15 @@
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
7F4057F5AC9B7A7AC7E70240 /* TaskViewWidget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TaskViewWidget.appex; sourceTree = BUILT_PRODUCTS_DIR; };
873F85DE4FF5908A8CA6A467 /* WidgetSnapshotModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WidgetSnapshotModel.swift; sourceTree = "<group>"; };
AB10F00D00000000000000A1 /* OpenTaskIntent.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OpenTaskIntent.swift; sourceTree = "<group>"; };
958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; };
9D90258576D7FDAEEBD7298B /* TaskViewWidgetBundle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TaskViewWidgetBundle.swift; sourceTree = "<group>"; };
AAA632B02F69F429007C705D /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
AAA632B12F6BB598007C705D /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
EB7495A9A40AB2365CC0624E /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
F1EEEB83D95DC32C9728EAC4 /* TaskViewWidget.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = TaskViewWidget.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -42,9 +83,25 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
8331741D08C05C5C85275E50 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A71181DC51C8DA03879B72ED /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
4961C8EBB434EDD15B55395B /* Frameworks */ = {
isa = PBXGroup;
children = (
7E3EA782F4406B2B6D7A0121 /* iOS */,
);
name = Frameworks;
sourceTree = "<group>";
};
504EC2FB1FED79650016851F = {
isa = PBXGroup;
children = (
@@ -52,6 +109,8 @@
958DCC722DB07C7200EA8C5F /* debug.xcconfig */,
504EC3061FED79650016851F /* App */,
504EC3051FED79650016851F /* Products */,
4961C8EBB434EDD15B55395B /* Frameworks */,
79D12E01B1FBD77844324865 /* TaskViewWidget */,
);
sourceTree = "<group>";
};
@@ -59,6 +118,7 @@
isa = PBXGroup;
children = (
504EC3041FED79650016851F /* App.app */,
7F4057F5AC9B7A7AC7E70240 /* TaskViewWidget.appex */,
);
name = Products;
sourceTree = "<group>";
@@ -79,9 +139,49 @@
path = App;
sourceTree = "<group>";
};
79D12E01B1FBD77844324865 /* TaskViewWidget */ = {
isa = PBXGroup;
children = (
9D90258576D7FDAEEBD7298B /* TaskViewWidgetBundle.swift */,
4822ABF5AC8CD3030A5CE020 /* TodayWidget.swift */,
873F85DE4FF5908A8CA6A467 /* WidgetSnapshotModel.swift */,
AB10F00D00000000000000A1 /* OpenTaskIntent.swift */,
EB7495A9A40AB2365CC0624E /* Info.plist */,
F1EEEB83D95DC32C9728EAC4 /* TaskViewWidget.entitlements */,
41EECE9262690D5E2EF23CE9 /* Assets.xcassets */,
);
name = TaskViewWidget;
path = TaskViewWidget;
sourceTree = "<group>";
};
7E3EA782F4406B2B6D7A0121 /* iOS */ = {
isa = PBXGroup;
children = (
029A385E0A8D9B1FCBD9CC75 /* Foundation.framework */,
);
name = iOS;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
13D58B519FF779A139CB09EB /* TaskViewWidget */ = {
isa = PBXNativeTarget;
buildConfigurationList = BA6B4CB7C019DA2E47F2AE5D /* Build configuration list for PBXNativeTarget "TaskViewWidget" */;
buildPhases = (
2B48B6699C53A2E975CEE972 /* Sources */,
8331741D08C05C5C85275E50 /* Frameworks */,
8F380AD759A2116160808CCC /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TaskViewWidget;
productName = TaskViewWidget;
productReference = 7F4057F5AC9B7A7AC7E70240 /* TaskViewWidget.appex */;
productType = "com.apple.product-type.app-extension";
};
504EC3031FED79650016851F /* App */ = {
isa = PBXNativeTarget;
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */;
@@ -89,10 +189,12 @@
504EC3001FED79650016851F /* Sources */,
504EC3011FED79650016851F /* Frameworks */,
504EC3021FED79650016851F /* Resources */,
E6DDB52E9A683D16F43B337C /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
70738FEC670F89727813A33B /* PBXTargetDependency */,
);
name = App;
packageProductDependencies = (
@@ -135,6 +237,7 @@
projectRoot = "";
targets = (
504EC3031FED79650016851F /* App */,
13D58B519FF779A139CB09EB /* TaskViewWidget */,
);
};
/* End PBXProject section */
@@ -154,19 +257,48 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
8F380AD759A2116160808CCC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
30163AB74EEE183517D215DC /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
2B48B6699C53A2E975CEE972 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
30F5ABA9CC301D3E3BA985C7 /* TaskViewWidgetBundle.swift in Sources */,
336711805E8F2928D054F35E /* TodayWidget.swift in Sources */,
ABED08BA434D23E174534E03 /* WidgetSnapshotModel.swift in Sources */,
AB10F00D00000000000000A2 /* OpenTaskIntent.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
504EC3001FED79650016851F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
AB10F00D00000000000000A3 /* OpenTaskIntent.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
70738FEC670F89727813A33B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = TaskViewWidget;
target = 13D58B519FF779A139CB09EB /* TaskViewWidget */;
targetProxy = 8471300B26377F4336A38D36 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
504EC30B1FED79650016851F /* Main.storyboard */ = {
isa = PBXVariantGroup;
@@ -304,7 +436,7 @@
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.48.1;
CURRENT_PROJECT_VERSION = 1.49.2;
DEVELOPMENT_TEAM = H2W2SG48JT;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -312,7 +444,12 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.48.1;
MARKETING_VERSION = 1.49.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-weak_framework",
AppIntents,
);
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -329,7 +466,7 @@
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.48.1;
CURRENT_PROJECT_VERSION = 1.49.2;
DEVELOPMENT_TEAM = H2W2SG48JT;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -337,7 +474,12 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.48.1;
MARKETING_VERSION = 1.49.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-weak_framework",
AppIntents,
);
PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -346,6 +488,63 @@
};
name = Release;
};
578B436A7C18D66B2BDFCF0B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_ENTITLEMENTS = TaskViewWidget/TaskViewWidget.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.49.2;
DEVELOPMENT_TEAM = H2W2SG48JT;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TaskViewWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "TaskView Widget";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.49.2;
PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) WIDGET_EXTENSION";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
AC8696991AE5C61127E97D40 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_ENTITLEMENTS = TaskViewWidget/TaskViewWidget.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.49.2;
DEVELOPMENT_TEAM = H2W2SG48JT;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TaskViewWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "TaskView Widget";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.49.2;
PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) WIDGET_EXTENSION";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -367,6 +566,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
BA6B4CB7C019DA2E47F2AE5D /* Build configuration list for PBXNativeTarget "TaskViewWidget" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AC8696991AE5C61127E97D40 /* Release */,
578B436A7C18D66B2BDFCF0B /* Debug */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
+4
View File
@@ -4,5 +4,9 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.handscream.taskview.app</string>
</array>
</dict>
</plist>
+11 -9
View File
@@ -12,14 +12,15 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.1.0"),
.package(name: "CapacitorFirebaseMessaging", path: "../../../../node_modules/.pnpm/@capacitor-firebase+messaging@8.1.0_@capacitor+core@8.1.0_firebase@12.10.0/node_modules/@capacitor-firebase/messaging"),
.package(name: "CapacitorApp", path: "../../../../node_modules/.pnpm/@capacitor+app@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/app"),
.package(name: "CapacitorBrowser", path: "../../../../node_modules/.pnpm/@capacitor+browser@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/browser"),
.package(name: "CapacitorDevice", path: "../../../../node_modules/.pnpm/@capacitor+device@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/device"),
.package(name: "CapacitorPreferences", path: "../../../../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/preferences"),
.package(name: "CapacitorPushNotifications", path: "../../../../node_modules/.pnpm/@capacitor+push-notifications@8.0.2_@capacitor+core@8.1.0/node_modules/@capacitor/push-notifications"),
.package(name: "CapacitorSplashScreen", path: "../../../../node_modules/.pnpm/@capacitor+splash-screen@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/splash-screen"),
.package(name: "CapgoCapacitorUpdater", path: "../../../../node_modules/.pnpm/@capgo+capacitor-updater@8.43.8_@capacitor+core@8.1.0/node_modules/@capgo/capacitor-updater")
.package(name: "CapacitorFirebaseMessaging", path: "../../../../../node_modules/.pnpm/@capacitor-firebase+messaging@8.1.0_@capacitor+core@8.1.0_firebase@12.11.0/node_modules/@capacitor-firebase/messaging"),
.package(name: "CapacitorApp", path: "../../../../../node_modules/.pnpm/@capacitor+app@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/app"),
.package(name: "CapacitorBrowser", path: "../../../../../node_modules/.pnpm/@capacitor+browser@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/browser"),
.package(name: "CapacitorDevice", path: "../../../../../node_modules/.pnpm/@capacitor+device@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/device"),
.package(name: "CapacitorPreferences", path: "../../../../../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/preferences"),
.package(name: "CapacitorPushNotifications", path: "../../../../../node_modules/.pnpm/@capacitor+push-notifications@8.0.3_@capacitor+core@8.1.0/node_modules/@capacitor/push-notifications"),
.package(name: "CapacitorSplashScreen", path: "../../../../../node_modules/.pnpm/@capacitor+splash-screen@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/splash-screen"),
.package(name: "CapgoCapacitorUpdater", path: "../../../../../node_modules/.pnpm/@capgo+capacitor-updater@8.43.2_@capacitor+core@8.1.0/node_modules/@capgo/capacitor-updater"),
.package(name: "CapacitorWidgetBridge", path: "../../../node_modules/capacitor-widget-bridge")
],
targets: [
.target(
@@ -34,7 +35,8 @@ let package = Package(
.product(name: "CapacitorPreferences", package: "CapacitorPreferences"),
.product(name: "CapacitorPushNotifications", package: "CapacitorPushNotifications"),
.product(name: "CapacitorSplashScreen", package: "CapacitorSplashScreen"),
.product(name: "CapgoCapacitorUpdater", package: "CapgoCapacitorUpdater")
.product(name: "CapgoCapacitorUpdater", package: "CapgoCapacitorUpdater"),
.product(name: "CapacitorWidgetBridge", package: "CapacitorWidgetBridge")
]
)
]
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "logo.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@@ -0,0 +1,39 @@
import Foundation
import AppIntents
#if !WIDGET_EXTENSION
import UIKit
#endif
@available(iOS 18.0, *)
struct OpenTaskIntent: AppIntent {
static let title: LocalizedStringResource = "Open Task"
static let isDiscoverable = false
static let openAppWhenRun = true
@Parameter(title: "URL")
var urlString: String
init() {
urlString = "taskview://open"
}
init(urlString: String) {
self.urlString = urlString
}
private var resolvedURL: URL {
URL(string: urlString) ?? URL(string: "taskview://open")!
}
#if WIDGET_EXTENSION
func perform() async throws -> some IntentResult & OpensIntent {
.result(opensIntent: OpenURLIntent(resolvedURL))
}
#else
@MainActor
func perform() async throws -> some IntentResult {
await UIApplication.shared.open(resolvedURL)
return .result()
}
#endif
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.handscream.taskview.app</string>
</array>
</dict>
</plist>
@@ -0,0 +1,9 @@
import WidgetKit
import SwiftUI
@main
struct TaskViewWidgetBundle: WidgetBundle {
var body: some Widget {
TodayWidget()
}
}
@@ -0,0 +1,251 @@
import WidgetKit
import SwiftUI
struct TodayEntry: TimelineEntry {
let date: Date
let snapshot: WidgetSnapshot?
}
struct TodayProvider: TimelineProvider {
func placeholder(in context: Context) -> TodayEntry {
TodayEntry(date: .now, snapshot: nil)
}
func getSnapshot(in context: Context, completion: @escaping (TodayEntry) -> Void) {
completion(TodayEntry(date: .now, snapshot: WidgetSnapshot.load()))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<TodayEntry>) -> Void) {
let entry = TodayEntry(date: .now, snapshot: WidgetSnapshot.load())
let refresh = Calendar.current.date(byAdding: .minute, value: 30, to: .now) ?? .now
completion(Timeline(entries: [entry], policy: .after(refresh)))
}
}
struct TodayWidget: Widget {
let kind = "TaskViewTodayWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: TodayProvider()) { entry in
TodayWidgetView(entry: entry)
.containerBackground(for: .widget) {
Color(uiColor: .systemBackground)
}
}
.configurationDisplayName("TaskView")
.description("Today's tasks / Задачи на сегодня")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
.contentMarginsDisabled()
}
}
struct TodayWidgetView: View {
@Environment(\.widgetFamily) private var family
let entry: TodayEntry
private var strings: WidgetStrings {
WidgetStrings.forLocale(entry.snapshot?.locale)
}
private var maxSlots: Int {
switch family {
case .systemLarge: return 8
case .systemSmall: return 4
default: return 3
}
}
var body: some View {
TaskListTodayView(
snapshot: entry.snapshot,
strings: strings,
maxSlots: maxSlots,
compact: family == .systemSmall
)
}
}
struct TaskListTodayView: View {
let snapshot: WidgetSnapshot?
let strings: WidgetStrings
let maxSlots: Int
let compact: Bool
private var horizontalPadding: CGFloat { compact ? 12 : 16 }
private var rowVerticalPadding: CGFloat { compact ? 6 : 10 }
private var dividerInset: CGFloat { compact ? 38 : 48 }
private var isUpcoming: Bool {
snapshot?.isUpcoming ?? false
}
private var visibleTasks: [WidgetSnapshotTask] {
guard let snapshot else { return [] }
let count = snapshot.activeCount > maxSlots ? maxSlots - 1 : maxSlots
return Array(snapshot.tasks.prefix(count))
}
private var hiddenCount: Int {
guard let snapshot else { return 0 }
return max(0, snapshot.activeCount - visibleTasks.count)
}
var body: some View {
VStack(spacing: 0) {
HStack(spacing: compact ? 6 : 8) {
WidgetLogoView(size: compact ? 16 : 20)
Text(isUpcoming ? strings.upcoming : strings.today)
.font(compact ? .footnote.weight(.semibold) : .headline)
Spacer()
Text("\(snapshot?.activeCount ?? 0)")
.font(.caption2.weight(.bold))
.contentTransition(.numericText(countsDown: true))
.foregroundStyle(.white)
.padding(.horizontal, compact ? 6 : 8)
.padding(.vertical, compact ? 2 : 3)
.background(WidgetPalette.accent, in: Capsule())
}
.padding(.horizontal, horizontalPadding)
.padding(.top, 12)
.padding(.bottom, compact ? 8 : 12)
.background(WidgetPalette.headerBackground)
if !visibleTasks.isEmpty {
VStack(spacing: 0) {
ForEach(Array(visibleTasks.enumerated()), id: \.element.id) { index, task in
VStack(spacing: 0) {
if index > 0 {
Divider()
.padding(.leading, dividerInset)
}
TaskRowView(task: task, strings: strings, compact: compact, showDate: isUpcoming)
.padding(.horizontal, horizontalPadding)
.padding(.vertical, rowVerticalPadding)
}
.transition(.opacity.combined(with: .move(edge: .trailing)))
}
if hiddenCount > 0 {
Divider()
.padding(.leading, dividerInset)
Text(strings.more(hiddenCount))
.font(compact ? .caption2 : .footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, dividerInset)
.padding(.trailing, horizontalPadding)
.padding(.vertical, rowVerticalPadding)
}
}
Spacer(minLength: 0)
} else {
Spacer()
Text(snapshot == nil ? strings.openApp : strings.empty)
.font(compact ? .caption : .footnote)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.horizontal, horizontalPadding)
Spacer()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.widgetURL(compact ? URL(string: "taskview://open") : nil)
}
}
struct TaskRowView: View {
let task: WidgetSnapshotTask
let strings: WidgetStrings
let compact: Bool
let showDate: Bool
private var destination: URL {
task.deepLinkURL ?? URL(string: "taskview://open")!
}
var body: some View {
if compact {
if #available(iOS 18.0, *) {
Button(intent: OpenTaskIntent(urlString: destination.absoluteString)) {
row
}
.buttonStyle(.plain)
} else {
row
}
} else {
Link(destination: destination) {
row
}
}
}
private var row: some View {
HStack(spacing: compact ? 4 : 6) {
Circle()
.strokeBorder(WidgetPalette.priority(task.priority), lineWidth: compact ? 1.2 : 1.5)
.frame(width: compact ? 14 : 18, height: compact ? 14 : 18)
.padding(4)
content
}
}
private var content: some View {
HStack(spacing: compact ? 8 : 10) {
Text(task.title)
.font(compact ? .caption : .subheadline)
.foregroundStyle(.primary)
.lineLimit(1)
Spacer(minLength: 4)
if showDate {
if let endDate = task.shortEndDate {
Text(endDate)
.font(compact ? .caption2 : .caption)
.foregroundStyle(.secondary)
}
} else if task.overdue {
Text(compact ? "!" : strings.overdue)
.font(compact ? .caption.weight(.bold) : .caption.weight(.medium))
.foregroundStyle(WidgetPalette.overdue)
} else if let endTime = task.shortEndTime {
Text(endTime)
.font(compact ? .caption2 : .caption)
.foregroundStyle(.secondary)
}
}
}
}
struct WidgetLogoView: View {
let size: CGFloat
var body: some View {
Image("WidgetLogo")
.resizable()
.frame(width: size, height: size)
.clipShape(RoundedRectangle(cornerRadius: size * 0.28))
}
}
enum WidgetPalette {
static let accent = Color(red: 0.086, green: 0.639, blue: 0.290)
static let overdue = Color(red: 1.0, green: 0.090, blue: 0.267)
static let headerBackground = Color(uiColor: .secondarySystemBackground).opacity(0.7)
static func priority(_ priority: Int) -> Color {
switch priority {
case 3: return Color(red: 1.0, green: 0.090, blue: 0.267)
case 2: return Color(red: 1.0, green: 0.569, blue: 0.0)
default: return Color(red: 0.220, green: 0.839, blue: 0.506)
}
}
}
@@ -0,0 +1,94 @@
import Foundation
struct WidgetSnapshotTask: Decodable, Identifiable {
let id: Int
let title: String
let priority: Int
let overdue: Bool
let endTime: String?
let endDate: String?
let path: String
var deepLinkURL: URL? {
guard let encoded = path.addingPercentEncoding(withAllowedCharacters: .alphanumerics) else { return nil }
return URL(string: "taskview://open?path=\(encoded)")
}
var shortEndTime: String? {
guard let endTime, endTime.count >= 5 else { return endTime }
return String(endTime.prefix(5))
}
var shortEndDate: String? {
guard let endDate else { return nil }
let parts = endDate.split(separator: "-")
guard parts.count == 3 else { return endDate }
return "\(parts[2]).\(parts[1])"
}
}
struct WidgetSnapshot: Decodable {
let v: Int
let generatedAt: String
let locale: String
let orgSlug: String?
let mode: String?
let todayCount: Int
let overdueCount: Int
let upcomingCount: Int?
let tasks: [WidgetSnapshotTask]
var isUpcoming: Bool {
mode == "upcoming"
}
var activeCount: Int {
isUpcoming ? (upcomingCount ?? tasks.count) : todayCount
}
static let appGroup = "group.com.handscream.taskview.app"
static let snapshotKey = "widgetSnapshot"
static func load() -> WidgetSnapshot? {
guard let defaults = UserDefaults(suiteName: appGroup),
let raw = defaults.string(forKey: snapshotKey),
let data = raw.data(using: .utf8) else { return nil }
return try? JSONDecoder().decode(WidgetSnapshot.self, from: data)
}
}
struct WidgetStrings {
let today: String
let upcoming: String
let empty: String
let overdue: String
let openApp: String
let moreFormat: String
static let ru = WidgetStrings(
today: "Сегодня",
upcoming: "Ближайшие",
empty: "Нет задач на сегодня",
overdue: "Просрочено",
openApp: "Откройте TaskView",
moreFormat: "и ещё %d"
)
static let en = WidgetStrings(
today: "Today",
upcoming: "Upcoming",
empty: "No tasks for today",
overdue: "Overdue",
openApp: "Open TaskView",
moreFormat: "+%d more"
)
func more(_ count: Int) -> String {
String(format: moreFormat, count)
}
static func forLocale(_ locale: String?) -> WidgetStrings {
let resolved = locale ?? Locale.preferredLanguages.first ?? "ru"
return resolved.hasPrefix("ru") ? ru : en
}
}
+3 -2
View File
@@ -2,10 +2,10 @@
"name": "web-nuxt-ui",
"private": true,
"type": "module",
"version": "1.49.1",
"version": "1.49.2",
"scripts": {
"dev": "vite",
"build:packages": "pnpm --filter taskview-db-schemas build && pnpm --filter taskview-api build",
"build:packages": "pnpm --filter taskview-db-schemas build && pnpm --filter taskview-api build && pnpm --filter capacitor-widget-bridge build",
"build": "pnpm run build:packages && pnpm run typecheck && vite build",
"build-only": "vite build",
"preview": "vite preview",
@@ -58,6 +58,7 @@
"@vueuse/core": "^14.1.0",
"arktype": "2.1.20",
"axios": "1.13.5",
"capacitor-widget-bridge": "workspace:^",
"centrifuge": "^5.5.3",
"chart.js": "^4.5.1",
"chartjs-plugin-annotation": "^3.1.0",
+7
View File
@@ -1,3 +1,5 @@
import { Capacitor } from '@capacitor/core'
import { WidgetBridge } from 'capacitor-widget-bridge'
import $api from '@/helpers/axios'
import { $ls, $tvApi } from '@/plugins/axios'
import { usePushNotifications } from '@/composables/usePushNotifications'
@@ -18,6 +20,11 @@ export async function useLogout() {
if (result) {
reset()
if (Capacitor.isNativePlatform()) {
await WidgetBridge.clearSnapshot().catch((err) => {
console.error('[Logout] Failed to clear widget snapshot:', err)
})
}
await $ls.invalidateTokens()
return true
}
+4 -4
View File
@@ -22,8 +22,6 @@ function isNewerVersion(server: string, current: string): boolean {
}
export async function useUpdater(canUpdate: boolean = false) {
// console.log(canUpdate);
// return;
try {
const BRANCH_MODE: 'prod' | 'dev' = (await $ls.getValue('update_loading')) === 'dev' ? 'dev' : 'prod'
@@ -42,9 +40,11 @@ export async function useUpdater(canUpdate: boolean = false) {
return
}
console.log('currentVersion', currentVersion, 'updateInfo', last)
const effectiveVersion = currentVersion || APP_VERSION
if (!currentVersion || isNewerVersion(last.version, currentVersion) || (version && version?.version !== last.version)) {
console.log('currentVersion', effectiveVersion, 'updateInfo', last)
if (isNewerVersion(last.version, effectiveVersion) || (version && version?.version !== last.version)) {
console.log('[Update] Downloading new version:', last.version)
version = await CapacitorUpdater.download({
+67
View File
@@ -0,0 +1,67 @@
import { onScopeDispose, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { Capacitor } from '@capacitor/core'
import { App } from '@capacitor/app'
import { useOrganizationStore } from '@/stores/organization.store'
import { useGoalsStore } from '@/stores/goals.store'
const OPEN_PREFIX = 'taskview://open'
let launchUrlHandled = false
export function useWidgetDeepLink() {
if (!Capacitor.isNativePlatform()) return
const router = useRouter()
const orgStore = useOrganizationStore()
const goalsStore = useGoalsStore()
const pendingPath = ref<string | null>(null)
const extractPath = (url: string): string | null => {
if (!url.startsWith(OPEN_PREFIX)) return null
const match = url.match(/[?&]path=([^&]+)/)
return match ? decodeURIComponent(match[1]) : null
}
const navigate = (url: string) => {
const path = extractPath(url)
console.log('[WidgetDeepLink] url:', url, '-> path:', path)
if (path) pendingPath.value = path
}
watch(
() => [pendingPath.value, orgStore.initialized, goalsStore.initialized] as const,
([path, orgReady, goalsReady]) => {
if (!path) return
console.log('[WidgetDeepLink] pending:', path, 'orgReady:', orgReady, 'goalsReady:', goalsReady)
if (!orgReady || !goalsReady) return
pendingPath.value = null
// If a task is already open, replace the history entry instead of pushing —
// otherwise consecutive widget taps stack task routes and closing one
// reopens the previous task.
const hasOpenTask = Boolean(router.currentRoute.value.params.taskId)
if (hasOpenTask) {
router.replace(path)
} else {
router.push(path)
}
},
{ immediate: true },
)
let remove: (() => void) | undefined
App.addListener('appUrlOpen', ({ url }) => navigate(url)).then((handle) => {
remove = () => handle.remove()
})
if (!launchUrlHandled) {
launchUrlHandled = true
App.getLaunchUrl().then((result) => {
console.log('[WidgetDeepLink] launchUrl:', result?.url ?? 'none')
if (result?.url) navigate(result.url)
})
}
onScopeDispose(() => remove?.())
}
+74
View File
@@ -0,0 +1,74 @@
import { watch } from 'vue'
import { useDebounceFn, useDateFormat } from '@vueuse/core'
import { Capacitor } from '@capacitor/core'
import { useI18n } from 'vue-i18n'
import { ALL_TASKS_LIST_ID } from 'taskview-api'
import { WidgetBridge, type WidgetSnapshot, type WidgetSnapshotTask } from 'capacitor-widget-bridge'
import { useBaseScreenStore } from '@/stores/base-screen.store'
import { useOrganizationStore } from '@/stores/organization.store'
import { useRefreshOnResume } from '@/composables/useRefreshOnResume'
import type { TaskItem } from '@/types/tasks.types'
const MAX_WIDGET_TASKS = 10
export function useWidgetSnapshot() {
if (!Capacitor.isNativePlatform()) return
const baseScreenStore = useBaseScreenStore()
const orgStore = useOrganizationStore()
const { locale } = useI18n()
const todayYmd = () => useDateFormat(new Date(), 'YYYY-MM-DD').value
const isOverdue = (task: TaskItem) => {
if (!task.endDate) return false
return useDateFormat(new Date(task.endDate), 'YYYY-MM-DD').value < todayYmd()
}
const toSnapshotTask = (task: TaskItem): WidgetSnapshotTask => ({
id: task.id,
title: task.description,
priority: task.priorityId,
overdue: isOverdue(task),
endTime: task.endTime,
endDate: task.endDate ? useDateFormat(new Date(task.endDate), 'YYYY-MM-DD').value : null,
path: `/${orgStore.currentOrgSlug}/${task.goalId}/${task.goalListId ?? ALL_TASKS_LIST_ID}/${task.id}`,
})
const buildSnapshot = (): WidgetSnapshot => {
const openToday = baseScreenStore.tasksToday.filter((task) => !task.complete)
const openUpcoming = baseScreenStore.tasksUpcoming
.filter((task) => !task.complete)
.sort((a, b) => new Date(a.endDate ?? 0).getTime() - new Date(b.endDate ?? 0).getTime())
const mode = openToday.length === 0 && openUpcoming.length > 0 ? 'upcoming' : 'today'
const shownTasks = mode === 'today' ? openToday : openUpcoming
return {
v: 3,
generatedAt: new Date().toISOString(),
locale: locale.value,
orgSlug: orgStore.currentOrgSlug || null,
mode,
todayCount: openToday.length,
overdueCount: openToday.filter(isOverdue).length,
upcomingCount: openUpcoming.length,
tasks: shownTasks.slice(0, MAX_WIDGET_TASKS).map(toSnapshotTask),
}
}
const writeSnapshot = useDebounceFn(async () => {
if (!baseScreenStore.wasCalled) return
await WidgetBridge.setSnapshot({ snapshot: JSON.stringify(buildSnapshot()) }).catch((err) => {
console.error('[WidgetSnapshot] Failed to write snapshot:', err)
})
}, 500)
watch(
() => [baseScreenStore.tasksToday, baseScreenStore.tasksUpcoming, orgStore.currentOrgSlug, locale.value] as const,
() => writeSnapshot(),
{ deep: true },
)
useRefreshOnResume(() => {
if (baseScreenStore.wasCalled) baseScreenStore.fetchAllState()
})
}
+5
View File
@@ -34,6 +34,8 @@ import DashboardSidebarSecond from '@/components/sidebars/DashboardSidebarSecond
import SearchAll from '@/components/features/main/screen-main/parts/SearchAll.vue'
import { useCentrifugo } from '@/composables/useCentrifugo'
import { usePushNotifications } from '@/composables/usePushNotifications'
import { useWidgetSnapshot } from '@/composables/useWidgetSnapshot'
import { useWidgetDeepLink } from '@/composables/useWidgetDeepLink'
import { useGoalsStore } from '@/stores/goals.store'
import { useOrganizationStore } from '@/stores/organization.store'
import { useTimeTrackingStore } from '@/stores/time-tracking.store'
@@ -49,6 +51,9 @@ const orgStore = useOrganizationStore()
const timeTrackingStore = useTimeTrackingStore()
const uiPrefsStore = useUiPreferencesStore()
useWidgetSnapshot()
useWidgetDeepLink()
watch(
() => timeTrackingStore.lastError,
(err) => {