From d865d623895dac4efb7afb710e210ccbce92d7e1 Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Wed, 1 Apr 2020 16:53:05 -0500 Subject: [PATCH 1/5] Add android fragment --- .../main/java/com/getcapacitor/Bridge.java | 21 +- .../java/com/getcapacitor/BridgeFragment.java | 198 ++++++++++++++++++ .../main/java/com/getcapacitor/Splash.java | 12 +- .../src/main/res/layout/fragment_bridge.xml | 13 ++ .../capacitor/src/main/res/values/attrs.xml | 6 + .../android/app/src/main/AndroidManifest.xml | 2 +- .../app/src/main/assets/page1/index.html | 5 + .../myapp/SimpleFragmentActivity.java | 27 +++ .../res/drawable/ic_launcher_foreground.xml | 34 +++ .../res/layout/activity_fragment_simple.xml | 17 ++ .../app/src/main/res/values/dimens.xml | 2 + .../app/src/main/res/values/strings.xml | 5 + .../app/src/main/res/values/styles.xml | 4 + 13 files changed, 342 insertions(+), 4 deletions(-) create mode 100644 android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java create mode 100644 android/capacitor/src/main/res/layout/fragment_bridge.xml create mode 100644 android/capacitor/src/main/res/values/attrs.xml create mode 100644 example/android/app/src/main/assets/page1/index.html create mode 100644 example/android/app/src/main/java/com/getcapacitor/myapp/SimpleFragmentActivity.java create mode 100644 example/android/app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 example/android/app/src/main/res/layout/activity_fragment_simple.xml diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index fd7b697cdf..b9f99dbbb2 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -861,7 +861,12 @@ public String getServerBasePath() { return this.localServer.getBasePath(); } - public void setServerBasePath(String path){ + /** + * Tell the local server to load files from the given + * file path instead of the assets path. + * @param path + */ + public void setServerBasePath(String path) { localServer.hostFiles(path); webView.post(new Runnable() { @Override @@ -871,6 +876,20 @@ public void run() { }); } + /** + * Tell the local server to load files from the given + * asset path. + * @param path + */ + public void setServerAssetPath(String path) { + localServer.hostAssets(path); + webView.post(new Runnable() { + @Override + public void run() { + webView.loadUrl(appUrl); + } + }); + } public String getLocalUrl() { return localUrl; diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java new file mode 100644 index 0000000000..37c0c58101 --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java @@ -0,0 +1,198 @@ +package com.getcapacitor; + +import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; +import android.net.Uri; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; + +import androidx.fragment.app.Fragment; + +import com.getcapacitor.android.R; +import com.getcapacitor.cordova.MockCordovaInterfaceImpl; +import com.getcapacitor.cordova.MockCordovaWebViewImpl; + +import org.apache.cordova.ConfigXmlParser; +import org.apache.cordova.CordovaPreferences; +import org.apache.cordova.PluginEntry; +import org.apache.cordova.PluginManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * A simple {@link Fragment} subclass. + * Activities that contain this fragment must implement the + * {@link BridgeFragment.OnFragmentInteractionListener} interface + * to handle interaction events. + * Use the {@link BridgeFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class BridgeFragment extends Fragment { + private static final String ARG_START_DIR = "startDir"; + + private String startDir; + + private OnFragmentInteractionListener mListener; + + private WebView webView; + protected Bridge bridge; + protected MockCordovaInterfaceImpl cordovaInterface; + protected boolean keepRunning = true; + private ArrayList pluginEntries; + private PluginManager pluginManager; + private CordovaPreferences preferences; + private MockCordovaWebViewImpl mockWebView; + private int activityDepth = 0; + private String bridgeStartDir; + + private String lastActivityPlugin; + + private List> initialPlugins = new ArrayList<>(); + + + public BridgeFragment() { + // Required empty public constructor + } + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param startDir the directory to serve content from + * @return A new instance of fragment BridgeFragment. + */ + public static BridgeFragment newInstance(String startDir) { + BridgeFragment fragment = new BridgeFragment(); + Bundle args = new Bundle(); + args.putString(ARG_START_DIR, startDir); + fragment.setArguments(args); + return fragment; + } + + protected void init(Bundle savedInstanceState) { + loadConfig(this.getActivity().getApplicationContext(), this.getActivity()); + } + + /** + * Load the WebView and create the Bridge + */ + protected void load(Bundle savedInstanceState) { + Log.d(LogUtils.getCoreTag(), "Starting BridgeActivity"); + + Bundle args = getArguments(); + String startDir = null; + + if (args != null) { + startDir = getArguments().getString(ARG_START_DIR); + } + + webView = getView().findViewById(R.id.webview); + cordovaInterface = new MockCordovaInterfaceImpl(this.getActivity()); + if (savedInstanceState != null) { + cordovaInterface.restoreInstanceState(savedInstanceState); + } + + mockWebView = new MockCordovaWebViewImpl(getActivity().getApplicationContext()); + mockWebView.init(cordovaInterface, pluginEntries, preferences, webView); + + pluginManager = mockWebView.getPluginManager(); + cordovaInterface.onCordovaInit(pluginManager); + + if (preferences == null) { + preferences = new CordovaPreferences(); + } + + bridge = new Bridge(this.getActivity(), webView, initialPlugins, cordovaInterface, pluginManager, preferences); + + if (startDir != null) { + bridge.setServerAssetPath(startDir); + } + + if (savedInstanceState != null) { + bridge.restoreInstanceState(savedInstanceState); + } + this.keepRunning = preferences.getBoolean("KeepRunning", true); + // this.onNewIntent(getIntent()); + } + + public void loadConfig(Context context, Activity activity) { + ConfigXmlParser parser = new ConfigXmlParser(); + parser.parse(context); + preferences = parser.getPreferences(); + preferences.setPreferencesBundle(activity.getIntent().getExtras()); + pluginEntries = parser.getPluginEntries(); + } + + + @Override + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { + super.onInflate(context, attrs, savedInstanceState); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.bridge_fragment); + CharSequence c = a.getString(R.styleable.bridge_fragment_start_dir); + + if (c != null) { + String startDir = c.toString(); + Bundle args = new Bundle(); + args.putString(ARG_START_DIR, startDir); + setArguments(args); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_bridge, container, false); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + this.init(savedInstanceState); + this.load(savedInstanceState); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnFragmentInteractionListener) { + mListener = (OnFragmentInteractionListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + /** + * This interface must be implemented by activities that contain this + * fragment to allow an interaction in this fragment to be communicated + * to the activity and potentially other fragments contained in that + * activity. + *

+ * See the Android Training lesson Communicating with Other Fragments for more information. + */ + public interface OnFragmentInteractionListener { + void onFragmentInteraction(Uri uri); + } +} diff --git a/android/capacitor/src/main/java/com/getcapacitor/Splash.java b/android/capacitor/src/main/java/com/getcapacitor/Splash.java index 9deb1cbc4e..f917d9e29c 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Splash.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Splash.java @@ -3,6 +3,7 @@ import android.animation.Animator; import android.app.Activity; import android.content.Context; +import android.content.res.Resources; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.PixelFormat; @@ -47,7 +48,14 @@ private static void buildViews(Context c) { String splashResourceName = Config.getString(CONFIG_KEY_PREFIX + "androidSplashResourceName", "splash"); int splashId = c.getResources().getIdentifier(splashResourceName, "drawable", c.getPackageName()); - Drawable splash = c.getResources().getDrawable(splashId, c.getTheme()); + + Drawable splash; + try { + splash = c.getResources().getDrawable(splashId, c.getTheme()); + } catch (Resources.NotFoundException ex) { + Log.w(LogUtils.getCoreTag(), "No splash screen found, not displaying"); + return; + } if (splash instanceof Animatable) { ((Animatable) splash).start(); @@ -257,7 +265,7 @@ public void run() { try { wm.addView(splashImage, params); - } catch (IllegalStateException ex) { + } catch (IllegalStateException | IllegalArgumentException ex) { Log.d(LogUtils.getCoreTag(), "Could not add splash view"); } diff --git a/android/capacitor/src/main/res/layout/fragment_bridge.xml b/android/capacitor/src/main/res/layout/fragment_bridge.xml new file mode 100644 index 0000000000..b6123ea83a --- /dev/null +++ b/android/capacitor/src/main/res/layout/fragment_bridge.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/android/capacitor/src/main/res/values/attrs.xml b/android/capacitor/src/main/res/values/attrs.xml new file mode 100644 index 0000000000..23a103710e --- /dev/null +++ b/android/capacitor/src/main/res/values/attrs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 75c05281f7..0bfbb403c7 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ package="com.getcapacitor.myapp"> diff --git a/example/android/app/src/main/assets/page1/index.html b/example/android/app/src/main/assets/page1/index.html new file mode 100644 index 0000000000..2b029a621f --- /dev/null +++ b/example/android/app/src/main/assets/page1/index.html @@ -0,0 +1,5 @@ + + +

Hello from HTML

+ + \ No newline at end of file diff --git a/example/android/app/src/main/java/com/getcapacitor/myapp/SimpleFragmentActivity.java b/example/android/app/src/main/java/com/getcapacitor/myapp/SimpleFragmentActivity.java new file mode 100644 index 0000000000..df68da5136 --- /dev/null +++ b/example/android/app/src/main/java/com/getcapacitor/myapp/SimpleFragmentActivity.java @@ -0,0 +1,27 @@ +package com.getcapacitor.myapp; + +import android.net.Uri; +import android.os.Bundle; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.viewpager.widget.ViewPager; + +import com.getcapacitor.BridgeFragment; + +public class SimpleFragmentActivity extends AppCompatActivity implements BridgeFragment.OnFragmentInteractionListener { + private TextView mTextMessage; + private ViewPager mViewPager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_fragment_simple); + } + + + + @Override + public void onFragmentInteraction(Uri uri) { + } +} diff --git a/example/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/example/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000000..84330a705f --- /dev/null +++ b/example/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/example/android/app/src/main/res/layout/activity_fragment_simple.xml b/example/android/app/src/main/res/layout/activity_fragment_simple.xml new file mode 100644 index 0000000000..cab60a4de5 --- /dev/null +++ b/example/android/app/src/main/res/layout/activity_fragment_simple.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/example/android/app/src/main/res/values/dimens.xml b/example/android/app/src/main/res/values/dimens.xml index 59a0b0c4f5..4d6ddf5548 100644 --- a/example/android/app/src/main/res/values/dimens.xml +++ b/example/android/app/src/main/res/values/dimens.xml @@ -1,3 +1,5 @@ 16dp + 16dp + 16dp diff --git a/example/android/app/src/main/res/values/strings.xml b/example/android/app/src/main/res/values/strings.xml index 02dab81839..eae1ddce37 100644 --- a/example/android/app/src/main/res/values/strings.xml +++ b/example/android/app/src/main/res/values/strings.xml @@ -2,4 +2,9 @@ MyApp MyApp com.getcapacitor.myapp + FragmentActivity + + Home + Dashboard + Notifications diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml index f50918c621..c3cae287a9 100644 --- a/example/android/app/src/main/res/values/styles.xml +++ b/example/android/app/src/main/res/values/styles.xml @@ -18,4 +18,8 @@ + +