Skip to content
44 changes: 44 additions & 0 deletions hook/build.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'dart:io';
import 'package:native_assets_cli/native_assets_cli.dart';
import 'package:native_assets_cli/code_assets.dart';

void main(List<String> args) async {
await build(args, (input, output) async {
// Check if code assets are supported/requested
// Note: hook/build.dart is only called if the package has native assets.

final targetOS = input.config.code.targetOS;
if (targetOS != OS.linux) return;

final rustDir = input.packageRoot.resolve('rust/');

// 1. Run cargo build --release
final result = await Process.run(
'cargo',
['build', '--release'],
workingDirectory: rustDir.toFilePath(),
);

if (result.exitCode != 0) {
stdout.write(result.stdout);
stderr.write(result.stderr);
throw Exception('Rust build failed');
}

// 2. Locate the library
final libName = 'libtc_helper.so';
final libPath = rustDir.resolve('target/release/$libName');

if (!await File.fromUri(libPath).exists()) {
throw Exception('Built library not found at $libPath');
}

// 3. Add the asset to the output
output.assets.code.add(CodeAsset(
package: input.packageName,
name: 'main.dart',
linkMode: DynamicLoadingBundled(),
file: libPath,
));
});
}
55 changes: 25 additions & 30 deletions lib/app/modules/home/controllers/home_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ class HomeController extends GetxController {
}

void changeInDirectory() {
print("directory change to ${splashController.baseDirectory.value.path}");
debugPrint("directory change to ${splashController.baseDirectory.value.path}");
storage = Storage(
Directory(
'${splashController.baseDirectory.value.path}/profiles/${splashController.currentProfile.value}',
Expand All @@ -701,12 +701,13 @@ class HomeController extends GetxController {
void initLanguageAndDarkMode() {
isDarkModeOn.value = AppSettings.isDarkMode;
selectedLanguage.value = AppSettings.selectedLanguage;
HomeWidget.saveWidgetData(
"themeMode", AppSettings.isDarkMode ? "dark" : "light");
HomeWidget.updateWidget(
androidName: "TaskWarriorWidgetProvider",
iOSName: "TaskWarriorWidgets");
// print("called and value is${isDarkModeOn.value}");
if (Platform.isAndroid || Platform.isIOS) {
HomeWidget.saveWidgetData(
"themeMode", AppSettings.isDarkMode ? "dark" : "light");
HomeWidget.updateWidget(
androidName: "TaskWarriorWidgetProvider",
iOSName: "TaskWarriorWidgets");
}
}

final addKey = GlobalKey();
Expand Down Expand Up @@ -740,17 +741,14 @@ class HomeController extends GetxController {
Future.delayed(
const Duration(milliseconds: 500),
() {
SaveTourStatus.getInAppTourStatus().then((value) => {
if (value == false)
{
tutorialCoachMark.show(context: context),
}
else
{
// ignore: avoid_print
debugPrint('User has seen this page'),
// User has seen this page
}
SaveTourStatus.getInAppTourStatus().then((value) {
if (value == false) {
tutorialCoachMark.show(context: context);
} else {
// ignore: avoid_print
debugPrint('User has seen this page');
// User has seen this page
}
});
},
);
Expand Down Expand Up @@ -785,16 +783,13 @@ class HomeController extends GetxController {
Future.delayed(
const Duration(milliseconds: 500),
() {
SaveTourStatus.getFilterTourStatus().then((value) => {
if (value == false)
{
tutorialCoachMark.show(context: context),
}
else
{
// ignore: avoid_print
print('User has seen this page'),
}
SaveTourStatus.getFilterTourStatus().then((value) {
if (value == false) {
tutorialCoachMark.show(context: context);
} else {
// ignore: avoid_print
debugPrint('User has seen this page');
}
});
},
);
Expand All @@ -817,8 +812,8 @@ class HomeController extends GetxController {

void showTaskSwipeTutorial(BuildContext context) {
SaveTourStatus.getTaskSwipeTutorialStatus().then((value) {
print("value is $value");
print("tasks is ${tasks.isNotEmpty}");
debugPrint("value is $value");
debugPrint("tasks is ${tasks.isNotEmpty}");
if (value == false) {
initTaskSwipeTutorial();
tutorialCoachMark.show(context: context);
Expand Down
16 changes: 10 additions & 6 deletions lib/app/modules/home/controllers/widget.controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,19 @@ class WidgetController extends GetxController {
}
}
}
await HomeWidget.saveWidgetData("tasks", jsonEncode(l));
if (Platform.isAndroid || Platform.isIOS) {
await HomeWidget.saveWidgetData("tasks", jsonEncode(l));
}
}

Future updateWidget() async {
try {
return HomeWidget.updateWidget(
name: 'TaskWarriorWidgetProvider', iOSName: 'TaskWarriorWidgets');
} on PlatformException catch (exception) {
debugPrint('Error Updating Widget. $exception');
if (Platform.isAndroid || Platform.isIOS) {
try {
return HomeWidget.updateWidget(
name: 'TaskWarriorWidgetProvider', iOSName: 'TaskWarriorWidgets');
} on PlatformException catch (exception) {
debugPrint('Error Updating Widget. $exception');
}
}
}
}
2 changes: 1 addition & 1 deletion lib/app/modules/splash/controllers/splash_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:taskwarrior/app/models/storage.dart';
import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart';
import 'package:taskwarrior/app/routes/app_pages.dart';
import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart';
import 'package:taskwarrior/app/utils/taskfunctions/profiles.dart';
import 'package:taskwarrior/app/v3/models/task.dart';
import 'package:taskwarrior/app/services/deep_link_service.dart';
Expand Down Expand Up @@ -154,6 +153,7 @@ class SplashController extends GetxController {
}

Future<void> checkForUpdate() async {
if (!Platform.isAndroid) return;
try {
AppUpdateInfo updateInfo = await InAppUpdate.checkForUpdate();
if (updateInfo.updateAvailability == UpdateAvailability.updateAvailable) {
Expand Down
5 changes: 4 additions & 1 deletion lib/app/utils/app_settings/app_settings.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:io';
import 'package:get/get.dart';
import 'package:home_widget/home_widget.dart';
import 'package:shared_preferences/shared_preferences.dart';
Expand All @@ -14,7 +15,9 @@ class AppSettings {
static final RxBool use24HourFormatRx = false.obs;

static Future init() async {
await HomeWidget.setAppGroupId("group.taskwarrior");
if (Platform.isIOS){
await HomeWidget.setAppGroupId("group.taskwarrior");
}
await SelectedTheme.init();
await SelectedLanguage.init();
await SaveTourStatus.init();
Expand Down
2 changes: 1 addition & 1 deletion lib/app/v3/db/task_database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ class TaskDatabase {
final List<
Map<String,
dynamic>> result = await taskDatabase._database!.rawQuery(
'SELECT DISTINCT project FROM Tasks WHERE project IS NOT NULL AND status IS NOT "deleted"');
"SELECT DISTINCT project FROM Tasks WHERE project IS NOT NULL AND status IS NOT 'deleted'");

return result.map((row) => row['project'] as String).toList();
}
Expand Down
51 changes: 28 additions & 23 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,68 +1,73 @@
import 'dart:ffi';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/foundation.dart' show kIsWeb, debugPrintSynchronously;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:taskwarrior/app/services/deep_link_service.dart';

import 'package:taskwarrior/app/utils/app_settings/app_settings.dart';
import 'package:taskwarrior/app/utils/debug_logger/log_databse_helper.dart';
import 'package:taskwarrior/app/utils/themes/dark_theme.dart';
import 'package:taskwarrior/app/utils/themes/light_theme.dart';
import 'package:taskwarrior/rust_bridge/frb_generated.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';

import 'app/routes/app_pages.dart';

LogDatabaseHelper _logDatabaseHelper = LogDatabaseHelper();

DynamicLibrary loadNativeLibrary() {
ExternalLibrary loadNativeLibrary() {
if (kIsWeb) {
throw UnsupportedError("Native libraries are not supported on Web");
}

if (Platform.isIOS) {
return DynamicLibrary.open('Frameworks/tc_helper.framework/tc_helper');
return ExternalLibrary.open('Frameworks/tc_helper.framework/tc_helper');
} else if (Platform.isAndroid) {
return DynamicLibrary.open('libtc_helper.so');
return ExternalLibrary.open('libtc_helper.so');
} else if (Platform.isMacOS) {
return DynamicLibrary.open('tc_helper.framework/tc_helper');
return ExternalLibrary.open('tc_helper.framework/tc_helper');
} else if (Platform.isLinux) {
return ExternalLibrary.open('libtc_helper.so');
} else if (Platform.isWindows) { // Add Windows back in!
return ExternalLibrary.open('tc_helper.dll');
Comment on lines +29 to +33
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

build_file="$(fd '^build\.dart$' . | head -n1)"

echo "== build hook =="
echo "${build_file:-build.dart not found}"
if [ -n "${build_file:-}" ]; then
  sed -n '1,240p' "$build_file"
fi

echo
echo "== native asset / tc_helper references =="
rg -n -C2 --glob '!**/build/**' --glob '!**/.dart_tool/**' \
  'tc_helper|cargo build|Platform\.is(Linux|Windows|MacOS)|target_os|linux|windows|macos' .

Repository: CCExtractor/taskwarrior-flutter

Length of output: 50387


Windows and macOS are opt-in in code but not in the build system.

hook/build.dart line 11 gates the entire build to Linux only with if (targetOS != OS.linux) return;—when building for Windows or macOS, the hook skips all compilation and asset generation. However, lines 29–33 now load tc_helper.framework/tc_helper and tc_helper.dll, and line 41 initializes the Rust FFI for all three desktops. On Windows and macOS, the app will crash at startup trying to load libraries that were never built.

Remove the Windows and macOS branches from lib/main.dart (lines 29–33), or extend hook/build.dart to handle those platforms. Currently, only Linux has a working native asset pipeline.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/main.dart` around lines 29 - 33, The code is attempting to load
macOS/Windows native libs even though the build hook only builds Linux; remove
the Platform.isWindows and macOS branches that call ExternalLibrary.open (i.e.,
drop the tc_helper.dll and tc_helper.framework/tc_helper cases) and ensure the
Rust FFI initialization call currently run for all desktops is gated by
Platform.isLinux (so ExternalLibrary.open and the FFI init only run when
Platform.isLinux), or alternatively update the build hook's early-return check
(the targetOS != OS.linux guard) to include proper build steps for macOS and
Windows if you intend to support those platforms.

}
throw UnsupportedError(
'Platform ${Platform.operatingSystem} is not supported');
}

void main() async {
// 1. Keep your Desktop SQLite fix
if (!kIsWeb && (Platform.isLinux || Platform.isWindows || Platform.isMacOS)) {
sqfliteFfiInit();
databaseFactory = databaseFactoryFfi;
}
// 2. Accept the project's early initialization
WidgetsFlutterBinding.ensureInitialized();

// Move the logger override ABOVE the first boot print!
// 3. Keep the shared Logger setup
debugPrint = (String? message, {int? wrapWidth}) {
if (message != null) {
debugPrintSynchronously(message, wrapWidth: wrapWidth);
_logDatabaseHelper.insertLog(message);
}
};

debugPrint("🚀 BOOT: main() started");

loadNativeLibrary();
await RustLib.init();

// 4. Keep your Native Library loader
final lib = loadNativeLibrary();
await RustLib.init(externalLibrary: lib);
await AppSettings.init();

// fix: Actually await the service initialization so the OS intent is caught BEFORE runApp.
// 5. Accept the new DeepLink logic from UPSTREAM
await Get.putAsync<DeepLinkService>(() async {
final service = DeepLinkService();
await service.init();
return service;
}, permanent: true);
});
runApp(
GetMaterialApp(
darkTheme: darkTheme,
theme: lightTheme,
title: "Application",
initialRoute: AppPages.INITIAL,
unknownRoute: AppPages.routes.firstWhere(
(page) => page.name == AppPages.INITIAL,
orElse: () {
debugPrint("⚠️ Unknown route requested, falling back to default");
return AppPages.routes.first;
},
),
getPages: AppPages.routes,
themeMode: AppSettings.isDarkMode ? ThemeMode.dark : ThemeMode.light,
),
Expand Down
6 changes: 4 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,17 @@ dependencies:
textfield_tags: ^3.0.1
path_provider: ^2.1.5
flutter_rust_bridge: ^2.11.1
ffi: any # Required for FFI
ffi: any # Required for FFI
app_links: ^6.4.1
sqflite_common_ffi: ^2.3.6+1
path: ^1.9.1
native_assets_cli: ^0.18.0

dev_dependencies:
build_runner: null
flutter_gen_runner: null
flutter_lints: ^5.0.0
http_mock_adapter: ^0.6.1
sqflite_common_ffi: ^2.0.0
ffigen: ^8.0.1

flutter_gen:
Expand Down