@@ -10,31 +10,32 @@ class Subscription {
1010 final String ? name;
1111 final DateTime ? lastUpdate;
1212 List <ServerConfig > servers;
13-
13+
1414 Subscription ({
1515 required this .url,
1616 this .name,
1717 this .lastUpdate,
1818 this .servers = const [],
1919 });
20-
20+
2121 Map <String , dynamic > toJson () => {
22- 'url' : url,
23- 'name' : name,
24- 'lastUpdate' : lastUpdate? .toIso8601String (),
25- 'servers' : servers.map ((s) => s.toJson ()).toList (),
26- };
27-
22+ 'url' : url,
23+ 'name' : name,
24+ 'lastUpdate' : lastUpdate? .toIso8601String (),
25+ 'servers' : servers.map ((s) => s.toJson ()).toList (),
26+ };
27+
2828 factory Subscription .fromJson (Map <String , dynamic > json) => Subscription (
29- url: json['url' ] as String ,
30- name: json['name' ] as String ? ,
31- lastUpdate: json['lastUpdate' ] != null
32- ? DateTime .parse (json['lastUpdate' ] as String )
33- : null ,
34- servers: (json['servers' ] as List ? )
35- ? .map ((s) => ServerConfig .fromJson (s as Map <String , dynamic >))
36- .toList () ?? [],
37- );
29+ url: json['url' ] as String ,
30+ name: json['name' ] as String ? ,
31+ lastUpdate: json['lastUpdate' ] != null
32+ ? DateTime .parse (json['lastUpdate' ] as String )
33+ : null ,
34+ servers: (json['servers' ] as List ? )
35+ ? .map ((s) => ServerConfig .fromJson (s as Map <String , dynamic >))
36+ .toList () ??
37+ [],
38+ );
3839}
3940
4041/// Server configuration model
@@ -45,7 +46,7 @@ class ServerConfig {
4546 final String address;
4647 final int port;
4748 int ? latencyMs;
48-
49+
4950 ServerConfig ({
5051 required this .url,
5152 required this .remark,
@@ -54,25 +55,25 @@ class ServerConfig {
5455 required this .port,
5556 this .latencyMs,
5657 });
57-
58+
5859 Map <String , dynamic > toJson () => {
59- 'url' : url,
60- 'remark' : remark,
61- 'protocol' : protocol,
62- 'address' : address,
63- 'port' : port,
64- 'latencyMs' : latencyMs,
65- };
66-
60+ 'url' : url,
61+ 'remark' : remark,
62+ 'protocol' : protocol,
63+ 'address' : address,
64+ 'port' : port,
65+ 'latencyMs' : latencyMs,
66+ };
67+
6768 factory ServerConfig .fromJson (Map <String , dynamic > json) => ServerConfig (
68- url: json['url' ] as String ,
69- remark: json['remark' ] as String ,
70- protocol: json['protocol' ] as String ,
71- address: json['address' ] as String ,
72- port: json['port' ] as int ,
73- latencyMs: json['latencyMs' ] as int ? ,
74- );
75-
69+ url: json['url' ] as String ,
70+ remark: json['remark' ] as String ,
71+ protocol: json['protocol' ] as String ,
72+ address: json['address' ] as String ,
73+ port: json['port' ] as int ,
74+ latencyMs: json['latencyMs' ] as int ? ,
75+ );
76+
7677 factory ServerConfig .fromV2RayURL (V2RayURL v2rayUrl) {
7778 final config = v2rayUrl.parse ();
7879 return ServerConfig (
@@ -92,7 +93,7 @@ class PingResult {
9293 final int latencyInMs;
9394 final bool success;
9495 final String ? error;
95-
96+
9697 PingResult ({
9798 required this .subscriptionIndex,
9899 required this .serverIndex,
@@ -105,13 +106,13 @@ class PingResult {
105106/// Subscription manager
106107class SubscriptionManager {
107108 final List <Subscription > _subscriptions = [];
108- final StreamController <PingResult > _pingResultController =
109+ final StreamController <PingResult > _pingResultController =
109110 StreamController <PingResult >.broadcast ();
110-
111+
111112 Stream <PingResult > get onPingResult => _pingResultController.stream;
112-
113+
113114 List <Subscription > get subscriptions => List .unmodifiable (_subscriptions);
114-
115+
115116 /// Add subscription
116117 void addSubscription ({required String subscriptionURL, String ? name}) {
117118 final subscription = Subscription (
@@ -120,63 +121,63 @@ class SubscriptionManager {
120121 );
121122 _subscriptions.add (subscription);
122123 }
123-
124+
124125 /// Clear all subscriptions
125126 void clearSubscriptions () {
126127 _subscriptions.clear ();
127128 }
128-
129+
129130 /// Update subscription by downloading and parsing
130131 Future <bool > updateSubscription ({required int subscriptionIndex}) async {
131132 if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptions.length) {
132133 return false ;
133134 }
134-
135+
135136 final subscription = _subscriptions[subscriptionIndex];
136-
137+
137138 try {
138139 final response = await http.get (Uri .parse (subscription.url));
139-
140+
140141 if (response.statusCode != 200 ) {
141142 return false ;
142143 }
143-
144+
144145 // Decode base64 subscription content
145146 String content;
146147 try {
147148 content = utf8.decode (base64.decode (response.body));
148149 } catch (_) {
149150 content = response.body;
150151 }
151-
152+
152153 // Parse servers from subscription
153154 final lines = content.split ('\n ' );
154155 final servers = < ServerConfig > [];
155-
156+
156157 for (final line in lines) {
157158 final trimmed = line.trim ();
158159 if (trimmed.isEmpty) continue ;
159-
160+
160161 final v2rayUrl = parseV2RayURL (trimmed);
161162 if (v2rayUrl != null ) {
162163 servers.add (ServerConfig .fromV2RayURL (v2rayUrl));
163164 }
164165 }
165-
166+
166167 // Update subscription
167168 _subscriptions[subscriptionIndex] = Subscription (
168169 url: subscription.url,
169170 name: subscription.name,
170171 lastUpdate: DateTime .now (),
171172 servers: servers,
172173 );
173-
174+
174175 return true ;
175176 } catch (e) {
176177 return false ;
177178 }
178179 }
179-
180+
180181 /// Ping server
181182 Future <void > pingServer ({
182183 required int subscriptionIndex,
@@ -193,7 +194,7 @@ class SubscriptionManager {
193194 ));
194195 return ;
195196 }
196-
197+
197198 final subscription = _subscriptions[subscriptionIndex];
198199 if (serverIndex < 0 || serverIndex >= subscription.servers.length) {
199200 _pingResultController.add (PingResult (
@@ -205,28 +206,28 @@ class SubscriptionManager {
205206 ));
206207 return ;
207208 }
208-
209+
209210 final server = subscription.servers[serverIndex];
210-
211+
211212 try {
212213 final stopwatch = Stopwatch ()..start ();
213-
214+
214215 // Simple TCP connection test
215216 // TODO: Implement proper proxy-through ping
216217 final socket = await Socket .connect (
217218 server.address,
218219 server.port,
219220 timeout: const Duration (seconds: 5 ),
220221 );
221-
222+
222223 stopwatch.stop ();
223224 final latency = stopwatch.elapsedMilliseconds;
224-
225+
225226 await socket.close ();
226-
227+
227228 // Update server latency
228229 server.latencyMs = latency;
229-
230+
230231 _pingResultController.add (PingResult (
231232 subscriptionIndex: subscriptionIndex,
232233 serverIndex: serverIndex,
@@ -243,7 +244,7 @@ class SubscriptionManager {
243244 ));
244245 }
245246 }
246-
247+
247248 /// Get server configuration
248249 ServerConfig ? getServer ({
249250 required int subscriptionIndex,
@@ -252,18 +253,17 @@ class SubscriptionManager {
252253 if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptions.length) {
253254 return null ;
254255 }
255-
256+
256257 final subscription = _subscriptions[subscriptionIndex];
257258 if (serverIndex < 0 || serverIndex >= subscription.servers.length) {
258259 return null ;
259260 }
260-
261+
261262 return subscription.servers[serverIndex];
262263 }
263-
264+
264265 /// Dispose resources
265266 void dispose () {
266267 _pingResultController.close ();
267268 }
268269}
269-
0 commit comments