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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 4 additions & 15 deletions lib/app/modules/home/controllers/home_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ import 'package:taskwarrior/app/utils/app_settings/app_settings.dart';
import 'package:taskwarrior/app/v3/champion/replica.dart';
import 'package:taskwarrior/app/v3/champion/models/task_for_replica.dart';
import 'package:taskwarrior/app/v3/db/task_database.dart';
import 'package:taskwarrior/app/v3/db/update.dart';
import 'package:taskwarrior/app/v3/models/task.dart';
import 'package:taskwarrior/app/v3/net/fetch.dart';
import 'package:textfield_tags/textfield_tags.dart';
import 'package:taskwarrior/app/utils/themes/theme_extension.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
Expand Down Expand Up @@ -168,16 +166,6 @@ class HomeController extends GetxController {
debugPrint("Tasks from Replica: ${tasks.length}");
}

Future<void> refreshTasks(String clientId, String encryptionSecret) async {
TaskDatabase taskDatabase = TaskDatabase();
await taskDatabase.open();
List<TaskForC> tasksFromServer =
await fetchTasks(clientId, encryptionSecret);
await updateTasksInDatabase(tasksFromServer);
List<TaskForC> fetchedTasks = await taskDatabase.fetchTasksFromDatabase();
tasks.value = fetchedTasks;
}

Future<void> fetchTasksFromDB() async {
debugPrint("Fetching tasks from DB ${taskReplica.value}");
await _loadTaskChampion();
Expand Down Expand Up @@ -580,9 +568,10 @@ class HomeController extends GetxController {
await refreshReplicaTasks();
}
} else if (taskchampion.value) {
if (clientId != null && encryptionSecret != null) {
await refreshTasks(clientId, encryptionSecret);
}
// CCSync HTTP sync has been retired; the local TaskChampion database is
// the source of truth for this mode, so reload it without a remote
// round-trip.
await fetchTasksFromDB();
} else {
await synchronize(context, false);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/app/modules/home/views/home_page_app_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class HomePageAppBar extends StatelessWidget implements PreferredSizeWidget {
.sentences
.homePageFetchingTasks);

await controller.refreshTasks(c, e);
await controller.fetchTasksFromDB();

ScaffoldMessenger.of(context)
.hideCurrentSnackBar();
Expand Down
4 changes: 0 additions & 4 deletions lib/app/modules/home/views/show_tasks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import 'package:taskwarrior/app/utils/themes/theme_extension.dart';
import 'package:taskwarrior/app/utils/language/sentence_manager.dart';
import 'package:taskwarrior/app/v3/db/task_database.dart';
import 'package:taskwarrior/app/v3/models/task.dart';
import 'package:taskwarrior/app/v3/net/complete.dart';
import 'package:taskwarrior/app/v3/net/delete.dart';

class TaskViewBuilder extends StatelessWidget {
const TaskViewBuilder({
Expand Down Expand Up @@ -230,14 +228,12 @@ class TaskViewBuilder extends StatelessWidget {
TaskDatabase taskDatabase = TaskDatabase();
await taskDatabase.open();
taskDatabase.markTaskAsCompleted(uuid);
completeTask('email', uuid);
}

void _markTaskAsDeleted(String uuid) async {
TaskDatabase taskDatabase = TaskDatabase();
await taskDatabase.open();
taskDatabase.markTaskAsDeleted(uuid);
deleteTask('email', uuid);
}

Color _getPriorityColor(String priority) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart';
import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart';
import 'package:taskwarrior/app/v3/net/origin.dart';
import 'package:http/http.dart' as http;
import 'package:taskwarrior/app/v3/champion/replica.dart';
import 'package:taskwarrior/rust_bridge/api.dart';

class ManageTaskChampionCredsController extends GetxController {
final encryptionSecretController = TextEditingController();
final clientIdController = TextEditingController();
final ccsyncBackendUrlController = TextEditingController();
final syncServerUrlController = TextEditingController();
var profilesWidget = Get.find<SplashController>();
RxBool isCheckingCreds = false.obs;
RxBool taskReplica = false.obs;
Expand All @@ -25,50 +23,42 @@ class ManageTaskChampionCredsController extends GetxController {
encryptionSecretController.text =
await CredentialsStorage.getEncryptionSecret() ?? '';
clientIdController.text = await CredentialsStorage.getClientId() ?? '';
ccsyncBackendUrlController.text =
syncServerUrlController.text =
await CredentialsStorage.getApiUrl() ?? '';
final SharedPreferences prefs = await SharedPreferences.getInstance();
taskReplica.value = prefs.getBool('settings_taskr_repl') ?? false;
}

/// Validates and persists sync credentials through a single path: the native
/// TaskChampion sync via the Rust FFI bridge.
///
/// The entered credentials are validated with a real [sync_] *before* they
/// are persisted — a successful sync confirms they are valid, while invalid
/// credentials raise a Rust-level exception that surfaces here as a thrown
/// error. This ordering guarantees we never write unverified credentials into
/// the active profile. (Because validation is a live sync, saving requires
/// connectivity to the sync server.)
Future<int> saveCredentials() async {
if (taskReplica.value) {
profilesWidget.setTaskcCreds(
profilesWidget.currentProfile.value,
clientIdController.text,
encryptionSecretController.text,
ccsyncBackendUrlController.text);
return 0;
}
isCheckingCreds.value = true;
String baseUrl = ccsyncBackendUrlController.text;
String uuid = clientIdController.text;
String encryptionSecret = encryptionSecretController.text;
try {
String url =
'$baseUrl/tasks?email=email&origin=$origin&UUID=$uuid&encryptionSecret=$encryptionSecret';

var response = await http.get(Uri.parse(url), headers: {
"Content-Type": "application/json",
}).timeout(const Duration(seconds: 10000));
debugPrint("Fetch tasks response: ${response.statusCode}");
debugPrint("Fetch tasks body: ${response.body}");
if (response.statusCode == 200) {
List<dynamic> allTasks = jsonDecode(response.body);
debugPrint(allTasks.toString());
profilesWidget.setTaskcCreds(
profilesWidget.currentProfile.value,
clientIdController.text,
encryptionSecretController.text,
ccsyncBackendUrlController.text);

isCheckingCreds.value = false;
return 0;
} else {
throw Exception('Failed to load tasks');
}
} catch (e, s) {
debugPrint('Error fetching tasks: $e\n $s');
final String replicaPath = await Replica.getReplicaPath();
await sync_(
taskdbDirPath: replicaPath,
url: syncServerUrlController.text,
clientId: clientIdController.text,
encryptionSecret: encryptionSecretController.text,
);
// Only persist after the sync has confirmed the credentials are valid.
profilesWidget.setTaskcCreds(
profilesWidget.currentProfile.value,
clientIdController.text,
encryptionSecretController.text,
syncServerUrlController.text,
);
isCheckingCreds.value = false;
return 0;
} catch (err) {
debugPrint('Credential check failed: $err');
isCheckingCreds.value = false;
return 1;
}
Expand All @@ -78,7 +68,7 @@ class ManageTaskChampionCredsController extends GetxController {
void onClose() {
encryptionSecretController.dispose();
clientIdController.dispose();
ccsyncBackendUrlController.dispose();
syncServerUrlController.dispose();
super.onClose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,7 @@ class ManageTaskChampionCredsView
),
],
),
actions: [
// IconButton(
// icon: Icon(
// Icons.info,
// color: TaskWarriorColors.white,
// ),
// onPressed: () async {
// String url = !controller.taskReplica.value
// ? "https://github.com/its-me-abhishek/ccsync"
// : "https://github.com/GothenburgBitFactory/taskchampion";
// if (!await launchUrl(Uri.parse(url))) {
// throw Exception('Could not launch $url');
// }
// },
// ),
],
actions: const [],
leading: IconButton(
icon: Icon(Icons.arrow_back, color: TaskWarriorColors.white),
onPressed: () => Get.back(),
Expand All @@ -74,7 +59,7 @@ class ManageTaskChampionCredsView
labelText: SentenceManager(
currentLanguage: AppSettings.selectedLanguage)
.sentences
.ccsyncClientId,
.syncServerClientId,
labelStyle: TextStyle(color: tColors.primaryTextColor),
border: const OutlineInputBorder(),
),
Expand All @@ -93,26 +78,18 @@ class ManageTaskChampionCredsView
),
),
const SizedBox(height: 10),
Obx(() => TextField(
style: TextStyle(color: tColors.primaryTextColor),
controller: controller.ccsyncBackendUrlController,
decoration: InputDecoration(
labelText: controller.taskReplica.value
? SentenceManager(
currentLanguage:
AppSettings.selectedLanguage)
.sentences
.taskchampionBackendUrl
: SentenceManager(
currentLanguage:
AppSettings.selectedLanguage)
.sentences
.ccsyncBackendUrl,
labelStyle:
TextStyle(color: tColors.primaryTextColor),
border: const OutlineInputBorder(),
),
)),
TextField(
style: TextStyle(color: tColors.primaryTextColor),
controller: controller.syncServerUrlController,
decoration: InputDecoration(
labelText: SentenceManager(
currentLanguage: AppSettings.selectedLanguage)
.sentences
.syncServerBackendUrl,
labelStyle: TextStyle(color: tColors.primaryTextColor),
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 20),
Obx(() => SizedBox(
width: double.infinity,
Expand Down Expand Up @@ -196,7 +173,7 @@ class ManageTaskChampionCredsView
SentenceManager(
currentLanguage: AppSettings.selectedLanguage)
.sentences
.ccsyncEasySyncTitle,
.syncServerEasySyncTitle,
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
Expand All @@ -208,7 +185,7 @@ class ManageTaskChampionCredsView
SentenceManager(
currentLanguage: AppSettings.selectedLanguage)
.sentences
.ccsyncIntro,
.syncServerIntro,
style: TextStyle(
fontSize: 14,
color: tColors.primaryTextColor?.withValues(alpha: 0.8),
Expand All @@ -220,7 +197,7 @@ class ManageTaskChampionCredsView
SentenceManager(
currentLanguage: AppSettings.selectedLanguage)
.sentences
.ccsyncLoginInstruction,
.syncServerLoginInstruction,
style: TextStyle(
fontSize: 14,
color: tColors.primaryTextColor?.withValues(alpha: 0.8),
Expand All @@ -235,7 +212,7 @@ class ManageTaskChampionCredsView
SentenceManager(
currentLanguage: AppSettings.selectedLanguage)
.sentences
.ccsyncOpenButton,
.syncServerOpenButton,
style: TextStyle(color: tColors.primaryTextColor),
),
style: OutlinedButton.styleFrom(
Expand All @@ -261,7 +238,7 @@ class ManageTaskChampionCredsView
SentenceManager(
currentLanguage: AppSettings.selectedLanguage)
.sentences
.ccsyncSelfHosted,
.syncServerSelfHosted,
style: TextStyle(
fontSize: 13,
color: tColors.primaryTextColor?.withValues(alpha: 0.6),
Expand Down
15 changes: 2 additions & 13 deletions lib/app/modules/profile/views/profile_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -264,17 +264,6 @@ class ProfileView extends GetView<ProfileController> {
});
},
),
// CCSync v3 is deprecated, so hiding it for now
// RadioListTile<String>(
// title: const Text('CCSync (v3)'),
// value: 'TW3',
// groupValue: selectedMode,
// onChanged: (String? value) {
// setState(() {
// selectedMode = value;
// });
// },
// ),
RadioListTile<String>(
title: const Text('TaskServer'),
value: 'TW2',
Expand Down Expand Up @@ -327,8 +316,8 @@ class ProfileView extends GetView<ProfileController> {
AppSettings.selectedLanguage)
.sentences
.profilePageSuccessfullyChangedProfileModeTo +
((selectedMode ?? "") == "TW3"
? "CCSync"
((selectedMode ?? "") == "TW3C"
? "Taskchampion"
: "Taskserver"),
style: TextStyle(
color: tColors.primaryTextColor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import 'package:taskwarrior/app/v3/models/annotation.dart';
import 'package:taskwarrior/app/v3/models/task.dart';
import 'package:taskwarrior/app/v3/champion/replica.dart';
import 'package:taskwarrior/app/v3/champion/models/task_for_replica.dart';
import 'package:taskwarrior/app/v3/net/modify.dart';

enum UnsavedChangesAction { save, discard, cancel }

Expand Down Expand Up @@ -249,16 +248,6 @@ class TaskcDetailsController extends GetxController {
hasChanges.value = false;
debugPrint('Task saved in local DB ${description.string}');
processTagsLists();
await modifyTaskOnTaskwarrior(
description.string,
project.string,
DateTime.parse(due.string).toIso8601String(),
priority.string,
status.string,
initialTask.uuid!,
initialTask.id.toString(),
tags.toList(),
);
} else if (initialTask is TaskForReplica) {
debugPrint(
'Saving replica task changes... status ${status.string} ${tags.join(", ")}');
Expand Down
Loading
Loading