@@ -3,6 +3,8 @@ package net.ivpn.core.common.prefs
33import android.content.SharedPreferences
44import net.ivpn.core.common.Mapper
55import net.ivpn.core.common.dagger.ApplicationScope
6+ import net.ivpn.core.rest.data.model.FavoriteIdentifier
7+ import net.ivpn.core.rest.data.model.Host
68import net.ivpn.core.rest.data.model.Server
79import net.ivpn.core.rest.data.model.ServerLocation
810import net.ivpn.core.rest.data.model.ServerLocation.Companion.from
@@ -45,9 +47,12 @@ class ServersPreference @Inject constructor(
4547 companion object {
4648 private const val CURRENT_ENTER_SERVER = " CURRENT_ENTER_SERVER"
4749 private const val CURRENT_EXIT_SERVER = " CURRENT_EXIT_SERVER"
50+ private const val CURRENT_ENTER_HOST = " CURRENT_ENTER_HOST"
51+ private const val CURRENT_EXIT_HOST = " CURRENT_EXIT_HOST"
4852 private const val SERVERS_LIST = " SERVERS_LIST"
4953 private const val LOCATION_LIST = " LOCATION_LIST"
5054 private const val FAVOURITES_SERVERS_LIST = " FAVOURITES_SERVERS_LIST"
55+ private const val UNIFIED_FAVOURITES_LIST = " UNIFIED_FAVOURITES_LIST"
5156 private const val EXCLUDED_FASTEST_SERVERS = " EXCLUDED_FASTEST_SERVERS"
5257 private const val SETTINGS_FASTEST_SERVER = " SETTINGS_FASTEST_SERVER"
5358 private const val SETTINGS_RANDOM_ENTER_SERVER = " SETTINGS_RANDOM_ENTER_SERVER"
@@ -91,28 +96,75 @@ class ServersPreference @Inject constructor(
9196 return Mapper .serverListFrom(sharedPreferences.getString(SERVERS_LIST , null ))
9297 }
9398
99+ /* *
100+ * Returns the list of favorite servers for the current protocol.
101+ * This uses the unified favorites system where favorites are stored as
102+ * protocol-agnostic identifiers (gateway prefix for locations, dns_name for hosts).
103+ * When retrieving, it matches these identifiers against the current protocol's servers.
104+ */
94105 val favouritesServersList: MutableList <Server >
95106 get() {
96- val sharedPreferences = properSharedPreference
97- val servers =
98- Mapper .serverListFrom(sharedPreferences.getString(FAVOURITES_SERVERS_LIST , null ))
99- return servers ? : ArrayList ()
107+ val identifiers = unifiedFavouritesList
108+ val currentServers = serversList ? : return ArrayList ()
109+
110+ val favourites = ArrayList <Server >()
111+ for (server in currentServers) {
112+ for (identifier in identifiers) {
113+ if (identifier.matches(server)) {
114+ favourites.add(server)
115+ break
116+ }
117+ }
118+ }
119+ return favourites
120+ }
121+
122+ /* *
123+ * Returns the unified list of favorite identifiers.
124+ * These identifiers are protocol-agnostic and work across OpenVPN and WireGuard.
125+ */
126+ val unifiedFavouritesList: MutableList <FavoriteIdentifier >
127+ get() {
128+ // First try to get from unified storage
129+ val sharedPreferences = preference.stickySharedPreferences
130+ val identifiers = Mapper .favoriteIdentifierListFrom(
131+ sharedPreferences.getString(UNIFIED_FAVOURITES_LIST , null )
132+ )
133+ return identifiers ? : ArrayList ()
100134 }
101135
102136 val openvpnFavouritesServersList: MutableList <Server >
103137 get() {
104- val sharedPreferences = preference.serversSharedPreferences
105- val servers =
106- Mapper .serverListFrom(sharedPreferences.getString(FAVOURITES_SERVERS_LIST , null ))
107- return servers ? : ArrayList ()
138+ val identifiers = unifiedFavouritesList
139+ val currentServers = openvpnServersList ? : return ArrayList ()
140+
141+ val favourites = ArrayList <Server >()
142+ for (server in currentServers) {
143+ for (identifier in identifiers) {
144+ if (identifier.matches(server)) {
145+ favourites.add(server)
146+ break
147+ }
148+ }
149+ }
150+ return favourites
108151 }
109152
110153 val wireguardFavouritesServersList: MutableList <Server >
111154 get() {
112- val sharedPreferences = preference.wireguardServersSharedPreferences
113- val servers =
114- Mapper .serverListFrom(sharedPreferences.getString(FAVOURITES_SERVERS_LIST , null ))
115- return servers ? : ArrayList ()
155+ val identifiers = unifiedFavouritesList
156+ val currentServers = wireguardServersList ? : return ArrayList ()
157+
158+ val favourites = ArrayList <Server >()
159+ for (server in currentServers) {
160+ for (identifier in identifiers) {
161+ if (identifier.matches(server)) {
162+ favourites.add(server)
163+ break
164+ }
165+ }
166+ }
167+ return favourites
116168 }
117169
118170 val excludedServersList: MutableList <Server >
@@ -200,40 +252,138 @@ class ServersPreference @Inject constructor(
200252 return Mapper .from(sharedPreferences.getString(serverKey, null ))
201253 }
202254
255+ fun setCurrentHost (serverType : ServerType ? , host : Host ? ) {
256+ if (serverType == null ) return
257+ val hostKey =
258+ if (serverType == ServerType .ENTRY ) CURRENT_ENTER_HOST else CURRENT_EXIT_HOST
259+ preference.serversSharedPreferences.edit {
260+ putString(hostKey, Mapper .stringFromHost(host))
261+ }
262+ preference.wireguardServersSharedPreferences.edit {
263+ putString(hostKey, Mapper .stringFromHost(host))
264+ }
265+ }
266+
267+ fun getCurrentHost (serverType : ServerType ? ): Host ? {
268+ if (serverType == null ) return null
269+ val sharedPreferences = properSharedPreference
270+ val hostKey =
271+ if (serverType == ServerType .ENTRY ) CURRENT_ENTER_HOST else CURRENT_EXIT_HOST
272+ return Mapper .hostFrom(sharedPreferences.getString(hostKey, null ))
273+ }
274+
275+ fun clearCurrentHost (serverType : ServerType ? ) {
276+ if (serverType == null ) return
277+ val hostKey =
278+ if (serverType == ServerType .ENTRY ) CURRENT_ENTER_HOST else CURRENT_EXIT_HOST
279+ preference.serversSharedPreferences.edit { remove(hostKey) }
280+ preference.wireguardServersSharedPreferences.edit { remove(hostKey) }
281+ }
282+
283+ /* *
284+ * Adds a server to the unified favorites list.
285+ * The server is stored as a protocol-agnostic identifier:
286+ * - For locations: normalized gateway (with .wg. replaced by .gw.)
287+ * - For hosts: dns_name
288+ */
203289 fun addFavouriteServer (server : Server ? ) {
204- val openvpnServer = openvpnServersList?.first { it == server }
205- val wireguardServer = wireguardServersList?.first { it == server }
206- if (server == null || openvpnServer == null || wireguardServer == null ) {
290+ if (server == null ) return
291+
292+ val identifier = FavoriteIdentifier .fromServer(server)
293+ val identifiers = unifiedFavouritesList
294+
295+ // Check if already in favorites
296+ if (identifiers.any { it == identifier }) {
207297 return
208298 }
209- val openvpnServers = openvpnFavouritesServersList
210- val wireguardServers = wireguardFavouritesServersList
211- if (! openvpnServers.contains(openvpnServer)) {
212- openvpnServers.add(openvpnServer)
213- preference.serversSharedPreferences.edit()
214- .putString(FAVOURITES_SERVERS_LIST , Mapper .stringFrom(openvpnServers)).apply ()
299+
300+ identifiers.add(identifier)
301+ saveUnifiedFavourites(identifiers)
302+ }
303+
304+ /* *
305+ * Removes a server from the unified favorites list.
306+ */
307+ fun removeFavouriteServer (server : Server ) {
308+ val identifier = FavoriteIdentifier .fromServer(server)
309+ val identifiers = unifiedFavouritesList
310+
311+ identifiers.removeAll { it == identifier }
312+ saveUnifiedFavourites(identifiers)
313+ }
314+
315+ /* *
316+ * Adds a specific host to favorites by dns_name.
317+ */
318+ fun addFavouriteHost (dnsName : String ) {
319+ val identifier = FavoriteIdentifier .forHost(dnsName)
320+ val identifiers = unifiedFavouritesList
321+
322+ if (identifiers.any { it == identifier }) {
323+ return
215324 }
216- if (! wireguardServers.contains(wireguardServer)) {
217- wireguardServers.add(wireguardServer)
218- preference.wireguardServersSharedPreferences.edit()
219- .putString(FAVOURITES_SERVERS_LIST , Mapper .stringFrom(wireguardServers)).apply ()
325+
326+ identifiers.add(identifier)
327+ saveUnifiedFavourites(identifiers)
328+ }
329+
330+ /* *
331+ * Removes a specific host from favorites by dns_name.
332+ */
333+ fun removeFavouriteHost (dnsName : String ) {
334+ val identifier = FavoriteIdentifier .forHost(dnsName)
335+ val identifiers = unifiedFavouritesList
336+
337+ identifiers.removeAll { it == identifier }
338+ saveUnifiedFavourites(identifiers)
339+ }
340+
341+ /* *
342+ * Checks if a server is in the favorites list.
343+ */
344+ fun isFavourite (server : Server ): Boolean {
345+ val identifier = FavoriteIdentifier .fromServer(server)
346+ return unifiedFavouritesList.any { it == identifier }
347+ }
348+
349+ /* *
350+ * Saves the unified favorites list.
351+ */
352+ private fun saveUnifiedFavourites (identifiers : List <FavoriteIdentifier >) {
353+ preference.stickySharedPreferences.edit {
354+ putString(UNIFIED_FAVOURITES_LIST , Mapper .stringFromFavoriteIdentifiers(identifiers))
220355 }
221356 }
222357
223- fun removeFavouriteServer (server : Server ) {
224- val openvpnServer = openvpnServersList?.first { it == server }
225- val wireguardServer = wireguardServersList?.first { it == server }
226- if (openvpnServer == null || wireguardServer == null ) {
358+ /* *
359+ * Migrates old per-protocol favorites to the new unified format.
360+ * This should be called once during app upgrade.
361+ */
362+ fun migrateOldFavouritesToUnified () {
363+ // Check if already migrated
364+ if (preference.stickySharedPreferences.contains(UNIFIED_FAVOURITES_LIST )) {
227365 return
228366 }
229- val openvpnServers = openvpnFavouritesServersList
230- val wireguardServers = wireguardFavouritesServersList
231- openvpnServers.remove(openvpnServer)
232- wireguardServers.remove(wireguardServer)
233- preference.serversSharedPreferences.edit()
234- .putString(FAVOURITES_SERVERS_LIST , Mapper .stringFrom(openvpnServers)).apply ()
235- preference.wireguardServersSharedPreferences.edit()
236- .putString(FAVOURITES_SERVERS_LIST , Mapper .stringFrom(wireguardServers)).apply ()
367+
368+ val identifiers = mutableSetOf<FavoriteIdentifier >()
369+
370+ val oldOpenvpnFavourites = Mapper .serverListFrom(
371+ preference.serversSharedPreferences.getString(FAVOURITES_SERVERS_LIST , null )
372+ )
373+ oldOpenvpnFavourites?.forEach { server ->
374+ identifiers.add(FavoriteIdentifier .fromServer(server))
375+ }
376+
377+ val oldWireguardFavourites = Mapper .serverListFrom(
378+ preference.wireguardServersSharedPreferences.getString(FAVOURITES_SERVERS_LIST , null )
379+ )
380+ oldWireguardFavourites?.forEach { server ->
381+ identifiers.add(FavoriteIdentifier .fromServer(server))
382+ }
383+
384+ if (identifiers.isNotEmpty()) {
385+ saveUnifiedFavourites(identifiers.toList())
386+ }
237387 }
238388
239389 fun addToExcludedServersList (server : Server ? ) {
0 commit comments