diff options
21 files changed, 413 insertions, 168 deletions
diff --git a/app/src/main/java/com/wireguard/android/PlaceholderFragment.java b/app/src/main/java/com/wireguard/android/PlaceholderFragment.java deleted file mode 100644 index 8c15e757..00000000 --- a/app/src/main/java/com/wireguard/android/PlaceholderFragment.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.wireguard.android; - -import android.app.Fragment; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -/** - * Fragment containing a simple placeholder message. - */ - -public class PlaceholderFragment extends Fragment { - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { - return inflater.inflate(R.layout.placeholder_fragment, parent, false); - } -} diff --git a/app/src/main/java/com/wireguard/android/ProfileActivity.java b/app/src/main/java/com/wireguard/android/ProfileActivity.java index 040feb23..722f1dca 100644 --- a/app/src/main/java/com/wireguard/android/ProfileActivity.java +++ b/app/src/main/java/com/wireguard/android/ProfileActivity.java @@ -1,54 +1,53 @@ package com.wireguard.android; -import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; import android.app.FragmentTransaction; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; +import android.content.res.Configuration; import android.os.Bundle; -import android.os.IBinder; import android.view.Menu; import android.view.MenuItem; -import com.wireguard.config.Profile; - -import java.util.ArrayList; -import java.util.List; - /** * Activity that allows creating/viewing/editing/deleting WireGuard profiles. */ -public class ProfileActivity extends Activity { - private final ServiceConnection connection = new ProfileServiceConnection(); +public class ProfileActivity extends ServiceClientActivity<ProfileServiceInterface> { + public static final String KEY_PROFILE_NAME = "profile_name"; + + // FIXME: These must match the constants in profile_list_activity.xml + private static final String TAG_DETAIL = "detail"; + private static final String TAG_LIST = "list"; + + private String currentProfile; private boolean isSplitLayout; - private final List<ServiceConnectionListener> listeners = new ArrayList<>(); - private ProfileServiceInterface service; - public void addServiceConnectionListener(ServiceConnectionListener listener) { - listeners.add(listener); + public ProfileActivity() { + super(ProfileService.class); } - public ProfileServiceInterface getService() { - return service; + @Override + public void onBackPressed() { + if (!isSplitLayout && currentProfile != null) { + onProfileSelected(null); + } else { + super.onBackPressed(); + } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // This layout consists only of containers for fragments. + // Restore the saved profile if there is one; otherwise grab it from the intent. + if (savedInstanceState != null) + currentProfile = savedInstanceState.getString(KEY_PROFILE_NAME); + else + currentProfile = getIntent().getStringExtra(KEY_PROFILE_NAME); + // Set up the base layout and fill it with fragments. setContentView(R.layout.profile_activity); - isSplitLayout = findViewById(R.id.detail_fragment_container) != null; - // Fill the layout with the initial set of fragments. - final FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.add(R.id.list_fragment_container, new ProfileListFragment()); - if (isSplitLayout) - transaction.add(R.id.detail_fragment_container, new PlaceholderFragment()); - transaction.commit(); - // Ensure the long-running service is started. This only needs to happen once. - final Intent intent = new Intent(this, ProfileService.class); - startService(intent); + final int orientation = getResources().getConfiguration().orientation; + isSplitLayout = orientation == Configuration.ORIENTATION_LANDSCAPE; + updateLayout(currentProfile); } @Override @@ -57,50 +56,54 @@ public class ProfileActivity extends Activity { return true; } - public void onMenuSettings(MenuItem item) { + public void onMenuEdit(MenuItem item) { } - public void onProfileSelected(Profile profile) { + public void onMenuSave(MenuItem item) { } - @Override - public void onStart() { - super.onStart(); - Intent intent = new Intent(this, ProfileService.class); - bindService(intent, connection, Context.BIND_AUTO_CREATE); - } + public void onMenuSettings(MenuItem item) { - @Override - public void onStop() { - super.onStop(); - if (service != null) { - unbindService(connection); - for (ServiceConnectionListener listener : listeners) - listener.onServiceDisconnected(); - service = null; - } } - public void removeServiceConnectionListener(ServiceConnectionListener listener) { - listeners.remove(listener); + public void onProfileSelected(String profile) { + updateLayout(profile); + currentProfile = profile; } - private class ProfileServiceConnection implements ServiceConnection { - @Override - public void onServiceConnected(ComponentName component, IBinder binder) { - service = (ProfileServiceInterface) binder; - for (ServiceConnectionListener listener : listeners) - listener.onServiceConnected(service); - } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(KEY_PROFILE_NAME, currentProfile); + } - @Override - public void onServiceDisconnected(ComponentName component) { - // This function is only called when the service crashes or goes away unexpectedly. - for (ServiceConnectionListener listener : listeners) - listener.onServiceDisconnected(); - service = null; + private void updateLayout(String profile) { + final FragmentManager fm = getFragmentManager(); + final Fragment detailFragment = fm.findFragmentByTag(TAG_DETAIL); + final Fragment listFragment = fm.findFragmentByTag(TAG_LIST); + final FragmentTransaction transaction = fm.beginTransaction(); + if (profile != null) { + if (isSplitLayout) { + if (listFragment.isHidden()) + transaction.show(listFragment); + } else { + transaction.hide(listFragment); + } + if (detailFragment.isHidden()) + transaction.show(detailFragment); + } else { + if (isSplitLayout) { + if (detailFragment.isHidden()) + transaction.show(detailFragment); + } else { + transaction.hide(detailFragment); + } + if (listFragment.isHidden()) + transaction.show(listFragment); } + transaction.commit(); + ((ProfileDetailFragment) detailFragment).setProfile(profile); } } diff --git a/app/src/main/java/com/wireguard/android/ProfileActivityFragment.java b/app/src/main/java/com/wireguard/android/ProfileActivityFragment.java deleted file mode 100644 index 9ec9f51d..00000000 --- a/app/src/main/java/com/wireguard/android/ProfileActivityFragment.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.wireguard.android; - -import android.app.Fragment; -import android.content.Context; - -/** - * Base class for fragments that are part of a ProfileActivity. - */ - -public class ProfileActivityFragment extends Fragment implements ServiceConnectionListener { - private ProfileActivity activity; - protected ProfileServiceInterface service; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - activity = (ProfileActivity) context; - } - - @Override - public void onDetach() { - super.onDetach(); - activity = null; - } - - @Override - public void onStart() { - super.onStart(); - activity.addServiceConnectionListener(this); - // If the service is already connected, there will be no callback, so run the handler now. - final ProfileServiceInterface service = activity.getService(); - if (service != null) - onServiceConnected(service); - } - - @Override - public void onStop() { - super.onStop(); - activity.removeServiceConnectionListener(this); - } - - @Override - public void onServiceConnected(ProfileServiceInterface service) { - this.service = service; - } - - @Override - public void onServiceDisconnected() { - service = null; - } -} diff --git a/app/src/main/java/com/wireguard/android/ProfileDetailFragment.java b/app/src/main/java/com/wireguard/android/ProfileDetailFragment.java new file mode 100644 index 00000000..4af7fb25 --- /dev/null +++ b/app/src/main/java/com/wireguard/android/ProfileDetailFragment.java @@ -0,0 +1,56 @@ +package com.wireguard.android; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.wireguard.android.databinding.ProfileDetailFragmentBinding; + +/** + * Fragment for viewing and editing a WireGuard profile. + */ + +public class ProfileDetailFragment extends ServiceClientFragment<ProfileServiceInterface> { + private ProfileDetailFragmentBinding binding; + private String name; + + public ProfileDetailFragment() { + super(); + setArguments(new Bundle()); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + name = getArguments().getString(ProfileActivity.KEY_PROFILE_NAME); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.profile_detail, menu); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { + binding = ProfileDetailFragmentBinding.inflate(inflater, parent, false); + return binding.getRoot(); + } + + @Override + public void onServiceConnected(ProfileServiceInterface service) { + super.onServiceConnected(service); + binding.setProfile(service.getProfiles().get(name)); + } + + public void setProfile(String name) { + this.name = name; + getArguments().putString(ProfileActivity.KEY_PROFILE_NAME, name); + final ProfileServiceInterface service = getService(); + if (binding != null && service != null) + binding.setProfile(service.getProfiles().get(name)); + } +} diff --git a/app/src/main/java/com/wireguard/android/ProfileListFragment.java b/app/src/main/java/com/wireguard/android/ProfileListFragment.java index be9b8d42..ed72b63b 100644 --- a/app/src/main/java/com/wireguard/android/ProfileListFragment.java +++ b/app/src/main/java/com/wireguard/android/ProfileListFragment.java @@ -11,10 +11,10 @@ import com.wireguard.android.databinding.ProfileListFragmentBinding; import com.wireguard.config.Profile; /** - * Fragment containing the list of available WireGuard profiles. Must be part of a ProfileActivity. + * Fragment containing the list of available WireGuard profiles. */ -public class ProfileListFragment extends ProfileActivityFragment { +public class ProfileListFragment extends ServiceClientFragment<ProfileServiceInterface> { private ProfileListFragmentBinding binding; @Override @@ -25,7 +25,7 @@ public class ProfileListFragment extends ProfileActivityFragment { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final Profile profile = (Profile) parent.getItemAtPosition(position); - ((ProfileActivity) getActivity()).onProfileSelected(profile); + ((ProfileActivity) getActivity()).onProfileSelected(profile.getName()); } }); listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @@ -33,6 +33,7 @@ public class ProfileListFragment extends ProfileActivityFragment { public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { final Profile profile = (Profile) parent.getItemAtPosition(position); + final ProfileServiceInterface service = getService(); if (profile == null || service == null) return false; if (profile.getIsConnected()) diff --git a/app/src/main/java/com/wireguard/android/ProfileService.java b/app/src/main/java/com/wireguard/android/ProfileService.java index 96371d6c..1f40a0a1 100644 --- a/app/src/main/java/com/wireguard/android/ProfileService.java +++ b/app/src/main/java/com/wireguard/android/ProfileService.java @@ -38,6 +38,9 @@ public class ProfileService extends Service { @Override public void onCreate() { rootShell = new RootShell(this); + // Ensure the service sticks around after being unbound. This only needs to happen once. + final Intent intent = new Intent(this, ProfileService.class); + startService(intent); new ProfileLoader().execute(getFilesDir().listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { diff --git a/app/src/main/java/com/wireguard/android/ProfileServiceInterface.java b/app/src/main/java/com/wireguard/android/ProfileServiceInterface.java index 54b32595..65dc27a0 100644 --- a/app/src/main/java/com/wireguard/android/ProfileServiceInterface.java +++ b/app/src/main/java/com/wireguard/android/ProfileServiceInterface.java @@ -8,7 +8,7 @@ import com.wireguard.config.Profile; * Interface for the background connection service. */ -public interface ProfileServiceInterface { +interface ProfileServiceInterface { /** * Attempt to set up and enable an interface for this profile. The profile's connection state * will be updated if connection is successful. If this profile is already connected, or it is diff --git a/app/src/main/java/com/wireguard/android/ServiceClientActivity.java b/app/src/main/java/com/wireguard/android/ServiceClientActivity.java new file mode 100644 index 00000000..263e012c --- /dev/null +++ b/app/src/main/java/com/wireguard/android/ServiceClientActivity.java @@ -0,0 +1,75 @@ +package com.wireguard.android; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; + +import java.util.ArrayList; +import java.util.List; + +/** + * Base class for activities that maintain a connection to a background service. + */ + +abstract class ServiceClientActivity<T> extends Activity implements ServiceConnectionProvider<T> { + private final ServiceConnectionCallbacks callbacks = new ServiceConnectionCallbacks(); + private final List<ServiceConnectionListener<T>> listeners = new ArrayList<>(); + private T service; + private final Class<?> serviceClass; + + protected ServiceClientActivity(Class<?> serviceClass) { + this.serviceClass = serviceClass; + } + + @Override + public void addServiceConnectionListener(ServiceConnectionListener<T> listener) { + listeners.add(listener); + } + + public T getService() { + return service; + } + + @Override + public void onStart() { + super.onStart(); + bindService(new Intent(this, serviceClass), callbacks, Context.BIND_AUTO_CREATE); + } + + @Override + public void onStop() { + super.onStop(); + if (service != null) { + service = null; + unbindService(callbacks); + for (ServiceConnectionListener listener : listeners) + listener.onServiceDisconnected(); + } + } + + @Override + public void removeServiceConnectionListener(ServiceConnectionListener<T> listener) { + listeners.remove(listener); + } + + private class ServiceConnectionCallbacks implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName component, IBinder binder) { + @SuppressWarnings("unchecked") + final T localBinder = (T) binder; + service = localBinder; + for (ServiceConnectionListener<T> listener : listeners) + listener.onServiceConnected(service); + } + + @Override + public void onServiceDisconnected(ComponentName component) { + service = null; + for (ServiceConnectionListener<T> listener : listeners) + listener.onServiceDisconnected(); + } + } +} diff --git a/app/src/main/java/com/wireguard/android/ServiceClientFragment.java b/app/src/main/java/com/wireguard/android/ServiceClientFragment.java new file mode 100644 index 00000000..3b209438 --- /dev/null +++ b/app/src/main/java/com/wireguard/android/ServiceClientFragment.java @@ -0,0 +1,61 @@ +package com.wireguard.android; + +import android.app.Fragment; +import android.content.Context; + +/** + * Base class for fragments in activities that maintain a connection to a background service. + */ + +abstract class ServiceClientFragment<T> extends Fragment implements ServiceConnectionListener<T> { + private ServiceConnectionProvider<T> provider; + private T service; + + protected T getService() { + return service; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + @SuppressWarnings("unchecked") + final ServiceConnectionProvider<T> localContext = (ServiceConnectionProvider<T>) context; + provider = localContext; + } + + @Override + public void onDetach() { + super.onDetach(); + provider = null; + } + + @Override + public void onStart() { + super.onStart(); + provider.addServiceConnectionListener(this); + // Run the handler if the connection state changed while we were not paying attention. + final T localService = provider.getService(); + if (localService != service) { + if (localService != null) + onServiceConnected(localService); + else + onServiceDisconnected(); + } + } + + @Override + public void onStop() { + super.onStop(); + provider.removeServiceConnectionListener(this); + } + + @Override + public void onServiceConnected(T service) { + this.service = service; + } + + @Override + public void onServiceDisconnected() { + service = null; + } +} diff --git a/app/src/main/java/com/wireguard/android/ServiceConnectionListener.java b/app/src/main/java/com/wireguard/android/ServiceConnectionListener.java index 5f3c8d0a..c77fe931 100644 --- a/app/src/main/java/com/wireguard/android/ServiceConnectionListener.java +++ b/app/src/main/java/com/wireguard/android/ServiceConnectionListener.java @@ -1,11 +1,11 @@ package com.wireguard.android; /** - * Interface for fragments that need notification about connection changes to the ProfileService. + * Interface for fragments that need notification about service connection changes. */ -interface ServiceConnectionListener { - void onServiceConnected(ProfileServiceInterface service); +interface ServiceConnectionListener<T> { + void onServiceConnected(T service); void onServiceDisconnected(); } diff --git a/app/src/main/java/com/wireguard/android/ServiceConnectionProvider.java b/app/src/main/java/com/wireguard/android/ServiceConnectionProvider.java new file mode 100644 index 00000000..79a0efde --- /dev/null +++ b/app/src/main/java/com/wireguard/android/ServiceConnectionProvider.java @@ -0,0 +1,13 @@ +package com.wireguard.android; + +/** + * Interface for activities that provide a connection to a service. + */ + +interface ServiceConnectionProvider<T> { + void addServiceConnectionListener(ServiceConnectionListener<T> listener); + + T getService(); + + void removeServiceConnectionListener(ServiceConnectionListener<T> listener); +} diff --git a/app/src/main/res/drawable/ic_action_edit.xml b/app/src/main/res/drawable/ic_action_edit.xml new file mode 100644 index 00000000..c910949f --- /dev/null +++ b/app/src/main/res/drawable/ic_action_edit.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" + android:fillColor="#FFFFFF"/> +</vector> diff --git a/app/src/main/res/drawable/ic_action_save.xml b/app/src/main/res/drawable/ic_action_save.xml new file mode 100644 index 00000000..08280124 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_save.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFF" + android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" /> +</vector> diff --git a/app/src/main/res/layout-land/profile_activity.xml b/app/src/main/res/layout-land/profile_activity.xml deleted file mode 100644 index acc56925..00000000 --- a/app/src/main/res/layout-land/profile_activity.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:baselineAligned="false" - android:orientation="horizontal"> - - <FrameLayout - android:id="@+id/list_fragment_container" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" /> - - <FrameLayout - android:id="@+id/detail_fragment_container" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="2" /> -</LinearLayout> diff --git a/app/src/main/res/layout/placeholder_fragment.xml b/app/src/main/res/layout/placeholder_fragment.xml deleted file mode 100644 index 8a321c6b..00000000 --- a/app/src/main/res/layout/placeholder_fragment.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/text" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center" - android:text="@string/placeholder_text" /> diff --git a/app/src/main/res/layout/profile_activity.xml b/app/src/main/res/layout/profile_activity.xml index d9b6a1f6..2645552b 100644 --- a/app/src/main/res/layout/profile_activity.xml +++ b/app/src/main/res/layout/profile_activity.xml @@ -1,5 +1,21 @@ <?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/list_fragment_container" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="match_parent" + android:baselineAligned="false" + android:orientation="horizontal"> + + <fragment + android:name="com.wireguard.android.ProfileListFragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:tag="list" /> + + <fragment + android:name="com.wireguard.android.ProfileDetailFragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="2" + android:tag="detail" /> +</LinearLayout> diff --git a/app/src/main/res/layout/profile_detail_fragment.xml b/app/src/main/res/layout/profile_detail_fragment.xml new file mode 100644 index 00000000..248f11a1 --- /dev/null +++ b/app/src/main/res/layout/profile_detail_fragment.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <data> + + <import type="android.view.View" /> + + <variable + name="profile" + type="com.wireguard.config.Profile" /> + </data> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="16dp"> + + <TextView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:text="@string/placeholder_text" + android:visibility="@{profile == null ? View.VISIBLE : View.GONE}" /> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="@{profile == null ? View.GONE : View.VISIBLE}"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/profile_name_label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:labelFor="@+id/profile_name_text" + android:text="@string/profile_name" /> + + <TextView + android:id="@+id/profile_name_text" + style="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/profile_name_label" + android:text="@{profile.name}" /> + + <TextView + android:id="@+id/public_key_label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/profile_name_text" + android:labelFor="@+id/public_key_text" + android:text="@string/public_key" /> + + <TextView + android:id="@+id/public_key_text" + style="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/public_key_label" + android:ellipsize="end" + android:maxLines="1" + android:text="@{profile.interface.publicKey}" /> + </RelativeLayout> + </ScrollView> + </FrameLayout> +</layout> diff --git a/app/src/main/res/layout/profile_list_fragment.xml b/app/src/main/res/layout/profile_list_fragment.xml index c0edd444..f5954092 100644 --- a/app/src/main/res/layout/profile_list_fragment.xml +++ b/app/src/main/res/layout/profile_list_fragment.xml @@ -4,6 +4,7 @@ <data> + <!--suppress AndroidDomInspection --> <variable name="profiles" type="android.databinding.ObservableArrayMap<String, com.wireguard.config.Profile>" /> diff --git a/app/src/main/res/menu/profile_detail.xml b/app/src/main/res/menu/profile_detail.xml new file mode 100644 index 00000000..499adcdb --- /dev/null +++ b/app/src/main/res/menu/profile_detail.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:alphabeticShortcut="e" + android:icon="@drawable/ic_action_edit" + android:onClick="onMenuEdit" + android:showAsAction="always" + android:title="@string/edit" /> +</menu> diff --git a/app/src/main/res/menu/profile_detail_edit.xml b/app/src/main/res/menu/profile_detail_edit.xml new file mode 100644 index 00000000..ad97fcbf --- /dev/null +++ b/app/src/main/res/menu/profile_detail_edit.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:alphabeticShortcut="s" + android:icon="@drawable/ic_action_save" + android:onClick="onMenuSave" + android:showAsAction="always" + android:title="@string/save" /> +</menu> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cbedef43..4010e776 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,6 +3,9 @@ <string name="app_name">WireGuard</string> <string name="connected">Connected</string> <string name="disconnected">Disconnected</string> - <string name="placeholder_text">No profile selected.</string> + <string name="placeholder_text">No profile selected</string> + <string name="profile_name">Profile name</string> + <string name="public_key">Public key</string> + <string name="save">Save</string> <string name="settings">Settings</string> </resources> |