diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2017-10-19 01:59:16 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2017-10-19 01:59:16 +0900 |
commit | 3ca62eec575da4200823662ac95f092c3271aa59 (patch) | |
tree | f6ef1f7671a33896dba1374f36b54b7c916b1d87 | |
parent | a45a6b24126d023f1ea2e28216c612860afd051f (diff) | |
download | SmileEssence-3ca62eec575da4200823662ac95f092c3271aa59.tar.gz |
uaua
58 files changed, 1161 insertions, 1256 deletions
diff --git a/app/build.gradle b/app/build.gradle index 082033f9..6add0b86 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,6 +67,9 @@ dependencies { implementation 'org.twitter4j:twitter4j-media-support:4.0.6' implementation 'org.twitter4j:twitter4j-stream:4.0.6' + // Klaxon + implementation 'com.beust:klaxon:0.30' + // LeakCanary debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4' releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' diff --git a/app/licenses.yml b/app/licenses.yml index 4bcfa3aa..394900a2 100644 --- a/app/licenses.yml +++ b/app/licenses.yml @@ -10,6 +10,7 @@ name: kotlinx-coroutines-core copyrightHolder: JetBrains s.r.o. license: The Apache License, Version 2.0 + licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt - artifact: org.jetbrains.kotlinx:kotlinx-coroutines-android:+ skip: true - artifact: org.jetbrains.kotlin:kotlin-android-extensions-runtime:+ @@ -53,6 +54,11 @@ license: Apache License 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0 url: http://twitter4j.org/ +- artifact: com.beust:klaxon:+ + name: Klaxon + copyrightHolder: <><><><> + license: The Apache Software License, Version 2.0 + licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt - artifact: com.squareup.leakcanary:leakcanary-android-no-op:+ name: No op LeakCanary for Android copyrightHolder: Square, Inc. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b34c1a63..caca3a96 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ android:launchMode="singleTask"> <intent-filter> <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter android:label="@string/intent_post"> @@ -35,11 +36,14 @@ </intent-filter> <intent-filter android:label="@string/intent_post"> <action android:name="android.intent.action.SEND" /> + <data android:mimeType="text/plain" /> + <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.SEND" /> + <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="image/jpeg" /> @@ -75,7 +79,6 @@ android:documentLaunchMode="intoExisting" android:label="@string/app_name" android:windowSoftInputMode="stateHidden|adjustResize"> - <intent-filter> <action android:name="android.intent.action.VIEW" /> @@ -93,23 +96,27 @@ android:excludeFromRecents="true" android:label="@string/activity_setting" android:launchMode="singleTask" - android:parentActivityName="net.lacolaco.smileessence.activity.MainActivity" /> + android:parentActivityName=".activity.MainActivity" /> <activity android:name=".activity.EditExtractionActivity" android:configChanges="keyboardHidden|orientation" android:excludeFromRecents="true" android:label="@string/activity_edit_extraction" android:launchMode="singleTask" - android:parentActivityName="net.lacolaco.smileessence.activity.MainActivity" /> + android:parentActivityName=".activity.MainActivity" /> <activity android:name=".activity.LicenseActivity" android:label="@string/activity_licenses" - android:parentActivityName="net.lacolaco.smileessence.activity.SettingActivity" /> + android:parentActivityName=".activity.SettingActivity" /> <activity android:name=".activity.OAuthActivity" android:configChanges="keyboardHidden|orientation" android:excludeFromRecents="false" android:label="@string/activity_authenticate" android:parentActivityName=".activity.ManageAccountsActivity" /> + <activity + android:name=".activity.ManagePagesActivity" + android:label="@string/title_activity_page_manage" /> </application> + </manifest> diff --git a/app/src/main/java/net/lacolaco/smileessence/Application.kt b/app/src/main/java/net/lacolaco/smileessence/Application.kt index 075f134f..17c6e6d6 100644 --- a/app/src/main/java/net/lacolaco/smileessence/Application.kt +++ b/app/src/main/java/net/lacolaco/smileessence/Application.kt @@ -3,11 +3,7 @@ package net.lacolaco.smileessence import android.support.annotation.StringRes import android.widget.Toast import com.squareup.leakcanary.LeakCanary -import net.lacolaco.smileessence.data.Account import net.lacolaco.smileessence.data.DbHelper -import net.lacolaco.smileessence.data.ExtractionWord -import java.lang.ref.WeakReference -import java.util.* class Application : android.app.Application() { override fun onCreate() { @@ -18,15 +14,12 @@ class Application : android.app.Application() { instance = this DbHelper.setup(this) - Account.load() - - // XXX - ExtractionWord.load()} + World.load() + } companion object { lateinit var instance: Application private set - private val worlds = HashMap<Long, WeakReference<World>>() // XXX fun toast(@StringRes id: Int) { Toast.makeText(instance, id, Toast.LENGTH_LONG).show() @@ -37,15 +30,5 @@ class Application : android.app.Application() { } var currentWorld: World? = null - - fun getWorld(id: Long): World { - val w0 = worlds[id]?.get() - if (w0 != null) - return w0 - val w = World(Account[id]) - val ww = WeakReference(w) - worlds.put(id, ww) - return w - } } } diff --git a/app/src/main/java/net/lacolaco/smileessence/World.kt b/app/src/main/java/net/lacolaco/smileessence/World.kt index 80b04d31..bf748fe5 100644 --- a/app/src/main/java/net/lacolaco/smileessence/World.kt +++ b/app/src/main/java/net/lacolaco/smileessence/World.kt @@ -1,22 +1,27 @@ package net.lacolaco.smileessence +import android.content.ContentValues import android.content.Intent import android.support.annotation.StringRes import android.support.design.widget.Snackbar +import android.view.View import net.lacolaco.smileessence.activity.MainActivity import net.lacolaco.smileessence.compat.Twitter4J -import net.lacolaco.smileessence.data.Account -import net.lacolaco.smileessence.entity.DirectMessage -import net.lacolaco.smileessence.entity.Event -import net.lacolaco.smileessence.entity.SavedSearch -import net.lacolaco.smileessence.entity.Tweet +import net.lacolaco.smileessence.data.DbHelper +import net.lacolaco.smileessence.data.PageInfo +import net.lacolaco.smileessence.entity.* import net.lacolaco.smileessence.logging.Logger import net.lacolaco.smileessence.twitter.TwitterTaskException import net.lacolaco.smileessence.twitter.UserStreamListener import net.lacolaco.smileessence.twitter.task.* import net.lacolaco.smileessence.util.launchBg import net.lacolaco.smileessence.util.launchUi +import twitter4j.Twitter +import twitter4j.TwitterFactory import twitter4j.TwitterStream +import twitter4j.TwitterStreamFactory +import twitter4j.auth.AccessToken +import twitter4j.conf.ConfigurationBuilder import java.lang.ref.WeakReference import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -24,7 +29,11 @@ import java.util.concurrent.ConcurrentHashMap /** * World contains data that are specific to an account. */ -class World(val account: Account) { +class World private constructor(private val persistentData: PersistentData) { + val id = persistentData.id + val user: User by lazy { + User.fetch(id) ?: User._makeSkeleton(id, persistentData.screenName, persistentData.profileImageUrl) + } // XXX: Workaround for a bug in Android var mainActivityIntent: Intent? = null // Timelines @@ -48,6 +57,8 @@ class World(val account: Account) { // Notification private var mainActivity: WeakReference<MainActivity>? = null private var isMainActivityActive: Boolean = false + // Pages + val pages = persistentData.pageInfos // Startup @@ -57,17 +68,75 @@ class World(val account: Account) { return initialized = true - refreshListSubscriptions() - refreshUserMuteList() - refreshSavedSearches() + launchBg { + try { + val lists = getUserListsAsync().await() + listSubscriptions.clear() + listSubscriptions.addAll(lists) + } catch (e: TwitterTaskException) { + } + } + launchBg { + try { + val blockIds = getBlocksIdsAsync() + val muteIds = getMutesIdsAsync() + muteUserIds += blockIds.await() + muteUserIds += muteIds.await() + } catch (e: TwitterTaskException) { + } + } + launchBg { + try { + val ssl = getSavedSearchesAsync().await() + savedSearches.clear() + for (ss in ssl) + savedSearches.put(ss.id, ss) + } catch (e: TwitterTaskException) { + } + } + launchBg { + try { + val jobs = listOf(getHomeTimelineAsync(), getMentionsTimelineAsync()) + jobs.forEach { addTweetAll(it.await()) } + } catch (e: TwitterTaskException) { + notifyError(R.string.notice_error_get_home) + } + } - stream = account.twitterStream + val stream = TwitterStreamFactory().instance + stream.oAuthAccessToken = AccessToken( + persistentData.oauthToken, persistentData.oauthTokenSecret) userStreamListener = UserStreamListener(this) - Twitter4J.twitterStreamAddListener(stream!!, userStreamListener!!) - stream!!.addConnectionLifeCycleListener(userStreamListener) - stream!!.user() + Twitter4J.twitterStreamAddListener(stream, userStreamListener!!) + stream.addConnectionLifeCycleListener(userStreamListener) + stream.user() } + fun checkpoint() { + persistentData.screenName = user.screenName + persistentData.profileImageUrl = user.profileImageUrl + persistentData.save() + } + + val useDarkTheme + get() = persistentData.themeIndex == 0 + + var themeColor + get() = persistentData.themeColor + set(value) { + persistentData.themeColor = value + } + + val twitter: Twitter + get() { + val cb = ConfigurationBuilder() + cb.setTweetModeExtended(true) + val twitter = TwitterFactory(cb.build()).instance + twitter.oAuthAccessToken = AccessToken( + persistentData.oauthToken, persistentData.oauthTokenSecret) + return twitter + } + // MainActivity holder fun setMainActivity(activity: MainActivity) { @@ -145,15 +214,6 @@ class World(val account: Account) { // Lists - fun refreshListSubscriptions() = launchBg { - try { - val lists = getUserListsAsync().await() - listSubscriptions.clear() - listSubscriptions.addAll(lists) - } catch (e: TwitterTaskException) { - } - } - fun addListSubscription(fullName: String): Boolean { return listSubscriptions.add(fullName) } @@ -162,34 +222,6 @@ class World(val account: Account) { return listSubscriptions.remove(fullName) } - // Mute - - fun refreshUserMuteList() = launchBg { - try { - val blockIds = getBlocksIdsAsync() - val muteIds = getMutesIdsAsync() - muteUserIds += blockIds.await() - muteUserIds += muteIds.await() - } catch (e: TwitterTaskException) { - } - } - - fun isMutedUserListContains(id: Long): Boolean { - return muteUserIds.contains(id) - } - - // Saved search queries - - fun refreshSavedSearches() = launchBg { - try { - val ssl = getSavedSearchesAsync().await() - savedSearches.clear() - for (ss in ssl) - savedSearches.put(ss.id, ss) - } catch (e: TwitterTaskException) { - } - } - // Notifications fun notify(text: String) { @@ -221,9 +253,10 @@ class World(val account: Account) { private fun doNotify(type: NotificationType, text: String) { launchUi { if (isMainActivityActive) { + val view: View = mainActivity!!.get()!!.findViewById(android.R.id.content) // TODO: Make errors distinguishable Logger.debug("notify(snackbar): $text") - Snackbar.make(mainActivity!!.get()!!.findViewById(android.R.id.content), text, Snackbar.LENGTH_SHORT).show() + Snackbar.make(view, text, Snackbar.LENGTH_SHORT).show() } else { Logger.debug(String.format("notify(toast): %s", text)) Application.toast(text) @@ -235,4 +268,98 @@ class World(val account: Account) { INFO, ALERT } + + companion object { + private val cache = LinkedHashMap<Long, World>() + + operator fun get(i: Long) = + cache[i] ?: throw IllegalStateException("[BUG] Account with id == $i not found") + + fun all() = ArrayList(cache.values) + + fun load() { + if (cache.isNotEmpty()) + throw IllegalStateException("World.load called twice") + PersistentData.fetchAll().forEach { cache[it.id] = World(it) } + Logger.info("Loaded ${cache.size} profiles") + } + + fun register(token: String, tokenSecret: String, userId: Long, screenName: String): World { + val world = World(PersistentData(userId, token, tokenSecret, screenName).apply { save() }) + cache[userId] = world + return world + } + } + + private data class PersistentData( + val id: Long, + val oauthToken: String, + val oauthTokenSecret: String, + var screenName: String, + var profileImageUrl: String = "https://abs.twimg.com/sticky/default_profile_images/default_profile.png", + var pageInfos: List<PageInfo> = makeDefaultPageInfos(screenName), + var themeIndex: Int = 0, + var themeColor: Int = 0 + ) { + fun save() { + val values = ContentValues() + values.put("id", id) + values.put("oauth_token", oauthToken) + values.put("oauth_token_secret", oauthTokenSecret) + values.put("screen_name", screenName) + values.put("profile_image_url", profileImageUrl) + Logger.error("pageinfo saving: ${PageInfo.stringifyList(pageInfos)}") + values.put("page_infos", PageInfo.stringifyList(pageInfos)) + values.put("theme", themeIndex) + values.put("theme_color", themeColor) + if (DbHelper.db.replaceOrThrow("accounts", null, values) == -1L) + throw RuntimeException("SQLiteDatabase#replaceOrThrow failed") + } + + companion object { + private fun makeDefaultPageInfos(screenName: String): List<PageInfo> { + val ret = arrayListOf<PageInfo>() + ret.add(PageInfo.ComposePageInfo()) + ret.add(PageInfo.TweetsPageInfo("Home", arrayListOf(""))) + ret.add(PageInfo.TweetsPageInfo("Mentions", arrayListOf("@$screenName"))) + ret.add(PageInfo.EventsPageInfo()) + ret.add(PageInfo.SearchPageInfo("")) + ret.add(PageInfo.ListPageInfo(null)) + return ret + } + + fun fetchAll(): List<PersistentData> { + val ret = arrayListOf<PersistentData>() + DbHelper.db.query("accounts", null, null, null, null, null, null).use { cursor -> + while (cursor != null && cursor.moveToNext()) { + val pd = PersistentData( + id = cursor.getLong( + cursor.getColumnIndexOrThrow("id")), + oauthToken = cursor.getString( + cursor.getColumnIndexOrThrow("oauth_token")), + oauthTokenSecret = cursor.getString( + cursor.getColumnIndexOrThrow("oauth_token_secret")), + screenName = cursor.getString( + cursor.getColumnIndexOrThrow("screen_name")), + profileImageUrl = cursor.getString( + cursor.getColumnIndexOrThrow("profile_image_url")), + pageInfos = PageInfo.parseList(cursor.getString( + cursor.getColumnIndexOrThrow("page_infos"))), + themeIndex = cursor.getInt( + cursor.getColumnIndexOrThrow("theme")), + themeColor = cursor.getInt( + cursor.getColumnIndexOrThrow("theme_color")) + ) + // XXX: Remove this + if (pd.pageInfos.isEmpty()) { + pd.pageInfos = makeDefaultPageInfos(pd.screenName) + pd.save() + } + ret.add(pd) + } + } + return ret + } + } + } } diff --git a/app/src/main/java/net/lacolaco/smileessence/activity/MainActivity.kt b/app/src/main/java/net/lacolaco/smileessence/activity/MainActivity.kt index 249121f5..caedbd6b 100644 --- a/app/src/main/java/net/lacolaco/smileessence/activity/MainActivity.kt +++ b/app/src/main/java/net/lacolaco/smileessence/activity/MainActivity.kt @@ -7,7 +7,6 @@ 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 @@ -18,34 +17,38 @@ 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.PageInfo import net.lacolaco.smileessence.entity.Tweet import net.lacolaco.smileessence.entity.User 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.util.bg +import net.lacolaco.smileessence.util.browse +import net.lacolaco.smileessence.util.getMainActivityOrCancel +import net.lacolaco.smileessence.util.launchUi import net.lacolaco.smileessence.view.DialogHelper import net.lacolaco.smileessence.view.MainFragmentPagerAdapter 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 net.lacolaco.smileessence.view.page.PostFragment 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 { +class MainActivity : AppCompatActivity() { + val 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()) + World[userIdValue.toLong()] } private lateinit var pagerAdapter: MainFragmentPagerAdapter fun openHomePage() { - viewPager.setCurrentItem(pagerAdapter.getPageIndex(TAG_PAGE_HOME), true) + viewPager.setCurrentItem(world.pages.indexOfFirst { it is PageInfo.TweetsPageInfo }, true) } private fun setSelectedPageIndex(position: Int, smooth: Boolean = true) { @@ -53,55 +56,35 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { } fun openPostPageAndReplyTo(tweet: Tweet, prefix: String) { - val postPage = pagerAdapter.getPageFragment(TAG_PAGE_POST) as PostFragment - postPage.setInReplyTo(tweet, prefix) - setSelectedPageIndex(pagerAdapter.getPageIndex(TAG_PAGE_POST), true) + val postPagePosition = world.pages.indexOfFirst { it is PageInfo.ComposePageInfo } + assert(postPagePosition != -1) + (pagerAdapter.getPageFragmentAt(postPagePosition) as PostFragment).setInReplyTo(tweet, prefix) + setSelectedPageIndex(postPagePosition, true) } fun openPostPageAndReplyTo(user: User) { - val postPage = pagerAdapter.getPageFragment(TAG_PAGE_POST) as PostFragment - postPage.setInReplyTo(user) - setSelectedPageIndex(pagerAdapter.getPageIndex(TAG_PAGE_POST), true) + val postPagePosition = world.pages.indexOfFirst { it is PageInfo.ComposePageInfo } + assert(postPagePosition != -1) + (pagerAdapter.getPageFragmentAt(postPagePosition) as PostFragment).setInReplyTo(user) + setSelectedPageIndex(postPagePosition, true) } fun openPostPageAndAppendText(text: String) { - val postPage = pagerAdapter.getPageFragment(TAG_PAGE_POST) as PostFragment - postPage.appendText(text) - setSelectedPageIndex(pagerAdapter.getPageIndex(TAG_PAGE_POST), true) - } - - 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)) - val postPage = pagerAdapter.getPageFragment(TAG_PAGE_POST) as PostFragment - postPage.setMediaFilePath(path) - 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.getPageFragment(TAG_PAGE_SEARCH) as SearchFragment - fragment.startSearch(query) - setSelectedPageIndex(pagerAdapter.getPageIndex(TAG_PAGE_SEARCH)) + val postPagePosition = world.pages.indexOfFirst { it is PageInfo.ComposePageInfo } + assert(postPagePosition != -1) + (pagerAdapter.getPageFragmentAt(postPagePosition) as PostFragment).appendText(text) + setSelectedPageIndex(postPagePosition, true) } 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 + val label = getString(R.string.app_name) + " - @" + world.user.screenName if (android.os.Build.VERSION.SDK_INT >= 21) { - setTaskDescription(ActivityManager.TaskDescription(label, null, world.account.themeColor)) - window.statusBarColor = world.account.themeColor + setTaskDescription(ActivityManager.TaskDescription(label, null, world.themeColor)) + window.statusBarColor = world.themeColor } - launchBg { world.account.syncProfileAndSaveIfNecessary() } - if (!world.account.user.profileImageUrl.startsWith("http")) + if (!world.user.profileImageUrl.startsWith("http")) return // XXX: It doesn't seem possible to change scaleType of the ImageButton to fitXY @@ -109,10 +92,8 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { 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) + inputStream = URL(world.user.profileImageUrl).openStream() + return@bg BitmapFactory.decodeStream(inputStream) } catch (e: IOException) { e.printStackTrace() return@bg null @@ -135,12 +116,12 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { toolbar.navigationIcon = BitmapDrawable(resources, scaledBitmap) val palette = dPalette.await() - val swatch = if (world.account.useDarkTheme) + val swatch = if (world.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}") + Logger.info("Unable to get a theme color of @${world.user.screenName}") } else { //actionBar.setBackgroundDrawable(ColorDrawable(swatch.rgb)) toolbar.setBackgroundColor(swatch.rgb) @@ -148,14 +129,13 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { window.statusBarColor = swatch.rgb setTaskDescription(ActivityManager.TaskDescription(label, bitmap, swatch.rgb)) } - world.account.themeColor = swatch.rgb - launchBg { world.account.save() } + world.themeColor = swatch.rgb } } } private fun processIntent(intent: Intent) { - val uri = intent.getParcelableExtra<Uri>(KEY_ORIGINAL_DATA) + val uri = intent.data if (uri != null) { if (uri.host == "twitter.com") { // /share and /intent/tweet: don't accept status parameter @@ -171,9 +151,10 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { val via = uri.getQueryParameter("via") if (!TextUtils.isEmpty(via)) text += " via @" + via - val postPage = pagerAdapter.getPageFragment(TAG_PAGE_POST) as PostFragment - postPage.setText(text) - viewPager.setCurrentItem(pagerAdapter.getPageIndex(TAG_PAGE_POST), true) + val postPagePosition = world.pages.indexOfFirst { it is PageInfo.ComposePageInfo } + assert(postPagePosition != -1) + (pagerAdapter.getPageFragmentAt(postPagePosition) as PostFragment).setText(text) + setSelectedPageIndex(postPagePosition, true) return } val statusMatcher = TWITTER_STATUS_PATTERN.matcher(uri.path) @@ -201,7 +182,7 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { } } else when (intent.action) { Intent.ACTION_SEND -> { - val type = intent.getStringExtra(KEY_ORIGINAL_TYPE) + val type = intent.type if (type == "text/plain") { val extra = intent.extras if (extra != null) { @@ -209,26 +190,28 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { if (!TextUtils.isEmpty(extra.getCharSequence(Intent.EXTRA_SUBJECT))) { text = extra.getCharSequence(Intent.EXTRA_SUBJECT).toString() + " " + text } - val postPage = pagerAdapter.getPageFragment(TAG_PAGE_POST) as PostFragment - postPage.setText(text) - viewPager.setCurrentItem(pagerAdapter.getPageIndex(TAG_PAGE_POST), true) + val postPagePosition = world.pages.indexOfFirst { it is PageInfo.ComposePageInfo } + assert(postPagePosition != -1) + (pagerAdapter.getPageFragmentAt(postPagePosition) as PostFragment).setText(text) + setSelectedPageIndex(postPagePosition, true) return } } else if (type != null && type.startsWith("image/")) { - openPostPageWithImage(intent.getParcelableExtra(Intent.EXTRA_STREAM)) + val postPagePosition = world.pages.indexOfFirst { it is PageInfo.ComposePageInfo } + assert(postPagePosition != -1) + (pagerAdapter.getPageFragmentAt(postPagePosition) as PostFragment) + .setMediaFile(intent.getParcelableExtra(Intent.EXTRA_STREAM)) } } } } - // ------------------------ OVERRIDE METHODS ------------------------ - override fun onBackPressed() { finish() } override fun finish() { - val homeIndex = pagerAdapter.getPageIndex(TAG_PAGE_HOME) + val homeIndex = world.pages.indexOfFirst { it is PageInfo.TweetsPageInfo } if (viewPager.currentItem != homeIndex) viewPager.setCurrentItem(homeIndex, true) else @@ -237,53 +220,41 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { 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 + REQUEST_CODE_MANAGE_PAGES -> { + if (resultCode == RESULT_OK) { + Application.toast("Restarting activity...") + super.finish() + startActivity(intent) } - openPostPageWithImage(data!!.data) } + else -> super.onActivityResult(requestCode, resultCode, data) } } - public override fun onCreate(savedInstanceState: Bundle?) { + override fun onCreate(savedInstanceState: Bundle?) { Logger.debug("onCreate") super.onCreate(savedInstanceState) world.setMainActivity(this) - setTheme(if (world.account.useDarkTheme) R.style.theme_dark else R.style.theme_light) + setTheme(if (world.useDarkTheme) R.style.theme_dark else R.style.theme_light) setContentView(R.layout.layout_main) setSupportActionBar(toolbar) - val args = Bundle() - args.putLong(PageFragment.KEY_WORLD_USER_ID, world.account.id) - pagerAdapter = MainFragmentPagerAdapter(fragmentManager, viewPager) - pagerAdapter.addPage(TAG_PAGE_POST, PostFragment::class.java, args) - pagerAdapter.addPage(TAG_PAGE_HOME, HomeFragment::class.java, args) - pagerAdapter.addPage(TAG_PAGE_MENTIONS, MentionsFragment::class.java, args) - pagerAdapter.addPage(TAG_PAGE_EVENTS, HistoryFragment::class.java, args) - pagerAdapter.addPage(TAG_PAGE_MESSAGES, MessagesFragment::class.java, args) - pagerAdapter.addPage(TAG_PAGE_SEARCH, SearchFragment::class.java, args) - pagerAdapter.addPage(TAG_PAGE_LIST, UserListFragment::class.java, args) - pagerAdapter.notifyDataSetChanged() - - // TODO: tab order? - getString(R.string.page_name_post) - getString(R.string.page_name_home) - getString(R.string.page_name_mentions) - getString(R.string.page_name_history) - getString(R.string.page_name_messages) - getString(R.string.page_name_search) - getString(R.string.page_name_list) - - viewPager.addOnPageChangeListener(this) + pagerAdapter = MainFragmentPagerAdapter(world, viewPager, fragmentManager) viewPager.offscreenPageLimit = pagerAdapter.count viewPager.adapter = pagerAdapter - viewPager.setCurrentItem(pagerAdapter.getPageIndex(TAG_PAGE_HOME), false) + viewPager.setCurrentItem(world.pages.indexOfFirst { it is PageInfo.TweetsPageInfo }, false) + viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) {} + + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} + + override fun onPageSelected(position: Int) { + Logger.verbose("Page #$position selected (${world.pages[position].name})") + title = "${world.user.screenName} / ${world.pages[position].name}" + } + }) // Set application title setTitle() @@ -305,8 +276,12 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { 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) + R.id.actionbar_manage_pages -> { + val intent = Intent(this, ManagePagesActivity::class.java) + intent.putExtra(ManagePagesActivity.INTENT_KEY_WORLD_ID, world.id) + startActivityForResult(intent, REQUEST_CODE_MANAGE_PAGES) + } + R.id.actionbar_aclog -> browse(world.user.aclogTimelineURL) else -> return super.onOptionsItemSelected(item) } return true @@ -316,6 +291,7 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { Logger.debug("onPause") super.onPause() world.setMainActivityActive(false) + world.checkpoint() } override fun onResume() { @@ -336,32 +312,10 @@ class MainActivity : AppCompatActivity(), ViewPager.OnPageChangeListener { } } - // --------------------- Interface OnPageChangeListener --------------------- - - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} - - override fun onPageSelected(position: Int) { - Logger.debug("Page selected: " + position) - //title = String.format("%s / %s", world.account.user.screenName, pagerAdapter.getName(viewPager.currentItem)) - //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) - - private val TAG_PAGE_POST = "PAGE_POST" - private val TAG_PAGE_HOME = "PAGE_HOME" - private val TAG_PAGE_MENTIONS = "PAGE_MENTIONS" - private val TAG_PAGE_EVENTS = "PAGE_EVENTS" - private val TAG_PAGE_MESSAGES = "PAGE_MESSAGES" - private val TAG_PAGE_SEARCH = "PAGE_SEARCH" - private val TAG_PAGE_LIST = "PAGE_LIST" + private val REQUEST_CODE_MANAGE_PAGES = 12 + private val TWITTER_POST_PATTERN = Pattern.compile("\\A/(intent/tweet|share)\\z") + private val TWITTER_STATUS_PATTERN = Pattern.compile("\\A(?:/#!)?/(?:\\w{1,15})/status(?:es)?/(\\d+)\\z") + private val TWITTER_USER_PATTERN = Pattern.compile("\\A(?:/#!)?/(\\w{1,15})/?\\z") } } diff --git a/app/src/main/java/net/lacolaco/smileessence/activity/ManageAccountsActivity.kt b/app/src/main/java/net/lacolaco/smileessence/activity/ManageAccountsActivity.kt index 0f5924b8..e3ca1818 100644 --- a/app/src/main/java/net/lacolaco/smileessence/activity/ManageAccountsActivity.kt +++ b/app/src/main/java/net/lacolaco/smileessence/activity/ManageAccountsActivity.kt @@ -1,5 +1,6 @@ package net.lacolaco.smileessence.activity +import android.Manifest import android.content.ComponentName import android.content.Intent import android.content.pm.PackageManager @@ -17,7 +18,6 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.widget.AdapterView import android.widget.BaseAdapter import android.widget.ImageView import com.android.volley.toolbox.ImageRequest @@ -26,7 +26,6 @@ import kotlinx.android.synthetic.main.list_item_account.view.* import net.lacolaco.smileessence.Application import net.lacolaco.smileessence.R import net.lacolaco.smileessence.World -import net.lacolaco.smileessence.data.Account import net.lacolaco.smileessence.data.ImageCache import net.lacolaco.smileessence.util.launchBg import net.lacolaco.smileessence.util.launchUi @@ -44,9 +43,10 @@ class ManageAccountsActivity : AppCompatActivity() { // If the activity is started from launcher if (!intent.getBooleanExtra(INTENT_KEY_NOINIT, false)) { - if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED) - ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) + ActivityCompat.requestPermissions(this, + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) else if (Application.currentWorld != null) { // Skip this activity if app is already started @@ -59,9 +59,9 @@ class ManageAccountsActivity : AppCompatActivity() { setSupportActionBar(toolbar) listview_edit_list.adapter = adapter - listview_edit_list.setOnItemClickListener { adapterView: AdapterView<*>, view1: View, i: Int, l: Long -> - val account = adapter.getItem(i) - goToWorld(Application.getWorld(account.id)) + listview_edit_list.setOnItemClickListener { _, _, i, _ -> + val world = adapter.getItem(i) + goToWorld(World[world.id]) } } @@ -70,10 +70,11 @@ class ManageAccountsActivity : AppCompatActivity() { receivedIntent = Intent(intent) } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, + grantResults: IntArray) { when (requestCode) { REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION -> { - if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { if (Application.currentWorld != null) { goToWorld(Application.currentWorld!!) } @@ -86,14 +87,10 @@ class ManageAccountsActivity : AppCompatActivity() { } private fun goToWorld(world: World) { - // Continue the existing MainActivity - receivedIntent.component = ComponentName(this, MainActivity::class.java) - receivedIntent.putExtra(MainActivity.KEY_ORIGINAL_DATA, receivedIntent.data) - receivedIntent.putExtra(MainActivity.KEY_ORIGINAL_TYPE, receivedIntent.type) - val intent = Intent(this, MainActivity::class.java) - intent.data = Uri.parse("smileessence://mainactivity/?user_id=" + world.account.id) finish() world.mainActivityIntent = receivedIntent + val intent = Intent(this, MainActivity::class.java) + intent.data = Uri.parse("smileessence://mainactivity/?user_id=" + world.id) startActivity(intent) } @@ -132,7 +129,7 @@ class ManageAccountsActivity : AppCompatActivity() { if (resultCode == RESULT_OK) { launchBg { data!! - adapter.add(Account.register( + adapter.add(World.register( data.getStringExtra(OAuthActivity.KEY_TOKEN), data.getStringExtra(OAuthActivity.KEY_TOKEN_SECRET), data.getLongExtra(OAuthActivity.KEY_USER_ID, -1L), @@ -143,53 +140,61 @@ class ManageAccountsActivity : AppCompatActivity() { } private inner class EditAccountsAdapter : BaseAdapter() { - private val accounts: MutableList<Account> = ArrayList(Account.all()) + private val worlds: MutableList<World> = ArrayList(World.all()) - override fun getCount(): Int = accounts.size + override fun getCount(): Int = worlds.size - override fun getItem(position: Int): Account = accounts[position] + override fun getItem(position: Int) = worlds[position] - override fun getItemId(position: Int): Long = accounts[position].id + override fun getItemId(position: Int): Long = worlds[position].id override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view = convertView ?: layoutInflater.inflate(R.layout.list_item_account, parent, false) - val account = getItem(position) - - view.account_accent_color_view.setBackgroundColor(if (account.themeColor != -1) account.themeColor else Color.TRANSPARENT) - view.account_icon.setImageUrl(account.user.profileImageUrl, ImageCache.getImageLoader()) - view.account_text_view.text = "@${account.user.screenName}" + val view = convertView ?: layoutInflater.inflate(R.layout.list_item_account, parent, + false) + val world = getItem(position) + + if (world.themeColor != -1) + view.account_accent_color_view.setBackgroundColor(world.themeColor) + else + view.account_accent_color_view.setBackgroundColor(Color.TRANSPARENT) + view.account_icon.setImageUrl(world.user.profileImageUrl, ImageCache.getImageLoader()) + view.account_text_view.text = "@${world.user.screenName}" view.account_action_menu.clear() view.account_action_menu.add("Remove") { - confirm(R.string.dialog_confirm_clear_account, account.user.screenName) { - accounts.removeAt(position) + confirm(R.string.dialog_confirm_clear_account, world.user.screenName) { + worlds.removeAt(position) notifyDataSetChanged() // TODO: Account.unregister(account.id) // TODO: Stop the world } } view.account_action_menu.add("Add to homescreen") { - val shortcutIntent = Intent(Intent.ACTION_VIEW, Uri.parse("smileessence://mainactivity/?user_id=" + account.id)) - val intent = Intent("com.android.launcher.action.INSTALL_SHORTCUT") - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent) - - ImageCache.getRequestQueue().add(ImageRequest(account.user.profileImageUrl, { bitmap -> - val shortcutInfo = ShortcutInfoCompat.Builder(this@ManageAccountsActivity, "mainactivity-${account.id}") - .setShortLabel("@${account.user.screenName}") - .setIcon(IconCompat.createWithAdaptiveBitmap(bitmap)) + val data = Uri.parse("smileessence://mainactivity/?user_id=" + world.id) + val shortcutIntent = Intent(Intent.ACTION_VIEW, data) + + ImageCache.getRequestQueue().add(ImageRequest(world.user.profileImageUrl, { b -> + if (isDestroyed) + return@ImageRequest + val id = "mainactivity-${world.id}" + val shortcutInfo = ShortcutInfoCompat.Builder(this@ManageAccountsActivity, id) + .setShortLabel("@${world.user.screenName}") + .setIcon(IconCompat.createWithAdaptiveBitmap(b)) .setIntent(shortcutIntent) .build() ShortcutManagerCompat.requestPinShortcut(this@ManageAccountsActivity, shortcutInfo, null) - }, 0, 0, ImageView.ScaleType.CENTER, Bitmap.Config.ARGB_4444, { error -> error.printStackTrace() })) + }, 0, 0, ImageView.ScaleType.CENTER, Bitmap.Config.ARGB_4444, { error -> + error.printStackTrace() + })) } return view } - fun add(account: Account): Int = if (!accounts.contains(account)) { - accounts.add(account) + fun add(world: World): Int = if (!worlds.contains(world)) { + worlds.add(world) notifyDataSetChanged() - accounts.size - 1 + worlds.size - 1 } else { - accounts.indexOf(account) + worlds.indexOf(world) } } diff --git a/app/src/main/java/net/lacolaco/smileessence/activity/ManagePagesActivity.kt b/app/src/main/java/net/lacolaco/smileessence/activity/ManagePagesActivity.kt new file mode 100644 index 00000000..21b6462e --- /dev/null +++ b/app/src/main/java/net/lacolaco/smileessence/activity/ManagePagesActivity.kt @@ -0,0 +1,127 @@ +package net.lacolaco.smileessence.activity + +import android.os.Bundle +import android.support.design.widget.Snackbar +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.DividerItemDecoration +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.helper.ItemTouchHelper +import android.view.View +import android.view.ViewGroup + +import kotlinx.android.synthetic.main.activity_page_manage.* +import kotlinx.android.synthetic.main.list_item_page.view.* +import net.lacolaco.smileessence.R +import net.lacolaco.smileessence.World +import net.lacolaco.smileessence.data.PageInfo + +class ManagePagesActivity : AppCompatActivity() { + private val world by lazy { + World[intent.getLongExtra(INTENT_KEY_WORLD_ID, -1)] + } + private lateinit var pages: MutableList<PageInfo> + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setTheme(if (world.useDarkTheme) R.style.theme_dark else R.style.theme_light) + setContentView(R.layout.activity_page_manage) + setSupportActionBar(toolbar) + + pages = PageInfo.parseList(if (savedInstanceState == null) + PageInfo.stringifyList(world.pages) + else + savedInstanceState.getString(KEY_SAVED_PAGES) + ).toMutableList() + + fab.setOnClickListener { + val item = PageInfo.TweetsPageInfo("Tweets", listOf()) + pages.add(item) + recycler_view.adapter.notifyItemInserted(pages.size - 1) + openItemEditor(item) + } + val helper = ItemTouchHelper(object : ItemTouchHelper.Callback() { + override fun getMovementFlags(recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder): Int { + return makeMovementFlags(ItemTouchHelper.DOWN or ItemTouchHelper.UP, + ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) + } + + override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder): Boolean { + return (recycler_view.adapter as PagesAdapter) + .move(viewHolder.adapterPosition, target.adapterPosition) + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + (recycler_view.adapter as PagesAdapter).remove(viewHolder.adapterPosition) + } + }) + helper.attachToRecyclerView(recycler_view) + recycler_view.addItemDecoration(helper) + recycler_view.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL)) + recycler_view.layoutManager = LinearLayoutManager(this) + recycler_view.adapter = PagesAdapter() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putString(KEY_SAVED_PAGES, PageInfo.stringifyList(pages)) + } + + override fun finish() { + // MainActivity will restart itself on RESULT_OK + if (PageInfo.stringifyList(world.pages) == PageInfo.stringifyList(pages)) + setResult(RESULT_CANCELED) + else + setResult(RESULT_OK) + super.finish() + } + + private fun openItemEditor(item: PageInfo) { + // TODO + } + + companion object { + val INTENT_KEY_WORLD_ID = "WORLD_ID" + private val KEY_SAVED_PAGES = "SAVED_PAGES" + } + + private inner class PagesAdapter : RecyclerView.Adapter<PagesAdapter.ViewHolder>() { + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = pages[position] + val view = holder.itemView + view.page_kind_text_view.text = item.name + view.page_name_text_view.text = item.describe() + + view.setOnClickListener { + openItemEditor(item) + Snackbar.make(fab, "unya", Snackbar.LENGTH_SHORT).show() + } + } + + override fun getItemCount(): Int { + return pages.size + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(layoutInflater.inflate(R.layout.list_item_page, parent, false)) + } + + fun move(before: Int, after: Int): Boolean { + val item = pages.removeAt(before) + pages.add(after, item) + notifyItemMoved(before, after) + return true + } + + fun remove(position: Int) { + pages.removeAt(position) + notifyItemRemoved(position) + notifyItemRangeChanged(position, pages.size) + } + + private inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) + } +} + diff --git a/app/src/main/java/net/lacolaco/smileessence/data/Account.kt b/app/src/main/java/net/lacolaco/smileessence/data/Account.kt deleted file mode 100644 index bf016d66..00000000 --- a/app/src/main/java/net/lacolaco/smileessence/data/Account.kt +++ /dev/null @@ -1,113 +0,0 @@ -package net.lacolaco.smileessence.data - -import android.content.ContentValues -import net.lacolaco.smileessence.entity.User -import net.lacolaco.smileessence.logging.Logger -import twitter4j.Twitter -import twitter4j.TwitterFactory -import twitter4j.TwitterStream -import twitter4j.TwitterStreamFactory -import twitter4j.auth.AccessToken -import twitter4j.conf.ConfigurationBuilder - -class Account private constructor( - val id: Long, - private val oauthToken: String, - private val oauthTokenSecret: String, - private var screenName: String, - private var profileImageUrl: String, - private var themeIndex: Int, - var themeColor: Int -) { - val user: User by lazy { User.fetch(id) ?: User._makeSkeleton(id, screenName, profileImageUrl) } - - val twitter: Twitter - get() { - val cb = ConfigurationBuilder() - cb.setTweetModeExtended(true) - val twitter = TwitterFactory(cb.build()).instance - twitter.oAuthAccessToken = AccessToken(oauthToken, oauthTokenSecret) - return twitter - } - - val twitterStream: TwitterStream - get() { - val stream = TwitterStreamFactory().instance - stream.oAuthAccessToken = AccessToken(oauthToken, oauthTokenSecret) - return stream - } - - val useDarkTheme - get() = themeIndex == 0 - - fun syncProfileAndSaveIfNecessary() { - if (user.screenName != screenName || user.profileImageUrl != profileImageUrl) { - screenName = user.screenName - profileImageUrl = user.profileImageUrl - save() - } - } - - fun save() { - val values = ContentValues() - values.put("screen_name", screenName) - values.put("profile_image_url", profileImageUrl) - values.put("theme", themeIndex) - values.put("theme_color", themeColor) - DbHelper.db.update("accounts", values, "id = ?", arrayOf(id.toString())) - } - - companion object { - private val cache = LinkedHashMap<Long, Account>() - - operator fun get(i: Long) = - cache[i] ?: throw IllegalStateException("[BUG] Account with id == $i not found") - - fun all(): List<Account> = ArrayList(cache.values) - - fun load() { - if (cache.isNotEmpty()) - throw IllegalStateException("Account.load called twice") - DbHelper.db.query("accounts", null, null, null, null, null, null).use { cursor -> - while (cursor != null && cursor.moveToNext()) { - val id = cursor.getLong(cursor.getColumnIndexOrThrow("id")) - cache.put(id, Account( - id = id, - oauthToken = cursor.getString(cursor.getColumnIndexOrThrow("oauth_token")), - oauthTokenSecret = cursor.getString(cursor.getColumnIndexOrThrow("oauth_token_secret")), - screenName = cursor.getString(cursor.getColumnIndexOrThrow("screen_name")), - profileImageUrl = cursor.getString(cursor.getColumnIndexOrThrow("profile_image_url")), - themeIndex = cursor.getInt(cursor.getColumnIndexOrThrow("theme")), - themeColor = cursor.getInt(cursor.getColumnIndexOrThrow("theme_color")) - )) - } - } - Logger.error(cache.size) - } - - fun register(token: String, tokenSecret: String, userId: Long, screenName: String): Account { - val account = Account( - userId, - token, - tokenSecret, - screenName, - "https://abs.twimg.com/sticky/default_profile_images/default_profile.png", - 0, - 0) - - val values = ContentValues() - values.put("id", account.id) - values.put("oauth_token", account.oauthToken) - values.put("oauth_token_secret", account.oauthTokenSecret) - values.put("screen_name", account.screenName) - values.put("profile_image_url", account.profileImageUrl) - values.put("theme", account.themeIndex) - values.put("theme_color", account.themeColor) - DbHelper.db.insertOrThrow("accounts", null, values) - Logger.error(cache.size) - cache[userId] = account - - return account - } - } -} diff --git a/app/src/main/java/net/lacolaco/smileessence/data/DbHelper.kt b/app/src/main/java/net/lacolaco/smileessence/data/DbHelper.kt index 69ee9033..eb1b3a73 100644 --- a/app/src/main/java/net/lacolaco/smileessence/data/DbHelper.kt +++ b/app/src/main/java/net/lacolaco/smileessence/data/DbHelper.kt @@ -37,11 +37,18 @@ class DbHelper private constructor(context: Context) : ) """) } + + if (oldVersion < 5) { + // Add accounts.page_infos + db.execSQL(""" + ALTER TABLE accounts ADD COLUMN page_infos TEXT NOT NULL DEFAULT "[]" + """) + } } companion object { private val DATABASE_NAME = "main.db" - private val DATABASE_VERSION = 4 + private val DATABASE_VERSION = 5 // Lifetime is same as Application private lateinit var instance: DbHelper diff --git a/app/src/main/java/net/lacolaco/smileessence/data/Pages.kt b/app/src/main/java/net/lacolaco/smileessence/data/Pages.kt new file mode 100644 index 00000000..5bde7b1f --- /dev/null +++ b/app/src/main/java/net/lacolaco/smileessence/data/Pages.kt @@ -0,0 +1,137 @@ +package net.lacolaco.smileessence.data + +import com.beust.klaxon.* +import net.lacolaco.smileessence.logging.Logger +import net.lacolaco.smileessence.view.page.* + +sealed class PageInfo(val fragmentClass: Class<out PageFragment<*>>) { + abstract val name: String + abstract fun describe(): String + abstract fun toJson(): JsonObject + + class ComposePageInfo() : PageInfo(PostFragment::class.java) { + override val name = "Compose" + + override fun describe(): String { + return "" + } + + constructor(json: JsonObject) : this() + + override fun toJson() = json { + obj() + } + } + + class TweetsPageInfo(override val name: String, val patternStrings: List<String>) : + PageInfo(TweetsPageFragment::class.java) { + val patterns = patternStrings.map { Regex(it) } + + override fun describe(): String { + val sb = StringBuilder("Applied filters:") + patternStrings.forEach { sb.append("\n\t").append(it) } + return sb.toString() + } + + constructor(json: JsonObject) : this(json.string("name")!!, json.array<String>("patterns")!!.toList()) + + override fun toJson() = json { + obj("name" to name, "patterns" to array(patternStrings)) + } + } + + class MessagesPageInfo() : PageInfo(MessagesFragment::class.java) { + override val name = "Messages" + + override fun describe(): String { + return "" + } + + constructor(json: JsonObject) : this() + + override fun toJson() = json { + obj() + } + } + + class EventsPageInfo() : PageInfo(HistoryFragment::class.java) { + override val name = "Events" + + override fun describe(): String { + return "" + } + + constructor(json: JsonObject) : this() + + override fun toJson() = json { + obj() + } + } + + class SearchPageInfo(var query: String) : PageInfo(SearchFragment::class.java) { + override val name + get() = "Search" + + override fun describe(): String { + return "Search query:\n\t$query" + } + + constructor(json: JsonObject) : this(json.string("query")!!) + + override fun toJson() = json { + obj("query" to query) + } + } + + class ListPageInfo(var fullName: String?) : PageInfo(UserListFragment::class.java) { + override val name + get() = "List (${fullName ?: "<not selected>"})" + + override fun describe(): String { + return "" + } + + constructor(json: JsonObject) : this(json.string("full_name")) + + override fun toJson() = json { + obj("full_name" to fullName) + } + } + + // XXX: Yucks + companion object { + fun parseList(input: String): List<PageInfo> { + Logger.error("parsing: $input") + val parser = Parser() + val json = parser.parse(StringBuilder(input)) as JsonArray<*> + return json.map { + val data: JsonObject = (it as JsonObject).obj("data")!! + when (it.string("kind")) { + "compose" -> ComposePageInfo(data) + "tweets" -> TweetsPageInfo(data) + "messages" -> MessagesPageInfo(data) + "events" -> EventsPageInfo(data) + "search" -> SearchPageInfo(data) + "list" -> ListPageInfo(data) + else -> throw RuntimeException("invalid kind") + } + } + } + + fun stringifyList(input: List<PageInfo>): String { + return json { + array(input.map { + val kind = when (it) { + is ComposePageInfo -> "compose" + is TweetsPageInfo -> "tweets" + is MessagesPageInfo -> "messages" + is EventsPageInfo -> "events" + is SearchPageInfo -> "search" + is ListPageInfo -> "list" + } + obj("kind" to kind, "data" to it.toJson()) + }) + }.toJsonString() + } + } +} diff --git a/app/src/main/java/net/lacolaco/smileessence/entity/User.kt b/app/src/main/java/net/lacolaco/smileessence/entity/User.kt index acc10653..97ae4865 100644 --- a/app/src/main/java/net/lacolaco/smileessence/entity/User.kt +++ b/app/src/main/java/net/lacolaco/smileessence/entity/User.kt @@ -73,9 +73,6 @@ class User private constructor(override val id: Long, screenName: String, rawPro val aclogTimelineURL: String get() = String.format("https://aclog.rhe.jp/%s/timeline", screenName) - val formattedName: String - get() = screenName + " / " + name - // XXX val decoratedDescription: String get() { diff --git a/app/src/main/java/net/lacolaco/smileessence/preference/UserPreferenceHelper.kt b/app/src/main/java/net/lacolaco/smileessence/preference/UserPreferenceHelper.kt deleted file mode 100644 index 7d73423d..00000000 --- a/app/src/main/java/net/lacolaco/smileessence/preference/UserPreferenceHelper.kt +++ /dev/null @@ -1,56 +0,0 @@ -package net.lacolaco.smileessence.preference - -import android.content.SharedPreferences -import android.preference.PreferenceManager -import net.lacolaco.smileessence.Application - -class UserPreferenceHelper private constructor() { - private val preferences: SharedPreferences - get() = PreferenceManager.getDefaultSharedPreferences(Application.instance) - - operator fun get(key: Int, defaultValue: String): String { - return preferences.getString(getString(key), defaultValue) - } - - operator fun get(key: Int, defaultValue: Int): Int { - return try { - preferences.getInt(getString(key), defaultValue) - } catch (ex: ClassCastException) { - val ret = Integer.parseInt(get(key, defaultValue.toString())) - set(key, ret) - ret - } - - } - - operator fun get(key: Int, defaultValue: Boolean): Boolean { - return preferences.getBoolean(getString(key), defaultValue) - } - - operator fun set(key: Int, value: String): Boolean { - return preferences.edit() - .putString(getString(key), value) - .commit() - } - - operator fun set(key: Int, value: Int): Boolean { - return preferences.edit() - .putInt(getString(key), value) - .commit() - } - - operator fun set(key: Int, value: Boolean): Boolean { - return preferences.edit() - .putBoolean(getString(key), value) - .commit() - } - - private fun getString(resID: Int): String? { - return Application.instance.getString(resID) - - } - - companion object { - val instance = UserPreferenceHelper() - } -} diff --git a/app/src/main/java/net/lacolaco/smileessence/twitter/UserStreamListener.kt b/app/src/main/java/net/lacolaco/smileessence/twitter/UserStreamListener.kt index fcc209dd..2c042081 100644 --- a/app/src/main/java/net/lacolaco/smileessence/twitter/UserStreamListener.kt +++ b/app/src/main/java/net/lacolaco/smileessence/twitter/UserStreamListener.kt @@ -30,11 +30,11 @@ class UserStreamListener(private val world: World) : twitter4j.UserStreamListene // --------------------- Interface StatusListener --------------------- override fun onStatus(status: Status) { - val user = world.account.user + val user = world.user val tweet = Tweet.fromTwitter(status, user.id) world.addTweet(tweet) if (tweet.isRetweet) { - if (tweet.originalTweet.user.id == user.id) { + if (tweet.originalTweet.user == user) { addToHistory(Event(Event.EnumEvent.RETWEETED, tweet.user, tweet)) } } else if (tweet.mentions.contains(user.screenName)) { @@ -72,23 +72,23 @@ class UserStreamListener(private val world: World) : twitter4j.UserStreamListene override fun onFriendList(friendIds: LongArray) {} override fun onFavorite(source: twitter4j.User, target: twitter4j.User, favoritedStatus: Status) { - val tweet = Tweet.fromTwitter(favoritedStatus, world.account.id) + val tweet = Tweet.fromTwitter(favoritedStatus, world.id) tweet.addFavoriter(source.id) - if (User.fromTwitter(target) === world.account.user) { + if (User.fromTwitter(target) === world.user) { addToHistory(Event(Event.EnumEvent.FAVORITED, User.fromTwitter(source), tweet)) } } override fun onUnfavorite(source: twitter4j.User, target: twitter4j.User, unfavoritedStatus: twitter4j.Status) { - val tweet = Tweet.fromTwitter(unfavoritedStatus, world.account.id) + val tweet = Tweet.fromTwitter(unfavoritedStatus, world.id) tweet.removeFavoriter(source.id) - if (User.fromTwitter(target) === world.account.user) { + if (User.fromTwitter(target) === world.user) { addToHistory(Event(Event.EnumEvent.UNFAVORITED, User.fromTwitter(source), tweet)) } } override fun onFollow(source: twitter4j.User, followedUser: twitter4j.User) { - if (User.fromTwitter(followedUser) === world.account.user) { + if (User.fromTwitter(followedUser) === world.user) { addToHistory(Event(Event.EnumEvent.FOLLOWED, User.fromTwitter(source))) } } @@ -98,7 +98,7 @@ class UserStreamListener(private val world: World) : twitter4j.UserStreamListene override fun onDirectMessage(directMessage: twitter4j.DirectMessage) { val message = DirectMessage.fromTwitter(directMessage) world.addDirectMessage(message) - if (message.recipient === world.account.user) { + if (message.recipient === world.user) { addToHistory(Event(Event.EnumEvent.RECEIVE_MESSAGE, message.sender)) } } @@ -134,13 +134,13 @@ class UserStreamListener(private val world: World) : twitter4j.UserStreamListene override fun onUserDeletion(deletedUser: Long) {} override fun onBlock(source: twitter4j.User, blockedUser: twitter4j.User) { - if (User.fromTwitter(blockedUser) === world.account.user) { + if (User.fromTwitter(blockedUser) === world.user) { addToHistory(Event(Event.EnumEvent.BLOCKED, User.fromTwitter(source))) } } override fun onUnblock(source: twitter4j.User, unblockedUser: twitter4j.User) { - if (User.fromTwitter(unblockedUser) === world.account.user) { + if (User.fromTwitter(unblockedUser) === world.user) { addToHistory(Event(Event.EnumEvent.UNBLOCKED, User.fromTwitter(source))) } } diff --git a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Accounts.kt b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Accounts.kt index 2bc82511..90a66b07 100644 --- a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Accounts.kt +++ b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Accounts.kt @@ -8,7 +8,7 @@ import java.util.* // XXX fun World.getUserListsAsync() = bg { TwitterTaskException.wrap { - account.twitter.list().getUserLists(account.id) + twitter.list().getUserLists(id) }.map { it.fullName } } @@ -16,7 +16,7 @@ fun World.getBlocksIdsAsync() = bg { val idList = ArrayList<Long>() var cursor: Long = -1 while (cursor != 0L) { - val blocksIds = TwitterTaskException.wrap { account.twitter.getBlocksIDs(cursor) } + val blocksIds = TwitterTaskException.wrap { twitter.getBlocksIDs(cursor) } cursor = blocksIds.nextCursor idList.addAll(blocksIds.iDs.asList()) } @@ -27,7 +27,7 @@ fun World.getMutesIdsAsync() = bg { val idList = ArrayList<Long>() var cursor: Long = -1 while (cursor != 0L) { - val mutesIds = TwitterTaskException.wrap { account.twitter.getMutesIDs(cursor) } + val mutesIds = TwitterTaskException.wrap { twitter.getMutesIDs(cursor) } cursor = mutesIds.nextCursor idList.addAll(mutesIds.iDs.asList()) } diff --git a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Messages.kt b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Messages.kt index ea793cc3..bdf782a0 100644 --- a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Messages.kt +++ b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Messages.kt @@ -7,24 +7,24 @@ import net.lacolaco.smileessence.util.bg fun World.createMessageAsync(recipient: Long, text: String) = bg { DirectMessage.fromTwitter(TwitterTaskException.wrap { - account.twitter.directMessages().sendDirectMessage(recipient, text) + twitter.directMessages().sendDirectMessage(recipient, text) }) } fun World.destroyMessageAsync(id: Long) = bg { DirectMessage.fromTwitter(TwitterTaskException.wrap { - account.twitter.directMessages().destroyDirectMessage(id) + twitter.directMessages().destroyDirectMessage(id) }) } fun World.getReceivedMessagesAsync(sinceId: Long? = null, maxId: Long? = null) = bg { DirectMessage.fromTwitter(TwitterTaskException.wrap { - account.twitter.directMessages().getDirectMessages(makePaging(sinceId, maxId)) + twitter.directMessages().getDirectMessages(makePaging(sinceId, maxId)) }) } fun World.getSentMessagesAsync(sinceId: Long? = null, maxId: Long? = null) = bg { DirectMessage.fromTwitter(TwitterTaskException.wrap { - account.twitter.directMessages().getSentDirectMessages(makePaging(sinceId, maxId)) + twitter.directMessages().getSentDirectMessages(makePaging(sinceId, maxId)) }) } diff --git a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Searches.kt b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Searches.kt index e46166de..a44d40f6 100644 --- a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Searches.kt +++ b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Searches.kt @@ -8,18 +8,18 @@ import net.lacolaco.smileessence.util.bg import twitter4j.Query fun World.createSavedSearchAsync(query: String) = bg { - SavedSearch.fromTwitter(TwitterTaskException.wrap { account.twitter.savedSearches().createSavedSearch(query) }) + SavedSearch.fromTwitter(TwitterTaskException.wrap { twitter.savedSearches().createSavedSearch(query) }) } fun World.destroySavedSearchAsync(id: Long) = bg { - TwitterTaskException.wrap { account.twitter.savedSearches().destroySavedSearch(id) } + TwitterTaskException.wrap { twitter.savedSearches().destroySavedSearch(id) } } fun World.getSavedSearchesAsync() = bg { - TwitterTaskException.wrap { account.twitter.savedSearches().savedSearches }.map { SavedSearch.fromTwitter(it) } + TwitterTaskException.wrap { twitter.savedSearches().savedSearches }.map { SavedSearch.fromTwitter(it) } } // XXX fun World.doSearch(query: Query) = bg { - Tweet.fromTwitter(TwitterTaskException.wrap { account.twitter.search(query).tweets }, account.id) + Tweet.fromTwitter(TwitterTaskException.wrap { twitter.search(query).tweets }, id) } diff --git a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Timelines.kt b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Timelines.kt index a3fae8ed..80ea1640 100644 --- a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Timelines.kt +++ b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Timelines.kt @@ -18,26 +18,26 @@ fun makePaging(sinceId: Long? = null, maxId: Long? = null): twitter4j.Paging { fun World.getHomeTimelineAsync(sinceId: Long? = null, maxId: Long? = null) = bg { Tweet.fromTwitter(TwitterTaskException.wrap { - account.twitter.timelines().getHomeTimeline(makePaging(sinceId, maxId)) - }, account.id) + twitter.timelines().getHomeTimeline(makePaging(sinceId, maxId)) + }, id) } fun World.getMentionsTimelineAsync(sinceId: Long? = null, maxId: Long? = null) = bg { Tweet.fromTwitter(TwitterTaskException.wrap { - account.twitter.timelines().getMentionsTimeline(makePaging(sinceId, maxId)) - }, account.id) + twitter.timelines().getMentionsTimeline(makePaging(sinceId, maxId)) + }, id) } fun World.getUserTimelineAsync(id: Long, sinceId: Long? = null, maxId: Long? = null) = bg { Tweet.fromTwitter(TwitterTaskException.wrap { - account.twitter.timelines().getUserTimeline(id, makePaging(sinceId, maxId)) - }, account.id) + twitter.timelines().getUserTimeline(id, makePaging(sinceId, maxId)) + }, id) } // XXX: Use numeric ID fun World.getListTimelineAsync(listName: String, sinceId: Long? = null, maxId: Long? = null) = bg { val strings = listName.split("/".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() Tweet.fromTwitter(TwitterTaskException.wrap { - account.twitter.list().getUserListStatuses(strings[0], strings[1], makePaging(sinceId, maxId)) - }, account.id) + twitter.list().getUserListStatuses(strings[0], strings[1], makePaging(sinceId, maxId)) + }, id) } diff --git a/app/src/main/java/net/lacolaco/smileessence/twitter/task/TweetReactions.kt b/app/src/main/java/net/lacolaco/smileessence/twitter/task/TweetReactions.kt index 2fab3eca..b130edf4 100644 --- a/app/src/main/java/net/lacolaco/smileessence/twitter/task/TweetReactions.kt +++ b/app/src/main/java/net/lacolaco/smileessence/twitter/task/TweetReactions.kt @@ -6,13 +6,13 @@ import net.lacolaco.smileessence.twitter.TwitterTaskException import net.lacolaco.smileessence.util.bg fun World.retweetAsync(id: Long) = bg { - Tweet.fromTwitter(TwitterTaskException.wrap { account.twitter.tweets().retweetStatus(id) }, account.id) + Tweet.fromTwitter(TwitterTaskException.wrap { twitter.tweets().retweetStatus(id) }, id) } fun World.favoriteAsync(id: Long) = bg { - Tweet.fromTwitter(TwitterTaskException.wrap { account.twitter.favorites().createFavorite(id) }, account.id) + Tweet.fromTwitter(TwitterTaskException.wrap { twitter.favorites().createFavorite(id) }, id) } fun World.unfavoriteAsync(id: Long) = bg { - Tweet.fromTwitter(TwitterTaskException.wrap { account.twitter.favorites().destroyFavorite(id) }, account.id) + Tweet.fromTwitter(TwitterTaskException.wrap { twitter.favorites().destroyFavorite(id) }, id) } diff --git a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Tweets.kt b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Tweets.kt index da9de889..c6fe960b 100644 --- a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Tweets.kt +++ b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Tweets.kt @@ -22,14 +22,14 @@ fun World.getTweetAsync(id: Long, fetchAlways: Boolean = true) = async(CommonPoo } else { null } ?: TwitterTaskException.wrap { - Tweet.fromTwitter(account.twitter.tweets().showStatus(id), account.id) + Tweet.fromTwitter(twitter.tweets().showStatus(id), id) } } fun World.deleteTweetAsync(id: Long) = async(CommonPool) { Tweet.fromTwitter(TwitterTaskException.wrap { - account.twitter.tweets().destroyStatus(id) - }, account.id) + twitter.tweets().destroyStatus(id) + }, id) } // XXX @@ -78,8 +78,8 @@ fun World.createTweetAsync(update: StatusUpdate, mediaPath: String, resizeFlag: } } Tweet.fromTwitter(TwitterTaskException.wrap { - account.twitter.tweets().updateStatus(update) - }, account.id) + twitter.tweets().updateStatus(update) + }, id) } finally { if (tempFilePath != null) { File(tempFilePath).delete() diff --git a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Users.kt b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Users.kt index 98f08db2..6ba6bf12 100644 --- a/app/src/main/java/net/lacolaco/smileessence/twitter/task/Users.kt +++ b/app/src/main/java/net/lacolaco/smileessence/twitter/task/Users.kt @@ -6,33 +6,33 @@ import net.lacolaco.smileessence.twitter.TwitterTaskException import net.lacolaco.smileessence.util.bg fun World.getUserAsync(id: Long) = bg { - User.fromTwitter(TwitterTaskException.wrap { account.twitter.users().showUser(id) }) + User.fromTwitter(TwitterTaskException.wrap { twitter.users().showUser(id) }) } fun World.getUserAsync(screenName: String) = bg { - User.fromTwitter(TwitterTaskException.wrap { account.twitter.users().showUser(screenName) }) + User.fromTwitter(TwitterTaskException.wrap { twitter.users().showUser(screenName) }) } fun World.followAsync(id: Long) = bg { - User.fromTwitter(TwitterTaskException.wrap { account.twitter.friendsFollowers().createFriendship(id) }) + User.fromTwitter(TwitterTaskException.wrap { twitter.friendsFollowers().createFriendship(id) }) } fun World.unfollowAsync(id: Long) = bg { - User.fromTwitter(TwitterTaskException.wrap { account.twitter.friendsFollowers().destroyFriendship(id) }) + User.fromTwitter(TwitterTaskException.wrap { twitter.friendsFollowers().destroyFriendship(id) }) } fun World.blockAsync(id: Long) = bg { - User.fromTwitter(TwitterTaskException.wrap { account.twitter.users().createBlock(id) }) + User.fromTwitter(TwitterTaskException.wrap { twitter.users().createBlock(id) }) } fun World.unblockAsync(id: Long) = bg { - User.fromTwitter(TwitterTaskException.wrap { account.twitter.users().destroyBlock(id) }) + User.fromTwitter(TwitterTaskException.wrap { twitter.users().destroyBlock(id) }) } fun World.reportSpamAsync(id: Long) = bg { - User.fromTwitter(TwitterTaskException.wrap { account.twitter.spamReporting().reportSpam(id) }) + User.fromTwitter(TwitterTaskException.wrap { twitter.spamReporting().reportSpam(id) }) } fun World.getRelationshipAsync(id: Long) = bg { - account.twitter.friendsFollowers().showFriendship(account.twitter.id, id) + twitter.friendsFollowers().showFriendship(this@getRelationshipAsync.id, id) } diff --git a/app/src/main/java/net/lacolaco/smileessence/util/BitmapOptimizer.kt b/app/src/main/java/net/lacolaco/smileessence/util/BitmapOptimizer.kt deleted file mode 100644 index 9b2fa385..00000000 --- a/app/src/main/java/net/lacolaco/smileessence/util/BitmapOptimizer.kt +++ /dev/null @@ -1,96 +0,0 @@ -package net.lacolaco.smileessence.util - -import android.app.Activity -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Matrix -import android.media.ExifInterface -import android.os.Environment -import net.lacolaco.smileessence.logging.Logger - -import java.io.File -import java.io.FileOutputStream -import java.io.IOException -import java.io.OutputStream - -object BitmapOptimizer { - - // -------------------------- STATIC METHODS -------------------------- - - fun rotateImageByExif(activity: Activity, filePath: String): String { - var filePath = filePath - filePath = filePath.replace("file://", "") - val degree = getRotateDegreeFromExif(filePath) - if (degree > 0) { - var out: OutputStream? = null - var bitmap: Bitmap? = null - var rotatedImage: Bitmap? = null - try { - val mat = Matrix() - mat.postRotate(degree.toFloat()) - val opt = BitmapFactory.Options() - opt.inJustDecodeBounds = true - BitmapFactory.decodeFile(filePath, opt) - val width = 480 - var scale = 1 - if (opt.outWidth > width) { - scale = opt.outWidth / width + 2 - } - opt.inJustDecodeBounds = false - opt.inSampleSize = scale - bitmap = BitmapFactory.decodeFile(filePath, opt) - rotatedImage = Bitmap.createBitmap(bitmap, 0, 0, bitmap!!.width, bitmap.height, mat, true) - val file = File(filePath) - val outPath = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES).toString() + "/" + file.name - Logger.debug(outPath) - out = FileOutputStream(outPath) - rotatedImage!!.compress(Bitmap.CompressFormat.JPEG, 100, out) - return outPath - } catch (e: Exception) { - e.printStackTrace() - Logger.error(e) - } finally { - if (out != null) { - try { - out.close() - } catch (e: IOException) { - e.printStackTrace() - Logger.error(e) - } - - } - if (bitmap != null) { - bitmap.recycle() - } - if (rotatedImage != null) { - rotatedImage.recycle() - } - } - } - return filePath - } - - private fun getRotateDegreeFromExif(filePath: String): Int { - var degree = 0 - try { - val exifInterface = ExifInterface(filePath) - val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED) - when (orientation) { - ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90 - ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180 - ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270 - } - if (degree != 0) { - exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, "0") - exifInterface.saveAttributes() - } - } catch (e: IOException) { - degree = -1 - e.printStackTrace() - Logger.error(e) - } - - Logger.debug("Exif degree = " + degree) - return degree - } -} diff --git a/app/src/main/java/net/lacolaco/smileessence/util/BitmapURLTask.kt b/app/src/main/java/net/lacolaco/smileessence/util/BitmapURLTask.kt deleted file mode 100644 index 65ca7b53..00000000 --- a/app/src/main/java/net/lacolaco/smileessence/util/BitmapURLTask.kt +++ /dev/null @@ -1,36 +0,0 @@ -package net.lacolaco.smileessence.util - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.os.AsyncTask -import android.widget.ImageView - -import java.io.IOException -import java.io.InputStream -import java.net.URL - -class BitmapURLTask(private val url: String, private val imageView: ImageView) : AsyncTask<Void, Void, Bitmap>() { - override fun onPostExecute(bitmap: Bitmap?) { - if (bitmap != null) - imageView.setImageBitmap(bitmap) - } - - override fun doInBackground(vararg params: Void): Bitmap? { - var inputStream: InputStream? = null - try { - inputStream = URL(url).openStream() - val opt = BitmapFactory.Options() - opt.inPurgeable = true // GC可能にする - return BitmapFactory.decodeStream(inputStream, null, opt) - } catch (e: IOException) { - e.printStackTrace() - return null - } finally { - try { - inputStream?.close() - } catch (e: IOException) { - e.printStackTrace() - } - } - } -} diff --git a/app/src/main/java/net/lacolaco/smileessence/util/UIHelpers.kt b/app/src/main/java/net/lacolaco/smileessence/util/UIHelpers.kt index 99fd60eb..8d405eaf 100644 --- a/app/src/main/java/net/lacolaco/smileessence/util/UIHelpers.kt +++ b/app/src/main/java/net/lacolaco/smileessence/util/UIHelpers.kt @@ -4,6 +4,7 @@ import android.util.Log import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.android.UI import net.lacolaco.smileessence.World +import java.lang.ref.WeakReference private val exceptionHandler = CoroutineExceptionHandler { c, throwable -> val tag = "CoroutineContext:$c" @@ -26,3 +27,11 @@ fun launchBg(block: suspend CoroutineScope.() -> Unit) = fun World.getMainActivityOrCancel() = getMainActivity() ?: throw CancellationException("MainActivity is gone") + +class CancellableReference<out T> internal constructor(obj: T) { + private val ref = WeakReference(obj) + + fun get() = ref.get() ?: throw CancellationException("Object is gone") +} + +fun <T> ref(obj: T) = CancellableReference(obj) diff --git a/app/src/main/java/net/lacolaco/smileessence/view/MainFragmentPagerAdapter.kt b/app/src/main/java/net/lacolaco/smileessence/view/MainFragmentPagerAdapter.kt index add4c958..a5ff3ef6 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/MainFragmentPagerAdapter.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/MainFragmentPagerAdapter.kt @@ -4,38 +4,29 @@ import android.app.Fragment import android.app.FragmentManager import android.os.Bundle import android.support.v13.app.FragmentPagerAdapter -import android.support.v4.util.ArrayMap import android.support.v4.view.ViewPager import android.view.ViewGroup +import net.lacolaco.smileessence.World +import net.lacolaco.smileessence.data.PageInfo import net.lacolaco.smileessence.view.page.PageFragment +import java.util.* -class MainFragmentPagerAdapter(fm: FragmentManager, private val viewPager: ViewPager) : +class MainFragmentPagerAdapter(private val world: World, private val viewPager: ViewPager, fm: FragmentManager) : FragmentPagerAdapter(fm) { - private val pageTags = ArrayList<String>() - private val pages = ArrayMap<String, PageInfo>() + private val pages = Collections.unmodifiableList(world.pages.map { Page(it) }) - fun addPage(tag: String, clazz: Class<out PageFragment>, args: Bundle? = null) { - assert(pages[tag] == null) - pages[tag] = PageInfo(clazz, args) - pageTags.add(tag) - } - - fun getPageFragment(tag: String): PageFragment { - // XXX: This is really an ugly workaround. - return pages[tag]!!.fragment ?: instantiateItem(viewPager, getPageIndex(tag)) as PageFragment - } - - fun getPageIndex(tag: String): Int { - return pageTags.indexOf(tag) - } + @Suppress("UNCHECKED_CAST") + fun getPageFragmentAt(position: Int): PageFragment<*> = + pages[position].fragment ?: instantiateItem(viewPager, position) as PageFragment<*> override fun getItem(position: Int): Fragment { - val info = pages[pageTags[position]]!! + val args = Bundle() + args.putLong(PageFragment.KEY_WORLD_USER_ID, world.id) + args.putInt(PageFragment.KEY_PAGE_POSITION, position) + + val info = pages[position] assert(info.fragment == null) - val fragment = info.clazz.newInstance() - if (info.args != null) - fragment.arguments = info.args - return fragment + return info.pageInfo.fragmentClass.newInstance().apply { arguments = args } } override fun getCount(): Int { @@ -43,9 +34,9 @@ class MainFragmentPagerAdapter(fm: FragmentManager, private val viewPager: ViewP } override fun instantiateItem(container: ViewGroup, position: Int): Any { - val info = pages[pageTags[position]]!! + val info = pages[position] val fragment = super.instantiateItem(container, position) - info.fragment = fragment as PageFragment + info.fragment = fragment as PageFragment<*> return fragment } @@ -53,9 +44,5 @@ class MainFragmentPagerAdapter(fm: FragmentManager, private val viewPager: ViewP throw IllegalStateException("Fragments must not be destroyed by ViewPager") } - private class PageInfo( - val clazz: Class<out PageFragment>, - val args: Bundle?, - var fragment: PageFragment? = null - ) + private class Page(var pageInfo: PageInfo, var fragment: PageFragment<*>? = null) } diff --git a/app/src/main/java/net/lacolaco/smileessence/view/OnOffImageView.kt b/app/src/main/java/net/lacolaco/smileessence/view/OnOffImageView.kt new file mode 100644 index 00000000..f5d01715 --- /dev/null +++ b/app/src/main/java/net/lacolaco/smileessence/view/OnOffImageView.kt @@ -0,0 +1,24 @@ +package net.lacolaco.smileessence.view + +import android.content.Context +import android.graphics.drawable.Drawable +import android.support.v7.widget.AppCompatImageView +import android.util.AttributeSet +import net.lacolaco.smileessence.R + +class OnOffImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : + AppCompatImageView(context, attrs, defStyleAttr) { + private val onSrc: Drawable + private val offSrc: Drawable + + init { + val ta = context.obtainStyledAttributes(attrs, R.styleable.OnOffImageView) + offSrc = ta.getDrawable(R.styleable.OnOffImageView_src_off) + onSrc = ta.getDrawable(R.styleable.OnOffImageView_src_on) + ta.recycle() + } + + fun setState(isOn: Boolean) { + setImageDrawable(if (isOn) onSrc else offSrc) + } +} diff --git a/app/src/main/java/net/lacolaco/smileessence/view/Partials.kt b/app/src/main/java/net/lacolaco/smileessence/view/Partials.kt index 2e91dc4e..8df541d2 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/Partials.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/Partials.kt @@ -14,41 +14,39 @@ import net.lacolaco.smileessence.entity.Tweet import net.lacolaco.smileessence.entity.User import net.lacolaco.smileessence.twitter.task.getTweetAsync import net.lacolaco.smileessence.util.StringUtils -import net.lacolaco.smileessence.util.bg +import net.lacolaco.smileessence.util.launchBg import net.lacolaco.smileessence.view.adapter.UnorderedCustomListAdapter import net.lacolaco.smileessence.view.dialog.MessageDetailDialogFragment import net.lacolaco.smileessence.view.dialog.StatusDetailDialogFragment import net.lacolaco.smileessence.view.dialog.UserDetailDialogFragment -import net.lacolaco.smileessence.view.listener.ListItemClickListener object Partials { fun getTweetView(tweet: Tweet, world: World, activity: Activity, convertView: View?, expandEmbeddedTweets: Boolean = true): View { val view: View = convertView ?: activity.layoutInflater.inflate(R.layout.list_item_status, null) - view.setOnClickListener(ListItemClickListener(activity) { + view.setOnClickListener { DialogHelper.showDialog(activity, StatusDetailDialogFragment.newInstance(tweet)) - }) + } - (view as ColoredRelativeLayout).setAccentVisibility(tweet.user === world.account.user) + (view as ColoredRelativeLayout).setAccentVisibility(tweet.user === world.user) updateViewUser(tweet.originalTweet.user, activity, view) updateViewBody(tweet, world, activity, view) - updateViewFavorited(tweet, world, view) + updateTweetReactionsViews(tweet, world, view) updateViewEmbeddeds(tweet, world, activity, view, expandEmbeddedTweets) return view } - private fun updateViewEmbeddeds(tweet: Tweet, world: World, activity: Activity, view: View, expandEmbeddedTweets: Boolean) { - if (expandEmbeddedTweets) { + if (expandEmbeddedTweets && tweet.embeddedStatusIDs.isNotEmpty()) { val embeddedTweetsAdapter = object : UnorderedCustomListAdapter<Tweet>() { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { return Partials.getTweetView(getItem(position), world, activity, convertView, false) } } - bg { + launchBg { val tasks = tweet.embeddedStatusIDs.map { id -> world.getTweetAsync(id, false) } for (task in tasks) { val t = try { @@ -69,64 +67,77 @@ object Partials { } private fun updateViewUser(user: User, activity: Activity, view: View) { - val iconUrl = user.profileImageUrl - view.imageview_status_icon.setImageUrl(iconUrl, ImageCache.getImageLoader()) - view.imageview_status_icon.setOnClickListener { v -> + view.imageview_status_icon.setImageUrl(user.profileImageUrl, ImageCache.getImageLoader()) + view.imageview_status_icon.setOnClickListener { DialogHelper.showDialog(activity, UserDetailDialogFragment.newInstance(user)) } - - view.textview_status_header.text = user.formattedName + view.textview_status_header.text = "${user.screenName} / ${user.name}" } private fun updateViewBody(tweet: Tweet, world: World, activity: Activity, view: View) { view.textview_status_text.text = tweet.text - view.textview_status_footer.text = getFooterText(tweet) + var footerText = "${StringUtils.dateToString(tweet.originalTweet.createdAt)}" + footerText += " via ${Html.fromHtml(tweet.originalTweet.source)}" + if (tweet.isRetweet) + footerText += " (RT by ${tweet.user.screenName})" + view.textview_status_footer.text = footerText val typedView = view as ColoredRelativeLayout when { tweet.isRetweet -> typedView.setHighlight(2) - tweet.mentions.contains(world.account.user.screenName) -> typedView.setHighlight(1) + tweet.mentions.contains(world.user.screenName) -> typedView.setHighlight(1) else -> typedView.setHighlight(0) } } - private fun updateViewFavorited(tweet: Tweet, world: World, view: View) { - view.imageview_status_favorited.visibility = if (tweet.isFavoritedBy(world.account.id)) View.VISIBLE else View.GONE - } - - private fun getFooterText(tweet: Tweet): String { - val builder = StringBuilder() - if (tweet.isRetweet) { - builder - .append("(RT: ") - .append(tweet.user.screenName) - .append(") ") + private fun updateTweetReactionsViews(tweet: Tweet, world: World, view: View) { + if (tweet.retweetCount > 0) { + view.tweet_retweet_count.visibility = View.VISIBLE + view.tweet_retweet_count.text = tweet.retweetCount.toString() + view.imageview_status_retweeted.visibility = View.VISIBLE + view.imageview_status_retweeted.setState(tweet.isRetweetedBy(world.id)) + } else { + view.tweet_retweet_count.visibility = View.INVISIBLE + view.imageview_status_retweeted.visibility = View.INVISIBLE + } + if (tweet.favoriteCount > 0) { + view.tweet_favorite_count.visibility = View.VISIBLE + view.tweet_favorite_count.text = tweet.favoriteCount.toString() + view.imageview_status_favorited.visibility = View.VISIBLE + view.imageview_status_favorited.setState(tweet.isFavoritedBy(world.id)) + } else { + view.tweet_favorite_count.visibility = View.INVISIBLE + view.imageview_status_favorited.visibility = View.INVISIBLE } - builder.append(StringUtils.dateToString(tweet.originalTweet.createdAt)) - builder.append(" via ") - builder.append(Html.fromHtml(tweet.originalTweet.source)) - return builder.toString() } fun getDirectMessageView(directMessage: DirectMessage, world: World, activity: Activity, convertView: View?): View { val view = convertView ?: activity.layoutInflater.inflate(R.layout.list_item_status, null) - view.setOnClickListener(ListItemClickListener(activity) { DialogHelper.showDialog(activity, MessageDetailDialogFragment.newInstance(directMessage)) }) + view.setOnClickListener { + DialogHelper.showDialog(activity, MessageDetailDialogFragment.newInstance(directMessage)) + } view.imageview_status_favorited.visibility = View.GONE //view.textview_status_header.setTextColor(getStyledColor(activity, world, R.attr.color_message_text_header)) view.setBackgroundColor(getStyledColor(activity, world, R.attr.color_message_bg_normal)) - (view as ColoredRelativeLayout).setAccentVisibility(directMessage.sender === world.account.user) + (view as ColoredRelativeLayout).setAccentVisibility(directMessage.sender === world.user) updateViewUser(directMessage.sender, activity, view) - updateViewBody(directMessage, world, view) + + view.textview_status_text.text = directMessage.text + var footerText = StringUtils.dateToString(directMessage.createdAt) + if (directMessage.sender == world.user) { + footerText += " to @${directMessage.recipient.screenName}" + } + view.textview_status_footer.text = footerText return view } private fun getStyledColor(context: Context, world: World, resId: Int): Int { - val themeresid = if (world.account.useDarkTheme) R.style.theme_dark else R.style.theme_light + val themeresid = if (world.useDarkTheme) R.style.theme_dark else R.style.theme_light val array = context.obtainStyledAttributes(themeresid, arrayOf(resId).toIntArray()) val color = array.getColor(0, 0) array.recycle() @@ -137,18 +148,6 @@ object Partials { } } - private fun getFooterText(directMessage: DirectMessage, world: World): String { - val builder = StringBuilder() - builder.append(StringUtils.dateToString(directMessage.createdAt)) - if (directMessage.sender == world.account.user) { - builder.append(" to @").append(directMessage.recipient.screenName) - } - return builder.toString() - } - - private fun updateViewBody(directMessage: DirectMessage, world: World, view: View) { - view.textview_status_text.text = directMessage.text - view.textview_status_footer.text = getFooterText(directMessage, world) } } diff --git a/app/src/main/java/net/lacolaco/smileessence/view/SettingFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/SettingFragment.kt index 5f3b74c0..f7c06fcb 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/SettingFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/SettingFragment.kt @@ -1,29 +1,16 @@ package net.lacolaco.smileessence.view import android.content.Intent -import android.content.SharedPreferences -import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.os.Bundle -import android.preference.ListPreference import android.preference.Preference import android.preference.PreferenceFragment import android.support.v7.app.AlertDialog import kotlinx.android.synthetic.main.dialog_app_info.view.* -import net.lacolaco.smileessence.Application import net.lacolaco.smileessence.BuildConfig import net.lacolaco.smileessence.R import net.lacolaco.smileessence.activity.LicenseActivity -class SettingFragment : PreferenceFragment(), OnSharedPreferenceChangeListener, Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener { - override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean { - if (preference.key.contentEquals(getString(R.string.key_setting_theme))) { - Application.toast(R.string.notice_theme_changed) - } - return true - } - - // --------------------- Interface OnPreferenceClickListener --------------------- - +class SettingFragment : PreferenceFragment(), Preference.OnPreferenceClickListener { override fun onPreferenceClick(preference: Preference): Boolean { val key = preference.key if (key.contentEquals(getString(R.string.key_setting_application_information))) { @@ -33,20 +20,9 @@ class SettingFragment : PreferenceFragment(), OnSharedPreferenceChangeListener, return true } - // --------------------- Interface OnSharedPreferenceChangeListener --------------------- - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { - setSummaryCurrentValue() - } - - // ------------------------ OVERRIDE METHODS ------------------------ - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) addPreferencesFromResource(R.xml.setting) - val themePreference = findPreference(R.string.key_setting_theme) as ListPreference - themePreference.summary = themePreference.entry - themePreference.onPreferenceChangeListener = this val appInfoPreference = findPreference(R.string.key_setting_application_information) appInfoPreference.onPreferenceClickListener = this appInfoPreference.summary = BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")" @@ -54,20 +30,6 @@ class SettingFragment : PreferenceFragment(), OnSharedPreferenceChangeListener, license.onPreferenceClickListener = this } - override fun onPause() { - super.onPause() - val sharedPreferences = preferenceScreen.sharedPreferences - sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) - } - - override fun onResume() { - super.onResume() - val sharedPreferences = preferenceScreen.sharedPreferences - sharedPreferences.registerOnSharedPreferenceChangeListener(this) - } - - // -------------------------- OTHER METHODS -------------------------- - private fun findPreference(preferenceResID: Int): Preference { return findPreference(getString(preferenceResID)) } @@ -81,9 +43,4 @@ class SettingFragment : PreferenceFragment(), OnSharedPreferenceChangeListener, .setView(contentView) .show() } - - private fun setSummaryCurrentValue() { - val themePreference = findPreference(R.string.key_setting_theme) as ListPreference - themePreference.summary = themePreference.entry - } } diff --git a/app/src/main/java/net/lacolaco/smileessence/view/adapter/EventListAdapter.kt b/app/src/main/java/net/lacolaco/smileessence/view/adapter/EventListAdapter.kt deleted file mode 100644 index 4312c696..00000000 --- a/app/src/main/java/net/lacolaco/smileessence/view/adapter/EventListAdapter.kt +++ /dev/null @@ -1,34 +0,0 @@ -package net.lacolaco.smileessence.view.adapter - -import android.app.Activity -import android.view.View -import android.view.ViewGroup -import kotlinx.android.synthetic.main.list_item_status.view.* -import net.lacolaco.smileessence.R -import net.lacolaco.smileessence.World -import net.lacolaco.smileessence.data.ImageCache -import net.lacolaco.smileessence.entity.Event -import net.lacolaco.smileessence.util.StringUtils -import net.lacolaco.smileessence.view.DialogHelper -import net.lacolaco.smileessence.view.dialog.UserDetailDialogFragment - -class EventListAdapter(private val world: World, private val activity: Activity) : CustomListAdapter<Event>() { - override val list: List<Event> - get() = world.events - - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view = convertView ?: activity.layoutInflater.inflate(R.layout.list_item_status, parent, false) - val event = getItem(position) - - view.imageview_status_icon.setImageUrl(event.source.profileImageUrl, ImageCache.getImageLoader()) - view.textview_status_header.text = event.formattedString - view.textview_status_text.text = event.targetObject?.text ?: "" - view.textview_status_footer.text = StringUtils.dateToString(event.createdAt) - view.imageview_status_favorited.visibility = View.GONE - view.setOnClickListener { - DialogHelper.showDialog(activity, UserDetailDialogFragment.newInstance(event.source)) - } - - return view - } -} diff --git a/app/src/main/java/net/lacolaco/smileessence/view/adapter/MessageListAdapter.kt b/app/src/main/java/net/lacolaco/smileessence/view/adapter/MessageListAdapter.kt index 6d80486f..c2c6a1cc 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/adapter/MessageListAdapter.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/adapter/MessageListAdapter.kt @@ -7,12 +7,13 @@ import net.lacolaco.smileessence.World import net.lacolaco.smileessence.entity.DirectMessage import net.lacolaco.smileessence.view.Partials -class MessageListAdapter(private val activity: Activity, private val world: World) : OrderedCustomListAdapter<DirectMessage>() { +class MessageListAdapter(private val activity: Activity, private val world: World) : + OrderedCustomListAdapter<DirectMessage>() { val lastID: Long - get() = if (count > 0) getItem(count - 1).id else -1 + get() = getItem(count - 1).id val topID: Long - get() = if (count > 0) getItem(0).id else -1 + get() = getItem(0).id override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { return Partials.getDirectMessageView(getItem(position), world, activity, convertView) diff --git a/app/src/main/java/net/lacolaco/smileessence/view/dialog/MessageDetailDialogFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/dialog/MessageDetailDialogFragment.kt index 58e26cc0..93639ac2 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/dialog/MessageDetailDialogFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/dialog/MessageDetailDialogFragment.kt @@ -114,8 +114,6 @@ class MessageDetailDialogFragment : StackableDialogFragment() { view.detail_dialog_divider_top.visibility = View.GONE view.button_status_detail_retweet.visibility = View.GONE view.button_status_detail_favorite.visibility = View.GONE - view.image_status_detail_fav_count.visibility = View.GONE - view.image_status_detail_rt_count.visibility = View.GONE val adapter = MessageListAdapter(activity, world) view.listview_status_detail_reply_to.adapter = adapter diff --git a/app/src/main/java/net/lacolaco/smileessence/view/dialog/StatusDetailDialogFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/dialog/StatusDetailDialogFragment.kt index 78c67ce6..a4be3a37 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/dialog/StatusDetailDialogFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/dialog/StatusDetailDialogFragment.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import kotlinx.android.synthetic.main.dialog_status_detail.view.* +import kotlinx.android.synthetic.main.menu_item_simple_text.view.* import net.lacolaco.smileessence.R import net.lacolaco.smileessence.activity.MainActivity import net.lacolaco.smileessence.command.Command @@ -29,9 +30,10 @@ class StatusDetailDialogFragment : StackableDialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val found = Tweet.fetch(arguments.getLong(KEY_STATUS_ID)) + val id = arguments.getLong(KEY_STATUS_ID) + val found = Tweet.fetch(id) if (found == null) { - world.notifyError(R.string.notice_error_show_status) + world.notifyError("Tweet id=$id not found") dismiss() return } @@ -45,7 +47,8 @@ class StatusDetailDialogFragment : StackableDialogFragment() { statusHeader.isClickable = false view.setBackgroundColor((statusHeader.background as ColorDrawable).color) - updateViewReactions(view) + view.button_status_detail_favorite.setState(tweet.isFavoritedBy(world.id)) + view.button_status_detail_retweet.setState(tweet.isRetweetedBy(world.id)) updateViewButtons(view) setupViewMenu(view) @@ -72,28 +75,6 @@ class StatusDetailDialogFragment : StackableDialogFragment() { return view } - private fun updateViewReactions(view: View) { - if (tweet.favoriteCount > 0) { - view.textview_status_detail_fav_count.text = Integer.toString(tweet.favoriteCount) - view.image_status_detail_fav_count.visibility = View.VISIBLE - view.textview_status_detail_fav_count.visibility = View.VISIBLE - } else { - view.image_status_detail_fav_count.visibility = View.GONE - view.textview_status_detail_fav_count.visibility = View.GONE - } - view.button_status_detail_favorite.setState(tweet.isFavoritedBy(world.account.id)) - - if (tweet.retweetCount > 0) { - view.textview_status_detail_rt_count.text = Integer.toString(tweet.retweetCount) - view.image_status_detail_rt_count.visibility = View.VISIBLE - view.textview_status_detail_rt_count.visibility = View.VISIBLE - } else { - view.image_status_detail_rt_count.visibility = View.GONE - view.textview_status_detail_rt_count.visibility = View.GONE - } - view.button_status_detail_retweet.setState(tweet.isRetweetedBy(world.account.id)) - } - private fun updateViewButtons(view: View) { view.button_status_detail_reply.setOnClickListener { val originalTweet = tweet.originalTweet @@ -102,7 +83,7 @@ class StatusDetailDialogFragment : StackableDialogFragment() { builder.append("@${originalTweet.user.screenName} ") for (screenName in originalTweet.mentions) { - if (screenName != world.account.user.screenName) + if (screenName != world.user.screenName) builder.append("@$screenName ") } val text = builder.toString() @@ -111,12 +92,11 @@ class StatusDetailDialogFragment : StackableDialogFragment() { (activity as MainActivity).openPostPageAndReplyTo(originalTweet, text) } view.button_status_detail_retweet.setOnClickListener { - val account = world.account - confirm({ - if (tweet.isRetweetedBy(account.id)) { + confirm(R.string.dialog_confirm_commands) { + if (tweet.isRetweetedBy(world.id)) { launchUi { try { - world.deleteTweetAsync(tweet.getRetweetIdBy(account.id)).await() + world.deleteTweetAsync(tweet.getRetweetIdBy(world.id)).await() world.notify(R.string.notice_status_delete_succeeded) updateViewButtons(view) } catch (e: TwitterTaskException) { @@ -126,7 +106,7 @@ class StatusDetailDialogFragment : StackableDialogFragment() { } else { launchUi { try { - world.retweetAsync(tweet.getRetweetIdBy(account.id)).await() + world.retweetAsync(tweet.getRetweetIdBy(world.id)).await() world.notify(R.string.notice_retweet_succeeded) updateViewButtons(view) } catch (e: TwitterTaskException) { @@ -134,11 +114,10 @@ class StatusDetailDialogFragment : StackableDialogFragment() { } } } - }) + } } view.button_status_detail_favorite.setOnClickListener { - val account = world.account - val favoriting = !tweet.isFavoritedBy(account.id) + val favoriting = !tweet.isFavoritedBy(world.id) launchUi { try { @@ -157,9 +136,12 @@ class StatusDetailDialogFragment : StackableDialogFragment() { } } } - view.button_status_detail_delete.visibility = if (tweet.originalTweet.user === world.account.user) View.VISIBLE else View.GONE + if (tweet.originalTweet.user === world.user) + view.button_status_detail_delete.visibility = View.VISIBLE + else + view.button_status_detail_delete.visibility = View.GONE view.button_status_detail_delete.setOnClickListener { - confirm({ + confirm(R.string.dialog_confirm_commands) { launchBg { try { world.deleteTweetAsync(tweet.originalTweet.id).await() @@ -169,7 +151,7 @@ class StatusDetailDialogFragment : StackableDialogFragment() { } } dismiss() - }) + } } view.button_status_detail_menu.setOnClickListener { val popup = PopupMenu(activity, view.button_status_detail_menu) @@ -216,7 +198,7 @@ class StatusDetailDialogFragment : StackableDialogFragment() { val adapter = object : UnorderedCustomListAdapter<Command>(commands) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val itemView = convertView ?: activity.layoutInflater.inflate(R.layout.menu_item_simple_text, parent, false) - TODO("not implemented") //To change body of created functions use File | Settings | File Templates.itemView.list_item_textview.text = getItem(position).text + itemView.list_item_textview.text = getItem(position).text return itemView } } @@ -231,10 +213,6 @@ class StatusDetailDialogFragment : StackableDialogFragment() { } } - private fun confirm(onYes: () -> Unit) { - confirm(R.string.dialog_confirm_commands, onOk = onYes) - } - companion object { private val KEY_STATUS_ID = "status_id" diff --git a/app/src/main/java/net/lacolaco/smileessence/view/dialog/UserDetailDialogFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/dialog/UserDetailDialogFragment.kt index d4bea45f..0cf2ea34 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/dialog/UserDetailDialogFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/dialog/UserDetailDialogFragment.kt @@ -229,8 +229,7 @@ class UserDetailDialogFragment : StackableDialogFragment() { } private fun updateRelationship() { - val account = world.account - if (user === account.user) { + if (user === world.user) { textview_user_detail_followed.setText(R.string.user_detail_followed_is_me) button_user_detail_follow.visibility = View.GONE } else { diff --git a/app/src/main/java/net/lacolaco/smileessence/view/listener/ListItemClickListener.kt b/app/src/main/java/net/lacolaco/smileessence/view/listener/ListItemClickListener.kt deleted file mode 100644 index 8a680eea..00000000 --- a/app/src/main/java/net/lacolaco/smileessence/view/listener/ListItemClickListener.kt +++ /dev/null @@ -1,21 +0,0 @@ -package net.lacolaco.smileessence.view.listener - -import android.app.Activity -import android.graphics.drawable.ColorDrawable -import android.support.v4.content.ContextCompat -import android.view.View -import net.lacolaco.smileessence.R -import net.lacolaco.smileessence.util.UIHandler - -// XXX -class ListItemClickListener(private val activity: Activity, private val callback: () -> Unit) : View.OnClickListener { - override fun onClick(v: View) { - val currentBgColor = (v.background as ColorDrawable).color - v.setBackgroundColor(ContextCompat.getColor(activity, R.color.metro_blue)) - v.invalidate() - UIHandler().post { - v.setBackgroundColor(currentBgColor) - callback() - } - } -} diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/CustomListFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/CustomListFragment.kt index 4e3fbc30..848fde52 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/CustomListFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/CustomListFragment.kt @@ -5,16 +5,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.AbsListView -import android.widget.ListView import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection import kotlinx.android.synthetic.main.fragment_list.* import kotlinx.android.synthetic.main.fragment_list.view.* import net.lacolaco.smileessence.R +import net.lacolaco.smileessence.data.PageInfo import net.lacolaco.smileessence.view.adapter.CustomListAdapter -abstract class CustomListFragment<out T : CustomListAdapter<*>> : PageFragment(), - AbsListView.OnScrollListener { +abstract class CustomListFragment<out TI : PageInfo, out T : CustomListAdapter<*>> : + PageFragment<TI>(), AbsListView.OnScrollListener { protected abstract val adapter: T override fun onScrollStateChanged(absListView: AbsListView, scrollState: Int) { @@ -31,7 +31,8 @@ abstract class CustomListFragment<out T : CustomListAdapter<*>> : PageFragment() override fun onScroll(absListView: AbsListView, i: Int, i2: Int, i3: Int) {} - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, + savedInstanceState: Bundle?): View { return inflater.inflate(R.layout.fragment_list, container, false) } @@ -58,11 +59,6 @@ abstract class CustomListFragment<out T : CustomListAdapter<*>> : PageFragment() protected open fun onSwipeUp(view: SwipyRefreshLayout) {} - @Deprecated("dontuse") - protected open fun getListView(page: View): ListView { - return page.fragment_list_listview - } - protected fun updateListViewWithNotice(addedToTop: Boolean) { if (isDetached) return diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/HistoryFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/HistoryFragment.kt index 20831213..e2e0a273 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/HistoryFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/HistoryFragment.kt @@ -1,15 +1,26 @@ package net.lacolaco.smileessence.view.page +import android.app.Activity import android.os.Bundle import android.view.View -import net.lacolaco.smileessence.view.adapter.EventListAdapter +import android.view.ViewGroup +import kotlinx.android.synthetic.main.list_item_status.view.* +import net.lacolaco.smileessence.R +import net.lacolaco.smileessence.World +import net.lacolaco.smileessence.data.ImageCache +import net.lacolaco.smileessence.data.PageInfo +import net.lacolaco.smileessence.entity.Event +import net.lacolaco.smileessence.util.StringUtils +import net.lacolaco.smileessence.view.DialogHelper +import net.lacolaco.smileessence.view.adapter.CustomListAdapter +import net.lacolaco.smileessence.view.dialog.UserDetailDialogFragment -class HistoryFragment : CustomListFragment<EventListAdapter>() { +class HistoryFragment : CustomListFragment<PageInfo.EventsPageInfo, HistoryFragment.EventListAdapter>() { override val adapter by lazy { EventListAdapter(world, activity) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - world.addEventNotifier(this) { adapter.update() } // XXX + world.addEventNotifier(this) { adapter.update() } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -17,5 +28,24 @@ class HistoryFragment : CustomListFragment<EventListAdapter>() { setSwipeRefreshEnabled(false) } - override fun refresh() {} + class EventListAdapter(private val world: World, private val activity: Activity) : CustomListAdapter<Event>() { + override val list: List<Event> + get() = world.events.reversed() + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: activity.layoutInflater.inflate(R.layout.list_item_status, parent, false) + val event = getItem(position) + + view.imageview_status_icon.setImageUrl(event.source.profileImageUrl, ImageCache.getImageLoader()) + view.textview_status_header.text = event.formattedString + view.textview_status_text.text = event.targetObject?.text ?: "" + view.textview_status_footer.text = StringUtils.dateToString(event.createdAt) + view.imageview_status_favorited.visibility = View.GONE + view.setOnClickListener { + DialogHelper.showDialog(activity, UserDetailDialogFragment.newInstance(event.source)) + } + + return view + } + } } diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/HomeFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/HomeFragment.kt deleted file mode 100644 index 169f3440..00000000 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/HomeFragment.kt +++ /dev/null @@ -1,61 +0,0 @@ -package net.lacolaco.smileessence.view.page - -import android.os.Bundle -import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout -import kotlinx.coroutines.experimental.Deferred -import net.lacolaco.smileessence.R -import net.lacolaco.smileessence.entity.Tweet -import net.lacolaco.smileessence.twitter.TwitterTaskException -import net.lacolaco.smileessence.twitter.task.getHomeTimelineAsync -import net.lacolaco.smileessence.util.launchUi -import net.lacolaco.smileessence.view.adapter.TimelineAdapter - -class HomeFragment : CustomListFragment<TimelineAdapter>() { - override val adapter by lazy { TimelineAdapter(activity, world) } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - world.addTimeline(this) { tweet -> - adapter.add(tweet) - adapter.update() - } - - refresh() - - // TODO: Check for streaming API connection, and disable SwipeRefreshLayout as necessary - } - - override fun refresh() { - runRefreshTask(world.getHomeTimelineAsync(), { adapter.updateForce() }) - } - - override fun onSwipeDown(view: SwipyRefreshLayout) { - if (world.isStreaming) { - updateListViewWithNotice(true) - view.isRefreshing = false - } else { - runRefreshTask(world.getHomeTimelineAsync(sinceId = adapter.topID)) { - updateListViewWithNotice(true) - view.isRefreshing = false - } - } - } - - override fun onSwipeUp(view: SwipyRefreshLayout) { - runRefreshTask(world.getHomeTimelineAsync(maxId = adapter.lastID - 1)) { - updateListViewWithNotice(false) - view.isRefreshing = false - } - } - - private fun runRefreshTask(task: Deferred<List<Tweet>>, onFinish: () -> Unit) = launchUi { - try { - val tweets = task.await() - world.addTweetAll(tweets) - } catch (e: TwitterTaskException) { - world.notifyError(R.string.notice_error_get_home) - } - onFinish() - } -} diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/MentionsFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/MentionsFragment.kt deleted file mode 100644 index 02f567a7..00000000 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/MentionsFragment.kt +++ /dev/null @@ -1,64 +0,0 @@ -package net.lacolaco.smileessence.view.page - -import android.os.Bundle -import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout -import kotlinx.coroutines.experimental.Deferred -import net.lacolaco.smileessence.R -import net.lacolaco.smileessence.data.ExtractionWord -import net.lacolaco.smileessence.entity.Tweet -import net.lacolaco.smileessence.twitter.TwitterTaskException -import net.lacolaco.smileessence.twitter.task.getMentionsTimelineAsync -import net.lacolaco.smileessence.util.launchUi -import net.lacolaco.smileessence.view.adapter.TimelineAdapter - -class MentionsFragment : CustomListFragment<TimelineAdapter>() { - override val adapter by lazy { TimelineAdapter(activity, world) } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - world.addTimeline(this) { tweet -> - if (tweet.mentions.contains(world.account.user.screenName)) { - adapter.add(tweet) - adapter.update() - } else { - for (word in ExtractionWord.cached()) { - if (word.pattern.matcher(tweet.originalTweet.text).find()) { - adapter.add(tweet) - adapter.update() - } - } - } - } - - refresh() - } - - override fun refresh() { - runRefreshTask(world.getMentionsTimelineAsync(), { adapter.updateForce() }) - } - - override fun onSwipeDown(view: SwipyRefreshLayout) { - runRefreshTask(world.getMentionsTimelineAsync(sinceId = adapter.topID)) { - updateListViewWithNotice(true) - view.isRefreshing = false - } - } - - override fun onSwipeUp(view: SwipyRefreshLayout) { - runRefreshTask(world.getMentionsTimelineAsync(maxId = adapter.lastID - 1)) { - updateListViewWithNotice(false) - view.isRefreshing = false - } - } - - private fun runRefreshTask(task: Deferred<List<Tweet>>, onFinish: () -> Unit) = launchUi { - try { - val tweets = task.await() - world.addTweetAll(tweets) - } catch (e: TwitterTaskException) { - world.notifyError(R.string.notice_error_get_mentions) - } - onFinish() - } -} diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/MessagesFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/MessagesFragment.kt index be4c5ce1..ac4ca5db 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/MessagesFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/MessagesFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout import kotlinx.coroutines.experimental.Deferred import net.lacolaco.smileessence.R +import net.lacolaco.smileessence.data.PageInfo import net.lacolaco.smileessence.entity.DirectMessage import net.lacolaco.smileessence.twitter.TwitterTaskException import net.lacolaco.smileessence.twitter.task.getReceivedMessagesAsync @@ -11,9 +12,15 @@ import net.lacolaco.smileessence.twitter.task.getSentMessagesAsync import net.lacolaco.smileessence.util.launchUi import net.lacolaco.smileessence.view.adapter.MessageListAdapter -class MessagesFragment : CustomListFragment<MessageListAdapter>() { +// TODO: Needs rework +class MessagesFragment : CustomListFragment<PageInfo.MessagesPageInfo, MessageListAdapter>() { override val adapter by lazy { MessageListAdapter(activity, world) } + override fun refresh() { + runRefreshTask(world.getReceivedMessagesAsync()) { adapter.updateForce() } + runRefreshTask(world.getSentMessagesAsync()) { adapter.updateForce() } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -25,34 +32,29 @@ class MessagesFragment : CustomListFragment<MessageListAdapter>() { refresh() } - override fun refresh() { - runRefreshTask(world.getReceivedMessagesAsync()) { adapter.updateForce() } - runRefreshTask(world.getSentMessagesAsync()) { adapter.updateForce() } - } - override fun onSwipeDown(view: SwipyRefreshLayout) { - runRefreshTask(world.getReceivedMessagesAsync(sinceId = adapter.topID)) { + val sinceId = if (adapter.count > 0) adapter.topID else null + runRefreshTask(world.getReceivedMessagesAsync(sinceId = sinceId)) { updateListViewWithNotice(true) view.isRefreshing = false } } override fun onSwipeUp(view: SwipyRefreshLayout) { - runRefreshTask(world.getReceivedMessagesAsync(maxId = adapter.lastID - 1)) { + val maxId = if (adapter.count > 0) adapter.lastID - 1 else null + runRefreshTask(world.getReceivedMessagesAsync(maxId = maxId)) { updateListViewWithNotice(false) view.isRefreshing = false } } - private fun runRefreshTask(task: Deferred<List<DirectMessage>>, onFinish: () -> Unit) { - launchUi { - try { - val messages = task.await() - world.addDirectMessage(messages) - } catch (e: TwitterTaskException) { - world.notifyError(R.string.notice_error_get_messages) - } - onFinish() + private fun runRefreshTask(task: Deferred<List<DirectMessage>>, onFinish: () -> Unit) = launchUi { + try { + val messages = task.await() + world.addDirectMessage(messages) + } catch (e: TwitterTaskException) { + world.notifyError(R.string.notice_error_get_messages) } + onFinish() } } diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/PageFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/PageFragment.kt index f23a9edf..9f1f3dca 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/PageFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/PageFragment.kt @@ -1,19 +1,23 @@ package net.lacolaco.smileessence.view.page import android.app.Fragment -import net.lacolaco.smileessence.Application +import net.lacolaco.smileessence.World +import net.lacolaco.smileessence.data.PageInfo -/** - * A PageFragment is always attached to a MainActivity. - */ -abstract class PageFragment : Fragment() { +abstract class PageFragment<out T : PageInfo> : Fragment() { protected val world by lazy { - Application.getWorld(arguments.getLong(KEY_WORLD_USER_ID)) + World[arguments.getLong(KEY_WORLD_USER_ID)] + } + protected val pageInfo by lazy { + @Suppress("UNCHECKED_CAST") + world.pages[arguments.getInt(KEY_PAGE_POSITION)] as T } - abstract fun refresh() + // Invoked when user click the refresh button. + open fun refresh() {} companion object { val KEY_WORLD_USER_ID = "KEY_WORLD_USER_ID" + val KEY_PAGE_POSITION = "KEY_PAGE_POSITION" } } diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/PostFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/PostFragment.kt index 93cb670f..30a9821a 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/PostFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/PostFragment.kt @@ -1,10 +1,13 @@ package net.lacolaco.smileessence.view.page import android.annotation.SuppressLint +import android.app.Activity +import android.app.Activity.RESULT_OK import android.content.Intent import android.net.Uri import android.os.Bundle import android.os.Parcelable +import android.provider.MediaStore import android.support.v4.content.ContextCompat import android.text.Editable import android.text.Spannable @@ -18,6 +21,7 @@ import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_post.* import net.lacolaco.smileessence.R import net.lacolaco.smileessence.activity.MainActivity +import net.lacolaco.smileessence.data.PageInfo import net.lacolaco.smileessence.entity.Tweet import net.lacolaco.smileessence.entity.User import net.lacolaco.smileessence.logging.Logger @@ -31,14 +35,22 @@ import net.lacolaco.smileessence.view.Partials import twitter4j.StatusUpdate import java.io.File -class PostFragment : PageFragment(), TextWatcher { +class PostFragment : PageFragment<PageInfo.ComposePageInfo>(), TextWatcher { private lateinit var postState: PostState - override fun refresh() {} - - fun setMediaFilePath(path: String) { - postState.mediaFilePath = path - onPostStateChange() + fun setMediaFile(uri: Uri) { + try { + activity.contentResolver.query(uri, null, null, null, null).use { c -> + c.moveToFirst() + val path = c.getString(c.getColumnIndex(MediaStore.MediaColumns.DATA)) + postState.mediaFilePath = path + onPostStateChange() + world.notify(R.string.notice_select_image_succeeded) + } + } catch (e: Exception) { + e.printStackTrace() + world.notifyError(R.string.notice_select_image_failed) + } } fun setInReplyTo(tweet: Tweet, prefix: String) { @@ -186,7 +198,7 @@ class PostFragment : PageFragment(), TextWatcher { val intent = Intent(Intent.ACTION_PICK) intent.type = "image/*" - startActivityForResult(intent, MainActivity.REQUEST_GET_PICTURE_FROM_GALLERY) + startActivityForResult(intent, REQUEST_GET_PICTURE_FROM_GALLERY) } image_post_media.setOnClickListener { val intent = Intent(Intent.ACTION_VIEW) @@ -204,7 +216,19 @@ class PostFragment : PageFragment(), TextWatcher { onPostStateChange() } - // FIXME: Remove @SuppressList + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + when (requestCode) { + REQUEST_GET_PICTURE_FROM_GALLERY -> { + if (resultCode != Activity.RESULT_OK) { + world.notifyError(R.string.notice_select_image_failed) + return + } + setMediaFile(data!!.data) + } + else -> super.onActivityResult(requestCode, resultCode, data) + } + } + @SuppressLint("ParcelCreator") @Parcelize private class PostState( @@ -230,6 +254,7 @@ class PostFragment : PageFragment(), TextWatcher { } companion object { + private val REQUEST_GET_PICTURE_FROM_GALLERY = 11 val KEY_POST_STATE = "POST_STATE" } } diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/SearchFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/SearchFragment.kt index c57d92fb..4def85c7 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/SearchFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/SearchFragment.kt @@ -2,7 +2,6 @@ package net.lacolaco.smileessence.view.page import android.os.Bundle import android.text.Spannable -import android.text.TextUtils import android.text.method.ArrowKeyMovementMethod import android.view.KeyEvent import android.view.LayoutInflater @@ -13,14 +12,12 @@ import android.widget.TextView import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout import kotlinx.android.synthetic.main.fragment_search.* import net.lacolaco.smileessence.R -import net.lacolaco.smileessence.activity.MainActivity -import net.lacolaco.smileessence.preference.UserPreferenceHelper +import net.lacolaco.smileessence.data.PageInfo import net.lacolaco.smileessence.twitter.TwitterTaskException import net.lacolaco.smileessence.twitter.task.createSavedSearchAsync import net.lacolaco.smileessence.twitter.task.destroySavedSearchAsync import net.lacolaco.smileessence.twitter.task.doSearch import net.lacolaco.smileessence.util.SystemServiceHelper -import net.lacolaco.smileessence.util.UIHandler import net.lacolaco.smileessence.util.launchUi import net.lacolaco.smileessence.view.PopupMenu import net.lacolaco.smileessence.view.adapter.TimelineAdapter @@ -28,76 +25,30 @@ import net.lacolaco.smileessence.view.confirm import twitter4j.Query import java.util.* -class SearchFragment : CustomListFragment<TimelineAdapter>() { +class SearchFragment : CustomListFragment<PageInfo.SearchPageInfo, TimelineAdapter>() { override val adapter by lazy { TimelineAdapter(activity, world) } - private lateinit var queryString: String - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setHasOptionsMenu(true) - queryString = UserPreferenceHelper.instance[R.string.key_last_used_search_query, ""] - } - - override fun refresh() { //TODO - if (!TextUtils.isEmpty(queryString)) { - startSearch(queryString) - } - } - - override fun onSwipeDown(view: SwipyRefreshLayout) { - if (TextUtils.isEmpty(queryString)) { - UIHandler().post { - notifyTextEmpty() - view.isRefreshing = false - } - return - } - val query = Query() - query.query = queryString - query.count = 200 - query.resultType = Query.RECENT - if (adapter.count > 0) { - query.sinceId = adapter.topID - } - runRefreshTask(query) { - updateListViewWithNotice(true) - view.isRefreshing = false - } - } - - override fun onSwipeUp(view: SwipyRefreshLayout) { - if (TextUtils.isEmpty(queryString)) { - UIHandler().post { - notifyTextEmpty() - view.isRefreshing = false - } - return - } - val query = Query() - query.query = queryString - query.count = 200 - query.resultType = Query.RECENT - if (adapter.count > 0) { - query.maxId = adapter.lastID - 1 - } - runRefreshTask(query) { - updateListViewWithNotice(false) - view.isRefreshing = false - } + override fun refresh() { + if (pageInfo.query.isNotBlank()) + startSearch(pageInfo.query) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View { - super.onCreateView(inflater, container, savedInstanceState) return inflater.inflate(R.layout.fragment_search, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setSwipeRefreshEnabled(pageInfo.query.isNotBlank()) + button_search_queries.setOnClickListener { val popup = PopupMenu(activity, button_search_queries) val sss = ArrayList(world.savedSearches.values) for (ss in sss) { - popup.add(ss.query) { (activity as MainActivity).openSearchPage(ss.query) } + popup.add(ss.query) { + edittext_search.setText(ss.query) + startSearch(ss.query) + } } popup.show() } @@ -116,7 +67,7 @@ class SearchFragment : CustomListFragment<TimelineAdapter>() { try { world.destroySavedSearchAsync(ss.id).await() world.notify(R.string.notice_search_query_deleted) - world.refreshSavedSearches() + world.savedSearches.remove(ss.id) } catch (e: TwitterTaskException) { world.notifyError("unable to delete search query") } @@ -129,8 +80,8 @@ class SearchFragment : CustomListFragment<TimelineAdapter>() { if (!hasFocus) SystemServiceHelper.hideIM(activity, edittext_search) } - edittext_search.setText(queryString) - edittext_search.setOnEditorActionListener { textView, i, keyEvent -> + edittext_search.setText(pageInfo.query) + edittext_search.setOnEditorActionListener { _, i, keyEvent -> if (i == EditorInfo.IME_ACTION_SEARCH || keyEvent != null && keyEvent.action == KeyEvent.ACTION_DOWN && keyEvent.keyCode == KeyEvent.KEYCODE_ENTER) { @@ -153,43 +104,56 @@ class SearchFragment : CustomListFragment<TimelineAdapter>() { refresh() } - private fun notifyTextEmpty() { - world.notifyError(R.string.notice_search_text_empty) + override fun onSwipeDown(view: SwipyRefreshLayout) { + val query = Query() + query.query = pageInfo.query + query.count = 200 + query.resultType = Query.RECENT + if (adapter.count > 0) { + query.sinceId = adapter.topID + } + runRefreshTask(query) { + updateListViewWithNotice(true) + view.isRefreshing = false + } } - private fun saveQuery() = launchUi { - val text = edittext_search.text.toString() - if (TextUtils.isEmpty(text)) { - world.notifyError(R.string.notice_query_is_empty) - } else { - try { - world.createSavedSearchAsync(text).await() - world.notify(R.string.notice_query_saved) - world.refreshSavedSearches() - } catch (e: TwitterTaskException) { - world.notifyError("Query is not saved") - } + override fun onSwipeUp(view: SwipyRefreshLayout) { + val query = Query() + query.query = pageInfo.query + query.count = 200 + query.resultType = Query.RECENT + if (adapter.count > 0) { + query.maxId = adapter.lastID - 1 + } + runRefreshTask(query) { + updateListViewWithNotice(false) + view.isRefreshing = false } } - private fun search() { + private fun saveQuery() = launchUi { val text = edittext_search.text.toString() - if (TextUtils.isEmpty(text)) { - world.notifyError(R.string.notice_query_is_empty) - } else { - startSearch(text) - SystemServiceHelper.hideIM(activity, edittext_search) + try { + val ss = world.createSavedSearchAsync(text).await() + world.notify(R.string.notice_query_saved) + world.savedSearches.put(ss.id, ss) + } catch (e: TwitterTaskException) { + world.notifyError("Query is not saved") } + } + private fun search() { + startSearch(edittext_search.text.toString()) + SystemServiceHelper.hideIM(activity, edittext_search) } - fun startSearch(queryString: String) { - UserPreferenceHelper.instance[R.string.key_last_used_search_query] = queryString - edittext_search.setText(queryString) - this.queryString = queryString + private fun startSearch(queryString: String) { + pageInfo.query = queryString + setSwipeRefreshEnabled(queryString.isNotBlank()) adapter.clear() adapter.updateForce() - if (!TextUtils.isEmpty(queryString)) { + if (queryString.isNotBlank()) { val query = Query() query.query = queryString query.count = 200 @@ -198,15 +162,13 @@ class SearchFragment : CustomListFragment<TimelineAdapter>() { } } - private fun runRefreshTask(query: Query, onFinish: () -> Unit) { - launchUi { - try { - val tweets = world.doSearch(query).await() - adapter.addAll(tweets.filter { !it.isRetweet }) - onFinish() - } catch (e: TwitterTaskException) { - world.notifyError(R.string.notice_error_search) - } + private fun runRefreshTask(query: Query, onFinish: () -> Unit) = launchUi { + try { + val tweets = world.doSearch(query).await() + adapter.addAll(tweets.filter { !it.isRetweet }) + onFinish() + } catch (e: TwitterTaskException) { + world.notifyError(R.string.notice_error_search) } } } diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/TweetsPageFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/TweetsPageFragment.kt new file mode 100644 index 00000000..a9bcfc60 --- /dev/null +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/TweetsPageFragment.kt @@ -0,0 +1,27 @@ +package net.lacolaco.smileessence.view.page + +import android.os.Bundle +import android.view.View +import net.lacolaco.smileessence.data.PageInfo +import net.lacolaco.smileessence.view.adapter.TimelineAdapter + +class TweetsPageFragment : CustomListFragment<PageInfo.TweetsPageInfo, TimelineAdapter>() { + override val adapter by lazy { TimelineAdapter(activity, world) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val patterns = pageInfo.patterns + world.addTimeline(this) { tweet -> + if (patterns.any { it.containsMatchIn(tweet.originalTweet.text) }) { + adapter.add(tweet) + adapter.update() + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setSwipeRefreshEnabled(false) + } +} diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/UserListFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/UserListFragment.kt index 55193aea..35bc4f7f 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/UserListFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/UserListFragment.kt @@ -1,7 +1,6 @@ package net.lacolaco.smileessence.view.page import android.os.Bundle -import android.text.TextUtils import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -9,58 +8,22 @@ import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout import kotlinx.android.synthetic.main.fragment_userlist.* import kotlinx.coroutines.experimental.Deferred import net.lacolaco.smileessence.R +import net.lacolaco.smileessence.data.PageInfo import net.lacolaco.smileessence.entity.Tweet -import net.lacolaco.smileessence.preference.UserPreferenceHelper import net.lacolaco.smileessence.twitter.TwitterTaskException import net.lacolaco.smileessence.twitter.task.getListTimelineAsync -import net.lacolaco.smileessence.util.UIHandler import net.lacolaco.smileessence.util.launchUi import net.lacolaco.smileessence.view.PopupMenu import net.lacolaco.smileessence.view.adapter.TimelineAdapter -class UserListFragment : CustomListFragment<TimelineAdapter>() { +class UserListFragment : CustomListFragment<PageInfo.ListPageInfo, TimelineAdapter>() { override val adapter by lazy { TimelineAdapter(activity, world) } - private var listFullName: String? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - refresh() - } - - override fun refresh() {//TODO - val lastUserList = UserPreferenceHelper.instance[R.string.key_last_used_user_list, ""] - if (!TextUtils.isEmpty(lastUserList)) { - startUserList(lastUserList) - } - } - - override fun onSwipeDown(view: SwipyRefreshLayout) { - if (listFullName == null) { - UIHandler().post { - notifyTextEmpty() - view.isRefreshing = false + override fun refresh() { + if (pageInfo.fullName != null) + runRefreshTask(world.getListTimelineAsync(pageInfo.fullName!!)) { + adapter.updateForce() } - return - } - runRefreshTask(world.getListTimelineAsync(listFullName!!, sinceId = adapter.topID)) { - updateListViewWithNotice(true) - view.isRefreshing = false - } - } - - override fun onSwipeUp(view: SwipyRefreshLayout) { - if (listFullName == null) { - UIHandler().post { - notifyTextEmpty() - view.isRefreshing = false - } - return - } - runRefreshTask(world.getListTimelineAsync(listFullName!!, maxId = adapter.lastID - 1)) { - updateListViewWithNotice(false) - view.isRefreshing = false - } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View { @@ -69,30 +32,41 @@ class UserListFragment : CustomListFragment<TimelineAdapter>() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setSwipeRefreshEnabled(pageInfo.fullName != null) + button_userlist_lists.setOnClickListener { val popup = PopupMenu(activity, button_userlist_lists) for (name in world.listSubscriptions) { popup.add(name) { textview_userlist_name.text = name - startUserList(name) + pageInfo.fullName = name + setSwipeRefreshEnabled(true) + adapter.clear() + adapter.updateForce() + refresh() } } popup.show() } - textview_userlist_name.text = if (listFullName != null) listFullName else "<none selected>" + textview_userlist_name.text = pageInfo.fullName ?: "<none selected>" + + refresh() } - private fun notifyTextEmpty() { - world.notifyError(R.string.notice_userlist_not_selected) + override fun onSwipeDown(view: SwipyRefreshLayout) { + val sinceId = if (adapter.count > 0) adapter.topID else null + runRefreshTask(world.getListTimelineAsync(pageInfo.fullName!!, sinceId = sinceId)) { + updateListViewWithNotice(true) + view.isRefreshing = false + } } - fun startUserList(listFullName: String) { - UserPreferenceHelper.instance[R.string.key_last_used_user_list] = listFullName - val adapter = adapter - this.listFullName = listFullName - adapter.clear() - adapter.updateForce() - runRefreshTask(world.getListTimelineAsync(listFullName)) { adapter.updateForce() } + override fun onSwipeUp(view: SwipyRefreshLayout) { + val maxId = if (adapter.count > 0) adapter.lastID - 1 else null + runRefreshTask(world.getListTimelineAsync(pageInfo.fullName!!, maxId = maxId)) { + updateListViewWithNotice(false) + view.isRefreshing = false + } } private fun runRefreshTask(task: Deferred<List<Tweet>>, onFinish: () -> Unit) = launchUi { diff --git a/app/src/main/java/net/lacolaco/smileessence/view/preference/IntegerListPreference.kt b/app/src/main/java/net/lacolaco/smileessence/view/preference/IntegerListPreference.kt deleted file mode 100644 index 7dbe3297..00000000 --- a/app/src/main/java/net/lacolaco/smileessence/view/preference/IntegerListPreference.kt +++ /dev/null @@ -1,27 +0,0 @@ -package net.lacolaco.smileessence.view.preference - -import android.annotation.TargetApi -import android.content.Context -import android.os.Build -import android.preference.ListPreference -import android.util.AttributeSet - -class IntegerListPreference : ListPreference { - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - constructor(context: Context) : super(context) - - override fun persistString(value: String): Boolean { - return persistInt(Integer.valueOf(value)!!) - } - - override fun getPersistedString(defaultReturnValue: String?): String { - return getPersistedInt(-1).toString() - } -} diff --git a/app/src/main/res/layout/activity_page_manage.xml b/app/src/main/res/layout/activity_page_manage.xml new file mode 100644 index 00000000..87af0c9d --- /dev/null +++ b/app/src/main/res/layout/activity_page_manage.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:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="net.lacolaco.smileessence.activity.ManagePagesActivity"> + + <android.support.design.widget.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="?attr/colorPrimary" + android:elevation="4dp" /> + + </android.support.design.widget.AppBarLayout> + + <android.support.v7.widget.RecyclerView + android:id="@+id/recycler_view" + 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="bottom|end" + 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/dialog_status_detail.xml b/app/src/main/res/layout/dialog_status_detail.xml index 28e774ea..d9140939 100644 --- a/app/src/main/res/layout/dialog_status_detail.xml +++ b/app/src/main/res/layout/dialog_status_detail.xml @@ -32,34 +32,6 @@ android:gravity="center_vertical" android:orientation="horizontal"> - <ImageView - android:id="@+id/image_status_detail_fav_count" - style="@style/custom_button_transparent" - android:layout_width="24dp" - android:layout_height="24dp" - android:scaleType="fitCenter" - android:src="@drawable/icon_favorite_on" /> - - <TextView - android:id="@+id/textview_status_detail_fav_count" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/orange" /> - - <ImageView - android:id="@+id/image_status_detail_rt_count" - style="@style/custom_button_transparent" - android:layout_width="24dp" - android:layout_height="24dp" - android:scaleType="fitCenter" - android:src="@drawable/icon_retweet_on" /> - - <TextView - android:id="@+id/textview_status_detail_rt_count" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/green" /> - <ImageButton android:id="@+id/button_status_detail_reply" style="@style/custom_button_transparent" diff --git a/app/src/main/res/layout/list_item_page.xml b/app/src/main/res/layout/list_item_page.xml new file mode 100644 index 00000000..93f76c25 --- /dev/null +++ b/app/src/main/res/layout/list_item_page.xml @@ -0,0 +1,26 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:descendantFocusability="blocksDescendants"> + + <TextView + android:id="@+id/page_kind_text_view" + android:layout_width="120dp" + android:layout_height="match_parent" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:paddingTop="14dp" + android:paddingBottom="14dp" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + <TextView + android:id="@+id/page_name_text_view" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:paddingTop="14dp" + android:paddingBottom="14dp" + android:textAppearance="?android:attr/textAppearanceSmall" /> +</LinearLayout> diff --git a/app/src/main/res/layout/list_item_status.xml b/app/src/main/res/layout/list_item_status.xml index f5f2d75b..77f52c50 100644 --- a/app/src/main/res/layout/list_item_status.xml +++ b/app/src/main/res/layout/list_item_status.xml @@ -2,7 +2,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:descendantFocusability="blocksDescendants" android:padding="5dp" + android:foreground="?android:attr/selectableItemBackground" app:accent_color="?attr/color_status_text_mine" app:highlight_none="?attr/color_status_bg_normal" app:highlight_type1="?attr/color_status_bg_mention" @@ -10,60 +12,91 @@ <com.android.volley.toolbox.NetworkImageView android:id="@+id/imageview_status_icon" - android:layout_width="36dp" - android:layout_height="36dp" - android:layout_alignParentTop="true" /> + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentTop="true" + android:layout_marginRight="5dp" /> <TextView android:id="@+id/textview_status_header" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" - android:layout_marginLeft="5dp" - android:layout_toRightOf="@+id/imageview_status_icon" - android:gravity="top" - android:textSize="@dimen/status_text_size" - android:textColor="?attr/color_status_text_header" /> + android:layout_toRightOf="@id/imageview_status_icon" + android:text="mmmmmmmmmmmmmmm / !!!NAME!!!" + android:textColor="?attr/color_status_text_header" + android:textSize="@dimen/status_text_size" /> <TextView android:id="@+id/textview_status_text" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignLeft="@+id/textview_status_header" + android:layout_below="@id/textview_status_header" + android:layout_alignLeft="@id/textview_status_header" android:layout_alignParentRight="true" - android:layout_below="@+id/textview_status_header" - android:layout_marginBottom="3dp" android:layout_marginTop="1dp" - android:textSize="@dimen/status_text_size" - android:textColor="?attr/color_status_text_normal" /> + android:layout_marginBottom="3dp" + android:text="!!!TEXT!!!" + android:textColor="?attr/color_status_text_normal" + android:textSize="@dimen/status_text_size" /> <TextView android:id="@+id/textview_status_footer" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignLeft="@+id/textview_status_text" - android:layout_below="@+id/textview_status_text" - android:layout_marginRight="2dp" - android:textSize="@dimen/status_meta_text_size" - android:textColor="?attr/color_status_text_footer" /> + android:layout_below="@id/textview_status_text" + android:layout_alignLeft="@id/textview_status_header" + android:text="2017-01-01 00:00:00 via Twitter Web Client" + android:textColor="?attr/color_status_text_footer" + android:textSize="@dimen/status_meta_text_size" /> + + <net.lacolaco.smileessence.view.OnOffImageView + android:id="@+id/imageview_status_retweeted" + android:layout_width="16dp" + android:layout_height="wrap_content" + android:layout_alignTop="@id/textview_status_footer" + android:layout_alignBottom="@id/textview_status_footer" + android:layout_toLeftOf="@id/tweet_retweet_count" + app:src_on="@drawable/icon_retweet_on" + app:src_off="?icon_retweet_off" /> - <ImageView + <TextView + android:id="@+id/tweet_retweet_count" + android:layout_width="16dp" + android:layout_height="wrap_content" + android:layout_alignTop="@id/textview_status_footer" + android:layout_alignBottom="@id/textview_status_footer" + android:layout_toLeftOf="@id/imageview_status_favorited" + android:text="999999" + android:textColor="?attr/color_status_text_footer" + android:textSize="@dimen/status_meta_text_size" /> + + <net.lacolaco.smileessence.view.OnOffImageView android:id="@+id/imageview_status_favorited" - android:layout_width="30dp" + android:layout_width="16dp" android:layout_height="wrap_content" - android:layout_alignBottom="@+id/textview_status_header" + android:layout_alignTop="@id/textview_status_footer" + android:layout_alignBottom="@id/textview_status_footer" + android:layout_toLeftOf="@id/tweet_favorite_count" + app:src_on="@drawable/icon_favorite_on" + app:src_off="?icon_favorite_off" /> + + <TextView + android:id="@+id/tweet_favorite_count" + android:layout_width="16dp" + android:layout_height="wrap_content" + android:layout_alignTop="@id/textview_status_footer" + android:layout_alignBottom="@id/textview_status_footer" android:layout_alignParentRight="true" - android:layout_alignParentTop="true" - android:layout_alignTop="@+id/textview_status_header" - android:contentDescription="" - android:src="?attr/icon_status_favorite_on" /> + android:text="999999" + android:textColor="?attr/color_status_text_footer" + android:textSize="@dimen/status_meta_text_size" /> <net.lacolaco.smileessence.view.ExpandedListView android:id="@+id/listview_status_embedded_status" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignLeft="@+id/textview_status_text" - android:layout_below="@+id/textview_status_footer" - android:layout_marginRight="1dp" + android:layout_alignLeft="@id/textview_status_header" + android:layout_below="@id/textview_status_footer" android:layout_marginTop="3dp" /> </net.lacolaco.smileessence.view.ColoredRelativeLayout> diff --git a/app/src/main/res/layout/manage_pages_edit_dialog.xml b/app/src/main/res/layout/manage_pages_edit_dialog.xml new file mode 100644 index 00000000..41dec21a --- /dev/null +++ b/app/src/main/res/layout/manage_pages_edit_dialog.xml @@ -0,0 +1,44 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:id="@+id/textView" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:text="Type" /> + + <Spinner + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="3" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="horizontal"> + + <TextView + android:id="@+id/textView2" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:text="Filters" /> + + <ListView + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="3" /> + </LinearLayout> + +</LinearLayout> diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index eea3bbb5..3e402b49 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -6,9 +6,9 @@ app:showAsAction="never" android:title="@string/actionbar_setting" /> <item - android:id="@+id/actionbar_edit_extraction" + android:id="@+id/actionbar_manage_pages" app:showAsAction="never" - android:title="@string/actionbar_edit_extraction" /> + android:title="Manage pages" /> <item android:id="@+id/actionbar_aclog" android:icon="@drawable/icon_website" diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index f26b3e07..95c9d67e 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -10,7 +10,6 @@ <attr name="button_round_red" format="reference" /> <attr name="button_round_orange" format="reference" /> <attr name="custom_button_transparent" format="reference" /> - <attr name="icon_status_favorite_on" format="reference" /> <attr name="color_status_text_header" format="color" /> <attr name="color_status_text_normal" format="color" /> <attr name="color_status_text_footer" format="color" /> @@ -47,4 +46,8 @@ <attr name="offSrc" format="reference" /> <attr name="onSrc" format="reference" /> </declare-styleable> + <declare-styleable name="OnOffImageView"> + <attr name="src_off" format="reference" /> + <attr name="src_on" format="reference" /> + </declare-styleable> </resources> diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimens.xml index cdf1de98..97fb5c40 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,5 +1,6 @@ -<?xml version="1.0" encoding="utf-8"?> <resources> <dimen name="status_text_size">11sp</dimen> <dimen name="status_meta_text_size">9sp</dimen> + + <dimen name="fab_margin">16dp</dimen> </resources> diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index 1f71fb27..9da6f6bd 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -1,8 +1,4 @@ <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"> - <string name="key_setting_theme">theme</string> <string name="key_setting_application_information">appInfo</string> <string name="key_setting_licenses">licenseNotice</string> - <string name="key_setting_resize_post_image">resizePostImage</string> - <string name="key_last_used_search_query">lastUsedSearchQuery</string> - <string name="key_last_used_user_list">lastUsedUserList</string> </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 02958e7c..10ad6d82 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,14 +24,7 @@ <string name="page_name_list">List</string> <string name="actionbar_setting">Settings</string> <string name="actionbar_aclog">Aclog</string> - <string name="actionbar_edit_extraction">Edit extraction</string> - <string name="setting_category_display_title">Display Setting</string> - <string name="setting_category_display_summary">Enable at next launch.</string> - <string name="setting_theme_title">Theme</string> - <string name="setting_theme_dialog_title">Choose theme</string> <string name="setting_category_system_title">System Setting</string> - <string name="setting_resize_post_image">Resize image</string> - <string name="setting_resize_post_image_summary">Compress large image for tweet</string> <string name="setting_category_about_title">About</string> <string name="setting_application_information_title">Application information</string> <string name="setting_licenses_title">Licenses</string> @@ -89,7 +82,6 @@ <string name="notice_select_image_failed">Failed to select image</string> <string name="notice_search_text_empty">Query is empty</string> <string name="notice_add_to_reply">Added to reply</string> - <string name="notice_theme_changed">Theme will be enabled on next launch</string> <string name="notice_copy_clipboard">Copied to clipboard</string> <string name="notice_userlist_not_selected">No List is selected</string> <string name="notice_search_query_deleted">Query was deleted</string> @@ -118,4 +110,5 @@ <string name="notice_error_get_list">Failed to get list timeline</string> <string name="notice_cant_remove_last_account">You can\'t remove last account</string> <string name="notice_error_storage_permission">Write access to external storage is required.</string> + <string name="title_activity_page_manage">PageManageActivity</string> </resources> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 6bc06af3..e15d6e7d 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -35,7 +35,6 @@ <item name="icon_message">@drawable/icon_message_white</item> <item name="icon_retweet_off">@drawable/icon_retweet_white</item> <item name="icon_favorite_off">@drawable/icon_favorite_off_white</item> - <item name="icon_status_favorite_on">@drawable/icon_favorite_on</item> <item name="icon_garbage">@drawable/icon_garbage_white</item> <item name="icon_search">@drawable/icon_search_white</item> <item name="icon_labels">@drawable/icon_labels_white</item> @@ -57,7 +56,6 @@ <item name="button_round_red">@drawable/button_round_red_light</item> <item name="button_round_orange">@drawable/button_round_orange_light</item> <item name="custom_button_transparent">@style/custom_button_transparent</item> - <item name="icon_status_favorite_on">@drawable/icon_favorite_on</item> <item name="color_status_text_header">@color/dark_green</item> <item name="color_status_text_normal">@color/gray_dark</item> <item name="color_status_text_footer">@color/gray_dark_light</item> @@ -85,4 +83,8 @@ <style name="custom_button_transparent"> <item name="android:background">?android:attr/selectableItemBackground</item> </style> + + <style name="theme_light.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> + + <style name="theme_light.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> </resources> diff --git a/app/src/main/res/xml/setting.xml b/app/src/main/res/xml/setting.xml index f01a4bd5..c616ae85 100644 --- a/app/src/main/res/xml/setting.xml +++ b/app/src/main/res/xml/setting.xml @@ -1,22 +1,4 @@ <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> - <PreferenceCategory - android:summary="@string/setting_category_display_summary" - android:title="@string/setting_category_display_title"> - <net.lacolaco.smileessence.view.preference.IntegerListPreference - android:defaultValue="0" - android:dialogTitle="@string/setting_theme_dialog_title" - android:entries="@array/setting_theme_names" - android:entryValues="@array/setting_theme_ids" - android:key="@string/key_setting_theme" - android:title="@string/setting_theme_title" /> - </PreferenceCategory> - <PreferenceCategory android:title="@string/setting_category_system_title"> - <SwitchPreference - android:defaultValue="true" - android:key="@string/key_setting_resize_post_image" - android:summary="@string/setting_resize_post_image_summary" - android:title="@string/setting_resize_post_image" /> - </PreferenceCategory> <PreferenceCategory android:title="@string/setting_category_about_title"> <Preference android:key="@string/key_setting_application_information" diff --git a/gradle.properties b/gradle.properties index aac7c9b4..869e8cea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m +org.gradle.jvmargs=-Xmx1024m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit |