diff --git a/Makefile b/Makefile index a110a295..8cffa2a0 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ $(RS_SRCDIR)/target/%/$(RELEASE_TYPE)/libaw_server.so: $(RS_SOURCES) WEBUI_SRCDIR := aw-server-rust/aw-webui WEBUI_OUTDIR := mobile/src/main/assets/webui WEBUI_SOURCES := $(shell find $(RS_SRCDIR) -type f -name *.rs) -export ON_ANDROID := -- --android # Disable check for updates in aw-webui +export ON_ANDROID := -- --android # Build specifically for Android (disabled update check, different default views, etc) aw-webui: $(WEBUI_OUTDIR) diff --git a/aw-server-rust b/aw-server-rust index 65e28bf9..11b8ffc3 160000 --- a/aw-server-rust +++ b/aw-server-rust @@ -1 +1 @@ -Subproject commit 65e28bf9232278e75f5e85c0ca10c910d4b62a5f +Subproject commit 11b8ffc3f66e333ec29cc70bafbc84425d43ec40 diff --git a/mobile/build.gradle b/mobile/build.gradle index b6fa2c84..a9090a61 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -12,7 +12,7 @@ android { targetSdkVersion 29 // Set in CI on tagged commit - versionName "0.9-dev" + versionName "0.10-dev" // Set in CI by `bundle exec fastlane update_version` versionCode 22 diff --git a/mobile/src/main/java/net/activitywatch/android/MainActivity.kt b/mobile/src/main/java/net/activitywatch/android/MainActivity.kt index 63ac0d1f..8999580a 100644 --- a/mobile/src/main/java/net/activitywatch/android/MainActivity.kt +++ b/mobile/src/main/java/net/activitywatch/android/MainActivity.kt @@ -13,40 +13,30 @@ import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.app_bar_main.* import androidx.fragment.app.Fragment import android.util.Log -import net.activitywatch.android.fragments.Bucket -import net.activitywatch.android.fragments.BucketListFragment import net.activitywatch.android.fragments.TestFragment import net.activitywatch.android.fragments.WebUIFragment import net.activitywatch.android.watcher.UsageStatsWatcher private const val TAG = "MainActivity" +const val baseURL = "http://127.0.0.1:5600" -class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, - BucketListFragment.OnListFragmentInteractionListener, WebUIFragment.OnFragmentInteractionListener { + +class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, WebUIFragment.OnFragmentInteractionListener { val version: String get() { return packageManager.getPackageInfo(packageName, 0).versionName } - override fun onListFragmentInteraction(item: Bucket?) { - Log.w(TAG, "Bucket onInteraction listener not implemented") - } - override fun onFragmentInteraction(item: Uri) { Log.w(TAG, "URI onInteraction listener not implemented") } - override fun onAttachFragment(fragment: Fragment) { - if (fragment is BucketListFragment) { - fragment.onAttach(this) - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + setSupportActionBar(toolbar) val toggle = ActionBarDrawerToggle( @@ -55,6 +45,9 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte drawer_layout.addDrawerListener(toggle) toggle.syncState() + // Hide the top menu/title bar + supportActionBar?.hide() + nav_view.setNavigationItemSelectedListener(this) val ri = RustInterface(this) @@ -66,7 +59,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte if (savedInstanceState != null) { return } - val firstFragment = TestFragment() + val firstFragment = WebUIFragment.newInstance(baseURL) supportFragmentManager.beginTransaction() .add(R.id.fragment_container, firstFragment).commit() } @@ -111,7 +104,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte override fun onNavigationItemSelected(item: MenuItem): Boolean { var fragmentClass: Class? = null var url: String? = null - val base = "http://127.0.0.1:5600" + // Handle navigation view item clicks here. when (item.itemId) { R.id.nav_dashboard -> { @@ -119,15 +112,15 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte } R.id.nav_activity -> { fragmentClass = WebUIFragment::class.java - url = "$base/#/activity/unknown/" + url = "$baseURL/#/activity/unknown/" } R.id.nav_buckets -> { fragmentClass = WebUIFragment::class.java - url = "$base/#/buckets/" + url = "$baseURL/#/buckets/" } R.id.nav_settings -> { fragmentClass = WebUIFragment::class.java - url = "$base/#/settings/" + url = "$baseURL/#/settings/" } R.id.nav_share -> { Snackbar.make(coordinator_layout, "The share button was clicked, but it's not yet implemented!", Snackbar.LENGTH_LONG) diff --git a/mobile/src/main/java/net/activitywatch/android/fragments/BucketFragment.kt b/mobile/src/main/java/net/activitywatch/android/fragments/BucketFragment.kt deleted file mode 100644 index 7d224a67..00000000 --- a/mobile/src/main/java/net/activitywatch/android/fragments/BucketFragment.kt +++ /dev/null @@ -1,34 +0,0 @@ -package net.activitywatch.android.fragments - -import androidx.lifecycle.ViewModelProviders -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup - -import net.activitywatch.android.R -import net.activitywatch.android.models.BucketViewModel - -class BucketFragment : Fragment() { - - companion object { - fun newInstance() = BucketFragment() - } - - private lateinit var viewModel: BucketViewModel - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.bucket_fragment, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - viewModel = ViewModelProviders.of(this).get(BucketViewModel::class.java) - // TODO: Use the ViewModel - } - -} diff --git a/mobile/src/main/java/net/activitywatch/android/fragments/BucketListFragment.kt b/mobile/src/main/java/net/activitywatch/android/fragments/BucketListFragment.kt deleted file mode 100644 index 9fad7aa6..00000000 --- a/mobile/src/main/java/net/activitywatch/android/fragments/BucketListFragment.kt +++ /dev/null @@ -1,106 +0,0 @@ -package net.activitywatch.android.fragments - -import android.content.Context -import android.os.Bundle -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import net.activitywatch.android.R -import net.activitywatch.android.RustInterface -import net.activitywatch.android.models.BucketsContent -import org.json.JSONObject - - -/** - * A fragment representing a list of Items. - * Activities containing this fragment MUST implement the - * [BucketListFragment.OnListFragmentInteractionListener] interface. - */ -class BucketListFragment : Fragment() { - val TAG = "BucketListFragment" - - // TODO: Customize parameters - private var columnCount = 1 - - private var listener: OnListFragmentInteractionListener? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - arguments?.let { - columnCount = it.getInt(ARG_COLUMN_COUNT) - } - - BucketsContent.reload() - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val view = inflater.inflate(R.layout.fragment_bucket_list, container, false) - - // Set the adapter - if (view is RecyclerView) { - with(view) { - layoutManager = when { - columnCount <= 1 -> LinearLayoutManager(context) - else -> GridLayoutManager(context, columnCount) - } - adapter = - MyBucketRecyclerViewAdapter(BucketsContent.ITEMS, listener) - } - } - return view - } - - override fun onAttach(context: Context) { - super.onAttach(context) - if (context is OnListFragmentInteractionListener) { - listener = context - } else { - throw RuntimeException(context.toString() + " must implement OnListFragmentInteractionListener") - } - } - - override fun onDetach() { - super.onDetach() - listener = 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](http://developer.android.com/training/basics/fragments/communicating.html) - * for more information. - */ - interface OnListFragmentInteractionListener { - // TODO: Update argument type and name - fun onListFragmentInteraction(item: Bucket?) - } - - companion object { - - // TODO: Customize parameter argument names - const val ARG_COLUMN_COUNT = "column-count" - - // TODO: Customize parameter initialization - @JvmStatic - fun newInstance(columnCount: Int) = - BucketListFragment().apply { - arguments = Bundle().apply { - putInt(ARG_COLUMN_COUNT, columnCount) - } - } - } -} diff --git a/mobile/src/main/java/net/activitywatch/android/fragments/MyBucketRecyclerViewAdapter.kt b/mobile/src/main/java/net/activitywatch/android/fragments/MyBucketRecyclerViewAdapter.kt deleted file mode 100644 index b04d0c20..00000000 --- a/mobile/src/main/java/net/activitywatch/android/fragments/MyBucketRecyclerViewAdapter.kt +++ /dev/null @@ -1,71 +0,0 @@ -package net.activitywatch.android.fragments - -import androidx.recyclerview.widget.RecyclerView -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView - - -import net.activitywatch.android.fragments.BucketListFragment.OnListFragmentInteractionListener - -import kotlinx.android.synthetic.main.fragment_bucket.view.* -import net.activitywatch.android.R -import org.json.JSONObject -import org.threeten.bp.DateTimeUtils -import org.threeten.bp.Instant -import org.threeten.bp.temporal.ChronoUnit -import java.text.SimpleDateFormat - -typealias Bucket = JSONObject - -/** - * [RecyclerView.Adapter] that can display a [DummyItem] and makes a call to the - * specified [OnListFragmentInteractionListener]. - * TODO: Replace the implementation with code for your data type. - */ -class MyBucketRecyclerViewAdapter( - private val mValues: List, - private val mListener: OnListFragmentInteractionListener? -) : RecyclerView.Adapter() { - - private val mOnClickListener: View.OnClickListener - - init { - mOnClickListener = View.OnClickListener { v -> - val item = v.tag as Bucket - // Notify the active callbacks interface (the activity, if the fragment is attached to - // one) that an item has been selected. - mListener?.onListFragmentInteraction(item) - } - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val view = LayoutInflater.from(parent.context) - .inflate(R.layout.fragment_bucket, parent, false) - return ViewHolder(view) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val isoFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'") - val item = mValues[position] - holder.mIdView.text = item.getString("id") - holder.mContentView.text = DateTimeUtils.toInstant(isoFormatter.parse(item.getString("created"))).truncatedTo(ChronoUnit.DAYS).toString().subSequence(0, 10) - - with(holder.mView) { - tag = item - setOnClickListener(mOnClickListener) - } - } - - override fun getItemCount(): Int = mValues.size - - inner class ViewHolder(val mView: View) : RecyclerView.ViewHolder(mView) { - val mIdView: TextView = mView.item_number - val mContentView: TextView = mView.content - - override fun toString(): String { - return super.toString() + " '" + mContentView.text + "'" - } - } -} diff --git a/mobile/src/main/java/net/activitywatch/android/fragments/WebUIFragment.kt b/mobile/src/main/java/net/activitywatch/android/fragments/WebUIFragment.kt index d31816fe..504a9fe9 100644 --- a/mobile/src/main/java/net/activitywatch/android/fragments/WebUIFragment.kt +++ b/mobile/src/main/java/net/activitywatch/android/fragments/WebUIFragment.kt @@ -1,5 +1,6 @@ package net.activitywatch.android.fragments +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo @@ -10,21 +11,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.webkit.WebView -import net.activitywatch.android.AssetExtractor -import net.activitywatch.android.R -import net.activitywatch.android.RustInterface -import java.io.File -import android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE -import android.os.Build -import android.os.Build.VERSION_CODES.KITKAT -import android.os.Build.VERSION.SDK_INT import android.content.Intent.ACTION_VIEW -import android.webkit.DownloadListener +import android.util.Log +import android.webkit.WebViewClient +import net.activitywatch.android.R +private const val TAG = "WebUI" -// TODO: Rename parameter arguments, choose names that match -// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private const val ARG_URL = "url" /** @@ -38,16 +32,9 @@ private const val ARG_URL = "url" */ class WebUIFragment : Fragment() { // TODO: Rename and change types of parameters - private var url: String? = null private var listener: OnFragmentInteractionListener? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.let { - url = it.getString(ARG_URL) - } - } - + @SuppressLint("SetJavaScriptEnabled") override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -63,6 +50,23 @@ class WebUIFragment : Fragment() { val myWebView: WebView = view.findViewById(R.id.webview) as WebView + class MyWebViewClient : WebViewClient() { + override fun onReceivedError( + view: WebView, + errorCode: Int, + description: String, + failingUrl: String + ) { + // Retry + // TODO: Find way to not show the blinking Android error page + Log.e(TAG, "WebView received error: $description") + arguments?.let { + myWebView.loadUrl(it.getString(ARG_URL)) + } + } + } + myWebView.webViewClient = MyWebViewClient() + myWebView.setDownloadListener { url, _, _, _, _ -> val i = Intent(ACTION_VIEW) i.data = Uri.parse(url) @@ -71,7 +75,6 @@ class WebUIFragment : Fragment() { myWebView.settings.javaScriptEnabled = true myWebView.settings.domStorageEnabled = true - //myWebView.loadUrl("http://127.0.0.1:5600") arguments?.let { myWebView.loadUrl(it.getString(ARG_URL)) } @@ -79,11 +82,6 @@ class WebUIFragment : Fragment() { return view } - // TODO: Rename method, update argument and hook method into UI event - fun onButtonPressed(uri: Uri) { - listener?.onFragmentInteraction(uri) - } - override fun onAttach(context: Context) { super.onAttach(context) if (context is OnFragmentInteractionListener) { @@ -115,14 +113,6 @@ class WebUIFragment : Fragment() { } companion object { - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param param1 Parameter 1. - * @param param2 Parameter 2. - * @return A new instance of fragment WebUIFragment. - */ // TODO: Rename and change types and number of parameters @JvmStatic fun newInstance(url: String) = diff --git a/mobile/src/main/res/layout/bucket_fragment.xml b/mobile/src/main/res/layout/bucket_fragment.xml deleted file mode 100644 index 4e968e09..00000000 --- a/mobile/src/main/res/layout/bucket_fragment.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/mobile/src/main/res/layout/fragment_bucket.xml b/mobile/src/main/res/layout/fragment_bucket.xml deleted file mode 100644 index 1f246d4a..00000000 --- a/mobile/src/main/res/layout/fragment_bucket.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - diff --git a/mobile/src/main/res/layout/fragment_bucket_list.xml b/mobile/src/main/res/layout/fragment_bucket_list.xml deleted file mode 100644 index 2518623c..00000000 --- a/mobile/src/main/res/layout/fragment_bucket_list.xml +++ /dev/null @@ -1,12 +0,0 @@ - - -