package net.lacolaco.smileessence.activity import android.app.ActivityManager import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable import android.net.Uri import android.os.Bundle import android.provider.MediaStore import android.support.v4.view.ViewPager import android.support.v7.app.AppCompatActivity import android.support.v7.graphics.Palette import android.text.TextUtils import android.view.Menu import android.view.MenuItem import android.view.WindowManager import kotlinx.android.synthetic.main.layout_main.* import net.lacolaco.smileessence.Application import net.lacolaco.smileessence.R import net.lacolaco.smileessence.World import net.lacolaco.smileessence.data.ExtractionWord import net.lacolaco.smileessence.logging.Logger import net.lacolaco.smileessence.twitter.TwitterTaskException import net.lacolaco.smileessence.twitter.task.getTweetAsync import net.lacolaco.smileessence.twitter.task.getUserAsync import net.lacolaco.smileessence.util.* import net.lacolaco.smileessence.view.DialogHelper import net.lacolaco.smileessence.view.adapter.PageListAdapter import net.lacolaco.smileessence.view.confirm import net.lacolaco.smileessence.view.dialog.StatusDetailDialogFragment import net.lacolaco.smileessence.view.dialog.UserDetailDialogFragment import net.lacolaco.smileessence.view.page.* import java.io.IOException import java.io.InputStream import java.net.URL import java.util.regex.Pattern class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { val world: World by lazy { val uri = intent.data ?: throw IllegalStateException("[BUG] data not set") val userIdValue = uri.getQueryParameter("user_id") ?: throw IllegalStateException("[BUG] user_id not set") Application.getWorld(userIdValue.toLong()) } private val pagerAdapter by lazy { PageListAdapter(this) } private fun setSelectedPageIndex(position: Int, smooth: Boolean = true) { viewPager.setCurrentItem(position, smooth) } fun openHomePage() { setSelectedPageIndex(pagerAdapter.getIndex(HomeFragment::class.java)) } fun openPostPage() { setSelectedPageIndex(pagerAdapter.getIndex(PostFragment::class.java)) } private fun openPostPageWithImage(uri: Uri) { try { val c = contentResolver.query(uri, null, null, null, null)!! c.moveToFirst() val path = c.getString(c.getColumnIndex(MediaStore.MediaColumns.DATA)) world.postState.beginTransaction() .setMediaFilePath(path) .commitWithOpen(this) world.notify(R.string.notice_select_image_succeeded) c.close() } catch (e: Exception) { e.printStackTrace() world.notifyError(R.string.notice_select_image_failed) } } fun openSearchPage(query: String) { val fragment = pagerAdapter.getCachedFragment(pagerAdapter.getIndex(SearchFragment::class.java)) as SearchFragment? if (fragment != null) { fragment.startSearch(query) setSelectedPageIndex(pagerAdapter.getIndex(SearchFragment::class.java)) } } fun openUserListPage(listFullName: String) { val fragment = pagerAdapter.getCachedFragment(pagerAdapter.getIndex(UserListFragment::class.java)) as UserListFragment? if (fragment != null) { fragment.startUserList(listFullName) setSelectedPageIndex(pagerAdapter.getIndex(UserListFragment::class.java)) } } private fun setTitle() { title = String.format("%s / %s", world.account.user.screenName, pagerAdapter.getName(viewPager.currentItem)) val label = getString(R.string.app_name) + " - @" + world.account.user.screenName if (android.os.Build.VERSION.SDK_INT >= 21) { setTaskDescription(ActivityManager.TaskDescription(label, null, world.account.themeColor)) window.statusBarColor = world.account.themeColor } launchBg { world.account.syncProfileAndSaveIfNecessary() } if (!world.account.user.profileImageUrl.startsWith("http")) return // XXX: It doesn't seem possible to change scaleType of the ImageButton to fitXY launchUi { val bitmap = bg { var inputStream: InputStream? = null try { inputStream = URL(world.account.user.profileImageUrl).openStream() val opt = BitmapFactory.Options() opt.inPurgeable = true // GC可能にする return@bg BitmapFactory.decodeStream(inputStream, null, opt) } catch (e: IOException) { e.printStackTrace() return@bg null } finally { try { inputStream?.close() } catch (e: IOException) { e.printStackTrace() } } }.await() ?: return@launchUi val dScaledBitmap = bg { val r = 32.0 * resources.displayMetrics.density Bitmap.createScaledBitmap(bitmap, r.toInt(), r.toInt(), false) } val dPalette = bg { Palette.from(bitmap).generate() } val scaledBitmap = dScaledBitmap.await() toolbar.navigationIcon = BitmapDrawable(resources, scaledBitmap) val palette = dPalette.await() val swatch = if (world.account.useDarkTheme) palette.darkVibrantSwatch ?: palette.darkMutedSwatch ?: palette.dominantSwatch else palette.vibrantSwatch ?: palette.mutedSwatch ?: palette.dominantSwatch if (swatch == null) { Logger.info("Unable to get a theme color of @${world.account.user.screenName}") } else { //actionBar.setBackgroundDrawable(ColorDrawable(swatch.rgb)) toolbar.setBackgroundColor(swatch.rgb) if (android.os.Build.VERSION.SDK_INT >= 21) { window.statusBarColor = swatch.rgb setTaskDescription(ActivityManager.TaskDescription(label, bitmap, swatch.rgb)) } world.account.themeColor = swatch.rgb //launchBg { world.account.save() } } } } private fun processIntent(intent: Intent) { val uri = intent.getParcelableExtra(KEY_ORIGINAL_DATA) if (uri != null) { if (uri.host == "twitter.com") { // /share and /intent/tweet: don't accept status parameter val postMatcher = TWITTER_POST_PATTERN.matcher(uri.path) if (postMatcher.find()) { var text = uri.getQueryParameter("text") ?: "" val url = uri.getQueryParameter("url") if (!TextUtils.isEmpty(url)) text += " " + url val hashtags = uri.getQueryParameter("hashtags") if (!TextUtils.isEmpty(hashtags)) text += " " + hashtags.trim { it <= ' ' }.replace(",".toRegex(), " #") val via = uri.getQueryParameter("via") if (!TextUtils.isEmpty(via)) text += " via @" + via world.postState.beginTransaction().setText(text).commitWithOpen(this) return } val statusMatcher = TWITTER_STATUS_PATTERN.matcher(uri.path) if (statusMatcher.find()) { return launchUi { try { val tweet = world.getTweetAsync(statusMatcher.group(1).toLong()).await() DialogHelper.showDialog(world.getMainActivityOrCancel(), StatusDetailDialogFragment.newInstance(tweet)) } catch (e: TwitterTaskException) { world.notifyError(R.string.error_intent_status_cannot_load) } } } val userMatcher = TWITTER_USER_PATTERN.matcher(uri.path) if (userMatcher.find()) { return launchUi { try { val user = world.getUserAsync(userMatcher.group(1)).await() DialogHelper.showDialog(world.getMainActivityOrCancel(), UserDetailDialogFragment.newInstance(user)) } catch (e: TwitterTaskException) { world.notifyError(R.string.notice_error_show_user) } } } } } else when (intent.action) { Intent.ACTION_SEND -> { val type = intent.getStringExtra(KEY_ORIGINAL_TYPE) if (type == "text/plain") { val extra = intent.extras if (extra != null) { var text = extra.getCharSequence(Intent.EXTRA_TEXT).toString() if (!TextUtils.isEmpty(extra.getCharSequence(Intent.EXTRA_SUBJECT))) { text = extra.getCharSequence(Intent.EXTRA_SUBJECT).toString() + " " + text } world.postState.beginTransaction().setText(text).commitWithOpen(this) return } } else if (type != null && type.startsWith("image/")) { openPostPageWithImage(intent.getParcelableExtra(Intent.EXTRA_STREAM)) } } } } // ------------------------ OVERRIDE METHODS ------------------------ override fun onBackPressed() { this.finish() } override fun finish() { val homeIndex = pagerAdapter.getIndex(HomeFragment::class.java) if (viewPager.currentItem != homeIndex) { viewPager.setCurrentItem(homeIndex, true) } else { confirm(R.string.dialog_confirm_finish_app) { super.finish() } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { REQUEST_GET_PICTURE_FROM_GALLERY -> { if (resultCode != RESULT_OK) { Logger.error(requestCode) world.notifyError(R.string.notice_select_image_failed) finish() return } openPostPageWithImage(data!!.data) } } } public override fun onCreate(savedInstanceState: Bundle?) { Logger.debug("onCreate") super.onCreate(savedInstanceState) world.setMainActivity(this) // XXX val account = world.account setTheme(if (account.useDarkTheme) R.style.theme_dark else R.style.theme_light) setContentView(R.layout.layout_main) setSupportActionBar(toolbar) viewPager.addOnPageChangeListener(this) //actionBar.setDisplayHomeAsUpEnabled(true) // TODO: tab order? val args = Bundle() args.putLong(PageFragment.KEY_WORLD_USER_ID, account.id) pagerAdapter.addPage(PostFragment::class.java, getString(R.string.page_name_post), args, false) pagerAdapter.addPage(HomeFragment::class.java, getString(R.string.page_name_home), args, false) pagerAdapter.addPage(MentionsFragment::class.java, getString(R.string.page_name_mentions), args, false) pagerAdapter.addPage(HistoryFragment::class.java, getString(R.string.page_name_history), args, false) pagerAdapter.addPage(MessagesFragment::class.java, getString(R.string.page_name_messages), args, false) pagerAdapter.addPage(SearchFragment::class.java, getString(R.string.page_name_search), args, false) pagerAdapter.addPage(UserListFragment::class.java, getString(R.string.page_name_list), args, false) pagerAdapter.notifyDataSetChanged() viewPager.offscreenPageLimit = pagerAdapter.count viewPager.adapter = pagerAdapter setSelectedPageIndex(pagerAdapter.getIndex(HomeFragment::class.java), false) ExtractionWord.load() // update cache world.refreshListSubscriptions() world.refreshUserMuteList() world.refreshSavedSearches() launchUi { val user = try { world.getUserAsync(world.account.id) } catch (e: TwitterTaskException) { return@launchUi } setTitle() } // Set application title setTitle() // refresh all pages for (i in 0 until pagerAdapter.count) { val pf = pagerAdapter.getCachedFragment(i) if (pf != null && pf.isAdded) { Logger.debug(String.format("PageFragment %s is already attached; refreshing", pf.javaClass.name)) pf.refresh() } } // start user stream world.setupStreaming() window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.main, menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { android.R.id.home -> { val intent = Intent(this, ManageAccountsActivity::class.java) intent.putExtra(ManageAccountsActivity.INTENT_KEY_NOINIT, true) startActivity(intent) } R.id.actionbar_setting -> startActivity(Intent(this, SettingActivity::class.java)) R.id.actionbar_edit_extraction -> startActivity(Intent(this, EditExtractionActivity::class.java)) R.id.actionbar_aclog -> browse(world.account.user.aclogTimelineURL) else -> return super.onOptionsItemSelected(item) } return true } override fun onPause() { Logger.debug("onPause") super.onPause() world.setMainActivityActive(false) } override fun onResume() { Logger.debug("onResume") super.onResume() Application.currentWorld = world world.setMainActivityActive(true) // With the current releases (~ 7) of Android, onNewIntent() doesn't seem to be called // properly when resuming an already running Activity that has configured as // documentLaunchMode=intoExisting. So, as a workaround, store the actual Intent in // the World associated and check it in onResume() for now. See also // ManageAccountsActivity::goToWorld(). val intent = world.mainActivityIntent if (intent != null) { world.mainActivityIntent = null processIntent(intent) } } // --------------------- Interface OnPageChangeListener --------------------- override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageSelected(position: Int) { Logger.debug("Page selected: " + position) setTitle() } override fun onPageScrollStateChanged(state: Int) {} companion object { val REQUEST_GET_PICTURE_FROM_GALLERY = 11 val KEY_ORIGINAL_DATA = "originalData" val KEY_ORIGINAL_TYPE = "originalType" private val TWITTER_POST_PATTERN = Pattern.compile("\\A/(intent/tweet|share)\\z", Pattern.CASE_INSENSITIVE) private val TWITTER_STATUS_PATTERN = Pattern.compile("\\A(?:/#!)?/(?:\\w{1,15})/status(?:es)?/(\\d+)\\z", Pattern.CASE_INSENSITIVE) private val TWITTER_USER_PATTERN = Pattern.compile("\\A(?:/#!)?/(\\w{1,15})/?\\z", Pattern.CASE_INSENSITIVE) } }