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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Unreleased
- [windows][linux] optionally show the flag of the current IP country as the system tray icon
- [all] show the current country flag in the connection card and the header chip

# 1.5.0
- [all] redesign ui with a modern dark theme

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Utility for power users that want to see VPN connection details
- Show location of connection in map
- Leak detection on your urls
- SysTray icon without the app being open
- Show the flag of the current IP country on the SysTray icon (optional, Windows & Linux)
- Start by startup
- Ability to minimize and hide from taskbar
- Show details of your ISP
Expand Down
10 changes: 5 additions & 5 deletions inno/Inno installer script.iss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "IRNet"
#define MyAppVersion "1.4.3"
#define MyAppVersion "1.5.0"
#define MyAppPublisher "BuildToApp, Inc."
#define MyAppURL "https://www.buildtoapp.com/"
#define MyAppExeName "ir_net.exe"
Expand All @@ -28,10 +28,10 @@ ArchitecturesAllowed=x64compatible
; the 64-bit view of the registry.
ArchitecturesInstallIn64BitMode=x64compatible
DisableProgramGroupPage=yes
LicenseFile=C:\Workspace\Flutter\ir_net\inno\installer license.txt
LicenseFile=installer license.txt
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
OutputBaseFilename=IRNet_windows_setup_1.4.3
OutputBaseFilename=IRNet_windows_setup_1.5.0
Compression=lzma
SolidCompression=yes
WizardStyle=modern
Expand All @@ -43,8 +43,8 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked

[Files]
Source: "C:\Workspace\Flutter\ir_net\build\windows\x64\runner\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\Workspace\Flutter\ir_net\build\windows\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\build\windows\x64\runner\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\build\windows\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Expand Down
9 changes: 8 additions & 1 deletion lib/bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,12 @@ class AppBloc with AppSystemTray {
_handleRefresh();
}

/// Applies a tray icon setting (country flag, leak status) right away instead
/// of waiting for the next IP check.
void onSysTrayIconSettingChanged() {
_updateCountryTrayIcon();
}

@override
void onSystemTrayRefreshButtonClick() {
_handleRefresh();
Expand All @@ -386,14 +392,15 @@ class AppBloc with AppSystemTray {
return;
}
final country = json['country'];
final countryCode = json['countryCode'];
bool isIran = country == 'Iran';
var tooltip = '';
if (_foundALeakedSite) {
tooltip = 'IRNet: $country (Leaked!)';
} else {
tooltip = 'IRNet: $country';
}
updateIconWhenCountryLoaded(_foundALeakedSite, isIran, tooltip);
updateIconWhenCountryLoaded(_foundALeakedSite, isIran, countryCode, tooltip);
debugPrint('Country => $country');
}

Expand Down
9 changes: 9 additions & 0 deletions lib/data/shared_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ class AppSharedPreferences {
(await _preference).setBool(_keyShowLeakInSysTray, value);
}

static Future<bool> get showCountryFlagInSysTray async {
return (await _preference).getBool(_keyShowCountryFlagInSysTray) ?? false;
}

static Future<void> setShowCountryFlagInSysTray(bool value) async {
(await _preference).setBool(_keyShowCountryFlagInSysTray, value);
}

static Future<bool> get isLeakPrePopulated async {
return (await _preference).getBool(_keyIsLeakPrePopulated) ?? false;
}
Expand Down Expand Up @@ -93,6 +101,7 @@ class AppSharedPreferences {

static const _keyIsLeakPrePopulated = 'isLeakPrePopulated';
static const _keyShowLeakInSysTray = 'showLeakInSysTray';
static const _keyShowCountryFlagInSysTray = 'showCountryFlagInSysTray';
static const _keyLeakCheckList = 'leakChecklist';
static const _keyKerioIP = 'kerioIP';
static const _keyKerioUsername = 'kerioUsername';
Expand Down
52 changes: 52 additions & 0 deletions lib/ui/components.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:country_flags/country_flags.dart';
import 'package:flutter/material.dart';

import 'theme.dart';
Expand Down Expand Up @@ -57,6 +58,57 @@ class AppCard extends StatelessWidget {
}
}

/// Flag of [countryCode], falling back to the globe marker while the country
/// is still unknown (or has no flag of its own).
///
/// Sized to a rounded square like [IconTile] so it can stand in for the globe
/// wherever the current location is shown.
class CountryFlagTile extends StatelessWidget {
const CountryFlagTile({
super.key,
required this.countryCode,
this.size = 44,
this.width,
this.iconSize = 22,
this.radius = 13,
});

final String? countryCode;
final double size;

/// Defaults to [size] (a square tile); set it wider for a flag-shaped swatch.
final double? width;
final double iconSize;
final double radius;

@override
Widget build(BuildContext context) {
final code = countryCode;
if (code == null || FlagCode.fromCountryCode(code.toUpperCase()) == null) {
return IconTile(
icon: Icons.public,
size: size,
iconSize: iconSize,
radius: radius,
);
}
final tileWidth = width ?? size;
return Container(
width: tileWidth,
height: size,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: AppColors.line2),
),
clipBehavior: Clip.antiAlias,
child: CountryFlag.fromCountryCode(
code,
theme: ImageTheme(width: tileWidth, height: size),
),
);
}
}

/// Rounded square holding a single icon (e.g. the globe / wallet markers).
class IconTile extends StatelessWidget {
const IconTile({
Expand Down
17 changes: 14 additions & 3 deletions lib/ui/connection_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,13 @@ class ConnectionChip extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.public, size: 15, color: AppColors.accent),
CountryFlagTile(
countryCode: info.countryCode,
size: 15,
width: 20,
iconSize: 15,
radius: 3,
),
const SizedBox(width: 9),
Text(label, style: AppText.ui(13, FontWeight.w500, AppColors.text2)),
const SizedBox(width: 9),
Expand Down Expand Up @@ -173,7 +179,12 @@ class ConnectionHeroCard extends StatelessWidget {
padding: const EdgeInsets.all(22),
child: Row(
children: [
const IconTile(icon: Icons.public, size: 56, iconSize: 28, radius: 16),
CountryFlagTile(
countryCode: info.countryCode,
size: 56,
iconSize: 28,
radius: 16,
),
const SizedBox(width: 20),
Expanded(
child: Column(
Expand Down Expand Up @@ -209,7 +220,7 @@ class ConnectionHeroCard extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const IconTile(icon: Icons.public, size: 42, iconSize: 22),
CountryFlagTile(countryCode: info.countryCode, size: 42, iconSize: 22),
VpnPill(state),
],
),
Expand Down
37 changes: 32 additions & 5 deletions lib/ui/settings_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:io';

import 'package:flutter/material.dart';
import 'package:ir_net/data/shared_preferences.dart';
import 'package:ir_net/main.dart';
import 'package:ir_net/utils/flag_tray_icon.dart';
import 'package:launch_at_startup/launch_at_startup.dart';

import 'components.dart';
Expand Down Expand Up @@ -31,7 +33,17 @@ class _SettingsViewState extends State<SettingsView> {
title: 'Show leak detection on tray icon',
subtitle: 'Reflect leak status in the system tray',
value: AppSharedPreferences.showLeakInSysTray,
onChanged: AppSharedPreferences.setShowLeakInSysTray,
onChanged: _trayIconSetting(AppSharedPreferences.setShowLeakInSysTray),
),
_futureToggle(
title: 'Show country flag on tray icon',
subtitle: 'Use the flag of the detected IP country as the tray icon',
value: AppSharedPreferences.showCountryFlagInSysTray,
onChanged:
_trayIconSetting(AppSharedPreferences.setShowCountryFlagInSysTray),
// The macOS tray only reads icons that ship with the app.
enabled: FlagTrayIcon.isSupported,
badge: 'Windows & Linux',
),
_launchAtStartup(),
]),
Expand Down Expand Up @@ -72,11 +84,23 @@ class _SettingsViewState extends State<SettingsView> {
);
}

/// Redraws the tray icon as soon as a setting that affects it is saved,
/// instead of waiting for the next IP check.
Future<void> Function(bool) _trayIconSetting(
Future<void> Function(bool) save) {
return (value) async {
await save(value);
bloc.onSysTrayIconSettingChanged();
};
}

Widget _futureToggle({
required String title,
required String subtitle,
required Future<bool> value,
required Future<void> Function(bool) onChanged,
bool enabled = true,
String? badge,
}) {
return FutureBuilder<bool>(
future: value,
Expand All @@ -85,12 +109,15 @@ class _SettingsViewState extends State<SettingsView> {
return _SettingRow(
title: title,
subtitle: subtitle,
badge: badge,
trailing: AppSwitch(
value: v,
onChanged: (next) async {
await onChanged(next);
if (mounted) setState(() {});
},
onChanged: enabled
? (next) async {
await onChanged(next);
if (mounted) setState(() {});
}
: null,
),
);
},
Expand Down
Loading