Feat/login by code - #243
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an alternative “login by code” authentication path to the app, wiring a new Flutter UI dialog through the platform channels (Android/iOS/Windows) into the native core bridge, and localizing the new UI strings.
Changes:
- Add
loginByCodemethod-channel support across Flutter (VpnBridge) and native platforms (Android/iOS/Windows + Windows DXCore bridge). - Introduce a new Settings dialog for code-based premium login and a tertiary button style.
- Update localization strings and regenerate platform/dependency lock/registrant artifacts.
Reviewed changes
Copilot reviewed 23 out of 26 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| windows/runner/vpn_channel_handler.cpp | Adds loginByCode method handling on Windows method channel. |
| windows/runner/dxcore_bridge.h | Declares LoginByCode and DLL function pointer typedef/member. |
| windows/runner/dxcore_bridge.cpp | Loads/calls WinLoginByCode and exposes DXCoreBridge::LoginByCode. |
| windows/flutter/generated_plugins.cmake | Updates generated Windows plugin/FFI plugin list. |
| linux/flutter/generated_plugins.cmake | Updates generated Linux plugin/FFI plugin list. |
| android/app/src/main/kotlin/com/defyx/defyx/VpnService.kt | Adds Android service wrapper loginByCode. |
| android/app/src/main/kotlin/com/defyx/defyx/MainActivity.kt | Adds method-channel routing and implementation for loginByCode. |
| ios/Runner/VPNPlugin.swift | Adds loginByCode method-channel handler on iOS. |
| lib/modules/core/vpn_bridge.dart | Adds Dart-facing loginByCode() wrapper on MethodChannel. |
| lib/modules/settings/presentation/widgets/settings_premium_login_dialog.dart | Adds “Login by code” entry point from the existing premium login dialog. |
| lib/modules/settings/presentation/widgets/settings_premium_login_by_code_dialog.dart | New UI dialog to submit access code and perform login. |
| lib/modules/settings/presentation/widgets/settings_premium_info_dialog.dart | Replaces hard-coded strings with localized strings. |
| lib/common/components/button.dart | Adds tertiary button variant styling used by the new flow. |
| lib/l10n/app_en.arb | Adds localized strings for code-login and connection-required messaging. |
| lib/l10n/app_fa.arb | Adds localized strings for code-login and connection-required messaging. |
| lib/l10n/app_ru.arb | Adds localized strings for code-login and connection-required messaging. |
| lib/l10n/app_zh.arb | Adds localized strings for code-login and connection-required messaging. |
| lib/l10n/app_localizations.dart | Adds new localization getters. |
| lib/l10n/app_localizations_en.dart | Adds English implementations for new localization getters. |
| lib/l10n/app_localizations_fa.dart | Adds Farsi implementations for new localization getters. |
| lib/l10n/app_localizations_ru.dart | Adds Russian implementations for new localization getters. |
| lib/l10n/app_localizations_zh.dart | Adds Chinese implementations for new localization getters. |
| pubspec.lock | Updates resolved Dart/Flutter SDK constraints and many dependency versions. |
| macos/Podfile.lock | Updates CocoaPods lock to reflect plugin/dependency resolution changes. |
| macos/Flutter/GeneratedPluginRegistrant.swift | Registers additional macOS plugins after regeneration. |
| ios/Podfile.lock | Updates iOS CocoaPods lock to reflect plugin/dependency resolution changes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| WinRequestHandshake_t pRequestHandshake_ = nullptr; | ||
| WinCompleteHandshake_t pCompleteHandshake_ = nullptr; | ||
| WinLogin_t pLogin_ = nullptr; | ||
|
|
||
| WinLogin_t pLogin_ = nullptr; | ||
| WinLoginByCode_t pLoginByCode_ = nullptr; |
|
|
||
| await widget.ref.read(authProvider.notifier).login(code, token); | ||
|
|
| bool isSubmitting = false; | ||
|
|
||
| final TextEditingController _codeController = TextEditingController(); | ||
|
|
| } finally { | ||
| setState(() => isSubmitting = false); | ||
| } |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 28 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
Previously missed (1) — in code that hasn't changed since the last review.
android/app/src/main/kotlin/com/defyx/defyx/MainActivity.kt:495
result.success(...)is called from the IO coroutine context. Flutter MethodChannel results should be delivered on the main thread; otherwise this can be flaky/crash depending on the engine/embedding.
val loginResult = DefyxVpnService.getInstance().loginByCode(code)
result.success(loginResult)
} catch (e: Exception) {
windows/runner/dxcore_bridge.h:89
- DXCoreBridge has a duplicated
pLogin_member declaration, which will cause a compilation error (redefinition of class member). Remove the duplicate and keep a singlepLogin_plus the newpLoginByCode_.
WinLogin_t pLogin_ = nullptr;
WinLogin_t pLogin_ = nullptr;
WinLoginByCode_t pLoginByCode_ = nullptr;
lib/modules/settings/presentation/widgets/settings_premium_login_by_code_dialog.dart:81
setStateinfinallywill run even if the dialog was dismissed while the async work was in flight, which can throw "setState() called after dispose". Guard thesetStatewithmounted.
} finally {
setState(() => isSubmitting = false);
}
lib/modules/settings/presentation/widgets/settings_premium_login_by_code_dialog.dart:44
_codeControlleris never disposed.TextEditingControllershould be disposed inState.dispose()to avoid leaking listeners/resources when the dialog is closed.
bool isSubmitting = false;
final TextEditingController _codeController = TextEditingController();
| Future<void> loginByCode(String token) async { | ||
| final storage = ref.read(secureStorageProvider); | ||
|
|
||
| await storage.write(premiumTokenKey, token); | ||
|
|
||
| state = AsyncData(AuthData(email: "", isLoggedIn: true)); | ||
| } |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (6)
Previously missed (3) — in code that hasn't changed since the last review.
lib/modules/settings/presentation/widgets/settings_premium_login_by_code_dialog.dart:8
- This import creates a circular dependency: settings_premium_login_by_code_dialog.dart imports settings_premium_login_dialog.dart, and settings_premium_login_dialog.dart imports this file. This can lead to brittle builds and harder refactors.
import 'package:defyx_vpn/modules/settings/presentation/widgets/settings_premium_login_dialog.dart';
android/app/src/main/kotlin/com/defyx/defyx/MainActivity.kt:495
- result.success(loginResult) is called from a Dispatchers.IO coroutine. Flutter MethodChannel results should be completed on the main thread to avoid threading issues (note errors are already marshalled to Dispatchers.Main).
val loginResult = DefyxVpnService.getInstance().loginByCode(code)
result.success(loginResult)
} catch (e: Exception) {
ios/Runner/VPNPlugin.swift:296
- loginByCode accepts an empty string for "code" (it only checks type, not content). Android/Windows reject empty codes, so iOS should match to keep the platform API consistent.
guard let args = arguments,
let code = args["code"] as? String
else {
lib/modules/settings/providers/auth_provider.dart:41
- loginByCode writes the premium token but does not clear/update premiumEmailKey, so an older stored email can persist across app restarts (build() reads premiumEmailKey) and show the wrong account context after logging in by code.
final storage = ref.read(secureStorageProvider);
await storage.write(premiumTokenKey, token);
state = AsyncData(AuthData(email: "", isLoggedIn: true));
lib/modules/settings/presentation/widgets/settings_premium_login_by_code_dialog.dart:81
- setState is called unconditionally in finally; if the dialog is dismissed while awaiting async work, this can trigger "setState() called after dispose()".
} finally {
setState(() => isSubmitting = false);
}
lib/modules/settings/presentation/widgets/settings_premium_login_by_code_dialog.dart:87
- The dialog creates a TextEditingController but never disposes it, which can leak resources/listeners.
void _handleOpenLogin() {
Navigator.of(context).pop();
SettingsPremiumLoginDialog.show(context, widget.ref);
}
Change Description
Briefly describe what this PR does and why. Keep it short and clear.
Related Platforms
Verification Checklist
Optional (for bigger changes)
Related Links
Closes #ID.