Skip to content
Closed
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
17 changes: 16 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,25 @@ jobs:
command: |
echo 'export PATH="$HOME"/.cargo/bin:"$PATH"' >> "$BASH_ENV"
- run: rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
- run:
name: Install Flutter SDK
command: |
git clone --depth 1 --branch stable https://github.com/flutter/flutter.git "$HOME/flutter"
echo 'export PATH="$HOME/flutter/bin:$PATH"' >> "$BASH_ENV"
source "$BASH_ENV"
flutter precache --android
- run:
name: Configure Flutter module
command: |
echo "flutter.sdk=$HOME/flutter" >> local.properties
cd flutter_module && flutter pub get
- run:
name: Flutter tests
command: cd flutter_module && flutter test
- android/restore-gradle-cache
- run:
name: Run Build and Tests
command: ./gradlew assembleDebug check -PCARGO_PROFILE=debug
command: ./gradlew assembleDebug check -PCARGO_PROFILE=debug -x :mobile:releaseOssLicensesTask -x :mobile:debugOssLicensesTask
- android/save-gradle-cache
- store_artifacts:
path: mobile/build/outputs/apk
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ jobs:
with:
targets: x86_64-linux-android

- uses: subosito/flutter-action@v2
with:
channel: stable

- name: Configure Flutter module
run: |
echo "flutter.sdk=$(which flutter | xargs dirname | xargs dirname)" >> local.properties
cd flutter_module && flutter pub get

- name: Flutter tests
run: cd flutter_module && flutter test

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ release/
# release apks
*.apk
.DS_Store

# E2E test screenshots
screen_*.png
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Helpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fun Project.setupCommon() {
android.apply {
compileSdkVersion(36)
defaultConfig {
minSdk = 23
minSdk = 24
targetSdk = 36
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
49 changes: 49 additions & 0 deletions flutter_module/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.DS_Store
.dart_tool/

.pub/

.idea/
.vagrant/
.sconsign.dblite
.svn/

migrate_working_dir/

*.swp
profile

DerivedData/

.generated/

*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3

!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3

xcuserdata

*.moved-aside

*.pyc
*sync/
Icon?
.tags*

build/
.android/
.ios/
README.md
.flutter-plugins-dependencies

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json
10 changes: 10 additions & 0 deletions flutter_module/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: "582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536"
channel: "stable"

project_type: module
4 changes: 4 additions & 0 deletions flutter_module/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
70 changes: 70 additions & 0 deletions flutter_module/lib/app.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'screens/about/about_screen.dart';
import 'screens/app_manager/app_manager_screen.dart';
import 'screens/custom_rules/custom_rules_screen.dart';
import 'screens/home/home_screen.dart';
import 'screens/profile_config/profile_config_screen.dart';
import 'screens/scanner/scanner_screen.dart';
import 'screens/settings/settings_screen.dart';
import 'screens/subscriptions/subscriptions_screen.dart';
import 'theme/app_theme.dart';

final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/profile/new',
builder: (context, state) => const ProfileConfigScreen(),
),
GoRoute(
path: '/profile/:id',
builder: (context, state) => ProfileConfigScreen(
profileId: state.pathParameters['id'],
),
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
),
GoRoute(
path: '/subscriptions',
builder: (context, state) => const SubscriptionsScreen(),
),
GoRoute(
path: '/custom-rules',
builder: (context, state) => const CustomRulesScreen(),
),
GoRoute(
path: '/scanner',
builder: (context, state) => const ScannerScreen(),
),
GoRoute(
path: '/app-manager',
builder: (context, state) => const AppManagerScreen(),
),
GoRoute(
path: '/about',
builder: (context, state) => const AboutScreen(),
),
],
);

class ShadowsocksApp extends StatelessWidget {
const ShadowsocksApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Shadowsocks',
debugShowCheckedModeBanner: false,
theme: AppTheme.light,
darkTheme: AppTheme.dark,
themeMode: ThemeMode.system,
routerConfig: _router,
);
}
}
73 changes: 73 additions & 0 deletions flutter_module/lib/channels/profile_channel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:flutter/services.dart';
import '../models/profile.dart';

class ProfileChannel {
static const _method = MethodChannel('com.github.shadowsocks/profiles');

Future<List<Profile>> getProfiles() async {
final result = await _method.invokeListMethod<Map>('getProfiles');
return result?.map((m) => Profile.fromMap(m)).toList() ?? [];
}

Future<Profile?> getProfile(int id) async {
final result =
await _method.invokeMapMethod<String, dynamic>('getProfile', {'id': id});
return result != null ? Profile.fromMap(result) : null;
}

Future<Profile?> createProfile(Map<String, dynamic> data) async {
final result =
await _method.invokeMapMethod<String, dynamic>('createProfile', data);
return result != null ? Profile.fromMap(result) : null;
}

Future<bool> updateProfile(Map<String, dynamic> data) async {
final result = await _method.invokeMethod<bool>('updateProfile', data);
return result ?? false;
}

Future<bool> deleteProfile(int id) async {
final result =
await _method.invokeMethod<bool>('deleteProfile', {'id': id});
return result ?? false;
}

Future<void> selectProfile(int id) async {
await _method.invokeMethod('selectProfile', {'id': id});
}

Future<int> getSelectedId() async {
final result = await _method.invokeMethod<int>('getSelectedId');
return result ?? 0;
}

Future<void> reorderProfiles(List<Map<String, int>> order) async {
await _method.invokeMethod('reorderProfiles', {'order': order});
}

Future<int> importFromText(String text) async {
final result =
await _method.invokeMethod<int>('importFromText', {'text': text});
return result ?? 0;
}

Future<int> importFromJson(String json) async {
final result =
await _method.invokeMethod<int>('importFromJson', {'json': json});
return result ?? 0;
}

Future<String> exportToJson() async {
final result = await _method.invokeMethod<String>('exportToJson');
return result ?? '[]';
}

Future<String?> getProfileUri(int id) async {
return await _method.invokeMethod<String>('getProfileUri', {'id': id});
}

Future<List<Map>> getPlugins() async {
final result = await _method.invokeListMethod<Map>('getPlugins');
return result ?? [];
}
}
42 changes: 42 additions & 0 deletions flutter_module/lib/channels/service_channel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:flutter/services.dart';
import '../models/service_state.dart';

class ServiceChannel {
static const _method = MethodChannel('com.github.shadowsocks/service');
static const _stateEvent = EventChannel('com.github.shadowsocks/state');

Future<ServiceState> getState() async {
final state = await _method.invokeMethod<int>('getState');
return ServiceState.fromValue(state ?? 0);
}

Future<bool> toggle() async {
final result = await _method.invokeMethod<bool>('toggle');
return result ?? false;
}

Future<bool> requestVpnPermission() async {
final result = await _method.invokeMethod<bool>('requestVpnPermission');
return result ?? false;
}

Future<String?> testConnection() async {
return await _method.invokeMethod<String>('testConnection');
}

/// Launches the plugin's native configuration activity.
/// Returns {'status': 'ok', 'options': '...'}, {'status': 'fallback'}, or {'status': 'cancelled'}.
Future<Map<String, dynamic>> configurePlugin(String pluginId, String options) async {
final result = await _method.invokeMapMethod<String, dynamic>(
'configurePlugin',
{'pluginId': pluginId, 'options': options},
);
return result ?? {'status': 'fallback'};
}

Stream<ServiceStatus> get stateStream {
return _stateEvent.receiveBroadcastStream().map((event) {
return ServiceStatus.fromMap(event as Map<dynamic, dynamic>);
});
}
}
73 changes: 73 additions & 0 deletions flutter_module/lib/channels/settings_channel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:flutter/services.dart';

class SettingsChannel {
static const _method = MethodChannel('com.github.shadowsocks/settings');

Future<String?> getString(String key) async {
return await _method.invokeMethod<String>('getString', {'key': key});
}

Future<void> putString(String key, String value) async {
await _method.invokeMethod('putString', {'key': key, 'value': value});
}

Future<bool?> getBool(String key) async {
return await _method.invokeMethod<bool>('getBool', {'key': key});
}

Future<void> putBool(String key, bool value) async {
await _method.invokeMethod('putBool', {'key': key, 'value': value});
}

Future<String> getServiceMode() async {
return await _method.invokeMethod<String>('getServiceMode') ?? 'vpn';
}

Future<void> setServiceMode(String mode) async {
await _method.invokeMethod('setServiceMode', {'mode': mode});
}

Future<int> getPortProxy() async {
return await _method.invokeMethod<int>('getPortProxy') ?? 1080;
}

Future<void> setPortProxy(int port) async {
await _method.invokeMethod('setPortProxy', {'port': port});
}

Future<int> getPortLocalDns() async {
return await _method.invokeMethod<int>('getPortLocalDns') ?? 5450;
}

Future<void> setPortLocalDns(int port) async {
await _method.invokeMethod('setPortLocalDns', {'port': port});
}

Future<int> getPortTransproxy() async {
return await _method.invokeMethod<int>('getPortTransproxy') ?? 8200;
}

Future<void> setPortTransproxy(int port) async {
await _method.invokeMethod('setPortTransproxy', {'port': port});
}

Future<bool> getPersistAcrossReboot() async {
return await _method.invokeMethod<bool>('getPersistAcrossReboot') ?? false;
}

Future<void> setPersistAcrossReboot(bool value) async {
await _method.invokeMethod('setPersistAcrossReboot', {'value': value});
}

Future<bool> getDirectBootAware() async {
return await _method.invokeMethod<bool>('getDirectBootAware') ?? false;
}

Future<void> setDirectBootAware(bool value) async {
await _method.invokeMethod('setDirectBootAware', {'value': value});
}

Future<String> getVersion() async {
return await _method.invokeMethod<String>('getVersion') ?? '';
}
}
Loading
Loading