aboutsummaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/androidTest/java/com/pitchedapps/frost/ExampleInstrumentedTest.java26
-rw-r--r--app/src/main/AndroidManifest.xml95
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt19
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt127
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt17
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FBURL.kt15
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt75
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Changelog.kt98
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/FragmentUtils.kt15
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Kotterknife.kt137
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt12
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt30
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt11
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt138
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/SwipeRefreshBase.kt31
-rw-r--r--app/src/main/res/drawable-nodpi/splash_logo.9.pngbin0 -> 13202 bytes
-rw-r--r--app/src/main/res/drawable/splash_screen.xml13
-rw-r--r--app/src/main/res/layout/activity_main.xml36
-rw-r--r--app/src/main/res/layout/changelog_content.xml27
-rw-r--r--app/src/main/res/layout/changelog_title.xml14
-rw-r--r--app/src/main/res/layout/fragment_main.xml12
-rw-r--r--app/src/main/res/layout/swipe_webview.xml15
-rw-r--r--app/src/main/res/menu/menu_main.xml15
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 2734 bytes
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin0 -> 4208 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 3454 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin0 -> 6114 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 5279 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin0 -> 10056 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 6850 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin0 -> 14696 bytes
-rw-r--r--app/src/main/res/values/colors.xml9
-rw-r--r--app/src/main/res/values/dimens.xml12
-rw-r--r--app/src/main/res/values/strings.xml11
-rw-r--r--app/src/main/res/values/styles.xml23
-rw-r--r--app/src/main/res/xml/changelog.xml11
-rw-r--r--app/src/test/java/com/pitchedapps/frost/ExampleUnitTest.java17
37 files changed, 1061 insertions, 0 deletions
diff --git a/app/src/androidTest/java/com/pitchedapps/frost/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/pitchedapps/frost/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..9e27deaf
--- /dev/null
+++ b/app/src/androidTest/java/com/pitchedapps/frost/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.pitchedapps.frost;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.pitchedapps.frost", appContext.getPackageName());
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..74428503
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.pitchedapps.frost">
+
+ <!-- To auto-complete the email text field in the login form with the user's emails -->
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.READ_PROFILE" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.USE_FINGERPRINT" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity
+ android:name=".StartActivity"
+ android:configChanges="orientation|screenSize|locale"
+ android:label="@string/app_name"
+ android:theme="@style/Theme.Splash">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".MainActivity"
+ android:configChanges="orientation|screenSize|locale"
+ android:hardwareAccelerated="true"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data
+ android:host="m.facebook.com"
+ android:scheme="http" />
+ <data
+ android:host="m.facebook.com"
+ android:scheme="https" />
+ <data
+ android:host="mobile.facebook.com"
+ android:scheme="http" />
+ <data
+ android:host="mobile.facebook.com"
+ android:scheme="https" />
+ <data
+ android:host="touch.facebook.com"
+ android:scheme="http" />
+ <data
+ android:host="touch.facebook.com"
+ android:scheme="https" />
+ <data
+ android:host="fb.com"
+ android:scheme="http" />
+ <data
+ android:host="fb.com"
+ android:scheme="https" />
+ <data
+ android:host="fb.me"
+ android:scheme="http" />
+ <data
+ android:host="fb.me"
+ android:scheme="https" />
+ <data
+ android:host="facebook.com"
+ android:scheme="http" />
+ <data
+ android:host="facebook.com"
+ android:scheme="https" />
+ <data
+ android:host="www.facebook.com"
+ android:scheme="http" />
+ <data
+ android:host="www.facebook.com"
+ android:scheme="https" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
new file mode 100644
index 00000000..4e287ee7
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
@@ -0,0 +1,19 @@
+package com.pitchedapps.frost
+
+import android.app.Application
+import com.pitchedapps.frost.utils.Prefs
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+class FrostApp : Application() {
+
+ companion object {
+ lateinit var prefs: Prefs
+ }
+
+ override fun onCreate() {
+ prefs = Prefs(applicationContext)
+ super.onCreate()
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
new file mode 100644
index 00000000..4922a292
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
@@ -0,0 +1,127 @@
+package com.pitchedapps.frost
+
+import android.os.Bundle
+import android.support.design.widget.FloatingActionButton
+import android.support.design.widget.Snackbar
+import android.support.design.widget.TabLayout
+import android.support.v4.app.Fragment
+import android.support.v4.app.FragmentManager
+import android.support.v4.app.FragmentPagerAdapter
+import android.support.v4.view.ViewPager
+import android.support.v7.app.AppCompatActivity
+import android.support.v7.widget.Toolbar
+import android.view.*
+import android.widget.TextView
+import butterknife.ButterKnife
+import com.pitchedapps.frost.fragments.WebFragment
+import com.pitchedapps.frost.utils.Changelog
+import com.pitchedapps.frost.utils.bindView
+
+class MainActivity : AppCompatActivity() {
+
+ private var mSectionsPagerAdapter: SectionsPagerAdapter? = null
+ val toolbar: Toolbar by bindView(R.id.toolbar)
+ val viewPager: ViewPager by bindView(R.id.container)
+ val fab: FloatingActionButton by bindView(R.id.fab)
+ val tabs: TabLayout by bindView(R.id.tabs)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ ButterKnife.bind(this)
+ setSupportActionBar(toolbar)
+ // Create the adapter that will return a fragment for each of the three
+ // primary sections of the activity.
+ mSectionsPagerAdapter = SectionsPagerAdapter(supportFragmentManager)
+
+ // Set up the ViewPager with the sections adapter.
+ viewPager.adapter = mSectionsPagerAdapter
+ viewPager.offscreenPageLimit = 5
+ tabs.setupWithViewPager(viewPager)
+
+ fab.setOnClickListener { view ->
+ Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show()
+ }
+
+ }
+
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ menuInflater.inflate(R.menu.menu_main, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ when (item.itemId) {
+ R.id.action_settings -> return true
+ R.id.action_changelog -> Changelog.show(this)
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ /**
+ * A placeholder fragment containing a simple view.
+ */
+ class PlaceholderFragment : Fragment() {
+
+ override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
+ savedInstanceState: Bundle?): View? {
+ val rootView = inflater!!.inflate(R.layout.fragment_main, container, false)
+ (rootView.findViewById(R.id.section_label) as TextView).text = getString(R.string.section_format, arguments.getInt(ARG_SECTION_NUMBER))
+ return rootView
+ }
+
+ companion object {
+ /**
+ * The fragment argument representing the section number for this
+ * fragment.
+ */
+ private val ARG_SECTION_NUMBER = "section_number"
+
+ /**
+ * Returns a new instance of this fragment for the given section
+ * number.
+ */
+ fun newInstance(sectionNumber: Int): PlaceholderFragment {
+ val fragment = PlaceholderFragment()
+ val args = Bundle()
+ args.putInt(ARG_SECTION_NUMBER, sectionNumber)
+ fragment.arguments = args
+ return fragment
+ }
+ }
+ }
+
+ /**
+ * A [FragmentPagerAdapter] that returns a fragment corresponding to
+ * one of the sections/tabs/pages.
+ */
+ inner class SectionsPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
+
+ override fun getItem(position: Int): Fragment {
+ // getItem is called to instantiate the fragment for the given page.
+ // Return a PlaceholderFragment (defined as a static inner class below).
+ return WebFragment.newInstance()
+ }
+
+ override fun getCount(): Int {
+ // Show 3 total pages.
+ return 3
+ }
+
+ override fun getPageTitle(position: Int): CharSequence? {
+ when (position) {
+ 0 -> return "SECTION 1"
+ 1 -> return "SECTION 2"
+ 2 -> return "SECTION 3"
+ }
+ return null
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
new file mode 100644
index 00000000..e8c65be3
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt
@@ -0,0 +1,17 @@
+package com.pitchedapps.frost
+
+import android.content.Intent
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+class StartActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ startActivity(Intent(this, MainActivity::class.java))
+ finish()
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FBURL.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FBURL.kt
new file mode 100644
index 00000000..489f12a7
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FBURL.kt
@@ -0,0 +1,15 @@
+package com.pitchedapps.frost.facebook
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ */
+enum class FBURL(val url: String) {
+ FEED("https://touch.facebook.com/"),
+ PROFILE("https://touch.facebook.com/me/"),
+ BOOKMARKS("https://touch.facebook.com/bookmarks"),
+ SEARCH("https://touch.facebook.com/search"),
+ EVENTS("https://touch.facebook.com/events/upcoming"),
+ FRIEND_REQUESTS("https://touch.facebook.com/requests"),
+ MESSAGES("https://touch.facebook.com/messages"),
+ NOTIFICATIONS("https://touch.facebook.com/notifications");
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt
new file mode 100644
index 00000000..140d8df1
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt
@@ -0,0 +1,75 @@
+package com.pitchedapps.frost.fragments
+
+import android.os.Bundle
+import android.support.v4.app.Fragment
+import android.support.v4.widget.SwipeRefreshLayout
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import butterknife.ButterKnife
+import butterknife.Unbinder
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.facebook.FBURL
+import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.bindView
+import com.pitchedapps.frost.utils.withBundle
+import com.pitchedapps.frost.views.FrostWebView
+import com.pitchedapps.frost.views.SwipeRefreshBase
+import com.pitchedapps.frost.views.WebStatus
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ */
+
+
+class WebFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
+
+ override fun onRefresh() {
+ web.reload()
+ }
+
+ companion object {
+ private val ARG_URL = "arg_url"
+ fun newInstance(url: String) = WebFragment().withBundle { b -> b.putString(ARG_URL, url) }
+ fun newInstance(url: FBURL = FBURL.FEED) = newInstance(url.url)
+ }
+
+ val refresh: SwipeRefreshBase by bindView(R.id.swipe_refresh)
+ val web: FrostWebView by bindView(R.id.frost_webview)
+ lateinit var url: String
+ private lateinit var unbinder: Unbinder
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ url = arguments.getString(ARG_URL)
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ super.onCreateView(inflater, container, savedInstanceState)
+ val view = inflater.inflate(R.layout.swipe_webview, container, false)
+ unbinder = ButterKnife.bind(this, view)
+ return view
+ }
+
+ override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ web.observable.subscribe {
+ t: WebStatus ->
+ when (t) {
+ WebStatus.LOADED, WebStatus.ERROR -> refresh.isRefreshing = false
+ WebStatus.LOADING -> refresh.isRefreshing = true
+ }
+ }
+ refresh.setOnRefreshListener(this)
+ refresh.shouldSwipe = {
+ L.e("Y ${web.scrollY}")
+ SwipeRefreshBase.shouldScroll(web)
+ }
+ web.loadUrl(url)
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ unbinder.unbind()
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Changelog.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Changelog.kt
new file mode 100644
index 00000000..14a095a1
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Changelog.kt
@@ -0,0 +1,98 @@
+package com.pitchedapps.frost.utils
+
+import android.content.Context
+import android.content.res.XmlResourceParser
+import android.os.Handler
+import android.support.annotation.LayoutRes
+import android.support.annotation.NonNull
+import android.support.annotation.XmlRes
+import android.support.v4.app.FragmentActivity
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import com.afollestad.materialdialogs.MaterialDialog
+import com.pitchedapps.frost.R
+import org.xmlpull.v1.XmlPullParser
+import java.util.*
+
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+class Changelog {
+ companion object {
+ fun show(@NonNull activity: FragmentActivity, @XmlRes xmlRes: Int = R.xml.changelog) {
+ val mHandler = Handler()
+ Thread(Runnable {
+ val items = parse(activity, xmlRes)
+ mHandler.post(object : TimerTask() {
+ override fun run() {
+ MaterialDialog.Builder(activity)
+ .title(R.string.changelog)
+ .positiveText(R.string.great)
+ .adapter(ChangelogAdapter(items), null)
+ .show()
+ }
+ })
+ }).start()
+ }
+ }
+}
+
+private class ChangelogAdapter(val items: List<Pair<String, ChangelogType>>) : RecyclerView.Adapter<ChangelogAdapter.ChangelogVH>() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ChangelogVH(LayoutInflater.from(parent.context)
+ .inflate(getLayout(viewType), parent, false))
+
+ private fun getLayout(position: Int) = items[position].second.layout
+
+ override fun onBindViewHolder(holder: ChangelogVH, position: Int) {
+ holder.text.text = items[position].first
+ }
+
+ override fun getItemId(position: Int) = position.toLong()
+
+ override fun getItemViewType(position: Int) = position
+
+ override fun getItemCount() = items.size
+
+ internal class ChangelogVH(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val text: TextView = itemView.findViewById(R.id.changelog_text) as TextView
+ }
+}
+
+private fun parse(context: Context, @XmlRes xmlRes: Int): List<Pair<String, ChangelogType>> {
+ val items = mutableListOf<Pair<String, ChangelogType>>()
+ context.resources.getXml(xmlRes).use {
+ parser ->
+ var eventType = parser.eventType
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG)
+ ChangelogType.values.any { it.add(parser, items) }
+ eventType = parser.next()
+ }
+ }
+ return items
+}
+
+private enum class ChangelogType(val tag: String, val attr: String, @LayoutRes val layout: Int) {
+ TITLE("title", "version", R.layout.changelog_title),
+ ITEM("item", "text", R.layout.changelog_content);
+
+ companion object {
+ val values = values()
+ }
+
+ /**
+ * Returns true if tag matches; false otherwise
+ */
+ fun add(parser: XmlResourceParser, list: MutableList<Pair<String, ChangelogType>>): Boolean {
+ if (parser.name != tag) return false
+ if (parser.getAttributeValue(null, attr).isNotBlank())
+ list.add(Pair(parser.getAttributeValue(null, attr), this))
+ return true
+ }
+}
+
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/FragmentUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/FragmentUtils.kt
new file mode 100644
index 00000000..cd638068
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/FragmentUtils.kt
@@ -0,0 +1,15 @@
+package com.pitchedapps.frost.utils
+
+import android.os.Bundle
+import android.support.v4.app.Fragment
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ */
+
+fun Fragment.withBundle(creator: (Bundle) -> Unit): Fragment {
+ val bundle = Bundle()
+ creator.invoke(bundle)
+ this.arguments = bundle
+ return this
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Kotterknife.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Kotterknife.kt
new file mode 100644
index 00000000..6e3b5c24
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Kotterknife.kt
@@ -0,0 +1,137 @@
+package com.pitchedapps.frost.utils
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ *
+ * Courtesy of Jake Wharton
+ *
+ * https://github.com/JakeWharton/kotterknife/blob/master/src/main/kotlin/kotterknife/ButterKnife.kt
+ */
+import android.app.Activity
+import android.app.Dialog
+import android.app.DialogFragment
+import android.app.Fragment
+import android.support.v7.widget.RecyclerView.ViewHolder
+import android.view.View
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+import android.support.v4.app.DialogFragment as SupportDialogFragment
+import android.support.v4.app.Fragment as SupportFragment
+
+public fun <V : View> View.bindView(id: Int)
+ : ReadOnlyProperty<View, V> = required(id, viewFinder)
+public fun <V : View> Activity.bindView(id: Int)
+ : ReadOnlyProperty<Activity, V> = required(id, viewFinder)
+public fun <V : View> Dialog.bindView(id: Int)
+ : ReadOnlyProperty<Dialog, V> = required(id, viewFinder)
+public fun <V : View> DialogFragment.bindView(id: Int)
+ : ReadOnlyProperty<DialogFragment, V> = required(id, viewFinder)
+public fun <V : View> SupportDialogFragment.bindView(id: Int)
+ : ReadOnlyProperty<SupportDialogFragment, V> = required(id, viewFinder)
+public fun <V : View> Fragment.bindView(id: Int)
+ : ReadOnlyProperty<Fragment, V> = required(id, viewFinder)
+public fun <V : View> SupportFragment.bindView(id: Int)
+ : ReadOnlyProperty<SupportFragment, V> = required(id, viewFinder)
+public fun <V : View> ViewHolder.bindView(id: Int)
+ : ReadOnlyProperty<ViewHolder, V> = required(id, viewFinder)
+
+public fun <V : View> View.bindOptionalView(id: Int)
+ : ReadOnlyProperty<View, V?> = optional(id, viewFinder)
+public fun <V : View> Activity.bindOptionalView(id: Int)
+ : ReadOnlyProperty<Activity, V?> = optional(id, viewFinder)
+public fun <V : View> Dialog.bindOptionalView(id: Int)
+ : ReadOnlyProperty<Dialog, V?> = optional(id, viewFinder)
+public fun <V : View> DialogFragment.bindOptionalView(id: Int)
+ : ReadOnlyProperty<DialogFragment, V?> = optional(id, viewFinder)
+public fun <V : View> SupportDialogFragment.bindOptionalView(id: Int)
+ : ReadOnlyProperty<SupportDialogFragment, V?> = optional(id, viewFinder)
+public fun <V : View> Fragment.bindOptionalView(id: Int)
+ : ReadOnlyProperty<Fragment, V?> = optional(id, viewFinder)
+public fun <V : View> SupportFragment.bindOptionalView(id: Int)
+ : ReadOnlyProperty<SupportFragment, V?> = optional(id, viewFinder)
+public fun <V : View> ViewHolder.bindOptionalView(id: Int)
+ : ReadOnlyProperty<ViewHolder, V?> = optional(id, viewFinder)
+
+public fun <V : View> View.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<View, List<V>> = required(ids, viewFinder)
+public fun <V : View> Activity.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<Activity, List<V>> = required(ids, viewFinder)
+public fun <V : View> Dialog.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<Dialog, List<V>> = required(ids, viewFinder)
+public fun <V : View> DialogFragment.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<DialogFragment, List<V>> = required(ids, viewFinder)
+public fun <V : View> SupportDialogFragment.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<SupportDialogFragment, List<V>> = required(ids, viewFinder)
+public fun <V : View> Fragment.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<Fragment, List<V>> = required(ids, viewFinder)
+public fun <V : View> SupportFragment.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<SupportFragment, List<V>> = required(ids, viewFinder)
+public fun <V : View> ViewHolder.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<ViewHolder, List<V>> = required(ids, viewFinder)
+
+public fun <V : View> View.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<View, List<V>> = optional(ids, viewFinder)
+public fun <V : View> Activity.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<Activity, List<V>> = optional(ids, viewFinder)
+public fun <V : View> Dialog.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<Dialog, List<V>> = optional(ids, viewFinder)
+public fun <V : View> DialogFragment.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<DialogFragment, List<V>> = optional(ids, viewFinder)
+public fun <V : View> SupportDialogFragment.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<SupportDialogFragment, List<V>> = optional(ids, viewFinder)
+public fun <V : View> Fragment.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<Fragment, List<V>> = optional(ids, viewFinder)
+public fun <V : View> SupportFragment.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<SupportFragment, List<V>> = optional(ids, viewFinder)
+public fun <V : View> ViewHolder.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<ViewHolder, List<V>> = optional(ids, viewFinder)
+
+private val View.viewFinder: View.(Int) -> View?
+ get() = { findViewById(it) }
+private val Activity.viewFinder: Activity.(Int) -> View?
+ get() = { findViewById(it) }
+private val Dialog.viewFinder: Dialog.(Int) -> View?
+ get() = { findViewById(it) }
+private val DialogFragment.viewFinder: DialogFragment.(Int) -> View?
+ get() = { dialog.findViewById(it) }
+private val SupportDialogFragment.viewFinder: SupportDialogFragment.(Int) -> View?
+ get() = { dialog.findViewById(it) }
+private val Fragment.viewFinder: Fragment.(Int) -> View?
+ get() = { view.findViewById(it) }
+private val SupportFragment.viewFinder: SupportFragment.(Int) -> View?
+ get() = { view!!.findViewById(it) }
+private val ViewHolder.viewFinder: ViewHolder.(Int) -> View?
+ get() = { itemView.findViewById(it) }
+
+private fun viewNotFound(id:Int, desc: KProperty<*>): Nothing =
+ throw IllegalStateException("View ID $id for '${desc.name}' not found.")
+
+@Suppress("UNCHECKED_CAST")
+private fun <T, V : View> required(id: Int, finder: T.(Int) -> View?)
+ = Lazy { t: T, desc -> t.finder(id) as V? ?: viewNotFound(id, desc) }
+
+@Suppress("UNCHECKED_CAST")
+private fun <T, V : View> optional(id: Int, finder: T.(Int) -> View?)
+ = Lazy { t: T, desc -> t.finder(id) as V? }
+
+@Suppress("UNCHECKED_CAST")
+private fun <T, V : View> required(ids: IntArray, finder: T.(Int) -> View?)
+ = Lazy { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } }
+
+@Suppress("UNCHECKED_CAST")
+private fun <T, V : View> optional(ids: IntArray, finder: T.(Int) -> View?)
+ = Lazy { t: T, desc -> ids.map { t.finder(it) as V? }.filterNotNull() }
+
+// Like Kotlin's lazy delegate but the initializer gets the target and metadata passed to it
+private class Lazy<T, V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> {
+ private object EMPTY
+ private var value: Any? = EMPTY
+
+ override fun getValue(thisRef: T, property: KProperty<*>): V {
+ if (value == EMPTY) {
+ value = initializer(thisRef, property)
+ }
+ @Suppress("UNCHECKED_CAST")
+ return value as V
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt
new file mode 100644
index 00000000..279d595e
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt
@@ -0,0 +1,12 @@
+package com.pitchedapps.frost.utils
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+class L {
+ companion object {
+ val TAG = "Frost"
+ fun e(s: String) = android.util.Log.e(com.pitchedapps.frost.utils.L.Companion.TAG, s)
+ fun d(s: String) = android.util.Log.d(com.pitchedapps.frost.utils.L.Companion.TAG, s)
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
new file mode 100644
index 00000000..c98fd5a5
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
@@ -0,0 +1,30 @@
+package com.pitchedapps.frost.utils
+
+import com.pitchedapps.frost.FrostApp
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+val prefs: Prefs by lazy { FrostApp.prefs }
+
+class Prefs(c: android.content.Context) {
+ private companion object {
+ val PREFERENCE_NAME = "${com.pitchedapps.frost.BuildConfig.APPLICATION_ID}.prefs"
+ val LAST_ACTIVE = "last_active"
+ }
+
+ var lastActive: Long
+ get() = prefs.getLong(com.pitchedapps.frost.utils.Prefs.Companion.LAST_ACTIVE, -1)
+ set(value) = set(com.pitchedapps.frost.utils.Prefs.Companion.LAST_ACTIVE, System.currentTimeMillis())
+
+ init {
+ lastActive = 0
+ }
+
+ private val prefs: android.content.SharedPreferences by lazy { c.getSharedPreferences(com.pitchedapps.frost.utils.Prefs.Companion.PREFERENCE_NAME, android.content.Context.MODE_PRIVATE) }
+
+ private fun set(key: String, value: Boolean) = prefs.edit().putBoolean(key, value).apply()
+ private fun set(key: String, value: Int) = prefs.edit().putInt(key, value).apply()
+ private fun set(key: String, value: Long) = prefs.edit().putLong(key, value).apply()
+ private fun set(key: String, value: String) = prefs.edit().putString(key, value).apply()
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
new file mode 100644
index 00000000..bbf0e1f0
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
@@ -0,0 +1,11 @@
+package com.pitchedapps.frost.utils
+
+import android.content.res.Resources
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+object Utils {
+ fun dpToPx(dp: Int) = (dp * android.content.res.Resources.getSystem().displayMetrics.density).toInt()
+ fun pxToDp(px:Int) = (px / android.content.res.Resources.getSystem().displayMetrics.density).toInt()
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
new file mode 100644
index 00000000..e7314c20
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
@@ -0,0 +1,138 @@
+package com.pitchedapps.frost.views
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Bitmap
+import android.support.v4.view.MotionEventCompat
+import android.support.v4.view.NestedScrollingChild
+import android.support.v4.view.NestedScrollingChildHelper
+import android.support.v4.view.ViewCompat
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import android.webkit.WebResourceError
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import com.pitchedapps.frost.utils.L
+import io.reactivex.subjects.BehaviorSubject
+import io.reactivex.subjects.Subject
+
+enum class WebStatus {
+ LOADING, LOADED, ERROR
+}
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ *
+ * Courtesy of takahirom
+ *
+ * https://github.com/takahirom/webview-in-coordinatorlayout/blob/master/app/src/main/java/com/github/takahirom/webview_in_coodinator_layout/NestedWebView.java
+ */
+class FrostWebView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : WebView(context, attrs, defStyleAttr), NestedScrollingChild {
+
+ private val childHelper = NestedScrollingChildHelper(this)
+ private var lastY: Int = 0
+ private val scrollOffset = IntArray(2)
+ private val scrollConsumed = IntArray(2)
+ private var nestedOffsetY: Int = 0
+ val observable: Subject<WebStatus>
+
+ init {
+ isNestedScrollingEnabled = true
+ observable = BehaviorSubject.create<WebStatus>()
+ setupWebview()
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ fun setupWebview() {
+ settings.javaScriptEnabled = true
+ setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ setWebViewClient(object : WebViewClient() {
+ override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
+ super.onReceivedError(view, request, error)
+ observable.onNext(WebStatus.ERROR)
+ L.e("Error ${request}")
+ }
+
+ override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
+ super.onPageStarted(view, url, favicon)
+ observable.onNext(WebStatus.LOADING)
+ }
+
+ override fun onPageFinished(view: WebView?, url: String?) {
+ super.onPageFinished(view, url)
+ observable.onNext(WebStatus.LOADED)
+// CookieManager.getInstance().flush()
+ }
+ })
+ }
+
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
+ val event = MotionEvent.obtain(ev)
+ val action = MotionEventCompat.getActionMasked(event)
+ if (action == MotionEvent.ACTION_DOWN)
+ nestedOffsetY = 0
+ val eventY = event.y.toInt()
+ event.offsetLocation(0f, nestedOffsetY.toFloat())
+ val returnValue: Boolean
+ when (action) {
+ MotionEvent.ACTION_MOVE -> {
+ var deltaY = lastY - eventY
+ // NestedPreScroll
+ if (dispatchNestedPreScroll(0, deltaY, scrollConsumed, scrollOffset)) {
+ deltaY -= scrollConsumed[1]
+ event.offsetLocation(0f, -scrollOffset[1].toFloat())
+ nestedOffsetY += scrollOffset[1]
+ }
+ lastY = eventY - scrollOffset[1]
+ returnValue = super.onTouchEvent(event)
+ // NestedScroll
+ if (dispatchNestedScroll(0, scrollOffset[1], 0, deltaY, scrollOffset)) {
+ event.offsetLocation(0f, scrollOffset[1].toFloat())
+ nestedOffsetY += scrollOffset[1]
+ lastY -= scrollOffset[1]
+ }
+ }
+ MotionEvent.ACTION_DOWN -> {
+ returnValue = super.onTouchEvent(event)
+ lastY = eventY
+ startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ returnValue = super.onTouchEvent(event)
+ stopNestedScroll()
+ }
+ else -> return false
+ }
+ return returnValue
+ }
+
+ // Nested Scroll implements
+ override fun setNestedScrollingEnabled(enabled: Boolean) {
+ childHelper.isNestedScrollingEnabled = enabled
+ }
+
+ override fun isNestedScrollingEnabled() = childHelper.isNestedScrollingEnabled
+
+ override fun startNestedScroll(axes: Int) = childHelper.startNestedScroll(axes)
+
+ override fun stopNestedScroll() = childHelper.stopNestedScroll()
+
+ override fun hasNestedScrollingParent() = childHelper.hasNestedScrollingParent()
+
+ override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?)
+ = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow)
+
+ override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?)
+ = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
+
+ override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean)
+ = childHelper.dispatchNestedFling(velocityX, velocityY, consumed)
+
+ override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float)
+ = childHelper.dispatchNestedPreFling(velocityX, velocityY)
+
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/SwipeRefreshBase.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/SwipeRefreshBase.kt
new file mode 100644
index 00000000..59d42722
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/SwipeRefreshBase.kt
@@ -0,0 +1,31 @@
+package com.pitchedapps.frost.views
+
+import android.content.Context
+import android.support.v4.widget.SwipeRefreshLayout
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.webkit.WebView
+import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.Utils
+
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+class SwipeRefreshBase @JvmOverloads constructor(
+ context: Context?, attrs: AttributeSet? = null
+) : SwipeRefreshLayout(context, attrs) {
+
+ lateinit var shouldSwipe: (ev: MotionEvent) -> Boolean
+
+ companion object {
+ private val SCROLL_BUFFER by lazy { Utils.dpToPx(5) }
+ fun shouldScroll(webview: WebView) = webview.scrollY <= SCROLL_BUFFER
+ }
+
+// override fun onInterceptTouchEvent(ev: MotionEvent):Boolean {
+// val b = shouldSwipe.invoke(ev) && super.onInterceptTouchEvent(ev)
+// L.e("Should swipe $b")
+// return b
+// }
+} \ No newline at end of file
diff --git a/app/src/main/res/drawable-nodpi/splash_logo.9.png b/app/src/main/res/drawable-nodpi/splash_logo.9.png
new file mode 100644
index 00000000..9c012d1d
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/splash_logo.9.png
Binary files differ
diff --git a/app/src/main/res/drawable/splash_screen.xml b/app/src/main/res/drawable/splash_screen.xml
new file mode 100644
index 00000000..49085412
--- /dev/null
+++ b/app/src/main/res/drawable/splash_screen.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:opacity="opaque">
+ <!-- The background color, preferably the same as your normal theme -->
+ <item android:drawable="@color/splashBackground"/>
+ <!-- Your product logo - 288px x 288px color version of your app icon -->
+ <item android:bottom="@dimen/splash_logo">
+ <bitmap
+ android:gravity="center"
+ android:src="@drawable/splash_logo"/>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..5ee18cf0
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main_content"
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:fitsSystemWindows="true" tools:context=".MainActivity">
+
+ <android.support.design.widget.AppBarLayout android:id="@+id/appbar"
+ android:layout_width="match_parent" android:layout_height="wrap_content"
+ android:paddingTop="@dimen/appbar_padding_top"
+ android:theme="@style/AppTheme.AppBarOverlay">
+
+ <android.support.v7.widget.Toolbar android:id="@+id/toolbar"
+ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary" app:layout_scrollFlags="scroll|enterAlways"
+ app:popupTheme="@style/AppTheme.PopupOverlay">
+
+ </android.support.v7.widget.Toolbar>
+
+ <android.support.design.widget.TabLayout
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </android.support.design.widget.AppBarLayout>
+
+ <android.support.v4.view.ViewPager android:id="@+id/container"
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+ <android.support.design.widget.FloatingActionButton android:id="@+id/fab"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:layout_gravity="end|bottom" android:layout_margin="@dimen/fab_margin"
+ app:srcCompat="@android:drawable/ic_dialog_email" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/changelog_content.xml b/app/src/main/res/layout/changelog_content.xml
new file mode 100644
index 00000000..e4084796
--- /dev/null
+++ b/app/src/main/res/layout/changelog_content.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="8.4sp"
+ android:paddingLeft="@dimen/dialog_margin"
+ android:paddingRight="@dimen/dialog_margin">
+
+ <!--padding bottom is 14sp * 0.6-->
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:lineSpacingMultiplier="1.6"
+ android:paddingRight="5dp"
+ android:text="@string/u2022"
+ android:textAppearance="@style/TextAppearance.AppCompat.Small" />
+
+ <TextView
+ android:id="@+id/changelog_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lineSpacingMultiplier="1.6"
+ android:textAppearance="@style/TextAppearance.AppCompat.Small" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/changelog_title.xml b/app/src/main/res/layout/changelog_title.xml
new file mode 100644
index 00000000..d00e54f1
--- /dev/null
+++ b/app/src/main/res/layout/changelog_title.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/changelog_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lineSpacingMultiplier="1.6"
+ android:paddingBottom="@dimen/dialog_margin_bottom"
+ android:paddingLeft="@dimen/dialog_margin"
+ android:paddingRight="@dimen/dialog_margin"
+ android:paddingTop="@dimen/dialog_margin_bottom"
+ android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+ android:textStyle="bold" />
+
diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml
new file mode 100644
index 00000000..6d4a0fab
--- /dev/null
+++ b/app/src/main/res/layout/fragment_main.xml
@@ -0,0 +1,12 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+ android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context="com.pitchedapps.frost.MainActivity$PlaceholderFragment">
+
+ <TextView android:id="@+id/section_label" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/swipe_webview.xml b/app/src/main/res/layout/swipe_webview.xml
new file mode 100644
index 00000000..87df67b2
--- /dev/null
+++ b/app/src/main/res/layout/swipe_webview.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.pitchedapps.frost.views.SwipeRefreshBase xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/swipe_refresh"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.pitchedapps.frost.views.FrostWebView
+ android:id="@+id/frost_webview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+</com.pitchedapps.frost.views.SwipeRefreshBase>
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 00000000..b4140da4
--- /dev/null
+++ b/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,15 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="com.pitchedapps.myapplication.MainActivity">
+ <item
+ android:id="@+id/action_settings"
+ android:orderInCategory="100"
+ android:title="@string/action_settings"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/action_changelog"
+ android:orderInCategory="200"
+ android:title="@string/changelog"
+ app:showAsAction="never" />
+</menu>
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..88b0e234
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9a078e3e
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..0e1a37d5
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..3af2608a
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..e0b0af6b
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9bec2e62
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..115ce95a
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..34947cd6
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..2215743c
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+ <color name="splashBackground">@color/facebook_blue</color>
+ <color name="facebook_blue">#3b5998</color>
+ <color name="facebook_blue_dark">#2e4b86</color>
+</resources>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..06c8ca73
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,12 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+ <dimen name="dialog_margin">24dp</dimen>
+ <dimen name="dialog_margin_bottom">16dp</dimen>
+
+ <dimen name="fab_margin">16dp</dimen>
+ <dimen name="appbar_padding_top">8dp</dimen>
+ <dimen name="splash_logo">16dp</dimen>
+
+</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..4142ff6b
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,11 @@
+<resources>
+ <string name="app_name">Frost for Facebook</string>
+
+ <string name="action_settings">Settings</string>
+ <string name="section_format">Hello World from section: %1$d</string>
+
+
+ <string name="changelog">Changelog</string>
+ <string name="great">Great</string>
+ <string name="u2022">•</string>
+</resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..c04a9609
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,23 @@
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+ </style>
+ <style name="AppTheme.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ </style>
+ <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+ <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+ <style name="Theme.Splash" parent="AppTheme">
+ <item name="android:windowBackground">@drawable/splash_screen</item>
+ <item name="android:navigationBarColor">@color/splashBackground</item>
+ <item name="colorPrimaryDark">@color/splashBackground</item>
+ <item name="colorAccent">@color/splashBackground</item>
+ </style>
+</resources>
diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml
new file mode 100644
index 00000000..58c3fd1d
--- /dev/null
+++ b/app/src/main/res/xml/changelog.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!--
+ <version title="v"/>
+ <item text="" />
+ -->
+
+ <version title="v0.1" />
+ <item text="Initial" />
+</resources> \ No newline at end of file
diff --git a/app/src/test/java/com/pitchedapps/frost/ExampleUnitTest.java b/app/src/test/java/com/pitchedapps/frost/ExampleUnitTest.java
new file mode 100644
index 00000000..a702863a
--- /dev/null
+++ b/app/src/test/java/com/pitchedapps/frost/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.pitchedapps.frost;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+} \ No newline at end of file