package com.pitchedapps.frost.activities import android.app.AlarmManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Build import android.os.Bundle import android.support.annotation.StringRes import android.support.design.widget.* import android.support.v4.app.ActivityOptionsCompat 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.widget.Toolbar import android.view.Menu import android.view.MenuItem import android.webkit.ValueCallback import android.webkit.WebChromeClient import ca.allanwang.kau.changelog.showChangelog import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult import ca.allanwang.kau.searchview.SearchItem import ca.allanwang.kau.searchview.SearchView import ca.allanwang.kau.searchview.bindSearchView import ca.allanwang.kau.utils.* import co.zsmb.materialdrawerkt.builders.Builder import co.zsmb.materialdrawerkt.builders.accountHeader import co.zsmb.materialdrawerkt.builders.drawer import co.zsmb.materialdrawerkt.draweritems.badgeable.primaryItem import co.zsmb.materialdrawerkt.draweritems.badgeable.secondaryItem import co.zsmb.materialdrawerkt.draweritems.divider import co.zsmb.materialdrawerkt.draweritems.profile.profile import co.zsmb.materialdrawerkt.draweritems.profile.profileSetting import com.crashlytics.android.answers.ContentViewEvent import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.materialdrawer.AccountHeader import com.mikepenz.materialdrawer.Drawer import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R import com.pitchedapps.frost.contracts.ActivityWebContract import com.pitchedapps.frost.contracts.FileChooserContract import com.pitchedapps.frost.contracts.FileChooserDelegate import com.pitchedapps.frost.dbflow.loadFbCookie import com.pitchedapps.frost.dbflow.loadFbTabs import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.facebook.FbCookie.switchUser import com.pitchedapps.frost.facebook.FbTab import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL import com.pitchedapps.frost.fragments.WebFragment import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.utils.iab.validatePro import com.pitchedapps.frost.views.BadgedIcon import com.pitchedapps.frost.views.FrostViewPager import com.pitchedapps.frost.web.SearchWebView import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import org.jsoup.Jsoup import java.util.concurrent.TimeUnit class MainActivity : BaseActivity(), SearchWebView.SearchContract, ActivityWebContract, FileChooserContract by FileChooserDelegate() { lateinit var adapter: SectionsPagerAdapter val toolbar: Toolbar by bindView(R.id.toolbar) val viewPager: FrostViewPager by bindView(R.id.container) val fab: FloatingActionButton by bindView(R.id.fab) val tabs: TabLayout by bindView(R.id.tabs) val appBar: AppBarLayout by bindView(R.id.appbar) val coordinator: CoordinatorLayout by bindView(R.id.main_content) lateinit var drawer: Drawer lateinit var drawerHeader: AccountHeader var webFragmentObservable = PublishSubject.create()!! var lastPosition = -1 val headerBadgeObservable = PublishSubject.create() var hiddenSearchView: SearchWebView? = null var firstLoadFinished = false set(value) { L.d("First fragment load has finished") field = value if (value && hiddenSearchView == null) { hiddenSearchView = SearchWebView(this, this) } } var searchView: SearchView? = null override val isSearchOpened: Boolean get() = searchView?.isOpen ?: false companion object { const val ACTIVITY_SETTINGS = 97 /* * Possible responses from the SettingsActivity * after the configurations have changed */ const val REQUEST_RESTART = 90909 const val REQUEST_REFRESH = 80808 const val REQUEST_WEB_ZOOM = 50505 const val REQUEST_NAV = 10101 const val REQUEST_SEARCH = 70707 const val REQUEST_RESTART_APPLICATION = 60606 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (BuildConfig.VERSION_CODE > Prefs.versionCode) { Prefs.versionCode = BuildConfig.VERSION_CODE if (!BuildConfig.DEBUG) { showChangelog(R.xml.changelog, Prefs.textColor) { theme() } frostAnswersCustom("Version") { putCustomAttribute("Version code", BuildConfig.VERSION_CODE) putCustomAttribute("Version name", BuildConfig.VERSION_NAME) putCustomAttribute("Build type", BuildConfig.BUILD_TYPE) putCustomAttribute("Frost id", Prefs.frostId) } } } setContentView(R.layout.activity_main) setSupportActionBar(toolbar) adapter = SectionsPagerAdapter(supportFragmentManager, loadFbTabs()) viewPager.adapter = adapter viewPager.offscreenPageLimit = 5 viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { override fun onPageSelected(position: Int) { super.onPageSelected(position) if (lastPosition == position) return if (lastPosition != -1) webFragmentObservable.onNext(-(lastPosition + 1)) webFragmentObservable.onNext(position) lastPosition = position } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { super.onPageScrolled(position, positionOffset, positionOffsetPixels) val delta: Float by lazy { positionOffset * (255 - 128).toFloat() } tabsForEachView { tabPosition, view -> view.setAllAlpha(when (tabPosition) { position -> 255.0f - delta position + 1 -> 128.0f + delta else -> 128f }) } } }) viewPager.post { webFragmentObservable.onNext(0); lastPosition = 0 } //trigger hook so title is set setupDrawer(savedInstanceState) setupTabs() fab.setOnClickListener { view -> Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show() } setFrostColors(toolbar, themeWindow = false, headers = arrayOf(tabs, appBar), backgrounds = arrayOf(viewPager)) validatePro() } fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) { (0 until tabs.tabCount).asSequence().forEach { i -> action(i, tabs.getTabAt(i)!!.customView as BadgedIcon) } } fun setupTabs() { viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs)) tabs.addOnTabSelectedListener(object : TabLayout.ViewPagerOnTabSelectedListener(viewPager) { override fun onTabReselected(tab: TabLayout.Tab) { super.onTabReselected(tab) currentFragment.web.scrollOrRefresh() } override fun onTabSelected(tab: TabLayout.Tab) { super.onTabSelected(tab) (tab.customView as BadgedIcon).badgeText = null } }) headerBadgeObservable.throttleFirst(15, TimeUnit.SECONDS).subscribeOn(Schedulers.newThread()) .map { Jsoup.parse(it) } .filter { it.select("[data-sigil=count]").size >= 0 } //ensure headers exist .map { val feed = it.select("[data-sigil*=feed] [data-sigil=count]") val requests = it.select("[data-sigil*=requests] [data-sigil=count]") val messages = it.select("[data-sigil*=messages] [data-sigil=count]") val notifications = it.select("[data-sigil*=notifications] [data-sigil=count]") return@map arrayOf(feed, requests, messages, notifications).map { it?.getOrNull(0)?.ownText() } } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (feed, requests, messages, notifications) -> tabsForEachView { _, view -> when (view.iicon) { FbTab.FEED.icon -> view.badgeText = feed FbTab.FRIENDS.icon -> view.badgeText = requests FbTab.MESSAGES.icon -> view.badgeText = messages FbTab.NOTIFICATIONS.icon -> view.badgeText = notifications } } } adapter.pages.forEach { tabs.addTab(tabs.newTab() .setCustomView(BadgedIcon(this).apply { iicon = it.icon })) } } fun setupDrawer(savedInstanceState: Bundle?) { val navBg = Prefs.bgColor.withMinAlpha(200).toLong() val navHeader = Prefs.headerColor.withMinAlpha(200) drawer = drawer { toolbar = this@MainActivity.toolbar savedInstance = savedInstanceState translucentStatusBar = false sliderBackgroundColor = navBg drawerHeader = accountHeader { textColor = Prefs.iconColor.toLong() backgroundDrawable = ColorDrawable(navHeader) selectionSecondLineShown = false paddingBelow = false cookies().forEach { (id, name) -> profile(name = name ?: "") { iconUrl = PROFILE_PICTURE_URL(id) textColor = Prefs.textColor.toLong() selectedTextColor = Prefs.textColor.toLong() selectedColor = 0x00000001.toLong() identifier = id } } profileSetting(nameRes = R.string.kau_logout) { iicon = GoogleMaterial.Icon.gmd_exit_to_app iconColor = Prefs.textColor.toLong() textColor = Prefs.textColor.toLong() identifier = -2L } profileSetting(nameRes = R.string.kau_add_account) { iconDrawable = IconicsDrawable(this@MainActivity, GoogleMaterial.Icon.gmd_add).actionBar().paddingDp(5).color(Prefs.textColor) textColor = Prefs.textColor.toLong() identifier = -3L } profileSetting(nameRes = R.string.kau_manage_account) { iicon = GoogleMaterial.Icon.gmd_settings iconColor = Prefs.textColor.toLong() textColor = Prefs.textColor.toLong() identifier = -4L } onProfileChanged { _, profile, current -> if (current) launchWebOverlay(FbTab.PROFILE.url) else when (profile.identifier) { -2L -> { val currentCookie = loadFbCookie(Prefs.userId) if (currentCookie == null) { toast(R.string.account_not_found) FbCookie.reset { launchLogin(cookies(), true) } } else { materialDialogThemed { title(R.string.kau_logout) content(String.format(string(R.string.kau_logout_confirm_as_x), currentCookie.name ?: Prefs.userId.toString())) positiveText(R.string.kau_yes) negativeText(R.string.kau_no) onPositive { _, _ -> FbCookie.logout(Prefs.userId) { val allCookies = cookies() allCookies.remove(currentCookie) launchLogin(allCookies, true) } } } } } -3L -> launchNewTask(LoginActivity::class.java, clearStack = false) -4L -> launchNewTask(SelectorActivity::class.java, cookies(), false) else -> { switchUser(profile.identifier, { refreshAll() }) tabsForEachView { _, view -> view.badgeText = null } } } false } } drawerHeader.setActiveProfile(Prefs.userId) primaryFrostItem(FbTab.FEED_MOST_RECENT) primaryFrostItem(FbTab.FEED_TOP_STORIES) primaryFrostItem(FbTab.ACTIVITY_LOG) divider() primaryFrostItem(FbTab.PHOTOS) primaryFrostItem(FbTab.GROUPS) primaryFrostItem(FbTab.PAGES) divider() primaryFrostItem(FbTab.EVENTS) primaryFrostItem(FbTab.BIRTHDAYS) primaryFrostItem(FbTab.ON_THIS_DAY) divider() primaryFrostItem(FbTab.NOTES) primaryFrostItem(FbTab.SAVED) } } fun Builder.primaryFrostItem(item: FbTab) = this.primaryItem(item.titleId) { iicon = item.icon iconColor = Prefs.textColor.toLong() textColor = Prefs.textColor.toLong() selectedIconColor = Prefs.textColor.toLong() selectedTextColor = Prefs.textColor.toLong() selectedColor = 0x00000001.toLong() identifier = item.titleId.toLong() onClick { _ -> frostAnswers { logContentView(ContentViewEvent() .putContentName(item.name) .putContentType("drawer_item")) } launchWebOverlay(item.url) false } } fun Builder.secondaryFrostItem(@StringRes title: Int, onClick: () -> Unit) = this.secondaryItem(title) { textColor = Prefs.textColor.toLong() selectedIconColor = Prefs.textColor.toLong() selectedTextColor = Prefs.textColor.toLong() selectedColor = 0x00000001.toLong() identifier = title.toLong() onClick { _ -> onClick(); false } } /** * Something happened where the normal search function won't work * Fallback to overlay style */ override fun searchOverlayDispose() { hiddenSearchView?.dispose() hiddenSearchView = null searchView = null //todo remove true searchview and add contract } override fun emitSearchResponse(items: List) { searchView?.results = items } fun refreshAll() { webFragmentObservable.onNext(WebFragment.REQUEST_REFRESH) } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_main, menu) toolbar.tint(Prefs.iconColor) setMenuIcons(menu, Prefs.iconColor, R.id.action_settings to GoogleMaterial.Icon.gmd_settings, R.id.action_search to GoogleMaterial.Icon.gmd_search) if (Prefs.searchBar) { if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = SearchWebView(this, this) if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) { textObserver = { observable, _ -> observable.observeOn(AndroidSchedulers.mainThread()).subscribe { hiddenSearchView?.query(it) } } foregroundColor = Prefs.textColor backgroundColor = Prefs.bgColor.withMinAlpha(200) openListener = { hiddenSearchView?.pauseLoad = false } closeListener = { hiddenSearchView?.pauseLoad = true } onItemClick = { _, key, _, _ -> launchWebOverlay(key) } } } else { searchOverlayDispose() menu.findItem(R.id.action_search).setOnMenuItemClickListener { _ -> launchWebOverlay(FbTab.SEARCH.url); true } } return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_settings -> { val intent = Intent(this, SettingsActivity::class.java) val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle() startActivityForResult(intent, ACTIVITY_SETTINGS, bundle) } else -> return super.onOptionsItemSelected(item) } return true } override fun openFileChooser(filePathCallback: ValueCallback>, fileChooserParams: WebChromeClient.FileChooserParams) { openFileChooser(this, filePathCallback, fileChooserParams) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (onActivityResultWeb(requestCode, resultCode, data)) return super.onActivityResult(requestCode, resultCode, data) if (requestCode == ACTIVITY_SETTINGS) { when (resultCode) { REQUEST_RESTART -> restart() REQUEST_REFRESH -> webFragmentObservable.onNext(WebFragment.REQUEST_REFRESH) REQUEST_NAV -> frostNavigationBar() REQUEST_WEB_ZOOM -> webFragmentObservable.onNext(WebFragment.REQUEST_TEXT_ZOOM) REQUEST_SEARCH -> invalidateOptionsMenu() REQUEST_RESTART_APPLICATION -> { //completely restart application L.d("Restart Application Requested") val intent = packageManager.getLaunchIntentForPackage(packageName) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) val pending = PendingIntent.getActivity(this, 666, intent, PendingIntent.FLAG_CANCEL_CURRENT) val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) alarm.setExactAndAllowWhileIdle(AlarmManager.RTC, System.currentTimeMillis() + 100, pending) else alarm.setExact(AlarmManager.RTC, System.currentTimeMillis() + 100, pending) finish() System.exit(0) } } } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) kauOnRequestPermissionsResult(permissions, grantResults) } override fun onResume() { super.onResume() FbCookie.switchBackUser { } } override fun onStart() { //validate some pro features if (!Prefs.pro) { if (Prefs.theme == Theme.CUSTOM.ordinal) Prefs.theme = Theme.DEFAULT.ordinal } super.onStart() } override fun onBackPressed() { if (searchView?.onBackPressed() ?: false) return if (currentFragment.onBackPressed()) return super.onBackPressed() } val currentFragment get() = supportFragmentManager.findFragmentByTag("android:switcher:${R.id.container}:${viewPager.currentItem}") as WebFragment inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List) : FragmentPagerAdapter(fm) { override fun getItem(position: Int): Fragment { val fragment = WebFragment(pages[position], position) //If first load hasn't occurred, add a listener if (!firstLoadFinished) { var disposable: Disposable? = null fragment.post { disposable = it.web.refreshObservable.subscribe { if (!it) { //Ensure first load finisher only happens once if (!firstLoadFinished) firstLoadFinished = true disposable?.dispose() disposable = null } } } } return fragment } override fun getCount() = pages.size override fun getPageTitle(position: Int): CharSequence = getString(pages[position].titleId) } }