diff options
Diffstat (limited to 'app/src/main/kotlin')
13 files changed, 261 insertions, 93 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index 4e287ee7..e050f285 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -1,7 +1,12 @@ package com.pitchedapps.frost import android.app.Application +import com.pitchedapps.frost.utils.CrashReportingTree import com.pitchedapps.frost.utils.Prefs +import io.realm.Realm +import timber.log.Timber +import timber.log.Timber.DebugTree + /** * Created by Allan Wang on 2017-05-28. @@ -13,7 +18,11 @@ class FrostApp : Application() { } override fun onCreate() { + if (BuildConfig.DEBUG) Timber.plant(DebugTree()) + else Timber.plant(CrashReportingTree()) + prefs = Prefs(applicationContext) + Realm.init(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 index 4922a292..0db5ee72 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt @@ -4,22 +4,30 @@ 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 android.view.Menu +import android.view.MenuItem import butterknife.ButterKnife +import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.loadFbTab +import com.pitchedapps.frost.fragments.BaseFragment import com.pitchedapps.frost.fragments.WebFragment import com.pitchedapps.frost.utils.Changelog +import com.pitchedapps.frost.utils.KeyPairObservable import com.pitchedapps.frost.utils.bindView +import com.pitchedapps.frost.utils.toDrawable +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.Subject -class MainActivity : AppCompatActivity() { +class MainActivity : AppCompatActivity(), KeyPairObservable { - private var mSectionsPagerAdapter: SectionsPagerAdapter? = null + override val observable: Subject<Pair<Int, Int>> = PublishSubject.create<Pair<Int, Int>>() + + lateinit var adapter: SectionsPagerAdapter val toolbar: Toolbar by bindView(R.id.toolbar) val viewPager: ViewPager by bindView(R.id.container) val fab: FloatingActionButton by bindView(R.id.fab) @@ -30,20 +38,23 @@ class MainActivity : AppCompatActivity() { 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 + adapter = SectionsPagerAdapter(supportFragmentManager, loadFbTab(this@MainActivity)) + viewPager.adapter = adapter viewPager.offscreenPageLimit = 5 - tabs.setupWithViewPager(viewPager) - + setupTabs() fab.setOnClickListener { view -> Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show() } + } + + fun setupTabs() { + viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs)) + tabs.addOnTabSelectedListener(TabLayout.ViewPagerOnTabSelectedListener(viewPager)) +// tabs.setupWithViewPager(viewPager) + adapter.pages.forEach { tabs.addTab(tabs.newTab().setIcon(it.icon.toDrawable(this))) } } @@ -65,63 +76,24 @@ class MainActivity : AppCompatActivity() { 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 - } - } + override fun onBackPressed() { + if (currentFragment.onBackPressed()) return + super.onBackPressed() } + val currentFragment: BaseFragment + get() = supportFragmentManager.findFragmentByTag("android:switcher:${R.id.container}:${viewPager.currentItem}") as BaseFragment + /** * A [FragmentPagerAdapter] that returns a fragment corresponding to * one of the sections/tabs/pages. */ - inner class SectionsPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) { + inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List<FbTab>) : 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 getItem(position: Int) = WebFragment.newInstance(position, pages[position].url) - override fun getCount(): Int { - // Show 3 total pages. - return 3 - } + override fun getCount() = pages.size - override fun getPageTitle(position: Int): CharSequence? { - when (position) { - 0 -> return "SECTION 1" - 1 -> return "SECTION 2" - 2 -> return "SECTION 3" - } - return null - } + override fun getPageTitle(position: Int): CharSequence = pages[position].title } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FBURL.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FBURL.kt deleted file mode 100644 index 489f12a7..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FBURL.kt +++ /dev/null @@ -1,15 +0,0 @@ -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/facebook/UrlData.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/UrlData.kt new file mode 100644 index 00000000..80972050 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/UrlData.kt @@ -0,0 +1,65 @@ +package com.pitchedapps.frost.facebook + +import android.content.Context +import android.support.annotation.StringRes +import com.mikepenz.community_material_typeface_library.CommunityMaterial +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic +import com.pitchedapps.frost.R +import com.pitchedapps.frost.utils.RealmFiles +import com.pitchedapps.frost.utils.realm +import io.realm.Realm +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +/** + * Created by Allan Wang on 2017-05-29. + */ +enum class FbUrl(@StringRes val titleId: Int, val icon: IIcon, val url: String) { + FEED(R.string.feed, CommunityMaterial.Icon.cmd_newspaper, "https://touch.facebook.com/"), + PROFILE(R.string.profile, CommunityMaterial.Icon.cmd_account, "https://touch.facebook.com/me/"), + EVENTS(R.string.events, GoogleMaterial.Icon.gmd_event, "https://touch.facebook.com/events/upcoming"), + FRIENDS(R.string.friends, GoogleMaterial.Icon.gmd_people, "https://touch.facebook.com/friends/center/requests/"), + MESSAGES(R.string.messages, MaterialDesignIconic.Icon.gmi_comments, "https://touch.facebook.com/messages"), + NOTIFICATIONS(R.string.notifications, MaterialDesignIconic.Icon.gmi_globe, "https://touch.facebook.com/notifications"); + + fun tabInfo(c: Context) = FbTab(c.getString(titleId), icon, url) +} + +//BOOKMARKS("https://touch.facebook.com/bookmarks"), +//SEARCH("https://touch.facebook.com/search"), + +class FbTab(var title: String, var icon: IIcon, var url: String) { + constructor(realm: FbTabRealm) : this(realm.title, when (realm.iconCategory) { + 0 -> GoogleMaterial.Icon.valueOf(realm.iconString) + 1 -> CommunityMaterial.Icon.valueOf(realm.iconString) + 2 -> MaterialDesignIconic.Icon.valueOf(realm.iconString) + else -> GoogleMaterial.Icon.gmd_error + }, realm.url) +} + +open class FbTabRealm(var title: String, var iconCategory: Int, var iconString: String, @PrimaryKey var url: String) : RealmObject() { + constructor(tab: FbTab) : this(tab.title, when (tab.icon.typeface) { + is GoogleMaterial -> 0 + is CommunityMaterial -> 1 + is MaterialDesignIconic -> 2 + else -> -1 + }, tab.icon.toString(), tab.url) + + constructor() : this("", -1, "", "") +} + +fun List<FbTab>.save() { + val list = RealmList(*this.map { FbTabRealm(it) }.toTypedArray()) + realm(RealmFiles.TABS, Realm.Transaction { it.copyToRealmOrUpdate(list) }) +} + +fun loadFbTab(c: Context): List<FbTab> { + val realmList = mutableListOf<FbTabRealm>() + realm(RealmFiles.TABS, Realm.Transaction { it.copyFromRealm(realmList) }) + if (realmList.isNotEmpty()) return realmList.map { FbTab(it) } + return FbUrl.values().map { it.tabInfo(c) } +} + diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/BaseFragment.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/BaseFragment.kt new file mode 100644 index 00000000..435b87a2 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/BaseFragment.kt @@ -0,0 +1,49 @@ +package com.pitchedapps.frost.fragments + +import android.content.Context +import android.support.v4.app.Fragment +import com.pitchedapps.frost.utils.KeyPairObservable +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.putInt +import io.reactivex.disposables.Disposable +import io.reactivex.functions.Consumer + +/** + * Created by Allan Wang on 2017-05-29. + */ +interface BaseFragmentContract { + fun onActivityEvent(position: Int, key: Int) + fun onBackPressed(): Boolean +} + +abstract class BaseFragment : Fragment(), Consumer<Pair<Int, Int>>, BaseFragmentContract { + var disposable: Disposable? = null + val position: Int by lazy { arguments.getInt(ARG_POSITION) } + + companion object { + val ARG_POSITION = "arg_position" + + fun <T : BaseFragment> newInstance(fragment: T, position: Int): T { + fragment.putInt(ARG_POSITION, position) + return fragment + } + } + + override fun onAttach(context: Context?) { + super.onAttach(context) + if (activity is KeyPairObservable && disposable == null) + disposable = (activity as KeyPairObservable).observable.subscribe(this, Consumer { + t: Throwable -> + L.e(t.message ?: "Observable error") + }) + } + + override fun onDestroyView() { + disposable?.dispose() + disposable = null + super.onDestroyView() + } + + override fun accept(t: Pair<Int, Int>) = onActivityEvent(t.first, t.second) + +}
\ 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 index 140d8df1..d7bf4061 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt @@ -1,7 +1,6 @@ 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 @@ -9,10 +8,10 @@ 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.facebook.FbUrl import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.bindView -import com.pitchedapps.frost.utils.withBundle +import com.pitchedapps.frost.utils.putString import com.pitchedapps.frost.views.FrostWebView import com.pitchedapps.frost.views.SwipeRefreshBase import com.pitchedapps.frost.views.WebStatus @@ -22,7 +21,11 @@ import com.pitchedapps.frost.views.WebStatus */ -class WebFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { +class WebFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener { + + override fun onActivityEvent(position: Int, key: Int) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } override fun onRefresh() { web.reload() @@ -30,8 +33,8 @@ class WebFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { 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) + fun newInstance(position: Int, url: String) = BaseFragment.newInstance(WebFragment(), position).putString(ARG_URL, url) + fun newInstance(position: Int, url: FbUrl = FbUrl.FEED) = newInstance(position, url.url) } val refresh: SwipeRefreshBase by bindView(R.id.swipe_refresh) @@ -72,4 +75,12 @@ class WebFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { super.onDestroyView() unbinder.unbind() } + + override fun onBackPressed(): Boolean { + if (web.canGoBack()) { + web.goBack() + return true + } + return false + } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/FragmentUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/FragmentUtils.kt index cd638068..e7a38227 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/FragmentUtils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/FragmentUtils.kt @@ -2,14 +2,24 @@ package com.pitchedapps.frost.utils import android.os.Bundle import android.support.v4.app.Fragment +import com.pitchedapps.frost.fragments.BaseFragment /** * Created by Allan Wang on 2017-05-29. */ -fun Fragment.withBundle(creator: (Bundle) -> Unit): Fragment { - val bundle = Bundle() - creator.invoke(bundle) - this.arguments = bundle +private fun Fragment.bundle(): Bundle { + if (this.arguments == null) + this.arguments = Bundle() + return this.arguments +} + +fun <T : Fragment> T.putString(key: String, value: String): T { + this.bundle().putString(key, value) + return this +} + +fun <T : Fragment> T.putInt(key: String, value: Int): T { + this.bundle().putInt(key, value) return this }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/IIconUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/IIconUtils.kt new file mode 100644 index 00000000..cc645d93 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/IIconUtils.kt @@ -0,0 +1,13 @@ +package com.pitchedapps.frost.utils + +import android.content.Context +import android.graphics.drawable.Drawable +import android.support.annotation.ColorRes +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon + +/** + * Created by Allan Wang on 2017-05-29. + */ +fun IIcon.toDrawable(c: Context, sizeDp: Int = 24, @ColorRes color: Int = android.R.color.white): Drawable + = IconicsDrawable(c).icon(this).colorRes(color).sizeDp(sizeDp)
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Interfaces.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Interfaces.kt new file mode 100644 index 00000000..79904c6e --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Interfaces.kt @@ -0,0 +1,12 @@ +package com.pitchedapps.frost.utils + +import io.reactivex.subjects.Subject + +/** + * Created by Allan Wang on 2017-05-29. + */ +interface ObservableContainer<T> { + val observable: Subject<T> +} + +interface KeyPairObservable : ObservableContainer<Pair<Int, Int>>
\ 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 index 279d595e..0151b0ae 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt @@ -1,12 +1,34 @@ package com.pitchedapps.frost.utils +import android.util.Log +import timber.log.Timber + + + /** * 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) + val TAG = "Frost: %s" + fun e(s: String) = Timber.e(TAG, s) + fun d(s: String) = Timber.d(TAG, s) + } +} + +internal class CrashReportingTree : Timber.Tree() { + override fun log(priority: Int, tag: String, message: String, t: Throwable?) { + if (priority == Log.VERBOSE || priority == Log.DEBUG) + return + Log.println(priority, tag, message) +// FakeCrashLibrary.log(priority, tag, message) + +// if (t != null) { +// if (priority == Log.ERROR) { +// FakeCrashLibrary.logError(t) +// } else if (priority == Log.WARN) { +// FakeCrashLibrary.logWarning(t) +// } +// } } }
\ 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 index c98fd5a5..fc2e9d1c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt @@ -13,6 +13,8 @@ class Prefs(c: android.content.Context) { val LAST_ACTIVE = "last_active" } + private val prefs: android.content.SharedPreferences by lazy { c.getSharedPreferences(com.pitchedapps.frost.utils.Prefs.Companion.PREFERENCE_NAME, android.content.Context.MODE_PRIVATE) } + 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()) @@ -21,8 +23,6 @@ class Prefs(c: android.content.Context) { 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() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Realm.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Realm.kt new file mode 100644 index 00000000..1eded8c9 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Realm.kt @@ -0,0 +1,18 @@ +package com.pitchedapps.frost.utils + +import io.realm.Realm +import io.realm.RealmConfiguration + +/** + * Created by Allan Wang on 2017-05-29. + */ +@JvmOverloads fun realm(name: String = RealmFiles.main, transaction: Realm.Transaction) { + val realm = Realm.getInstance(RealmConfiguration.Builder().name(name).build()) + realm.executeTransaction(transaction) + realm.close() +} + +object RealmFiles { + val main = "frost.realm" + val TABS = "tabs.realm" +}
\ 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 index e7314c20..62115276 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt @@ -15,6 +15,7 @@ import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.ObservableContainer import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject @@ -31,14 +32,14 @@ enum class WebStatus { */ class FrostWebView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : WebView(context, attrs, defStyleAttr), NestedScrollingChild { +) : WebView(context, attrs, defStyleAttr), NestedScrollingChild, ObservableContainer<WebStatus> { 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> + override val observable: Subject<WebStatus> init { isNestedScrollingEnabled = true @@ -60,6 +61,7 @@ class FrostWebView @JvmOverloads constructor( override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) observable.onNext(WebStatus.LOADING) + L.d("Loading $url") } override fun onPageFinished(view: WebView?, url: String?) { |