diff --git a/app/build.gradle b/app/build.gradle
index cbc5dce..5c67bff 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,7 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
+androidExtensions {
+ experimental = true
+}
+
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
@@ -96,10 +101,6 @@ dependencies {
compile 'com.jakewharton.timber:timber:4.5.1'
// Misc
- compile 'nz.bradcampbell:paperparcel:2.0.1'
- compile 'nz.bradcampbell:paperparcel-kotlin:2.0.1'
- kapt 'nz.bradcampbell:paperparcel-compiler:2.0.1'
-
compile('com.mikepenz:materialdrawer:5.9.0@aar') {
transitive = true
}
@@ -109,6 +110,8 @@ dependencies {
compile 'uk.co.chrisjenx:calligraphy:2.2.0'
compile 'com.vanniktech:emoji-one:0.5.1'
+
+ compile 'commons-io:commons-io:2.5'
}
repositories {
jcenter()
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cee9f37..cf074a6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -44,6 +44,16 @@
android:name=".view.activity.SettingActivity"
android:label="@string/title_activity_setting"
android:windowSoftInputMode="adjustResize" />
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/fonts/Ricty-Regular.ttf b/app/src/main/assets/fonts/Ricty-Regular.ttf
new file mode 100755
index 0000000..a41f63c
Binary files /dev/null and b/app/src/main/assets/fonts/Ricty-Regular.ttf differ
diff --git a/app/src/main/java/com/geckour/egret/App.kt b/app/src/main/java/com/geckour/egret/App.kt
index ea04bac..cda28af 100644
--- a/app/src/main/java/com/geckour/egret/App.kt
+++ b/app/src/main/java/com/geckour/egret/App.kt
@@ -12,11 +12,8 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.vanniktech.emoji.EmojiManager
import com.vanniktech.emoji.one.EmojiOneProvider
-import paperparcel.Adapter
-import paperparcel.ProcessorConfig
import timber.log.Timber
-@ProcessorConfig(adapters = arrayOf(Adapter(SpannedTypeAdapter::class)))
class App: Application() {
companion object {
diff --git a/app/src/main/java/com/geckour/egret/api/MastodonClient.kt b/app/src/main/java/com/geckour/egret/api/MastodonClient.kt
index 6c4944a..ccd1e5c 100644
--- a/app/src/main/java/com/geckour/egret/api/MastodonClient.kt
+++ b/app/src/main/java/com/geckour/egret/api/MastodonClient.kt
@@ -41,9 +41,11 @@ class MastodonClient(baseUrl: String) {
password: String
): Single = service.authUser(clientId, clientSecret, username, password)
- fun getSelfAccount(): Single = service.getSelfAccount()
+ fun getOwnAccount(): Single = service.getOwnAccount()
- fun getAccount(accountId: Long): Observable = service.getAccount(accountId)
+ fun getAccount(accountId: Long): Single = service.getAccount(accountId)
+
+ fun updateOwnAccount(displayName: String? = null, note: String? = null, avatarUrl: String? = null, headerUrl: String? = null): Single = service.updateOwnAccount(displayName, note, avatarUrl, headerUrl)
fun getPublicTimelineAsStream(): Observable = streamService.getPublicTimelineAsStream()
@@ -63,6 +65,8 @@ class MastodonClient(baseUrl: String) {
fun getNotificationTimeline(maxId: Long? = null, sinceId: Long? = null): Single>> = service.getNotificationTimeline(maxId, sinceId)
+ fun getFavouriteTimeline(maxId: Long? = null, sinceId: Long? = null): Single>> = service.getFavouriteTimeline(maxId, sinceId)
+
fun getAccountAllToots(accountId: Long, maxId: Long? = null, sinceId: Long? = null): Single>> = service.getAccountAllToots(accountId, maxId, sinceId)
fun favoriteByStatusId(statusId: Long): Single = service.favoriteStatusById(statusId)
diff --git a/app/src/main/java/com/geckour/egret/api/model/Account.kt b/app/src/main/java/com/geckour/egret/api/model/Account.kt
index 8643a42..4899241 100644
--- a/app/src/main/java/com/geckour/egret/api/model/Account.kt
+++ b/app/src/main/java/com/geckour/egret/api/model/Account.kt
@@ -30,19 +30,19 @@ data class Account(
@SerializedName("statuses_count")
var statusesCount: Long,
- val note: String,
+ var note: String,
val url: URL,
@SerializedName("avatar")
- val avatarUrl: String,
+ var avatarUrl: String,
@SerializedName("avatar_static")
- val avatarImg: String,
+ var avatarUrlStatic: String,
@SerializedName("header")
- val headerUrl: String,
+ var headerUrl: String,
@SerializedName("header_static")
- val headerImg: String
+ var headerUrlStatic: String
): Serializable
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/api/model/Attachment.kt b/app/src/main/java/com/geckour/egret/api/model/Attachment.kt
index 3917485..68647f9 100644
--- a/app/src/main/java/com/geckour/egret/api/model/Attachment.kt
+++ b/app/src/main/java/com/geckour/egret/api/model/Attachment.kt
@@ -5,7 +5,7 @@ import com.google.gson.annotations.SerializedName
data class Attachment(
val id: Long,
- var type: String,
+ var type: Type,
var url: String,
@@ -18,9 +18,10 @@ data class Attachment(
@SerializedName("text_url")
var urlInText: String?
) {
- enum class Type(val rowValue: Int) {
- image(0),
- video(1),
- gifv(2)
- }
+
+ enum class Type {
+ image,
+ video,
+ gifv
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/api/model/Relationship.kt b/app/src/main/java/com/geckour/egret/api/model/Relationship.kt
index eeb73a8..ad39bfc 100644
--- a/app/src/main/java/com/geckour/egret/api/model/Relationship.kt
+++ b/app/src/main/java/com/geckour/egret/api/model/Relationship.kt
@@ -16,5 +16,5 @@ data class Relationship(
var muting: Boolean,
@SerializedName("requested")
- var requestedAllowToFollow: Boolean
+ var hasSendFollowRequest: Boolean
)
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/api/service/MastodonService.kt b/app/src/main/java/com/geckour/egret/api/service/MastodonService.kt
index 695d95f..8b64131 100644
--- a/app/src/main/java/com/geckour/egret/api/service/MastodonService.kt
+++ b/app/src/main/java/com/geckour/egret/api/service/MastodonService.kt
@@ -56,13 +56,29 @@ interface MastodonService {
): Single
@GET("api/v1/accounts/verify_credentials")
- fun getSelfAccount(): Single
+ fun getOwnAccount(): Single
@GET("api/v1/accounts/{id}")
fun getAccount(
@Path("id")
accountId: Long
- ): Observable
+ ): Single
+
+ @FormUrlEncoded
+ @PATCH("api/v1/accounts/update_credentials")
+ fun updateOwnAccount(
+ @Field("display_name")
+ displayName: String? = null,
+
+ @Field("note")
+ note: String? = null,
+
+ @Field("avatar")
+ avatar: String? = null,
+
+ @Field("header")
+ headeer: String? = null
+ ): Single
@GET("api/v1/streaming/public")
@Streaming
@@ -131,6 +147,18 @@ interface MastodonService {
limit: Long? = 30
): Single>>
+ @GET("api/v1/favourites")
+ fun getFavouriteTimeline(
+ @Query("max_id")
+ maxId: Long? = null,
+
+ @Query("since_id")
+ sinceId: Long? = null,
+
+ @Query("limit")
+ limit: Long? = 40
+ ): Single>>
+
@GET("api/v1/accounts/{id}/statuses")
fun getAccountAllToots(
@Path("id")
diff --git a/app/src/main/java/com/geckour/egret/model/AccessToken.kt b/app/src/main/java/com/geckour/egret/model/AccessToken.kt
index d3295e5..023ad8c 100644
--- a/app/src/main/java/com/geckour/egret/model/AccessToken.kt
+++ b/app/src/main/java/com/geckour/egret/model/AccessToken.kt
@@ -4,7 +4,7 @@ import com.github.gfx.android.orma.annotation.*
import java.util.*
@Table
-class AccessToken(
+data class AccessToken(
@Setter("id") @PrimaryKey(autoincrement = true) val id: Long = -1L,
@Setter("access_token") @Column val token: String = "",
@Setter("instance_id") @Column val instanceId: Long = -1L,
diff --git a/app/src/main/java/com/geckour/egret/model/Draft.kt b/app/src/main/java/com/geckour/egret/model/Draft.kt
new file mode 100644
index 0000000..83ce697
--- /dev/null
+++ b/app/src/main/java/com/geckour/egret/model/Draft.kt
@@ -0,0 +1,27 @@
+package com.geckour.egret.model
+
+import com.geckour.egret.api.model.Attachment
+import com.geckour.egret.api.service.MastodonService
+import com.github.gfx.android.orma.annotation.Column
+import com.github.gfx.android.orma.annotation.PrimaryKey
+import com.github.gfx.android.orma.annotation.Setter
+import com.github.gfx.android.orma.annotation.Table
+
+@Table
+data class Draft(
+ @Setter("id") @PrimaryKey(autoincrement = true) val id: Long = -1L,
+ @Setter("tokenId") @Column(indexed = true) var tokenId: Long = -1L,
+ @Setter("body") @Column var body: String = "",
+ @Setter("alertBody") @Column var alertBody: String = "",
+ @Setter("inReplyToId") @Column var inReplyToId: Long? = null,
+ @Setter("inReplyToName") @Column var inReplyToName: String? = null,
+ @Setter("attachments") @Column var attachments: Attachments,
+ @Setter("warning") @Column var warning: Boolean = false,
+ @Setter("sensitive") @Column var sensitive: Boolean = false,
+ @Setter("visibility") @Column var visibility: Int = MastodonService.Visibility.public.ordinal,
+ @Setter("createdAt") @Column(indexed = true) var createdAt: Long = System.currentTimeMillis()
+) {
+ data class Attachments(
+ val value: List = ArrayList()
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/model/InstanceAuthInfo.kt b/app/src/main/java/com/geckour/egret/model/InstanceAuthInfo.kt
index f5658b5..2d24a1a 100644
--- a/app/src/main/java/com/geckour/egret/model/InstanceAuthInfo.kt
+++ b/app/src/main/java/com/geckour/egret/model/InstanceAuthInfo.kt
@@ -7,7 +7,7 @@ import com.github.gfx.android.orma.annotation.Table
import java.util.*
@Table
-class InstanceAuthInfo(
+data class InstanceAuthInfo(
@Setter("id") @PrimaryKey(autoincrement = true) val id: Long = -1L,
@Setter("instance") @Column val instance: String = "",
@Setter("auth_id") @Column var authId: Long = -1L,
diff --git a/app/src/main/java/com/geckour/egret/model/MuteHashTag.kt b/app/src/main/java/com/geckour/egret/model/MuteHashTag.kt
index dfcce4c..ddcb29a 100644
--- a/app/src/main/java/com/geckour/egret/model/MuteHashTag.kt
+++ b/app/src/main/java/com/geckour/egret/model/MuteHashTag.kt
@@ -6,7 +6,7 @@ import com.github.gfx.android.orma.annotation.Setter
import com.github.gfx.android.orma.annotation.Table
@Table
-class MuteHashTag(
+data class MuteHashTag(
@Setter("id") @PrimaryKey(autoincrement = true) val id: Long = -1L,
@Setter("hashTag") @Column var hashTag: String = ""
)
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/model/MuteInstance.kt b/app/src/main/java/com/geckour/egret/model/MuteInstance.kt
index e4095b8..e4f3316 100644
--- a/app/src/main/java/com/geckour/egret/model/MuteInstance.kt
+++ b/app/src/main/java/com/geckour/egret/model/MuteInstance.kt
@@ -6,7 +6,7 @@ import com.github.gfx.android.orma.annotation.Setter
import com.github.gfx.android.orma.annotation.Table
@Table
-class MuteInstance(
+data class MuteInstance(
@Setter("id") @PrimaryKey(autoincrement = true) val id: Long = -1L,
@Setter("instance") @Column var instance: String = ""
)
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/model/MuteKeyword.kt b/app/src/main/java/com/geckour/egret/model/MuteKeyword.kt
index bb1d73c..18a305e 100644
--- a/app/src/main/java/com/geckour/egret/model/MuteKeyword.kt
+++ b/app/src/main/java/com/geckour/egret/model/MuteKeyword.kt
@@ -6,7 +6,7 @@ import com.github.gfx.android.orma.annotation.Setter
import com.github.gfx.android.orma.annotation.Table
@Table
-class MuteKeyword(
+data class MuteKeyword(
@Setter("id") @PrimaryKey(autoincrement = true) val id: Long = -1L,
@Setter("is_regex") @Column var isRegex: Boolean = false,
@Setter("hashTag") @Column var keyword: String = ""
diff --git a/app/src/main/java/com/geckour/egret/util/Common.kt b/app/src/main/java/com/geckour/egret/util/Common.kt
index 01f4d1b..2d6098c 100644
--- a/app/src/main/java/com/geckour/egret/util/Common.kt
+++ b/app/src/main/java/com/geckour/egret/util/Common.kt
@@ -15,15 +15,12 @@ import android.text.Spanned
import android.text.format.DateFormat
import android.text.method.LinkMovementMethod
import android.text.method.MovementMethod
-import android.util.DisplayMetrics
-import android.util.TypedValue
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.TextView
import com.emojione.Emojione
import com.geckour.egret.App
-import com.geckour.egret.NotificationService
import com.geckour.egret.R
import com.geckour.egret.api.MastodonClient
import com.geckour.egret.api.model.Account
@@ -57,7 +54,7 @@ class Common {
}
private fun requestWeatherCertified(domain: String, callback: (hasCertified: Boolean, accountId: Long) -> Any) {
- MastodonClient(domain).getSelfAccount()
+ MastodonClient(domain).getOwnAccount()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ account ->
@@ -104,6 +101,7 @@ class Common {
status.url,
status.account.id,
status.account.avatarUrl,
+ status.account.isLocked,
Emojione.shortnameToUnicode(status.account.displayName),
"@${status.account.acct}",
Date(status.createdAt.time),
@@ -128,6 +126,7 @@ class Common {
it.id,
it.type,
it.account.id,
+ it.account.isLocked,
it.account.avatarUrl,
it.account.displayName,
"@${it.account.acct}",
@@ -139,8 +138,12 @@ class Common {
else TimelineContent()
fun getProfileContent(account: Account): ProfileContent = ProfileContent(
+ account.id,
account.avatarUrl,
+ null,
account.headerUrl,
+ null,
+ account.isLocked,
account.displayName,
"@${account.acct}",
getSpannedWithoutExtraMarginFromHtml("${account.url}"),
diff --git a/app/src/main/java/com/geckour/egret/util/OrmaAttachmentsAdapter.java b/app/src/main/java/com/geckour/egret/util/OrmaAttachmentsAdapter.java
new file mode 100644
index 0000000..55b8d1a
--- /dev/null
+++ b/app/src/main/java/com/geckour/egret/util/OrmaAttachmentsAdapter.java
@@ -0,0 +1,19 @@
+package com.geckour.egret.util;
+
+import com.geckour.egret.App;
+import com.geckour.egret.model.Draft;
+import com.github.gfx.android.orma.annotation.StaticTypeAdapter;
+
+@StaticTypeAdapter(
+ targetType = Draft.Attachments.class,
+ serializedType = String.class
+)
+public class OrmaAttachmentsAdapter {
+ public static String serialize(Draft.Attachments attachments) {
+ return App.Companion.getGson().toJson(attachments);
+ }
+
+ public static Draft.Attachments deserialize(String string) {
+ return App.Companion.getGson().fromJson(string, Draft.Attachments.class);
+ }
+}
diff --git a/app/src/main/java/com/geckour/egret/view/activity/MainActivity.kt b/app/src/main/java/com/geckour/egret/view/activity/MainActivity.kt
index 7eadbf3..43e1e10 100644
--- a/app/src/main/java/com/geckour/egret/view/activity/MainActivity.kt
+++ b/app/src/main/java/com/geckour/egret/view/activity/MainActivity.kt
@@ -9,6 +9,7 @@ import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
import android.support.design.widget.Snackbar
+import android.support.v4.app.ShareCompat
import android.support.v4.content.ContextCompat
import android.support.v7.widget.SearchView
import android.text.Html
@@ -39,19 +40,17 @@ import com.mikepenz.materialdrawer.DrawerBuilder
import com.mikepenz.materialdrawer.model.DividerDrawerItem
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
-import com.mikepenz.materialdrawer.model.interfaces.IProfile
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
-import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper
-class MainActivity : BaseActivity() {
+class MainActivity : BaseActivity(), ListDialogFragment.OnItemClickListener {
lateinit var binding: ActivityMainBinding
- lateinit var drawer: Drawer
+ lateinit private var drawer: Drawer
lateinit private var accountHeader: AccountHeader
private val sharedPref: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
lateinit private var currentCategory: TimelineFragment.Category
@@ -59,12 +58,6 @@ class MainActivity : BaseActivity() {
companion object {
const val STATE_KEY_THEME_MODE = "stateKeyThemeMode"
const val ARGS_KEY_CATEGORY = "argsKeyCategory"
- const val NAV_ITEM_LOGIN: Long = 0
- const val NAV_ITEM_TL_PUBLIC: Long = 1
- const val NAV_ITEM_TL_LOCAL: Long = 2
- const val NAV_ITEM_TL_USER: Long = 3
- const val NAV_ITEM_TL_NOTIFICATION: Long = 4
- const val NAV_ITEM_SETTINGS: Long = 5
const val REQUEST_CODE_NOTIFICATION = 0
fun getIntent(context: Context, category: TimelineFragment.Category? = null): Intent {
@@ -75,6 +68,20 @@ class MainActivity : BaseActivity() {
}
}
+ enum class NavItem {
+ NAV_ITEM_LOGIN,
+ NAV_ITEM_TL_PUBLIC,
+ NAV_ITEM_TL_LOCAL,
+ NAV_ITEM_TL_USER,
+ NAV_ITEM_TL_NOTIFICATION,
+ NAV_ITEM_SETTINGS,
+ NAV_ITEM_OTHERS
+ }
+
+ interface OnBackPressedListener {
+ fun onBackPressedInMainActivity(callback: (doBack: Boolean) -> Any)
+ }
+
val timelineListener = object: TimelineAdapter.Callbacks {
override val copyTootUrlToClipboard = { url: String ->
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
@@ -82,6 +89,15 @@ class MainActivity : BaseActivity() {
clipboard.primaryClip = clip
}
+ override val shareToot = { content: TimelineContent.TimelineStatus ->
+ ShareCompat.IntentBuilder.from(this@MainActivity).apply {
+ setChooserTitle(R.string.dialog_title_share_toot)
+ setSubject(getString(R.string.dialog_subject_share_toot))
+ setText("${content.nameStrong}(${content.nameWeak}):\n${content.body}")
+ setType("text/plain")
+ }.startChooser()
+ }
+
override val showTootInBrowser = { content: TimelineContent.TimelineStatus ->
val uri = Uri.parse(content.tootUrl)
if (Common.isModeDefaultBrowser(this@MainActivity)) {
@@ -112,7 +128,7 @@ class MainActivity : BaseActivity() {
}
)
)
- 2 -> Pair(R.string.array_item_mute_hash_tag, if (content.tags.isEmpty()) "" else s.format(content.tags.map { tag -> "#$tag" }.joinToString()))
+ 2 -> Pair(R.string.array_item_mute_hash_tag, if (content.tags.isEmpty()) "" else s.format(content.tags.joinToString { tag -> "#$tag" }))
3 -> {
var instance = content.nameWeak.replace(Regex("^@.+@(.+)$"), "@$1")
@@ -132,85 +148,25 @@ class MainActivity : BaseActivity() {
ListDialogFragment.newInstance(
getString(R.string.dialog_title_mute),
items,
- object: ListDialogFragment.OnItemClickListener {
- override fun onClick(resId: Int) {
- when (resId) {
- R.string.array_item_mute_account -> {
- Common.resetAuthInfo()?.let { domain ->
- MastodonClient(domain).getSelfAccount()
- .flatMap { if (it.id == content.accountId) Single.never() else MastodonClient(domain).muteAccount(content.accountId) }
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe({
- Snackbar.make(binding.root, "Muted account: ${content.nameWeak}", Snackbar.LENGTH_SHORT).show()
- }, Throwable::printStackTrace)
- }
- }
-
- R.string.array_item_mute_keyword -> {
- val fragment = KeywordMuteFragment.newInstance(content.body.toString())
- supportFragmentManager.beginTransaction()
- .replace(R.id.container, fragment, KeywordMuteFragment.TAG)
- .addToBackStack(KeywordMuteFragment.TAG)
- .commit()
- }
-
- R.string.array_item_mute_hash_tag -> {
- val fragment = HashTagMuteFragment.newInstance(content.tags)
- supportFragmentManager.beginTransaction()
- .replace(R.id.container, fragment, HashTagMuteFragment.TAG)
- .addToBackStack(HashTagMuteFragment.TAG)
- .commit()
- }
-
- R.string.array_item_mute_instance -> {
- var instance = content.nameWeak.replace(Regex("^@.+@(.+)$"), "@$1")
-
- if (content.nameWeak == instance) {
- instance = ""
- Common.getCurrentAccessToken()?.instanceId?.let {
- instance = "@${OrmaProvider.db.selectFromInstanceAuthInfo().idEq(it).last().instance}"
- }
- }
-
- if (!TextUtils.isEmpty(instance)) {
- OrmaProvider.db.prepareInsertIntoMuteInstanceAsSingle()
- .map { inserter -> inserter.execute(MuteInstance(-1L, instance)) }
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .compose(bindToLifecycle())
- .subscribe({
- Snackbar.make(binding.root, "Muted instance: $instance", Snackbar.LENGTH_SHORT).show()
- }, Throwable::printStackTrace)
- }
- }
-
- R.string.array_item_mute_client -> {
- content.app?.let {
- OrmaProvider.db.prepareInsertIntoMuteClientAsSingle()
- .map { inserter -> inserter.execute(MuteClient(-1L, it)) }
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .compose(bindToLifecycle())
- .subscribe({
- Snackbar.make(binding.root, "Muted client: ${content.app}", Snackbar.LENGTH_SHORT).show()
- }, Throwable::printStackTrace)
- }
- }
- }
- }
- }).show(supportFragmentManager, ListDialogFragment.TAG)
+ content
+ ).show(supportFragmentManager, ListDialogFragment.TAG)
}
override val showProfile = { accountId: Long ->
- AccountProfileFragment.newObservableInstance(accountId)
- .subscribe( {
- fragment ->
- supportFragmentManager.beginTransaction()
- .replace(R.id.container, fragment, AccountProfileFragment.TAG)
- .addToBackStack(AccountProfileFragment.TAG)
- .commit()
- }, Throwable::printStackTrace)
+ Common.resetAuthInfo()?.let {
+ MastodonClient(it).getAccount(accountId)
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(bindToLifecycle())
+ .subscribe({ account ->
+ val fragment = AccountProfileFragment.newInstance(account)
+
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.container, fragment, AccountProfileFragment.TAG)
+ .addToBackStack(AccountProfileFragment.TAG)
+ .commit()
+ })
+ } ?: let {}
}
override val onReply = { content: TimelineContent.TimelineStatus ->
@@ -276,9 +232,11 @@ class MainActivity : BaseActivity() {
}
currentCategory =
- if (intent.extras?.containsKey(ARGS_KEY_CATEGORY) ?: false) intent.extras[ARGS_KEY_CATEGORY] as TimelineFragment.Category
- else if (sharedPref.contains(STATE_KEY_CATEGORY)) TimelineFragment.Category.values()[sharedPref.getInt(STATE_KEY_CATEGORY, TimelineFragment.Category.Public.ordinal)]
- else TimelineFragment.Category.Public
+ when {
+ intent.extras?.containsKey(ARGS_KEY_CATEGORY) == true -> intent.extras[ARGS_KEY_CATEGORY] as TimelineFragment.Category
+ sharedPref.contains(STATE_KEY_CATEGORY) -> TimelineFragment.Category.values()[sharedPref.getInt(STATE_KEY_CATEGORY, TimelineFragment.Category.Public.ordinal)]
+ else -> TimelineFragment.Category.Public
+ }
binding.appBarMain.contentMain.apply {
simplicityTootBody.setOnKeyListener { v, keyCode, event ->
@@ -334,7 +292,9 @@ class MainActivity : BaseActivity() {
if (drawer.isDrawerOpen) {
drawer.closeDrawer()
} else {
- super.onBackPressed()
+ (supportFragmentManager.fragments.lastOrNull { it?.isVisible ?: false } as? OnBackPressedListener)?.let {
+ it.onBackPressedInMainActivity { if (it) super.onBackPressed() }
+ } ?: super.onBackPressed()
}
}
@@ -347,9 +307,7 @@ class MainActivity : BaseActivity() {
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
menu?.findItem(R.id.action_search)?.icon?.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
(menu?.findItem(R.id.action_search)?.actionView as SearchView?)?.setOnQueryTextListener(object: SearchView.OnQueryTextListener {
- override fun onQueryTextChange(text: String?): Boolean {
- return false
- }
+ override fun onQueryTextChange(text: String?): Boolean = false
override fun onQueryTextSubmit(text: String?): Boolean {
if (text != null) {
@@ -373,10 +331,6 @@ class MainActivity : BaseActivity() {
return super.onOptionsItemSelected(item)
}
- override fun attachBaseContext(newBase: Context?) {
- super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase))
- }
-
fun showSearchResult(query: String) {
Common.resetAuthInfo()?.let {
MastodonClient(it).search(query)
@@ -395,12 +349,79 @@ class MainActivity : BaseActivity() {
}
}
+ override fun onClickListDialogItem(resId: Int, content: TimelineContent.TimelineStatus) {
+ when (resId) {
+ R.string.array_item_mute_account -> {
+ Common.resetAuthInfo()?.let { domain ->
+ MastodonClient(domain).getOwnAccount()
+ .flatMap { if (it.id == content.accountId) Single.never() else MastodonClient(domain).muteAccount(content.accountId) }
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ Snackbar.make(binding.root, "Muted account: ${content.nameWeak}", Snackbar.LENGTH_SHORT).show()
+ }, Throwable::printStackTrace)
+ }
+ }
+
+ R.string.array_item_mute_keyword -> {
+ val fragment = KeywordMuteFragment.newInstance(content.body.toString())
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.container, fragment, KeywordMuteFragment.TAG)
+ .addToBackStack(KeywordMuteFragment.TAG)
+ .commit()
+ }
+
+ R.string.array_item_mute_hash_tag -> {
+ val fragment = HashTagMuteFragment.newInstance(content.tags)
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.container, fragment, HashTagMuteFragment.TAG)
+ .addToBackStack(HashTagMuteFragment.TAG)
+ .commit()
+ }
+
+ R.string.array_item_mute_instance -> {
+ var instance = content.nameWeak.replace(Regex("^@.+@(.+)$"), "@$1")
+
+ if (content.nameWeak == instance) {
+ instance = ""
+ Common.getCurrentAccessToken()?.instanceId?.let {
+ instance = "@${OrmaProvider.db.selectFromInstanceAuthInfo().idEq(it).last().instance}"
+ }
+ }
+
+ if (!TextUtils.isEmpty(instance)) {
+ OrmaProvider.db.prepareInsertIntoMuteInstanceAsSingle()
+ .map { inserter -> inserter.execute(MuteInstance(-1L, instance)) }
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(bindToLifecycle())
+ .subscribe({
+ Snackbar.make(binding.root, "Muted instance: $instance", Snackbar.LENGTH_SHORT).show()
+ }, Throwable::printStackTrace)
+ }
+ }
+
+ R.string.array_item_mute_client -> {
+ content.app?.let {
+ OrmaProvider.db.prepareInsertIntoMuteClientAsSingle()
+ .map { inserter -> inserter.execute(MuteClient(-1L, it)) }
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(bindToLifecycle())
+ .subscribe({
+ Snackbar.make(binding.root, "Muted client: ${content.app}", Snackbar.LENGTH_SHORT).show()
+ }, Throwable::printStackTrace)
+ }
+ }
+ }
+ }
+
fun commitAccountsIntoAccountHeader() {
accountHeader.clear()
Observable.fromIterable(OrmaProvider.db.selectFromAccessToken())
.flatMap {
- MastodonClient(Common.setAuthInfo(it) ?: throw IllegalArgumentException()).getSelfAccount()
+ MastodonClient(Common.setAuthInfo(it) ?: throw IllegalArgumentException()).getOwnAccount()
.map { account -> Pair(it, account) }
.toObservable()
}
@@ -454,7 +475,7 @@ class MainActivity : BaseActivity() {
.withOnAccountHeaderListener { v, profile, current ->
if (v.id == R.id.material_drawer_account_header_current) {
Common.resetAuthInfo()?.let {
- MastodonClient(it).getSelfAccount()
+ MastodonClient(it).getOwnAccount()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
@@ -500,44 +521,51 @@ class MainActivity : BaseActivity() {
.withActionBarDrawerToggleAnimated(true)
.withToolbar(binding.appBarMain.toolbar)
.addDrawerItems(
- PrimaryDrawerItem().withName(R.string.navigation_drawer_item_tl_public).withIdentifier(NAV_ITEM_TL_PUBLIC).withIcon(R.drawable.ic_public_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
- PrimaryDrawerItem().withName(R.string.navigation_drawer_item_tl_local).withIdentifier(NAV_ITEM_TL_LOCAL).withIcon(R.drawable.ic_place_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
- PrimaryDrawerItem().withName(R.string.navigation_drawer_item_tl_user).withIdentifier(NAV_ITEM_TL_USER).withIcon(R.drawable.ic_mood_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
- PrimaryDrawerItem().withName(R.string.navigation_drawer_item_tl_notification).withIdentifier(NAV_ITEM_TL_NOTIFICATION).withIcon(R.drawable.ic_notifications_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
+ PrimaryDrawerItem().withName(R.string.navigation_drawer_item_tl_public).withIdentifier(NavItem.NAV_ITEM_TL_PUBLIC.ordinal.toLong()).withIcon(R.drawable.ic_public_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
+ PrimaryDrawerItem().withName(R.string.navigation_drawer_item_tl_local).withIdentifier(NavItem.NAV_ITEM_TL_LOCAL.ordinal.toLong()).withIcon(R.drawable.ic_place_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
+ PrimaryDrawerItem().withName(R.string.navigation_drawer_item_tl_user).withIdentifier(NavItem.NAV_ITEM_TL_USER.ordinal.toLong()).withIcon(R.drawable.ic_mood_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
+ PrimaryDrawerItem().withName(R.string.navigation_drawer_item_tl_notification).withIdentifier(NavItem.NAV_ITEM_TL_NOTIFICATION.ordinal.toLong()).withIcon(R.drawable.ic_notifications_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
DividerDrawerItem(),
- PrimaryDrawerItem().withName(R.string.navigation_drawer_item_login).withIdentifier(NAV_ITEM_LOGIN).withIcon(R.drawable.ic_person_add_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
+ PrimaryDrawerItem().withName(R.string.navigation_drawer_item_login).withIdentifier(NavItem.NAV_ITEM_LOGIN.ordinal.toLong()).withIcon(R.drawable.ic_person_add_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
DividerDrawerItem(),
- PrimaryDrawerItem().withName(R.string.navigation_drawer_item_settings).withIdentifier(NAV_ITEM_SETTINGS).withIcon(R.drawable.ic_settings_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark)
+ PrimaryDrawerItem().withName(R.string.navigation_drawer_item_settings).withIdentifier(NavItem.NAV_ITEM_SETTINGS.ordinal.toLong()).withIcon(R.drawable.ic_settings_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark),
+ PrimaryDrawerItem().withName(R.string.navigation_drawer_item_others).withIdentifier(NavItem.NAV_ITEM_OTHERS.ordinal.toLong()).withIcon(R.drawable.ic_extension_black_24px).withIconTintingEnabled(true).withIconColorRes(R.color.icon_tint_dark)
)
.withOnDrawerItemClickListener { _, _, item ->
return@withOnDrawerItemClickListener when (item.identifier) {
- NAV_ITEM_LOGIN -> {
+ NavItem.NAV_ITEM_LOGIN.ordinal.toLong() -> {
startActivity(LoginActivity.getIntent(this))
false
}
- NAV_ITEM_TL_PUBLIC -> {
+ NavItem.NAV_ITEM_TL_PUBLIC.ordinal.toLong() -> {
showTimelineFragment(TimelineFragment.Category.Public)
false
}
- NAV_ITEM_TL_LOCAL -> {
+ NavItem.NAV_ITEM_TL_LOCAL.ordinal.toLong() -> {
showTimelineFragment(TimelineFragment.Category.Local)
false
}
- NAV_ITEM_TL_USER -> {
+ NavItem.NAV_ITEM_TL_USER.ordinal.toLong() -> {
showTimelineFragment(TimelineFragment.Category.User)
false
}
- NAV_ITEM_TL_NOTIFICATION -> {
+ NavItem.NAV_ITEM_TL_NOTIFICATION.ordinal.toLong() -> {
showTimelineFragment(TimelineFragment.Category.Notification)
false
}
- NAV_ITEM_SETTINGS -> {
- val intent = SettingActivity.getIntent(this)
+ NavItem.NAV_ITEM_SETTINGS.ordinal.toLong() -> {
+ val intent = SettingActivity.getIntent(this, SettingActivity.Type.Preference)
+ startActivity(intent)
+ false
+ }
+
+ NavItem.NAV_ITEM_OTHERS.ordinal.toLong() -> {
+ val intent = SettingActivity.getIntent(this, SettingActivity.Type.Misc)
startActivity(intent)
false
}
diff --git a/app/src/main/java/com/geckour/egret/view/activity/SettingActivity.kt b/app/src/main/java/com/geckour/egret/view/activity/SettingActivity.kt
index e8ad908..066527b 100644
--- a/app/src/main/java/com/geckour/egret/view/activity/SettingActivity.kt
+++ b/app/src/main/java/com/geckour/egret/view/activity/SettingActivity.kt
@@ -7,19 +7,27 @@ import android.os.Bundle
import android.support.v7.widget.Toolbar
import com.geckour.egret.R
import com.geckour.egret.databinding.ActivityMainBinding
+import com.geckour.egret.view.fragment.MiscFragment
import com.geckour.egret.view.fragment.SettingMainFragment
+import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper
class SettingActivity: BaseActivity() {
- lateinit var binding: ActivityMainBinding
+ enum class Type {
+ Preference,
+ Misc
+ }
companion object {
- fun getIntent(context: Context): Intent {
- val intent = Intent(context, SettingActivity::class.java)
- return intent
- }
+ private val ARGS_KEY_TYPE = "argsKeyType"
+ fun getIntent(context: Context, type: Type) = Intent(context, SettingActivity::class.java)
+ .apply {
+ putExtra(ARGS_KEY_TYPE, type)
+ }
}
+ lateinit var binding: ActivityMainBinding
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(if (isModeDark()) R.style.AppTheme_Dark_NoActionBar else R.style.AppTheme_NoActionBar)
@@ -28,8 +36,26 @@ class SettingActivity: BaseActivity() {
setSupportActionBar(toolbar)
binding.appBarMain.contentMain.fab.hide()
- supportFragmentManager.beginTransaction()
- .replace(R.id.container, SettingMainFragment.newInstance(), SettingMainFragment.TAG)
- .commit()
+ if (savedInstanceState == null) {
+ if (intent.hasExtra(ARGS_KEY_TYPE)) {
+ when (intent.extras[ARGS_KEY_TYPE]) {
+ Type.Preference -> {
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.container, SettingMainFragment.newInstance(), SettingMainFragment.TAG)
+ .commit()
+ }
+
+ Type.Misc -> {
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.container, MiscFragment.newInstance(), MiscFragment.TAG)
+ .commit()
+ }
+ }
+ }
+ }
+ }
+
+ override fun attachBaseContext(newBase: Context?) {
+ super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase))
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/activity/ShareActivity.kt b/app/src/main/java/com/geckour/egret/view/activity/ShareActivity.kt
new file mode 100644
index 0000000..db5ac2b
--- /dev/null
+++ b/app/src/main/java/com/geckour/egret/view/activity/ShareActivity.kt
@@ -0,0 +1,30 @@
+package com.geckour.egret.view.activity
+
+import android.content.Intent
+import android.databinding.DataBindingUtil
+import android.os.Bundle
+import com.geckour.egret.R
+import com.geckour.egret.databinding.ActivityShareBinding
+import com.geckour.egret.util.Common
+import com.geckour.egret.view.fragment.NewTootCreateFragment
+
+class ShareActivity: BaseActivity() {
+
+ lateinit private var binding: ActivityShareBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_share)
+
+
+ val token = Common.getCurrentAccessToken() ?: return
+
+ intent.apply {
+ if (type != "text/plain") return
+
+ supportFragmentManager.beginTransaction()
+ .add(R.id.container, NewTootCreateFragment.newInstance(token.id, body = extras?.getString(Intent.EXTRA_TEXT, "")), NewTootCreateFragment.TAG)
+ .commit()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/activity/SplashActivity.kt b/app/src/main/java/com/geckour/egret/view/activity/SplashActivity.kt
index 042dd6e..78b4e5b 100644
--- a/app/src/main/java/com/geckour/egret/view/activity/SplashActivity.kt
+++ b/app/src/main/java/com/geckour/egret/view/activity/SplashActivity.kt
@@ -3,10 +3,14 @@ package com.geckour.egret.view.activity
import android.content.Context
import android.databinding.DataBindingUtil
import android.os.Bundle
+import com.bumptech.glide.Glide
import com.geckour.egret.R
import com.geckour.egret.databinding.ActivitySplashBinding
import com.geckour.egret.util.Common
import com.trello.rxlifecycle2.components.support.RxAppCompatActivity
+import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper
class SplashActivity : BaseActivity() {
@@ -18,7 +22,7 @@ class SplashActivity : BaseActivity() {
binding = DataBindingUtil.setContentView(this, R.layout.activity_splash)
- Common.hasCertified { hasCertified, accountId ->
+ Common.hasCertified { hasCertified, _ ->
val intent = if (hasCertified) MainActivity.getIntent(this@SplashActivity) else LoginActivity.getIntent(this@SplashActivity)
startActivity(intent)
}
diff --git a/app/src/main/java/com/geckour/egret/view/adapter/SearchResultAdapter.kt b/app/src/main/java/com/geckour/egret/view/adapter/SearchResultAdapter.kt
index 74b4a7b..9f022a5 100644
--- a/app/src/main/java/com/geckour/egret/view/adapter/SearchResultAdapter.kt
+++ b/app/src/main/java/com/geckour/egret/view/adapter/SearchResultAdapter.kt
@@ -54,19 +54,19 @@ class SearchResultAdapter(val listener: TimelineAdapter.Callbacks) : RecyclerVie
content.mediaUrls.indices.forEach {
when (it) {
0 -> {
- if (content.isSensitive ?: false) toggleMediaSpoiler(statusBinding.mediaSpoilerWrap1, true)
+ if (content.isSensitive == true) toggleMediaSpoiler(statusBinding.mediaSpoilerWrap1, true)
setupMedia(statusBinding.media1, content.mediaPreviewUrls, content.mediaUrls, it)
}
1 -> {
- if (content.isSensitive ?: false) toggleMediaSpoiler(statusBinding.mediaSpoilerWrap2, true)
+ if (content.isSensitive == true) toggleMediaSpoiler(statusBinding.mediaSpoilerWrap2, true)
setupMedia(statusBinding.media2, content.mediaPreviewUrls, content.mediaUrls, it)
}
2 -> {
- if (content.isSensitive ?: false) toggleMediaSpoiler(statusBinding.mediaSpoilerWrap3, true)
+ if (content.isSensitive == true) toggleMediaSpoiler(statusBinding.mediaSpoilerWrap3, true)
setupMedia(statusBinding.media3, content.mediaPreviewUrls, content.mediaUrls, it)
}
3 -> {
- if (content.isSensitive ?: false) toggleMediaSpoiler(statusBinding.mediaSpoilerWrap4, true)
+ if (content.isSensitive == true) toggleMediaSpoiler(statusBinding.mediaSpoilerWrap4, true)
setupMedia(statusBinding.media4, content.mediaPreviewUrls, content.mediaUrls, it)
}
}
@@ -101,7 +101,7 @@ class SearchResultAdapter(val listener: TimelineAdapter.Callbacks) : RecyclerVie
initVisibility(SearchResultFragment.Category.Account)
(accountBinding.root as ViewGroup).apply {
- for (i in 0..childCount - 1) getChildAt(i).setOnClickListener { listener.showProfile(accountBinding.account.id) }
+ for (i in 0 until childCount) getChildAt(i).setOnClickListener { listener.showProfile(accountBinding.account.id) }
}
accountBinding.account = content
}
diff --git a/app/src/main/java/com/geckour/egret/view/adapter/TimelineAdapter.kt b/app/src/main/java/com/geckour/egret/view/adapter/TimelineAdapter.kt
index 24cfa58..d3fc2fd 100644
--- a/app/src/main/java/com/geckour/egret/view/adapter/TimelineAdapter.kt
+++ b/app/src/main/java/com/geckour/egret/view/adapter/TimelineAdapter.kt
@@ -23,10 +23,13 @@ import io.reactivex.schedulers.Schedulers
import java.util.*
import kotlin.collections.ArrayList
-class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootCallback? = null, val doFilter: Boolean = true) : RecyclerView.Adapter() {
+class TimelineAdapter(
+ val listener: Callbacks,
+ private val onAddTootListener: OnAddTootCallback? = null,
+ private val doFilter: Boolean = true): RecyclerView.Adapter() {
companion object {
- val DEFAULT_ITEMS_LIMIT = 100
+ const val DEFAULT_ITEMS_LIMIT = 100
}
enum class ContentType {
@@ -39,6 +42,8 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
interface Callbacks {
val copyTootUrlToClipboard: (url: String) -> Any
+ val shareToot: (content: TimelineContent.TimelineStatus) -> Any
+
val showTootInBrowser: (content: TimelineContent.TimelineStatus) -> Any
val copyTootToClipboard: (content: TimelineContent.TimelineStatus) -> Any
@@ -87,19 +92,19 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
content.mediaUrls.indices.forEach {
when (it) {
0 -> {
- if (content.isSensitive ?: false) toggleMediaSpoiler(timelineBinding.mediaSpoilerWrap1, true)
+ if (content.isSensitive == true) toggleMediaSpoiler(timelineBinding.mediaSpoilerWrap1, true)
setupMedia(timelineBinding.media1, content.mediaPreviewUrls, content.mediaUrls, it)
}
1 -> {
- if (content.isSensitive ?: false) toggleMediaSpoiler(timelineBinding.mediaSpoilerWrap2, true)
+ if (content.isSensitive == true) toggleMediaSpoiler(timelineBinding.mediaSpoilerWrap2, true)
setupMedia(timelineBinding.media2, content.mediaPreviewUrls, content.mediaUrls, it)
}
2 -> {
- if (content.isSensitive ?: false) toggleMediaSpoiler(timelineBinding.mediaSpoilerWrap3, true)
+ if (content.isSensitive == true) toggleMediaSpoiler(timelineBinding.mediaSpoilerWrap3, true)
setupMedia(timelineBinding.media3, content.mediaPreviewUrls, content.mediaUrls, it)
}
3 -> {
- if (content.isSensitive ?: false) toggleMediaSpoiler(timelineBinding.mediaSpoilerWrap4, true)
+ if (content.isSensitive == true) toggleMediaSpoiler(timelineBinding.mediaSpoilerWrap4, true)
setupMedia(timelineBinding.media4, content.mediaPreviewUrls, content.mediaUrls, it)
}
}
@@ -155,7 +160,7 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
else toggleStatus(ContentType.Notification, true)
}
- fun initVisibility(type: ContentType) {
+ private fun initVisibility(type: ContentType) {
toggleAction(type, false)
toggleStatus(type, false)
initSpoiler(type)
@@ -164,6 +169,9 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
ContentType.Status -> {
timelineBinding.body.text = null
+ timelineBinding.actionLock.visibility = View.GONE
+ timelineBinding.lock.visibility = View.GONE
+
listOf(timelineBinding.media1, timelineBinding.media2, timelineBinding.media3, timelineBinding.media4)
.forEach {
it.apply {
@@ -182,6 +190,9 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
visibility = View.GONE
}
+ notificationBinding.actionLock.visibility = View.GONE
+ notificationBinding.lock.visibility = View.GONE
+
listOf(notificationBinding.media1, notificationBinding.media2, notificationBinding.media3, notificationBinding.media4)
.forEach {
it.apply {
@@ -196,7 +207,7 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
}
}
- fun bindAction(contentType: ContentType, notificationType: Notification.NotificationType) {
+ private fun bindAction(contentType: ContentType, notificationType: Notification.NotificationType) {
when (contentType) {
ContentType.Status -> {
if (notificationType == Notification.NotificationType.reblog) {
@@ -230,7 +241,7 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
}
}
- fun toggleAction(type: ContentType, show: Boolean) {
+ private fun toggleAction(type: ContentType, show: Boolean) {
when(type) {
ContentType.Status -> {
listOf(timelineBinding.indicateAction, timelineBinding.actionIcon, timelineBinding.actionBy, timelineBinding.actionName)
@@ -254,7 +265,7 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
}
}
- fun toggleStatus(type: ContentType, show: Boolean) {
+ private fun toggleStatus(type: ContentType, show: Boolean) {
when(type) {
ContentType.Status -> {
listOf(
@@ -292,7 +303,7 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
}
}
- fun initSpoiler(type: ContentType) {
+ private fun initSpoiler(type: ContentType) {
when(type) {
ContentType.Status -> {
timelineBinding.bodyAdditional.visibility = View.GONE
@@ -306,7 +317,7 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
}
}
- fun toggleBodySpoiler(type: ContentType, show: Boolean) {
+ private fun toggleBodySpoiler(type: ContentType, show: Boolean) {
when(type) {
ContentType.Status -> {
timelineBinding.clearSpoiler.apply {
@@ -328,7 +339,7 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
}
}
- fun showPopup(type: ContentType, view: View) {
+ private fun showPopup(type: ContentType, view: View) {
val popup = PopupMenu(view.context, view)
val currentAccountId = OrmaProvider.db.selectFromAccessToken().isCurrentEq(true).last().accountId
val contentAccountId = when(type) {
@@ -345,6 +356,11 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
true
}
+ R.id.action_share -> {
+ listener.shareToot(timelineBinding.status)
+ true
+ }
+
R.id.action_open -> {
listener.showTootInBrowser(timelineBinding.status)
true
@@ -402,14 +418,14 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
popup.show()
}
- fun toggleMediaSpoiler(view: View, show: Boolean) {
+ private fun toggleMediaSpoiler(view: View, show: Boolean) {
view.apply {
setOnClickListener { it.visibility = View.GONE }
visibility = if (show) View.VISIBLE else View.GONE
}
}
- fun setupMedia(view: ImageView, previewUrls: List, urls: List, position: Int) {
+ private fun setupMedia(view: ImageView, previewUrls: List, urls: List, position: Int) {
view.apply {
visibility = View.VISIBLE
setOnClickListener { listener.onClickMedia(urls, position) }
@@ -417,7 +433,7 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
Glide.with(view.context).load(previewUrls[position]).into(view)
}
- fun reflectTreeStatus() {
+ private fun reflectTreeStatus() {
when (timelineBinding.status.treeStatus) {
TimelineContent.TimelineStatus.TreeStatus.None -> {
timelineBinding.treeLineUpper.visibility = View.GONE
@@ -439,9 +455,8 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
}
}
- override fun getItemViewType(position: Int): Int {
- return getContent(position).let { if (it.status != null) ContentType.Status.ordinal else if (it.notification != null) ContentType.Notification.ordinal else -1 }
- }
+ override fun getItemViewType(position: Int): Int =
+ getContent(position).let { if (it.status != null) ContentType.Status.ordinal else if (it.notification != null) ContentType.Notification.ordinal else -1 }
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
if (viewType == ContentType.Status.ordinal) {
@@ -461,9 +476,7 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
item.notification?.let { holder?.bindData(it) }
}
- override fun getItemCount(): Int {
- return contents.size
- }
+ override fun getItemCount(): Int = contents.size
fun getContent(index: Int): TimelineContent = this.contents[index]
@@ -474,41 +487,31 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
fun getContents(): List = this.contents
fun addContent(content: TimelineContent, limit: Int = DEFAULT_ITEMS_LIMIT) {
- Single.just(content)
- .map { Pair(it, shouldMute(it)) }
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe({ (c, b) ->
- if (!b) {
- this.contents.add(0, c)
- notifyItemInserted(0)
- onAddTootListener?.onAddOnTop()
- removeItemsWhenOverLimit(limit)
- }
- }, Throwable::printStackTrace)
+ shouldMute(content).subscribe({ (c, b) ->
+ if (!b) {
+ this.contents.add(0, c)
+ notifyItemInserted(0)
+ onAddTootListener?.onAddOnTop()
+ removeItemsWhenOverLimit(limit)
+ }
+ }, Throwable::printStackTrace)
}
fun addContentAtLast(content: TimelineContent, limit: Int = DEFAULT_ITEMS_LIMIT) {
- Single.just(content)
- .map { Pair(it, shouldMute(it)) }
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe({ (c, b) ->
- if (!b) {
- this.contents.add(c)
- notifyItemInserted(this.contents.lastIndex)
- removeItemsWhenOverLimit(limit)
- }
- }, Throwable::printStackTrace)
+ shouldMute(content).subscribe({ (c, b) ->
+ if (!b) {
+ this.contents.add(c)
+ notifyItemInserted(this.contents.lastIndex)
+ removeItemsWhenOverLimit(limit)
+ }
+ }, Throwable::printStackTrace)
}
fun addAllContents(contents: List, limit: Int = DEFAULT_ITEMS_LIMIT) {
val cs: ArrayList = ArrayList()
Observable.fromIterable(contents)
- .map { Pair(it, shouldMute(it)) }
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
+ .flatMap { shouldMute(it).toObservable() }
.subscribe({ (c, b) ->
if (!b) {
cs.add(c)
@@ -525,7 +528,7 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
val cs: ArrayList = ArrayList()
Observable.fromIterable(contents)
- .map { Pair(it, shouldMute(it)) }
+ .flatMap { shouldMute(it).toObservable() }
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ (c, b) ->
@@ -563,32 +566,41 @@ class TimelineAdapter(val listener: Callbacks, val onAddTootListener: OnAddTootC
}
}
- fun shouldMute(content: TimelineContent): Boolean {
- if (!doFilter) return false
+ private fun shouldMute(content: TimelineContent): Single> {
+ return Single.just(content)
+ .map {
+ val mute: Boolean = run {
+ if (!doFilter) return@run false
- OrmaProvider.db.selectFromMuteClient().forEach {
- if (content.status?.app == it.client) return true
- }
- OrmaProvider.db.selectFromMuteHashTag().forEach { tag ->
- content.status?.tags?.forEach { if (tag.hashTag == it) return true }
- }
- OrmaProvider.db.selectFromMuteKeyword().forEach {
- if (it.isRegex) {
- if (content.status?.body?.toString()?.matches(Regex(it.keyword)) ?: false) return true
- } else if (content.status?.body?.toString()?.contains(it.keyword) ?: false) return true
- }
- OrmaProvider.db.selectFromMuteInstance().forEach {
- var instance = content.status?.nameWeak?.replace(Regex("^@.+@(.+)$"), "@$1") ?: ""
- if (content.status?.nameWeak == instance) {
- instance = Common.getCurrentAccessToken()?.instanceId?.let {
- "@${OrmaProvider.db.selectFromInstanceAuthInfo().idEq(it).last().instance}"
- } ?: ""
- }
+ OrmaProvider.db.selectFromMuteClient().forEach {
+ if (content.status?.app == it.client) return@run true
+ }
+ OrmaProvider.db.selectFromMuteHashTag().forEach { tag ->
+ content.status?.tags?.forEach { if (tag.hashTag == it) return@run true }
+ }
+ OrmaProvider.db.selectFromMuteKeyword().forEach {
+ if (it.isRegex) {
+ if (content.status?.body?.toString()?.matches(Regex(it.keyword)) == true) return@run true
+ } else if (content.status?.body?.toString()?.contains(it.keyword) == true) return@run true
+ }
+ OrmaProvider.db.selectFromMuteInstance().forEach {
+ var instance = content.status?.nameWeak?.replace(Regex("^@.+@(.+)$"), "@$1") ?: ""
+ if (content.status?.nameWeak == instance) {
+ instance = Common.getCurrentAccessToken()?.instanceId?.let {
+ "@${OrmaProvider.db.selectFromInstanceAuthInfo().idEq(it).last().instance}"
+ } ?: ""
+ }
- if (it.instance == instance) return true
- }
+ if (it.instance == instance) return@run true
+ }
- return false
+ return@run false
+ }
+
+ Pair(it, mute)
+ }
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
}
private fun removeItemsWhenOverLimit(limit: Int = DEFAULT_ITEMS_LIMIT) {
diff --git a/app/src/main/java/com/geckour/egret/view/adapter/model/ProfileContent.kt b/app/src/main/java/com/geckour/egret/view/adapter/model/ProfileContent.kt
index ba5123d..71025ca 100644
--- a/app/src/main/java/com/geckour/egret/view/adapter/model/ProfileContent.kt
+++ b/app/src/main/java/com/geckour/egret/view/adapter/model/ProfileContent.kt
@@ -3,8 +3,12 @@ package com.geckour.egret.view.adapter.model
import android.text.Spanned
data class ProfileContent(
+ val id: Long,
var iconUrl: String,
+ var iconImg: String?,
var headerUrl: String,
+ var headerImg: String?,
+ var locked: Boolean,
var screenName: String,
var username: String,
var url: Spanned,
@@ -12,5 +16,5 @@ data class ProfileContent(
var followingCount: Long,
var followerCount: Long,
var tootCount: Long,
- var createdAt: Long
+ val createdAt: Long
)
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/adapter/model/TimelineContent.kt b/app/src/main/java/com/geckour/egret/view/adapter/model/TimelineContent.kt
index c69effb..67bb641 100644
--- a/app/src/main/java/com/geckour/egret/view/adapter/model/TimelineContent.kt
+++ b/app/src/main/java/com/geckour/egret/view/adapter/model/TimelineContent.kt
@@ -1,7 +1,7 @@
package com.geckour.egret.view.adapter.model
import android.text.Spanned
-import com.geckour.egret.api.model.Notification
+import java.io.Serializable
import java.util.*
data class TimelineContent(
@@ -13,6 +13,7 @@ data class TimelineContent(
val tootUrl: String,
val accountId: Long,
var iconUrl: String,
+ var accountLocked: Boolean,
var nameStrong: String,
var nameWeak: String,
val time: Date,
@@ -29,7 +30,7 @@ data class TimelineContent(
var rebloggedStatusContent: TimelineStatus?,
var app: String?,
var treeStatus: TreeStatus
- ) {
+ ): Serializable {
enum class TreeStatus {
None,
Top,
@@ -42,6 +43,7 @@ data class TimelineContent(
val id: Long,
val type: String,
val accountId: Long,
+ var accountLocked: Boolean,
var iconUrl: String,
var nameStrong: String,
var nameWeak: String,
diff --git a/app/src/main/java/com/geckour/egret/view/adapter/model/adapter/SpannedTypeAdapter.kt b/app/src/main/java/com/geckour/egret/view/adapter/model/adapter/SpannedTypeAdapter.kt
index 447a445..4660369 100644
--- a/app/src/main/java/com/geckour/egret/view/adapter/model/adapter/SpannedTypeAdapter.kt
+++ b/app/src/main/java/com/geckour/egret/view/adapter/model/adapter/SpannedTypeAdapter.kt
@@ -1,46 +1,27 @@
package com.geckour.egret.view.adapter.model.adapter
import android.os.Build
-import android.os.Parcel
import android.text.Html
import android.text.Spanned
-import android.util.Log
import com.geckour.egret.util.Common
import com.google.gson.*
-import paperparcel.TypeAdapter
import java.lang.reflect.Type
-class SpannedTypeAdapter: TypeAdapter, JsonSerializer, JsonDeserializer, InstanceCreator {
+class SpannedTypeAdapter: JsonSerializer, JsonDeserializer {
companion object {
- val KEY = "Spanned"
+ const val KEY = "Spanned"
}
- override fun writeToParcel(value: Spanned, outParcel: Parcel, flags: Int) {
- outParcel.writeString(value.toString())
- }
-
- override fun readFromParcel(inParcel: Parcel): Spanned {
- return Common.getSpannedWithoutExtraMarginFromHtml(inParcel.readString())
- }
-
- override fun createInstance(type: Type?): Spanned {
- return Common.getSpannedWithoutExtraMarginFromHtml("")
- }
-
- override fun serialize(src: Spanned?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
- return JsonObject().apply {
- if (src == null) {
- addProperty(KEY, "")
- } else {
+ override fun serialize(src: Spanned?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement =
+ JsonObject().apply {
addProperty(KEY,
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) Html.toHtml(src, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)?.toString()
- else Html.toHtml(src).toString())
+ src?.let {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) Html.toHtml(src, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)?.toString()
+ else Html.toHtml(src).toString()
+ } ?: "")
}
- }
- }
- override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Spanned {
- return Common.getSpannedWithoutExtraMarginFromHtml(if (json == null) "" else json.asJsonObject.get(KEY)?.asString ?: "")
- }
+ override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Spanned =
+ Common.getSpannedWithoutExtraMarginFromHtml(json?.asJsonObject?.get(KEY)?.asString ?: "")
}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/AccountManageFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/AccountManageFragment.kt
index ae0a3d8..0d0665f 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/AccountManageFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/AccountManageFragment.kt
@@ -82,7 +82,7 @@ class AccountManageFragment: BaseFragment() {
preItems.clear()
Observable.fromIterable(OrmaProvider.db.selectFromAccessToken())
- .flatMap { token ->
+ .map { token ->
MastodonClient(Common.setAuthInfo(token) ?: throw IllegalArgumentException()).getAccount(token.accountId)
.map {
val domain = OrmaProvider.db.selectFromInstanceAuthInfo().idEq(token.instanceId).last().instance
@@ -90,11 +90,13 @@ class AccountManageFragment: BaseFragment() {
}
}
.subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
.subscribe({
- adapter.addItem(it)
- preItems.add(it)
+ it.observeOn(AndroidSchedulers.mainThread())
+ .subscribe({ content ->
+ adapter.addItem(content)
+ preItems.add(content)
+ }, Throwable::printStackTrace)
}, Throwable::printStackTrace)
}
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/AccountProfileFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/AccountProfileFragment.kt
index b6141e4..c4aa9f8 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/AccountProfileFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/AccountProfileFragment.kt
@@ -1,15 +1,25 @@
package com.geckour.egret.view.fragment
-import android.content.SharedPreferences
+import android.Manifest
+import android.app.Activity
+import android.content.ContentValues
+import android.content.Intent
+import android.content.pm.PackageManager
import android.databinding.DataBindingUtil
+import android.net.Uri
import android.os.Bundle
-import android.preference.PreferenceManager
+import android.provider.MediaStore
+import android.support.design.widget.Snackbar
+import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.widget.RecyclerView
+import android.text.method.TextKeyListener
+import android.util.Base64
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
+import android.widget.Button
import com.bumptech.glide.Glide
import com.geckour.egret.R
import com.geckour.egret.api.MastodonClient
@@ -20,41 +30,50 @@ import com.geckour.egret.databinding.FragmentAccountProfileBinding
import com.geckour.egret.util.Common
import com.geckour.egret.view.activity.MainActivity
import com.geckour.egret.view.adapter.TimelineAdapter
-import io.reactivex.Observable
+import com.geckour.egret.view.adapter.model.ProfileContent
+import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
+import org.apache.commons.io.IOUtils
import retrofit2.adapter.rxjava2.Result
+import java.io.File
+import java.io.InputStream
class AccountProfileFragment: BaseFragment() {
companion object {
val TAG: String = this::class.java.simpleName
- val ARGS_KEY_ACCOUNT = "account"
+ private val ARGS_KEY_ACCOUNT = "profile"
+ private val REQUEST_CODE_AVATAR_PICK_MEDIA = 101
+ private val REQUEST_CODE_AVATAR_CAPTURE_IMAGE = 102
+ private val REQUEST_CODE_HEADER_PICK_MEDIA = 201
+ private val REQUEST_CODE_HEADER_CAPTURE_IMAGE = 202
+ private val REQUEST_CODE_GRANT_READ_STORAGE_AVATAR = 1
+ private val REQUEST_CODE_GRANT_READ_STORAGE_HEADER = 2
+ private val REQUEST_CODE_GRANT_WRITE_STORAGE_AVATAR = 3
+ private val REQUEST_CODE_GRANT_WRITE_STORAGE_HEADER = 4
fun newInstance(account: Account): AccountProfileFragment = AccountProfileFragment().apply {
arguments = Bundle().apply {
putSerializable(ARGS_KEY_ACCOUNT, account)
}
}
+ }
- fun newObservableInstance(accountId: Long): Observable {
- return MastodonClient(Common.resetAuthInfo() ?: throw IllegalArgumentException()).getAccount(accountId)
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .flatMap { account ->
- val fragment = newInstance(account)
- Observable.just(fragment)
- }
- }
+ enum class UploadType {
+ Avatar,
+ Header
}
- private val account: Account by lazy { arguments[ARGS_KEY_ACCOUNT] as Account }
+ lateinit private var profile: ProfileContent
+ private val editedProfile: ProfileContent by lazy { profile.copy() }
lateinit private var relationship: Relationship
lateinit private var binding: FragmentAccountProfileBinding
private var onTop = true
private var inTouch = false
private val adapter: TimelineAdapter by lazy { TimelineAdapter((activity as MainActivity).timelineListener) }
- private val sharedPref: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(activity) }
+
+ private var capturedImageUri: Uri? = null
private var maxId: Long = -1
private var sinceId: Long = -1
@@ -62,12 +81,13 @@ class AccountProfileFragment: BaseFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activity as MainActivity).supportActionBar?.hide()
+ profile = Common.getProfileContent(arguments[ARGS_KEY_ACCOUNT] as Account)
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_account_profile, container, false)
- val content = Common.getProfileContent(account)
+ val content = profile
binding.content = content
binding.timeString = Common.getReadableDateString(content.createdAt, true)
@@ -79,15 +99,41 @@ class AccountProfileFragment: BaseFragment() {
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- binding.icon.setOnClickListener { showImageViewer(listOf(account.avatarUrl), 0) }
- binding.header.setOnClickListener { showImageViewer(listOf(account.headerUrl), 0) }
+ initViewsVisibility()
+
+ binding.icon.setOnClickListener { showImageViewer(listOf(profile.iconUrl), 0) }
+ binding.header.setOnClickListener { showImageViewer(listOf(profile.headerUrl), 0) }
+
+ if (profile.id == Common.getCurrentAccessToken()?.accountId) {
+ binding.buttonFavList.apply {
+ setOnClickListener { showFavList() }
+ visibility = View.VISIBLE
+ }
+
+ binding.buttonEdit.apply {
+ setOnClickListener {
+ val enter = text == getString(R.string.button_edit)
+ toggleEditMode(enter, this)
+ }
+ visibility = View.VISIBLE
+ }
+
+ listOf(
+ binding.screenName,
+ binding.note
+ )
+ .forEach {
+ it.tag = it.keyListener
+ it.setOnKeyListener(null)
+ }
+ }
val movementMethod = Common.getMovementMethodFromPreference(binding.root.context)
binding.url.movementMethod = movementMethod
binding.note.movementMethod = movementMethod
val domain = Common.resetAuthInfo()
- if (domain != null) MastodonClient(domain).getAccountRelationships(account.id)
+ if (domain != null) MastodonClient(domain).getAccountRelationships(profile.id)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
@@ -95,31 +141,33 @@ class AccountProfileFragment: BaseFragment() {
this.relationship = relationships.first()
val currentAccountId = Common.getCurrentAccessToken()?.accountId
- if (currentAccountId != null && currentAccountId != account.id) {
+ if (currentAccountId != null && currentAccountId != profile.id) {
binding.follow.visibility = View.VISIBLE
binding.block.visibility = View.VISIBLE
binding.mute.visibility = View.VISIBLE
}
- setFollowButtonState(this.relationship.following)
+ setFollowButtonState(this.relationship)
binding.follow.setOnClickListener {
+ if (this.relationship.hasSendFollowRequest) return@setOnClickListener
+
if (this.relationship.following) {
- MastodonClient(domain).unFollowAccount(account.id)
+ MastodonClient(domain).unFollowAccount(profile.id)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
.subscribe( { relation ->
this.relationship = relation
- setFollowButtonState(relation.following)
+ setFollowButtonState(relation)
}, Throwable::printStackTrace)
} else {
- MastodonClient(domain).followAccount(account.id)
+ MastodonClient(domain).followAccount(profile.id)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
.subscribe( { relation ->
this.relationship = relation
- setFollowButtonState(relation.following)
+ setFollowButtonState(relation)
}, Throwable::printStackTrace)
}
}
@@ -127,7 +175,7 @@ class AccountProfileFragment: BaseFragment() {
setBlockButtonState(this.relationship.blocking)
binding.block.setOnClickListener {
if (this.relationship.blocking) {
- MastodonClient(domain).unBlockAccount(account.id)
+ MastodonClient(domain).unBlockAccount(profile.id)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
@@ -136,7 +184,7 @@ class AccountProfileFragment: BaseFragment() {
setBlockButtonState(relation.blocking)
}, Throwable::printStackTrace)
} else {
- MastodonClient(domain).blockAccount(account.id)
+ MastodonClient(domain).blockAccount(profile.id)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
@@ -150,7 +198,7 @@ class AccountProfileFragment: BaseFragment() {
setMuteButtonState(this.relationship.blocking)
binding.mute.setOnClickListener {
if (this.relationship.muting) {
- MastodonClient(domain).unMuteAccount(account.id)
+ MastodonClient(domain).unMuteAccount(profile.id)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
@@ -159,7 +207,7 @@ class AccountProfileFragment: BaseFragment() {
setMuteButtonState(relation.muting)
}, Throwable::printStackTrace)
} else {
- MastodonClient(domain).muteAccount(account.id)
+ MastodonClient(domain).muteAccount(profile.id)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
@@ -222,7 +270,275 @@ class AccountProfileFragment: BaseFragment() {
refreshBarTitle()
}
- fun showImageViewer(urls: List, position: Int) {
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ when (requestCode) {
+ REQUEST_CODE_AVATAR_PICK_MEDIA -> {
+ if (resultCode == Activity.RESULT_OK) {
+ data?.let { processPostMedia(it, UploadType.Avatar) }
+ }
+ }
+
+ REQUEST_CODE_AVATAR_CAPTURE_IMAGE -> {
+ if (resultCode == Activity.RESULT_OK) {
+ capturedImageUri?.let { processPostImage(it, UploadType.Avatar) }
+ }
+ }
+
+ REQUEST_CODE_HEADER_PICK_MEDIA -> {
+ if (resultCode == Activity.RESULT_OK) {
+ data?.let { processPostMedia(it, UploadType.Header) }
+ }
+ }
+
+ REQUEST_CODE_HEADER_CAPTURE_IMAGE -> {
+ if (resultCode == Activity.RESULT_OK) {
+ capturedImageUri?.let { processPostImage(it, UploadType.Header) }
+ }
+ }
+ }
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ when (requestCode) {
+ REQUEST_CODE_GRANT_READ_STORAGE_AVATAR -> {
+ if (grantResults.isNotEmpty() &&
+ grantResults.none { it != PackageManager.PERMISSION_GRANTED }) {
+ pickMedia(UploadType.Avatar)
+ } else {
+ Snackbar.make(binding.root, R.string.message_necessity_wifi_grant, Snackbar.LENGTH_SHORT)
+ }
+ }
+
+ REQUEST_CODE_GRANT_READ_STORAGE_HEADER -> {
+ if (grantResults.isNotEmpty() &&
+ grantResults.none { it != PackageManager.PERMISSION_GRANTED }) {
+ pickMedia(UploadType.Header)
+ } else {
+ Snackbar.make(binding.root, R.string.message_necessity_wifi_grant, Snackbar.LENGTH_SHORT)
+ }
+ }
+
+ REQUEST_CODE_GRANT_WRITE_STORAGE_AVATAR -> {
+ if (grantResults.isNotEmpty() &&
+ grantResults.none { it != PackageManager.PERMISSION_GRANTED }) {
+ captureImage(UploadType.Avatar)
+ } else {
+ Snackbar.make(binding.root, R.string.message_necessity_wifi_grant, Snackbar.LENGTH_SHORT)
+ }
+ }
+
+ REQUEST_CODE_GRANT_WRITE_STORAGE_HEADER -> {
+ if (grantResults.isNotEmpty() &&
+ grantResults.none { it != PackageManager.PERMISSION_GRANTED }) {
+ captureImage(UploadType.Header)
+ } else {
+ Snackbar.make(binding.root, R.string.message_necessity_wifi_grant, Snackbar.LENGTH_SHORT)
+ }
+ }
+ }
+ }
+
+ private fun initViewsVisibility() {
+ listOf(
+ binding.buttonFavList,
+ binding.buttonCancelEdit,
+ binding.buttonEdit
+ )
+ .forEach { it.visibility = View.GONE }
+ }
+
+ private fun showFavList() {
+ (activity as MainActivity).showTimelineFragment(TimelineFragment.Category.Fav)
+ }
+
+ private fun toggleEditMode(enter: Boolean, button: Button, save: Boolean = true) { // FIXME: 常に編集できるようになってる
+ if (enter) {
+ button.text = getString(R.string.button_edit_save)
+ binding.buttonCancelEdit.apply {
+ setOnClickListener { toggleEditMode(false, button, false) }
+ visibility = View.VISIBLE
+ }
+
+ binding.icon.setOnClickListener { showImageUploader(UploadType.Avatar) }
+ binding.header.setOnClickListener { showImageUploader(UploadType.Header) }
+
+ listOf(
+ binding.screenName,
+ binding.note
+ )
+ .forEach { editText ->
+ (editText.tag as? TextKeyListener)?.let {
+ editText.keyListener = it
+ }
+ }
+ } else {
+ button.text = getString(R.string.button_edit)
+
+ binding.buttonCancelEdit.apply {
+ setOnClickListener {}
+ visibility = View.GONE
+ }
+
+ binding.icon.setOnClickListener { showImageViewer(listOf(profile.iconUrl), 0) }
+ binding.header.setOnClickListener { showImageViewer(listOf(profile.headerUrl), 0) }
+
+ listOf(
+ binding.screenName,
+ binding.note
+ )
+ .forEach {
+ it.tag = it.keyListener
+ it.setOnKeyListener(null)
+ }
+
+ if (save) submitEdit(button)
+ }
+ }
+
+ private fun showImageUploader(type: UploadType) = ChooseImageSourceDialogFragment.newInstance(type)
+ .apply {
+ setTargetFragment(this@AccountProfileFragment, 0)
+ show(this@AccountProfileFragment.activity.supportFragmentManager, ChooseImageSourceDialogFragment.TAG)
+ }
+
+ fun pickMedia(uploadType: UploadType) {
+ if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), when (uploadType) {
+ UploadType.Avatar -> REQUEST_CODE_GRANT_READ_STORAGE_AVATAR
+ UploadType.Header -> REQUEST_CODE_GRANT_READ_STORAGE_HEADER
+ })
+ } else {
+ val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
+ type = "image/* video/*"
+ }
+ startActivityForResult(intent, when (uploadType) {
+ UploadType.Avatar -> REQUEST_CODE_AVATAR_PICK_MEDIA
+ UploadType.Header -> REQUEST_CODE_HEADER_PICK_MEDIA
+ })
+ }
+ }
+
+ fun captureImage(uploadType: UploadType) {
+ if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), when (uploadType) {
+ UploadType.Avatar -> REQUEST_CODE_GRANT_WRITE_STORAGE_AVATAR
+ UploadType.Header -> REQUEST_CODE_GRANT_WRITE_STORAGE_HEADER
+ })
+ } else {
+ val values = ContentValues()
+ values.put(MediaStore.Images.Media.TITLE, "${System.currentTimeMillis()}.jpg")
+ capturedImageUri = activity.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
+
+ val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
+ putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri)
+ }
+
+ startActivityForResult(intent, when (uploadType) {
+ UploadType.Avatar -> REQUEST_CODE_AVATAR_CAPTURE_IMAGE
+ UploadType.Header -> REQUEST_CODE_HEADER_CAPTURE_IMAGE
+ })
+ }
+ }
+
+ private fun processPostMedia(data: Intent, type: UploadType) {
+ data.data?.let { uri ->
+ bindMedia(activity.contentResolver.openInputStream(uri), type)
+ }
+ }
+
+ private fun processPostImage(uri: Uri, type: UploadType) {
+ getImagePathFromUriAsSingle(uri)
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(bindToLifecycle())
+ .subscribe({ path ->
+ bindImage(File(path), type)
+ }, Throwable::printStackTrace)
+ }
+
+ private fun bindMedia(stream: InputStream, type: UploadType) {
+ val bytes = IOUtils.toByteArray(stream)
+ val base64String = "data:image/png;base64,${Base64.encodeToString(bytes, Base64.DEFAULT)}"
+
+ when (type) {
+ UploadType.Avatar -> {
+ Glide.with(activity).load(bytes).into(binding.icon)
+ editedProfile.iconImg = base64String
+ }
+
+ UploadType.Header -> {
+ Glide.with(activity).load(bytes).into(binding.header)
+ editedProfile.headerImg = base64String
+ }
+ }
+ }
+
+ private fun bindImage(file: File, type: UploadType) {
+ val bytes = file.readBytes()
+ val base64String = "data:image/jpeg;base64,${Base64.encodeToString(bytes, Base64.DEFAULT)}"
+
+ when (type) {
+ UploadType.Avatar -> {
+ Glide.with(activity).load(bytes).into(binding.icon)
+ editedProfile.iconImg = base64String
+ }
+
+ UploadType.Header -> {
+ Glide.with(activity).load(bytes).into(binding.header)
+ editedProfile.headerImg = base64String
+ }
+ }
+ }
+
+ private fun getImagePathFromUriAsSingle(uri: Uri): Single {
+ return Single.just(MediaStore.Images.Media.DATA)
+ .map { projection ->
+ val cursor = activity.contentResolver.query(uri, arrayOf(projection), null, null, null)
+ cursor.moveToFirst()
+
+ cursor.getString(cursor.getColumnIndexOrThrow(projection)).apply { cursor.close() }
+ }
+ }
+
+ private fun submitEdit(button: Button) {
+ editedProfile.apply {
+ screenName = binding.screenName.text.toString()
+ note = binding.note.text
+ }
+ val displayName = if (profile.screenName == editedProfile.screenName) null else editedProfile.screenName
+ val note = if (profile.note.toString() == editedProfile.note.toString()) null else editedProfile.note.toString()
+ val avatar = if (profile.iconImg == editedProfile.iconImg) null else editedProfile.iconImg
+ val header = if (profile.headerImg == editedProfile.headerImg) null else editedProfile.headerImg
+
+ profile = editedProfile.copy()
+
+ if (displayName == null &&
+ note == null &&
+ avatar == null &&
+ header == null) {
+ Snackbar.make(binding.root, R.string.none_update_edit_profile, Snackbar.LENGTH_SHORT).show()
+ return
+ }
+
+ Common.resetAuthInfo()?.let {
+ MastodonClient(it).updateOwnAccount(displayName, note, avatar, header)
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(bindToLifecycle())
+ .subscribe({
+ if (displayName != null) refreshBarTitle(displayName)
+ Snackbar.make(binding.root, R.string.succeed_edit_profile, Snackbar.LENGTH_SHORT).show()
+ toggleEditMode(false, button, false)
+ }, Throwable::printStackTrace)
+ }
+ }
+
+ private fun showImageViewer(urls: List, position: Int) {
val fragment = ShowImagesDialogFragment.newInstance(urls, position)
activity.supportFragmentManager.beginTransaction()
.add(fragment, ShowImagesDialogFragment.TAG)
@@ -230,11 +546,11 @@ class AccountProfileFragment: BaseFragment() {
.commit()
}
- fun showToots(loadNext: Boolean = false) {
+ private fun showToots(loadNext: Boolean = false) {
if (loadNext && maxId == -1L) return
Common.resetAuthInfo()?.let {
- MastodonClient(it).getAccountAllToots(account.id, if (loadNext) maxId else null, if (!loadNext && sinceId != -1L) sinceId else null)
+ MastodonClient(it).getAccountAllToots(profile.id, if (loadNext) maxId else null, if (!loadNext && sinceId != -1L) sinceId else null)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
@@ -244,10 +560,10 @@ class AccountProfileFragment: BaseFragment() {
}
}
- fun getRegexExtractSinceId() = Regex(".*since_id=(\\d+?)>.*")
- fun getRegexExtractMaxId() = Regex(".*max_id=(\\d+?)>.*")
+ private fun getRegexExtractSinceId() = Regex(".*since_id=(\\d+?)>.*")
+ private fun getRegexExtractMaxId() = Regex(".*max_id=(\\d+?)>.*")
- fun reflectStatuses(result: Result>, next: Boolean) {
+ private fun reflectStatuses(result: Result>, next: Boolean) {
result.response()?.let {
if (next) adapter.addAllContentsAtLast(it.body().map { Common.getTimelineContent(it) })
else adapter.addAllContents(it.body().map { Common.getTimelineContent(it) })
@@ -277,39 +593,51 @@ class AccountProfileFragment: BaseFragment() {
}
}
- fun setFollowButtonState(state: Boolean) {
- if (state) {
- binding.follow.setImageResource(R.drawable.ic_people_black_24px)
- binding.follow.setColorFilter(ContextCompat.getColor(activity, R.color.accent))
- } else {
- binding.follow.setImageResource(R.drawable.ic_person_add_black_24px)
- binding.follow.setColorFilter(ContextCompat.getColor(activity, R.color.icon_tint_dark))
+ private fun setFollowButtonState(relationship: Relationship) {
+ binding.follow.apply {
+ when {
+ relationship.hasSendFollowRequest -> {
+ isClickable = false
+ setImageResource(R.drawable.ic_hourglass_empty_black_24px)
+ setColorFilter(ContextCompat.getColor(activity, R.color.icon_tint_dark))
+ }
+ relationship.following -> {
+ isClickable = true
+ setImageResource(R.drawable.ic_people_black_24px)
+ setColorFilter(ContextCompat.getColor(activity, R.color.accent))
+ }
+ else -> {
+ isClickable = true
+ setImageResource(R.drawable.ic_person_add_black_24px)
+ setColorFilter(ContextCompat.getColor(activity, R.color.icon_tint_dark))
+ }
+ }
}
}
- fun setBlockButtonState(state: Boolean) {
+ private fun setBlockButtonState(state: Boolean) {
binding.block.setColorFilter(
ContextCompat.getColor(activity,
if (state) R.color.accent else R.color.icon_tint_dark))
}
- fun setMuteButtonState(state: Boolean) {
+ private fun setMuteButtonState(state: Boolean) {
binding.mute.setColorFilter(
ContextCompat.getColor(activity,
if (state) R.color.accent else R.color.icon_tint_dark))
}
- fun reflectSettings() {
+ private fun reflectSettings() {
val movementMethod = Common.getMovementMethodFromPreference(binding.root.context)
binding.url.movementMethod = movementMethod
binding.note.movementMethod = movementMethod
}
- fun refreshBarTitle() {
- (activity as MainActivity).supportActionBar?.title = "${account.displayName}'s profile"
+ private fun refreshBarTitle(name: String = profile.screenName) {
+ (activity as MainActivity).supportActionBar?.title = "$name's profile"
}
- fun toggleRefreshIndicatorState(show: Boolean) {
+ private fun toggleRefreshIndicatorState(show: Boolean) {
binding.timeline.swipeRefreshLayout.isRefreshing = show
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/BaseFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/BaseFragment.kt
index 6fe9204..ada5581 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/BaseFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/BaseFragment.kt
@@ -2,29 +2,41 @@ package com.geckour.egret.view.fragment
import android.os.Bundle
import android.view.View
+import com.bumptech.glide.Glide
import com.geckour.egret.util.Common
import com.geckour.egret.view.activity.MainActivity
import com.trello.rxlifecycle2.components.support.RxFragment
+import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
open class BaseFragment: RxFragment() {
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- if (activity is MainActivity) {
- (activity as MainActivity).supportActionBar?.show()
- (activity as MainActivity).binding.appBarMain.contentMain.fab.show()
- Common.setSimplicityPostBarVisibility((activity as MainActivity).binding.appBarMain.contentMain, false)
+ (activity as? MainActivity)?.apply {
+ supportActionBar?.show()
+ binding.appBarMain.contentMain.fab.show()
+ Common.setSimplicityPostBarVisibility(binding.appBarMain.contentMain, false)
}
}
override fun onResume() {
super.onResume()
- if (activity is MainActivity) {
- (activity as MainActivity).supportActionBar?.show()
- (activity as MainActivity).binding.appBarMain.contentMain.fab.show()
- Common.setSimplicityPostBarVisibility((activity as MainActivity).binding.appBarMain.contentMain, false)
+ Single.just(activity)
+ .map { Glide.get(it).clearDiskCache() }
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(bindToLifecycle())
+ .subscribe()
+
+ (activity as? MainActivity)?.apply {
+ supportActionBar?.show()
+ binding.appBarMain.toolbar.setOnClickListener(null)
+ binding.appBarMain.contentMain.fab.show()
+ Common.setSimplicityPostBarVisibility(binding.appBarMain.contentMain, false)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/ChooseImageSourceDialogFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/ChooseImageSourceDialogFragment.kt
new file mode 100644
index 0000000..7b2afea
--- /dev/null
+++ b/app/src/main/java/com/geckour/egret/view/fragment/ChooseImageSourceDialogFragment.kt
@@ -0,0 +1,37 @@
+package com.geckour.egret.view.fragment
+
+import android.app.Dialog
+import android.os.Bundle
+import android.support.v7.app.AlertDialog
+import com.geckour.egret.R
+import com.trello.rxlifecycle2.components.support.RxDialogFragment
+
+class ChooseImageSourceDialogFragment: RxDialogFragment() {
+
+ companion object {
+ val TAG: String = this::class.java.simpleName
+ private val ARGS_KEY_UPLOAD_TYPE = "argsKeyUploadType"
+
+ fun newInstance(type: AccountProfileFragment.UploadType): ChooseImageSourceDialogFragment = ChooseImageSourceDialogFragment().apply {
+ arguments = Bundle().apply {
+ putSerializable(ARGS_KEY_UPLOAD_TYPE, type)
+ }
+ }
+ }
+
+ private val uploadType: AccountProfileFragment.UploadType by lazy { arguments[ARGS_KEY_UPLOAD_TYPE] as AccountProfileFragment.UploadType }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return AlertDialog.Builder(activity)
+ .setTitle(R.string.dialog_title_choose_source)
+ .setMessage(R.string.dialog_message_choose_source)
+ .setPositiveButton(R.string.dialog_button_pick_media, { _, _ ->
+ parentFragment?.let { (it as? AccountProfileFragment)?.pickMedia(uploadType) } ?: targetFragment?.let { (it as? AccountProfileFragment)?.pickMedia(uploadType) }
+ })
+ .setNegativeButton(R.string.dialog_button_capture_image, { _, _ ->
+ parentFragment?.let { (it as? AccountProfileFragment)?.captureImage(uploadType) } ?: targetFragment?.let { (it as? AccountProfileFragment)?.captureImage(uploadType) }
+ })
+ .setNeutralButton(R.string.dialog_button_dismiss, { _, _ -> dismiss()})
+ .create()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/HashTagMuteFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/HashTagMuteFragment.kt
index 5a2dd6f..8492a18 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/HashTagMuteFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/HashTagMuteFragment.kt
@@ -15,7 +15,6 @@ import com.geckour.egret.util.Common
import com.geckour.egret.util.OrmaProvider
import com.geckour.egret.view.activity.MainActivity
import com.geckour.egret.view.adapter.MuteHashTagAdapter
-import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import io.reactivex.Observable
import io.reactivex.Single
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/LicenseFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/LicenseFragment.kt
new file mode 100644
index 0000000..a4664be
--- /dev/null
+++ b/app/src/main/java/com/geckour/egret/view/fragment/LicenseFragment.kt
@@ -0,0 +1,85 @@
+package com.geckour.egret.view.fragment
+
+import android.databinding.DataBindingUtil
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.geckour.egret.R
+import com.geckour.egret.databinding.FragmentLicenseBinding
+import com.geckour.egret.view.activity.SettingActivity
+
+class LicenseFragment : BaseFragment() {
+
+ enum class License {
+ Stetho,
+ RxJava,
+ RxAndroid,
+ RxKotlin,
+ RxLifecycle,
+ Orma,
+ Retrofit,
+ Glide,
+ Timber,
+ MaterialDrawer,
+ CircularImageView,
+ Calligraphy,
+ Emoji,
+ CommonsIO
+ }
+
+ companion object {
+ val TAG: String = this::class.java.simpleName
+
+ fun newInstance(): LicenseFragment = LicenseFragment()
+ }
+
+ lateinit private var binding: FragmentLicenseBinding
+
+ override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ binding = DataBindingUtil.inflate(inflater, R.layout.fragment_license, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ injectLicense()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ (activity as? SettingActivity)?.binding?.appBarMain?.toolbar?.title = getString(R.string.title_fragment_license)
+ }
+
+ private fun injectLicense() {
+ listOf(
+ binding.licenseStetho,
+ binding.licenseReactiveXJava,
+ binding.licenseReactiveXAndroid,
+ binding.licenseReactiveXKotlin,
+ binding.licenseReactiveXLifecycle,
+ binding.licenseOrma,
+ binding.licenseRetrofit,
+ binding.licenseGlide,
+ binding.licenseTimber,
+ binding.licenseMaterialDrawer,
+ binding.licenseCircularImageView,
+ binding.licenseCalligraphy,
+ binding.licenseEmoji,
+ binding.licenseCommonsIo
+ )
+ .forEach {
+ it.apply {
+ name.setOnClickListener {
+ body.visibility = if (body.visibility == View.VISIBLE) View.GONE else View.VISIBLE
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/ListDialogFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/ListDialogFragment.kt
index 134124f..0c756a5 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/ListDialogFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/ListDialogFragment.kt
@@ -7,28 +7,31 @@ import android.support.v7.app.AlertDialog
import com.geckour.egret.R
import com.geckour.egret.databinding.FragmentListDialogBinding
import com.geckour.egret.view.adapter.ListDialogAdapter
+import com.geckour.egret.view.adapter.model.TimelineContent
import com.trello.rxlifecycle2.components.support.RxDialogFragment
-class ListDialogFragment(val listener: OnItemClickListener? = null): RxDialogFragment() {
+class ListDialogFragment: RxDialogFragment() {
- lateinit var binding: FragmentListDialogBinding
+ lateinit private var binding: FragmentListDialogBinding
+ private val listener: OnItemClickListener? by lazy { (activity as? OnItemClickListener) }
+ private val content: TimelineContent.TimelineStatus by lazy { arguments[ARGS_KEY_CONTENT] as TimelineContent.TimelineStatus }
companion object {
val TAG: String = this::class.java.simpleName
val ARGS_KEY_TITLE = "title"
val ARGS_KEY_RES_IDS = "itemResIds"
val ARGS_KEY_STRINGS = "itemStrings"
-
- fun newInstance(title: String, items: List>, listener: OnItemClickListener): ListDialogFragment {
- val fragment = ListDialogFragment(listener)
- val args = Bundle()
- args.putString(ARGS_KEY_TITLE, title)
- args.putIntArray(ARGS_KEY_RES_IDS, items.map { it.first }.toIntArray())
- args.putStringArray(ARGS_KEY_STRINGS, items.map { it.second }.toTypedArray())
- fragment.arguments = args
-
- return fragment
- }
+ val ARGS_KEY_CONTENT = "argsKeyContent"
+
+ fun newInstance(title: String, items: List>, content: TimelineContent.TimelineStatus): ListDialogFragment = ListDialogFragment()
+ .apply {
+ arguments = Bundle().apply {
+ putString(ARGS_KEY_TITLE, title)
+ putIntArray(ARGS_KEY_RES_IDS, items.map { it.first }.toIntArray())
+ putStringArray(ARGS_KEY_STRINGS, items.map { it.second }.toTypedArray())
+ putSerializable(ARGS_KEY_CONTENT, content)
+ }
+ }
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@@ -43,7 +46,7 @@ class ListDialogFragment(val listener: OnItemClickListener? = null): RxDialogFra
},
object: ListDialogAdapter.OnItemClickListener {
override fun onClick(resId: Int) {
- listener?.onClick(resId)
+ listener?.onClickListDialogItem(resId, content)
dismiss()
}
})
@@ -53,6 +56,6 @@ class ListDialogFragment(val listener: OnItemClickListener? = null): RxDialogFra
}
interface OnItemClickListener {
- fun onClick(resId: Int)
+ fun onClickListDialogItem(resId: Int, content: TimelineContent.TimelineStatus)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/MiscFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/MiscFragment.kt
new file mode 100644
index 0000000..9c2ba67
--- /dev/null
+++ b/app/src/main/java/com/geckour/egret/view/fragment/MiscFragment.kt
@@ -0,0 +1,49 @@
+package com.geckour.egret.view.fragment
+
+import android.os.Bundle
+import android.support.v4.app.Fragment
+import android.support.v7.preference.PreferenceFragmentCompat
+import android.support.v7.preference.PreferenceScreen
+import com.geckour.egret.R
+import com.geckour.egret.view.activity.SettingActivity
+
+class MiscFragment: PreferenceFragmentCompat(), PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
+
+ companion object {
+ val TAG: String = this::class.java.simpleName
+
+ fun newInstance(): MiscFragment = MiscFragment()
+ }
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ setPreferencesFromResource(R.xml.preferences_misc, rootKey)
+ preferenceScreen.findPreference("show_licenses").setOnPreferenceClickListener { showLicenseFragment() }
+ }
+
+ override fun getCallbackFragment(): Fragment = this
+
+ override fun onPreferenceStartScreen(caller: PreferenceFragmentCompat?, pref: PreferenceScreen?): Boolean {
+ caller?.preferenceScreen = pref
+ return true
+ }
+
+ override fun onPause() {
+ super.onPause()
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ (activity as? SettingActivity)?.binding?.appBarMain?.toolbar?.title = getString(R.string.title_fragment_others)
+ }
+
+ private fun showLicenseFragment(): Boolean {
+ val fragment = LicenseFragment.newInstance()
+ (activity as SettingActivity).supportFragmentManager.beginTransaction()
+ .replace(R.id.container, fragment, LicenseFragment.TAG)
+ .addToBackStack(LicenseFragment.TAG)
+ .commit()
+
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/NewTootCreateFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/NewTootCreateFragment.kt
index 4b8c4d9..a6db2fd 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/NewTootCreateFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/NewTootCreateFragment.kt
@@ -3,8 +3,8 @@ package com.geckour.egret.view.fragment
import android.Manifest
import android.app.Activity
import android.content.ClipData
-import android.content.ContentUris
import android.content.ContentValues
+import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.databinding.DataBindingUtil
@@ -15,6 +15,7 @@ import android.provider.MediaStore
import android.support.design.widget.Snackbar
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
+import android.support.v7.app.AlertDialog
import android.text.Editable
import android.view.KeyEvent
import android.view.LayoutInflater
@@ -22,15 +23,17 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.EditText
-import android.widget.ImageView
import com.bumptech.glide.Glide
import com.geckour.egret.R
import com.geckour.egret.api.MastodonClient
+import com.geckour.egret.api.model.Attachment
import com.geckour.egret.api.service.MastodonService
import com.geckour.egret.databinding.FragmentCreateNewTootBinding
+import com.geckour.egret.model.Draft
import com.geckour.egret.util.Common
import com.geckour.egret.util.OrmaProvider
import com.geckour.egret.view.activity.MainActivity
+import com.geckour.egret.view.activity.ShareActivity
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -42,13 +45,17 @@ import okhttp3.RequestBody
import java.io.File
-class NewTootCreateFragment : BaseFragment() {
+class NewTootCreateFragment : BaseFragment(), MainActivity.OnBackPressedListener, SelectDraftDialogFragment.OnSelectDraftItemListener {
- lateinit var binding: FragmentCreateNewTootBinding
+ lateinit private var binding: FragmentCreateNewTootBinding
private val postMediaReqs: ArrayList = ArrayList()
- private var mediaCount: Int = 0
- private val mediaIds: ArrayList = ArrayList()
+ private val attachments: ArrayList = ArrayList()
private var capturedImageUri: Uri? = null
+ lateinit private var initialBody: String
+ lateinit private var initialAlertBody: String
+ private var isSuccessPost = false
+ private val drafts: ArrayList = ArrayList()
+ private var draft: Draft? = null
companion object {
val TAG: String = this::class.java.simpleName
@@ -56,6 +63,7 @@ class NewTootCreateFragment : BaseFragment() {
private val ARGS_KEY_POST_TOKEN_ID = "postTokenId"
private val ARGS_KEY_REPLY_TO_STATUS_ID = "replyToStatusId"
private val ARGS_KEY_REPLY_TO_ACCOUNT_NAME = "replyToAccountName"
+ private val ARGS_KEY_BODY = "argsKeyBody"
private val REQUEST_CODE_PICK_MEDIA = 1
private val REQUEST_CODE_CAPTURE_IMAGE = 2
private val REQUEST_CODE_GRANT_READ_STORAGE = 3
@@ -65,33 +73,30 @@ class NewTootCreateFragment : BaseFragment() {
currentTokenId: Long,
postTokenId: Long = currentTokenId,
replyToStatusId: Long? = null,
- replyToAccountName: String? = null): NewTootCreateFragment {
-
- val fragment = NewTootCreateFragment()
- val args = Bundle()
- args.putLong(ARGS_KEY_CURRENT_TOKEN_ID, currentTokenId)
- args.putLong(ARGS_KEY_POST_TOKEN_ID, postTokenId)
- if (replyToStatusId != null) args.putLong(ARGS_KEY_REPLY_TO_STATUS_ID, replyToStatusId)
- if (replyToAccountName != null) args.putString(ARGS_KEY_REPLY_TO_ACCOUNT_NAME, replyToAccountName)
- fragment.arguments = args
-
- return fragment
+ replyToAccountName: String? = null,
+ body: String? = null) = NewTootCreateFragment().apply {
+ arguments = Bundle().apply {
+ putLong(ARGS_KEY_CURRENT_TOKEN_ID, currentTokenId)
+ putLong(ARGS_KEY_POST_TOKEN_ID, postTokenId)
+ if (replyToStatusId != null) putLong(ARGS_KEY_REPLY_TO_STATUS_ID, replyToStatusId)
+ if (replyToAccountName != null) putString(ARGS_KEY_REPLY_TO_ACCOUNT_NAME, replyToAccountName)
+ if (body != null) putString(ARGS_KEY_BODY, body)
+ }
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- (activity as MainActivity).supportActionBar?.hide()
- (activity as MainActivity).binding.appBarMain.contentMain.fab.hide()
+ (activity as? MainActivity)?.supportActionBar?.hide()
+ (activity as? MainActivity)?.binding?.appBarMain?.contentMain?.fab?.hide()
}
override fun onResume() {
super.onResume()
- (activity as MainActivity).supportActionBar?.hide()
- (activity as MainActivity).binding.appBarMain.contentMain.fab.hide()
- (activity as MainActivity)
+ (activity as? MainActivity)?.supportActionBar?.hide()
+ (activity as? MainActivity)?.binding?.appBarMain?.contentMain?.fab?.hide()
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@@ -106,7 +111,7 @@ class NewTootCreateFragment : BaseFragment() {
val domain = OrmaProvider.db.selectFromInstanceAuthInfo().idEq(token.instanceId).last().instance
OrmaProvider.db.updateAccessToken().isCurrentEq(true).isCurrent(false).executeAsSingle()
.flatMap { OrmaProvider.db.updateAccessToken().idEq(token.id).isCurrent(true).executeAsSingle() }
- .flatMap { MastodonClient(Common.resetAuthInfo() ?: throw IllegalArgumentException()).getSelfAccount() }
+ .flatMap { MastodonClient(Common.resetAuthInfo() ?: throw IllegalArgumentException()).getOwnAccount() }
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
@@ -121,6 +126,19 @@ class NewTootCreateFragment : BaseFragment() {
binding.gallery.setOnClickListener { pickMedia() }
binding.camera.setOnClickListener { captureImage() }
+ if (arguments.containsKey(ARGS_KEY_BODY))
+ binding.tootBody.text = Editable.Factory.getInstance().newEditable(arguments.getString(ARGS_KEY_BODY))
+
+ if (arguments.containsKey(ARGS_KEY_REPLY_TO_STATUS_ID)
+ && arguments.containsKey(ARGS_KEY_REPLY_TO_ACCOUNT_NAME)
+ && arguments.getString(ARGS_KEY_REPLY_TO_ACCOUNT_NAME) != null) {
+ binding.replyTo.text = "reply to: ${arguments.getString(ARGS_KEY_REPLY_TO_ACCOUNT_NAME)}"
+ binding.replyTo.visibility = View.VISIBLE
+ val accountName = "${arguments.getString(ARGS_KEY_REPLY_TO_ACCOUNT_NAME)} "
+ binding.tootBody.text = Editable.Factory.getInstance().newEditable(accountName)
+ binding.tootBody.setSelection(accountName.length)
+ }
+
binding.tootBody.setOnKeyListener { v, keyCode, event ->
when (event.action) {
KeyEvent.ACTION_DOWN -> {
@@ -172,14 +190,24 @@ class NewTootCreateFragment : BaseFragment() {
postToot(binding.tootBody.text.toString())
}
- if (arguments.containsKey(ARGS_KEY_REPLY_TO_STATUS_ID)
- && arguments.containsKey(ARGS_KEY_REPLY_TO_ACCOUNT_NAME)
- && arguments.getString(ARGS_KEY_REPLY_TO_ACCOUNT_NAME) != null) {
- binding.replyTo.text = "reply to: ${arguments.getString(ARGS_KEY_REPLY_TO_ACCOUNT_NAME)}"
- binding.replyTo.visibility = View.VISIBLE
- val accountName = "${arguments.getString(ARGS_KEY_REPLY_TO_ACCOUNT_NAME)} "
- binding.tootBody.text = Editable.Factory.getInstance().newEditable(accountName)
- binding.tootBody.setSelection(accountName.length)
+ initialBody = binding.tootBody.text.toString()
+ initialAlertBody = binding.tootAlertBody.text.toString()
+
+ Common.getCurrentAccessToken()?.id?.let {
+ drafts.addAll(
+ OrmaProvider.db.relationOfDraft()
+ .tokenIdEq(it)
+ .orderByCreatedAtAsc()
+ )
+ }
+ binding.draft.apply {
+ if (drafts.isNotEmpty()) {
+ visibility = View.VISIBLE
+ setOnClickListener { onLoadDraft() }
+ } else {
+ visibility = View.INVISIBLE
+ setOnClickListener(null)
+ }
}
}
@@ -194,28 +222,109 @@ class NewTootCreateFragment : BaseFragment() {
when (requestCode) {
REQUEST_CODE_PICK_MEDIA -> {
if (resultCode == Activity.RESULT_OK) {
- data?.let { bindMedia(it) }
+ data?.let { postMedia(it) }
}
}
REQUEST_CODE_CAPTURE_IMAGE -> {
if (resultCode == Activity.RESULT_OK) {
- capturedImageUri?.let { bindImage(it) }
+ capturedImageUri?.let { postImage(it) }
}
}
}
}
- fun postToot(body: String) {
+ override fun onBackPressedInMainActivity(callback: (Boolean) -> Any) {
+ if (isSuccessPost.not() &&
+ (initialBody != binding.tootBody.text.toString() || initialAlertBody != binding.tootAlertBody.text.toString())) {
+ AlertDialog.Builder(activity)
+ .setTitle(R.string.dialog_title_confirm_save)
+ .setMessage(R.string.dialog_message_confirm_save)
+ .setPositiveButton(R.string.dialog_button_ok_confirm_save, { dialog, _ ->
+ saveAsDraft(dialog, callback)
+ })
+ .setNegativeButton(R.string.dialog_button_dismiss_confirm_save, { dialog, _ ->
+ dialog.dismiss()
+ callback(true)
+ })
+ .setNeutralButton(R.string.dialog_button_cancel_confirm_save, { dialog, _ ->
+ dialog.dismiss()
+ callback(false)
+ })
+ .show()
+ } else callback(true)
+ }
+
+ override fun onSelect(draft: Draft) {
+ this.draft = draft
+ OrmaProvider.db.relationOfDraft()
+ .deleter()
+ .idEq(draft.id)
+ .executeAsSingle()
+ .map {
+ Common.getCurrentAccessToken()?.id?.let {
+ OrmaProvider.db.relationOfDraft()
+ .tokenIdEq(it)
+ .orderByCreatedAtAsc()
+ .toList()
+ } ?: arrayListOf()
+ }
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({ drafts ->
+ this.drafts.apply {
+ clear()
+ addAll(drafts)
+ }
+ if (this.drafts.isEmpty()) binding.draft.apply {
+ visibility = View.INVISIBLE
+ setOnClickListener(null)
+ }
+ var account = ""
+ if (draft.inReplyToId != null && draft.inReplyToName != null) {
+ binding.replyTo.text = "reply to: ${draft.inReplyToName}"
+ binding.replyTo.visibility = View.VISIBLE
+ account = "${draft.inReplyToName} "
+ }
+ val body = "$account${draft.body}"
+ binding.tootBody.setText(body)
+ binding.tootBody.setSelection(body.length)
+ binding.tootAlertBody.setText(draft.alertBody)
+ this.attachments.apply {
+ clear()
+ addAll(draft.attachments.value)
+ }
+ Observable.fromIterable(this.attachments.mapIndexed { i, attachment -> Pair(i, attachment)})
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({ (i, attachment) ->
+ Glide.with(activity).load(attachment.previewImgUrl).into(
+ when (i) {
+ 0 -> binding.media1
+ 1 -> binding.media2
+ 2 -> binding.media3
+ 3 -> binding.media4
+ else -> throw IndexOutOfBoundsException("There are attachments over 4.")
+ }
+ )
+ }, Throwable::printStackTrace)
+ binding.switchCw.isChecked = draft.warning
+ binding.switchNsfw.isChecked = draft.sensitive
+ binding.spinnerVisibility.setSelection(draft.visibility)
+ }, Throwable::printStackTrace)
+ }
+
+ private fun postToot(body: String) {
if (body.isBlank()) {
Snackbar.make(binding.root, R.string.error_empty_toot, Snackbar.LENGTH_SHORT)
return
}
+
MastodonClient(Common.resetAuthInfo() ?: return)
.postNewToot(
body = body,
- inReplyToId = if (binding.replyTo.visibility == View.VISIBLE) arguments.getLong(ARGS_KEY_REPLY_TO_STATUS_ID) else null,
- mediaIds = if (mediaIds.size > 0) mediaIds else null,
+ inReplyToId = if (binding.replyTo.visibility == View.VISIBLE) draft?.inReplyToId ?: arguments.getLong(ARGS_KEY_REPLY_TO_STATUS_ID) else null,
+ mediaIds = if (attachments.size > 0) attachments.map { it.id } else null,
isSensitive = binding.switchNsfw.isChecked,
spoilerText = if (binding.switchCw.isChecked) binding.tootAlertBody.text.toString() else null,
visibility = when (binding.spinnerVisibility.selectedItemPosition) {
@@ -230,7 +339,80 @@ class NewTootCreateFragment : BaseFragment() {
.subscribe( { onPostSuccess() }, Throwable::printStackTrace)
}
- fun pickMedia() {
+ private fun saveAsDraft(dialog: DialogInterface, callback: (Boolean) -> Any, draftId: Long? = null) {
+ Common.getCurrentAccessToken()?.let { (id) ->
+ if (draftId == null) {
+ OrmaProvider.db.relationOfDraft()
+ .insertAsSingle {
+ Draft(
+ tokenId = id,
+ body = binding.tootBody.text.toString(),
+ alertBody = binding.tootAlertBody.text.toString(),
+ inReplyToId = if (binding.replyTo.visibility == View.VISIBLE) draft?.inReplyToId ?: arguments.getLong(ARGS_KEY_REPLY_TO_STATUS_ID) else null,
+ inReplyToName = if (binding.replyTo.visibility == View.VISIBLE) draft?.inReplyToName ?: arguments.getString(ARGS_KEY_REPLY_TO_ACCOUNT_NAME) else null,
+ attachments = Draft.Attachments(attachments),
+ warning = binding.switchCw.isChecked,
+ sensitive = binding.switchNsfw.isChecked,
+ visibility = binding.spinnerVisibility.selectedItemPosition
+ )
+ }
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ Snackbar.make(binding.root, R.string.complete_save_draft, Snackbar.LENGTH_SHORT).show()
+ dialog.dismiss()
+ callback(true)
+ }, { throwable ->
+ throwable.printStackTrace()
+ Snackbar.make(binding.root, R.string.failure_save_draft, Snackbar.LENGTH_SHORT).show()
+ dialog.dismiss()
+ callback(false)
+ })
+ } else {
+ OrmaProvider.db.relationOfDraft()
+ .upsertAsSingle(
+ Draft(
+ id = draftId,
+ tokenId = id,
+ body = binding.tootBody.text.toString(),
+ alertBody = binding.tootAlertBody.text.toString(),
+ inReplyToId = if (binding.replyTo.visibility == View.VISIBLE) arguments.getLong(ARGS_KEY_REPLY_TO_STATUS_ID) else null,
+ attachments = Draft.Attachments(attachments),
+ sensitive = binding.switchNsfw.isChecked,
+ visibility = when (binding.spinnerVisibility.selectedItemPosition) {
+ 0 -> MastodonService.Visibility.public.ordinal
+ 1 -> MastodonService.Visibility.unlisted.ordinal
+ 2 -> MastodonService.Visibility.private.ordinal
+ 3 -> MastodonService.Visibility.direct.ordinal
+ else -> -1
+ }
+ )
+ )
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ Snackbar.make(binding.root, R.string.complete_save_draft, Snackbar.LENGTH_SHORT).show()
+ dialog.dismiss()
+ callback(true)
+ }, { throwable ->
+ throwable.printStackTrace()
+ Snackbar.make(binding.root, R.string.failure_save_draft, Snackbar.LENGTH_SHORT).show()
+ dialog.dismiss()
+ callback(false)
+ })
+ }
+ }
+ }
+
+ private fun onLoadDraft() {
+ SelectDraftDialogFragment.newInstance(drafts)
+ .apply {
+ setTargetFragment(this@NewTootCreateFragment, 0)
+ }
+ .show(activity.supportFragmentManager, SelectDraftDialogFragment.TAG)
+ }
+
+ private fun pickMedia() {
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE_GRANT_READ_STORAGE)
@@ -243,7 +425,7 @@ class NewTootCreateFragment : BaseFragment() {
}
}
- fun captureImage() {
+ private fun captureImage() {
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_CODE_GRANT_WRITE_STORAGE)
@@ -265,7 +447,7 @@ class NewTootCreateFragment : BaseFragment() {
when (requestCode) {
REQUEST_CODE_GRANT_READ_STORAGE -> {
if (grantResults.isNotEmpty() &&
- grantResults.filter { it != PackageManager.PERMISSION_GRANTED }.isEmpty()) {
+ grantResults.none { it != PackageManager.PERMISSION_GRANTED }) {
pickMedia()
} else {
Snackbar.make(binding.root, R.string.message_necessity_read_storage_grant, Snackbar.LENGTH_SHORT).show()
@@ -274,7 +456,7 @@ class NewTootCreateFragment : BaseFragment() {
REQUEST_CODE_GRANT_WRITE_STORAGE -> {
if (grantResults.isNotEmpty() &&
- grantResults.filter { it != PackageManager.PERMISSION_GRANTED }.isEmpty()) {
+ grantResults.none { it != PackageManager.PERMISSION_GRANTED }) {
captureImage()
} else {
Snackbar.make(binding.root, R.string.message_necessity_write_storage_grant_capture, Snackbar.LENGTH_SHORT).show()
@@ -283,146 +465,90 @@ class NewTootCreateFragment : BaseFragment() {
}
}
- fun bindMedia(data: Intent) {
- Common.resetAuthInfo()?.let { domain ->
- if (data.clipData != null) {
- getMediaPathsFromClipDataAsObservable(data.clipData)
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .compose(bindToLifecycle())
- .subscribe({ (path, uri) ->
- if (++mediaCount < 5) {
- postMedia(domain, path, uri)
- } else {
- Snackbar.make(binding.root, R.string.error_too_many_media, Snackbar.LENGTH_SHORT).show()
+ private fun postMedia(data: Intent) {
+ if (attachments.size < 4) {
+ Common.resetAuthInfo()?.let { domain ->
+ if (data.clipData != null) {
+ getMediaPathsFromClipDataAsObservable(data.clipData)
+ .flatMap { (path, uri) ->
+ queryPostImageToAPI(domain, path, uri).toObservable()
}
- }, Throwable::printStackTrace)
- }
- if (data.data != null && ++mediaCount < 5) {
- getMediaPathFromUriAsSingle(data.data)
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .compose(bindToLifecycle())
- .subscribe({
- if (++mediaCount < 5) {
- postMedia(domain, it, data.data)
- } else {
- Snackbar.make(binding.root, R.string.error_too_many_media, Snackbar.LENGTH_SHORT).show()
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(bindToLifecycle())
+ .subscribe({
+ attachments.add(it)
+ indicateImage(it.previewImgUrl)
+ }, { throwable ->
+ throwable.printStackTrace()
+ Snackbar.make(binding.root, R.string.error_unable_upload_media, Snackbar.LENGTH_SHORT).show()
+ })
+ }
+
+ if (data.data != null) {
+ getMediaPathFromUriAsSingle(data.data)
+ .flatMap { (path, uri) ->
+ queryPostImageToAPI(domain, path, uri)
}
- }, Throwable::printStackTrace)
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(bindToLifecycle())
+ .subscribe({
+ attachments.add(it)
+ indicateImage(it.previewImgUrl)
+ }, { throwable ->
+ throwable.printStackTrace()
+ Snackbar.make(binding.root, R.string.error_unable_upload_media, Snackbar.LENGTH_SHORT).show()
+ })
+ }
}
- }
+ } else Snackbar.make(binding.root, R.string.error_too_many_media, Snackbar.LENGTH_SHORT).show()
}
- fun bindImage(uri: Uri) {
- getImagePathFromUriAsSingle(uri)
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .compose(bindToLifecycle())
- .subscribe({ path ->
- if (++mediaCount < 5) {
- Common.resetAuthInfo()?.let {
- postImage(it, path, uri)
+ private fun postImage(uri: Uri) {
+ if (attachments.size < 4) {
+ Common.resetAuthInfo()?.let { domain ->
+ getImagePathFromUriAsSingle(uri)
+ .flatMap { path ->
+ queryPostImageToAPI(domain, path, uri)
}
- } else {
- Snackbar.make(binding.root, R.string.error_too_many_media, Snackbar.LENGTH_SHORT).show()
- }
- }, Throwable::printStackTrace)
- }
-
- fun postMedia(domain: String, path: String, uri: Uri) {
- val file = File(path)
- val body = MultipartBody.Part.createFormData(
- "file",
- file.name,
- RequestBody.create(MediaType.parse(activity.contentResolver.getType(uri)), file))
-
- postMediaReqs.add(
- MastodonClient(domain).postNewMedia(body)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(bindToLifecycle())
.subscribe({
- mediaIds.add(it.id)
- indicateMedia(uri)
+ attachments.add(it)
+ indicateImage(it.url)
}, { throwable ->
throwable.printStackTrace()
- mediaCount--
Snackbar.make(binding.root, R.string.error_unable_upload_media, Snackbar.LENGTH_SHORT).show()
})
- )
+ }
+ } else {
+ Snackbar.make(binding.root, R.string.error_too_many_media, Snackbar.LENGTH_SHORT).show()
+ }
}
- fun postImage(domain: String, path: String, uri: Uri) {
+ private fun queryPostImageToAPI(domain: String, path: String, uri: Uri): Single {
val file = File(path)
val body = MultipartBody.Part.createFormData(
"file",
file.name,
- RequestBody.create(MediaType.parse("jpeg"), file))
+ RequestBody.create(MediaType.parse(activity.contentResolver.getType(uri)), file))
- postMediaReqs.add(
- MastodonClient(domain).postNewMedia(body)
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .compose(bindToLifecycle())
- .subscribe({
- mediaIds.add(it.id)
- indicateImage(uri)
- }, { throwable ->
- throwable.printStackTrace()
- mediaCount--
- Snackbar.make(binding.root, R.string.error_unable_upload_media, Snackbar.LENGTH_SHORT).show()
- })
- )
+ return MastodonClient(domain).postNewMedia(body)
}
- fun indicateMedia(uri: Uri) {
- Single.just(
- MediaStore.Images.Thumbnails.getThumbnail(
- activity.contentResolver,
- DocumentsContract.getDocumentId(uri).split(":").last().toLong(),
- MediaStore.Images.Thumbnails.MINI_KIND,
- null))
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .compose(bindToLifecycle())
- .subscribe({
- val mediaViews: List = listOf(
- binding.media1,
- binding.media2,
- binding.media3,
- binding.media4
- )
-
- mediaViews.filter { it.drawable == null }.firstOrNull()?.setImageBitmap(it)
- }, Throwable::printStackTrace)
- }
-
- fun indicateImage(uri: Uri) {
- Single.just(
- MediaStore.Images.Thumbnails.getThumbnail(
- activity.contentResolver,
- ContentUris.parseId(uri),
- MediaStore.Images.Thumbnails.MINI_KIND,
- null))
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .compose(bindToLifecycle())
- .subscribe({
- val mediaViews: List = listOf(
- binding.media1,
- binding.media2,
- binding.media3,
- binding.media4
- )
-
- mediaViews.filter { it.drawable == null }.firstOrNull()?.setImageBitmap(it)
-
- deleteTempImage()
- }, Throwable::printStackTrace)
+ private fun indicateImage(url: String, index: Int = attachments.size) {
+ when (index) {
+ 0 -> Glide.with(activity).load(url).into(binding.media1)
+ 1 -> Glide.with(activity).load(url).into(binding.media2)
+ 2 -> Glide.with(activity).load(url).into(binding.media3)
+ 3 -> Glide.with(activity).load(url).into(binding.media4)
+ else -> {}
+ }
}
- fun getMediaPathFromUriAsSingle(uri: Uri): Single {
+ private fun getMediaPathFromUriAsSingle(uri: Uri): Single> {
val projection = MediaStore.Images.Media.DATA
return Single.just(DocumentsContract.getDocumentId(uri).split(":").last())
@@ -434,15 +560,16 @@ class NewTootCreateFragment : BaseFragment() {
arrayOf(it), null)
cursor.moveToFirst()
- cursor.getString(cursor.getColumnIndexOrThrow(projection)).apply { cursor.close() }
+ val path = cursor.getString(cursor.getColumnIndexOrThrow(projection)).apply { cursor.close() }
+ Pair(path, uri)
}
}
- fun getMediaPathsFromClipDataAsObservable(clip: ClipData): Observable> {
+ private fun getMediaPathsFromClipDataAsObservable(clip: ClipData): Observable> {
val docIds: ArrayList> = ArrayList()
val projection = MediaStore.Images.Media.DATA
- (0..clip.itemCount - 1).mapTo(docIds) {
+ (0 until clip.itemCount).mapTo(docIds) {
val uri = clip.getItemAt(it).uri
Pair(DocumentsContract.getDocumentId(uri).split(":").last(), uri)
}
@@ -462,7 +589,7 @@ class NewTootCreateFragment : BaseFragment() {
}
}
- fun getImagePathFromUriAsSingle(uri: Uri): Single {
+ private fun getImagePathFromUriAsSingle(uri: Uri): Single {
return Single.just(MediaStore.Images.Media.DATA)
.map { projection ->
val cursor = activity.contentResolver.query(uri, arrayOf(projection), null, null, null)
@@ -472,7 +599,7 @@ class NewTootCreateFragment : BaseFragment() {
}
}
- fun deleteTempImage() {
+ private fun deleteTempImage() {
capturedImageUri?.let {
getImagePathFromUriAsSingle(it)
.subscribeOn(Schedulers.newThread())
@@ -487,7 +614,12 @@ class NewTootCreateFragment : BaseFragment() {
}
}
- fun onPostSuccess() {
- (activity as MainActivity).supportFragmentManager.popBackStack()
+ private fun onPostSuccess() {
+ isSuccessPost = true
+ (activity as? MainActivity)?.supportFragmentManager?.popBackStack()
+ (activity as? ShareActivity)?.apply {
+ supportFragmentManager?.popBackStack()
+ onBackPressed()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/SearchResultFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/SearchResultFragment.kt
index 6f637d6..f8c4c8c 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/SearchResultFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/SearchResultFragment.kt
@@ -22,7 +22,7 @@ class SearchResultFragment: BaseFragment() {
}
companion object {
- val TAG = "searchResultFragment"
+ val TAG: String = this::class.java.simpleName
private val ARGS_KEY_CATEGORY = "category"
private val ARGS_KEY_QUERY = "query"
private val ARGS_KEY_RESULT = "result"
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/SelectDraftDialogFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/SelectDraftDialogFragment.kt
new file mode 100644
index 0000000..9306500
--- /dev/null
+++ b/app/src/main/java/com/geckour/egret/view/fragment/SelectDraftDialogFragment.kt
@@ -0,0 +1,54 @@
+package com.geckour.egret.view.fragment
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.support.design.widget.Snackbar
+import android.support.v7.app.AlertDialog
+import com.geckour.egret.App
+import com.geckour.egret.R
+import com.geckour.egret.model.Draft
+import com.geckour.egret.view.activity.MainActivity
+import com.trello.rxlifecycle2.components.support.RxDialogFragment
+
+class SelectDraftDialogFragment: RxDialogFragment() {
+
+ companion object {
+ val TAG: String = this::class.java.simpleName
+ private val ARGS_KEY_DRAFTS = "argsKeyDrafts"
+
+ fun newInstance(drafts: List): SelectDraftDialogFragment = SelectDraftDialogFragment().apply {
+ arguments = Bundle().apply {
+ putStringArray(ARGS_KEY_DRAFTS, drafts.map { App.gson.toJson(it) }.toTypedArray())
+ }
+ }
+ }
+
+ interface OnSelectDraftItemListener {
+ fun onSelect(draft: Draft)
+ }
+
+ private var listener: OnSelectDraftItemListener? = null
+ private val drafts: List by lazy { arguments.getStringArray(ARGS_KEY_DRAFTS).map { App.gson.fromJson(it, Draft::class.java) } }
+
+ override fun onAttach(context: Context?) {
+ super.onAttach(context)
+
+ targetFragment?.let { listener = it as OnSelectDraftItemListener}
+ parentFragment?.let { listener = it as OnSelectDraftItemListener}
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return AlertDialog.Builder(activity)
+ .setTitle(R.string.dialog_message_select_draft)
+ .setItems(drafts.map { it.body }.toTypedArray(), { dialog, which ->
+ drafts.getOrNull(which)?.let {
+ listener?.onSelect(it)
+ } ?: Snackbar.make((activity as MainActivity).binding.root, R.string.error_unable_select_draft, Snackbar.LENGTH_SHORT).show()
+
+ dialog.dismiss()
+ })
+ .setNegativeButton(R.string.dialog_button_dismiss, null)
+ .create()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/SettingAppDataManageFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/SettingAppDataManageFragment.kt
new file mode 100644
index 0000000..a0c4f7e
--- /dev/null
+++ b/app/src/main/java/com/geckour/egret/view/fragment/SettingAppDataManageFragment.kt
@@ -0,0 +1,109 @@
+package com.geckour.egret.view.fragment
+
+import android.content.Intent
+import android.os.Bundle
+import android.support.design.widget.Snackbar
+import android.support.v4.app.Fragment
+import android.support.v7.preference.PreferenceFragmentCompat
+import android.support.v7.preference.PreferenceManager
+import android.support.v7.preference.PreferenceScreen
+import com.geckour.egret.R
+import com.geckour.egret.util.OrmaProvider
+import com.geckour.egret.view.activity.SettingActivity
+import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+
+class SettingAppDataManageFragment: PreferenceFragmentCompat(), PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
+
+ companion object {
+ val TAG: String = this::class.java.simpleName
+
+ fun newInstance(): SettingAppDataManageFragment = SettingAppDataManageFragment()
+ }
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ setPreferencesFromResource(R.xml.preferences_app_data, rootKey)
+
+ preferenceScreen.findPreference("clear_all_preference").setOnPreferenceClickListener { clearAllPreference() }
+ preferenceScreen.findPreference("clear_all_draft").setOnPreferenceClickListener { clearAllDraft() }
+ preferenceScreen.findPreference("clear_all_restriction").setOnPreferenceClickListener { clearAllRestriction() }
+ preferenceScreen.findPreference("clear_all_db_except_login_info").setOnPreferenceClickListener { clearAllDBExceptLoginInfo() }
+ preferenceScreen.findPreference("clear_all_db").setOnPreferenceClickListener { clearAllDB() }
+ }
+
+ override fun getCallbackFragment(): Fragment = this
+
+ override fun onPreferenceStartScreen(caller: PreferenceFragmentCompat?, pref: PreferenceScreen?): Boolean {
+ caller?.preferenceScreen = pref
+ return true
+ }
+
+ private fun clearAllPreference(): Boolean {
+ PreferenceManager.getDefaultSharedPreferences(activity).edit().clear().apply()
+ Snackbar.make((activity as SettingActivity).binding.root, R.string.message_complete_clear_pref, Snackbar.LENGTH_SHORT).show()
+ return true
+ }
+
+ private fun clearAllDraft(): Boolean {
+ OrmaProvider.db.deleteFromDraft().executeAsSingle()
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ Snackbar.make((activity as SettingActivity).binding.root, R.string.message_complete_clear_db, Snackbar.LENGTH_SHORT).show()
+ }, Throwable::printStackTrace)
+ return true
+ }
+
+ private fun clearAllRestriction(): Boolean {
+ Observable.merge(
+ listOf(
+ OrmaProvider.db.deleteFromMuteClient().executeAsSingle().toObservable(),
+ OrmaProvider.db.deleteFromMuteHashTag().executeAsSingle().toObservable(),
+ OrmaProvider.db.deleteFromMuteInstance().executeAsSingle().toObservable(),
+ OrmaProvider.db.deleteFromMuteKeyword().executeAsSingle().toObservable()
+ )
+ )
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({}, Throwable::printStackTrace, {
+ Snackbar.make((activity as SettingActivity).binding.root, R.string.message_complete_clear_db, Snackbar.LENGTH_SHORT).show()
+ })
+ return true
+ }
+
+ private fun clearAllDBExceptLoginInfo(): Boolean {
+ Observable.merge(
+ listOf(
+ OrmaProvider.db.deleteFromDraft().executeAsSingle().toObservable(),
+ OrmaProvider.db.deleteFromMuteClient().executeAsSingle().toObservable(),
+ OrmaProvider.db.deleteFromMuteHashTag().executeAsSingle().toObservable(),
+ OrmaProvider.db.deleteFromMuteInstance().executeAsSingle().toObservable(),
+ OrmaProvider.db.deleteFromMuteKeyword().executeAsSingle().toObservable()
+ )
+ )
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({}, Throwable::printStackTrace, {
+ Snackbar.make((activity as SettingActivity).binding.root, R.string.message_complete_clear_db, Snackbar.LENGTH_SHORT).show()
+ })
+ return true
+ }
+
+ private fun clearAllDB(): Boolean {
+ Single.just(OrmaProvider.db)
+ .map { it.deleteAll() }
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ Snackbar.make((activity as SettingActivity).binding.root, R.string.message_complete_clear_db, Snackbar.LENGTH_SHORT).show()
+ }, Throwable::printStackTrace)
+
+ val intent = activity.baseContext.packageManager.getLaunchIntentForPackage(activity.baseContext.packageName)
+ .apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) }
+ startActivity(intent)
+
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/SettingMainFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/SettingMainFragment.kt
index 2307d77..b01471c 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/SettingMainFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/SettingMainFragment.kt
@@ -7,29 +7,23 @@ import android.support.v7.preference.PreferenceFragmentCompat
import android.support.v7.preference.PreferenceScreen
import com.geckour.egret.App
import com.geckour.egret.R
-import com.geckour.egret.view.activity.SettingActivity
class SettingMainFragment: PreferenceFragmentCompat(), PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
companion object {
val TAG: String = this::class.java.simpleName
- fun newInstance(): SettingMainFragment {
- val fragment = SettingMainFragment()
-
- return fragment
- }
+ fun newInstance(): SettingMainFragment = SettingMainFragment()
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_main, rootKey)
preferenceScreen.findPreference("manage_accounts").setOnPreferenceClickListener { showAccountManageFragment() }
preferenceScreen.findPreference("manage_restrictions").setOnPreferenceClickListener { showRestrictFragment() }
+ preferenceScreen.findPreference("manage_held_data").setOnPreferenceClickListener { showSettingAppDataManageFragment() }
}
- override fun getCallbackFragment(): Fragment {
- return this
- }
+ override fun getCallbackFragment(): Fragment = this
override fun onPreferenceStartScreen(caller: PreferenceFragmentCompat?, pref: PreferenceScreen?): Boolean {
caller?.preferenceScreen = pref
@@ -48,9 +42,9 @@ class SettingMainFragment: PreferenceFragmentCompat(), PreferenceFragmentCompat.
}
}
- fun showAccountManageFragment(): Boolean {
+ private fun showAccountManageFragment(): Boolean {
val fragment = AccountManageFragment.newInstance()
- (activity as SettingActivity).supportFragmentManager.beginTransaction()
+ activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment, AccountManageFragment.TAG)
.addToBackStack(AccountManageFragment.TAG)
.commit()
@@ -58,13 +52,22 @@ class SettingMainFragment: PreferenceFragmentCompat(), PreferenceFragmentCompat.
return true
}
- fun showRestrictFragment(): Boolean {
+ private fun showRestrictFragment(): Boolean {
val fragment = SettingRestrictFragment.newInstance()
- (activity as SettingActivity).supportFragmentManager.beginTransaction()
+ activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment, SettingRestrictFragment.TAG)
.addToBackStack(SettingRestrictFragment.TAG)
.commit()
return true
}
+
+ private fun showSettingAppDataManageFragment(): Boolean {
+ val fragment = SettingAppDataManageFragment.newInstance()
+ activity.supportFragmentManager.beginTransaction()
+ .replace(R.id.container, fragment, SettingAppDataManageFragment.TAG)
+ .addToBackStack(SettingAppDataManageFragment.TAG)
+ .commit()
+ return true
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/SettingRestrictFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/SettingRestrictFragment.kt
index 76f5d0f..c0d2faf 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/SettingRestrictFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/SettingRestrictFragment.kt
@@ -12,11 +12,7 @@ class SettingRestrictFragment: PreferenceFragmentCompat(), PreferenceFragmentCom
companion object {
val TAG: String = this::class.java.simpleName
- fun newInstance(): SettingRestrictFragment {
- val fragment = SettingRestrictFragment()
-
- return fragment
- }
+ fun newInstance(): SettingRestrictFragment = SettingRestrictFragment()
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/ShowTootDetailFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/ShowTootDetailFragment.kt
index dcb1522..406adc7 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/ShowTootDetailFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/ShowTootDetailFragment.kt
@@ -20,7 +20,7 @@ import io.reactivex.schedulers.Schedulers
class ShowTootDetailFragment : BaseFragment() {
companion object {
- val TAG = "showTootDetailFragment"
+ val TAG: String = this::class.java.simpleName
val ARGS_KEY_STATUS_ID = "statusId"
fun newInstance(statusId: Long): ShowTootDetailFragment = ShowTootDetailFragment().apply {
diff --git a/app/src/main/java/com/geckour/egret/view/fragment/TimelineFragment.kt b/app/src/main/java/com/geckour/egret/view/fragment/TimelineFragment.kt
index 0fdd11f..0ea2846 100644
--- a/app/src/main/java/com/geckour/egret/view/fragment/TimelineFragment.kt
+++ b/app/src/main/java/com/geckour/egret/view/fragment/TimelineFragment.kt
@@ -47,6 +47,7 @@ class TimelineFragment: BaseFragment() {
User,
HashTag,
Notification,
+ Fav,
Unknown
}
@@ -55,7 +56,6 @@ class TimelineFragment: BaseFragment() {
val ARGS_KEY_CATEGORY = "category"
val ARGS_KEY_HASH_TAG = "hashTag"
val STATE_ARGS_KEY_CONTENTS = "contents"
- val STATE_ARGS_KEY_RESUME = "resume"
val REQUEST_CODE_GRANT_ACCESS_WIFI = 100
fun newInstance(category: Category, hashTag: String? = null): TimelineFragment = TimelineFragment().apply {
@@ -64,8 +64,6 @@ class TimelineFragment: BaseFragment() {
hashTag?.let { putString(ARGS_KEY_HASH_TAG, hashTag) }
}
}
-
- fun getCategoryById(rawValue: Int): Category = Category.values()[rawValue]
}
lateinit private var binding: FragmentTimelineBinding
@@ -139,6 +137,8 @@ class TimelineFragment: BaseFragment() {
Category.User -> showUserTimeline(loadPrev = true)
Category.Notification -> showNotificationTimeline(loadPrev = true)
Category.HashTag -> getHashTag()?.let { showHashTagTimeline(it, loadPrev = true) }
+ Category.Fav -> showFavouriteTimeline(true)
+ Category.Unknown -> {}
}
}
}
@@ -184,6 +184,8 @@ class TimelineFragment: BaseFragment() {
override fun onResume() {
super.onResume()
+ (activity as MainActivity).binding.appBarMain.toolbar.setOnClickListener { scrollToTop() }
+
(activity as MainActivity).supportActionBar?.show()
refreshBarTitle()
@@ -203,9 +205,9 @@ class TimelineFragment: BaseFragment() {
else if (sharedPref.contains(ARGS_KEY_HASH_TAG)) sharedPref.getString(ARGS_KEY_HASH_TAG, "")
else null
- fun existsNoRunningStream() = listOf(publicStream, localStream, userStream).none { !(it?.isDisposed ?: true) }
+ private fun existsNoRunningStream() = listOf(publicStream, localStream, userStream).none { !(it?.isDisposed ?: true) }
- fun refreshBarTitle() {
+ private fun refreshBarTitle() {
val instanceId = Common.getCurrentAccessToken()?.instanceId
val domain = if (instanceId == null) "not logged in" else OrmaProvider.db.selectFromInstanceAuthInfo().idEq(instanceId).last().instance
val category = getCategory()
@@ -213,11 +215,13 @@ class TimelineFragment: BaseFragment() {
when (category) {
Category.HashTag -> "$category TL${getHashTag()?.let { ": #$it" } ?: ""} - $domain"
+ Category.Fav -> "Your favourited toots list - $domain"
+
else -> "$category TL - $domain"
}
}
- fun restoreTimeline() {
+ private fun restoreTimeline() {
adapter.clearContents()
val storeContentsKey = getStoreContentsKey(getCategory()).apply { Log.d("restoreTimeline", "storeContentsKey: $this") }
@@ -243,28 +247,28 @@ class TimelineFragment: BaseFragment() {
.apply()
}
- fun reflectCategorySelection() {
+ private fun reflectCategorySelection() {
(activity as MainActivity).resetSelectionNavItem(
when (getCategory()) {
- Category.Public -> MainActivity.NAV_ITEM_TL_PUBLIC
- Category.Local -> MainActivity.NAV_ITEM_TL_LOCAL
- Category.User -> MainActivity.NAV_ITEM_TL_USER
- Category.Notification -> MainActivity.NAV_ITEM_TL_NOTIFICATION
+ Category.Public -> MainActivity.NavItem.NAV_ITEM_TL_PUBLIC.ordinal.toLong()
+ Category.Local -> MainActivity.NavItem.NAV_ITEM_TL_LOCAL.ordinal.toLong()
+ Category.User -> MainActivity.NavItem.NAV_ITEM_TL_USER.ordinal.toLong()
+ Category.Notification -> MainActivity.NavItem.NAV_ITEM_TL_NOTIFICATION.ordinal.toLong()
else -> -1
})
}
- fun toggleRefreshIndicatorState(show: Boolean) = Common.toggleRefreshIndicatorState(binding.swipeRefreshLayout, show)
+ private fun toggleRefreshIndicatorState(show: Boolean) = Common.toggleRefreshIndicatorState(binding.swipeRefreshLayout, show)
- fun toggleRefreshIndicatorActivity(show: Boolean) = Common.toggleRefreshIndicatorActivity(binding.swipeRefreshLayout, show)
+ private fun toggleRefreshIndicatorActivity(show: Boolean) = Common.toggleRefreshIndicatorActivity(binding.swipeRefreshLayout, show)
- fun forceStopRefreshing() {
+ private fun forceStopRefreshing() {
toggleRefreshIndicatorState(false)
binding.swipeRefreshLayout.destroyDrawingCache()
binding.swipeRefreshLayout.clearAnimation()
}
- fun showTimelineByCategory(category: Category) {
+ private fun showTimelineByCategory(category: Category) {
val prefStream = sharedPref.getString("manage_stream", "1")
if (prefStream == "1") {
@@ -291,6 +295,9 @@ class TimelineFragment: BaseFragment() {
Category.User -> showUserTimeline(true)
Category.HashTag -> getHashTag()?.let { showHashTagTimeline(it, true) }
Category.Notification -> showNotificationTimeline(true)
+ Category.Fav -> showFavouriteTimeline()
+ Category.Unknown -> {}
+
}
} else {
when (category) {
@@ -299,6 +306,8 @@ class TimelineFragment: BaseFragment() {
Category.User -> showUserTimeline()
Category.HashTag -> getHashTag()?.let { showHashTagTimeline(it) }
Category.Notification -> showNotificationTimeline()
+ Category.Fav -> showFavouriteTimeline()
+ Category.Unknown -> {}
}
}
}
@@ -308,7 +317,7 @@ class TimelineFragment: BaseFragment() {
when (requestCode) {
REQUEST_CODE_GRANT_ACCESS_WIFI -> {
if (grantResults.isNotEmpty() &&
- grantResults.filter { it != PackageManager.PERMISSION_GRANTED }.isEmpty()) {
+ grantResults.none { it != PackageManager.PERMISSION_GRANTED }) {
showTimelineByCategory(getCategory())
} else {
Snackbar.make(binding.root, R.string.message_necessity_wifi_grant, Snackbar.LENGTH_SHORT)
@@ -317,7 +326,7 @@ class TimelineFragment: BaseFragment() {
}
}
- fun postToot(contentMainBinding: ContentMainBinding) {
+ private fun postToot(contentMainBinding: ContentMainBinding) {
val button = contentMainBinding.buttonSimplicityToot.apply { isEnabled = false }
val body = contentMainBinding.simplicityTootBody.text.toString()
if (body.isBlank()) {
@@ -345,7 +354,7 @@ class TimelineFragment: BaseFragment() {
})
}
- fun stopTimelineStreams() {
+ private fun stopTimelineStreams() {
stopPublicTimelineStream()
stopLocalTimelineStream()
stopUserTimelineStream()
@@ -353,7 +362,7 @@ class TimelineFragment: BaseFragment() {
stopHashTagTimelineStream()
}
- fun startPublicTimelineStream() {
+ private fun startPublicTimelineStream() {
publicStream?.dispose()
publicStream = null
Common.resetAuthInfo()?.let {
@@ -374,11 +383,11 @@ class TimelineFragment: BaseFragment() {
}
}
- fun stopPublicTimelineStream() {
- if (!(publicStream?.isDisposed ?: true)) publicStream?.dispose()
+ private fun stopPublicTimelineStream() {
+ if (publicStream?.isDisposed == false) publicStream?.dispose()
}
- fun showPublicTimeline(loadStream: Boolean = false, loadPrev: Boolean = false) {
+ private fun showPublicTimeline(loadStream: Boolean = false, loadPrev: Boolean = false) {
if (loadPrev && maxId == -1L) return
MastodonClient(Common.resetAuthInfo() ?: return).getPublicTimeline(maxId = if (loadPrev) maxId else null, sinceId = if (!loadPrev && sinceId != -1L) sinceId else null)
@@ -394,7 +403,7 @@ class TimelineFragment: BaseFragment() {
})
}
- fun startUserTimelineStream() {
+ private fun startUserTimelineStream() {
userStream?.dispose()
userStream = null
@@ -416,11 +425,11 @@ class TimelineFragment: BaseFragment() {
}
}
- fun stopUserTimelineStream() {
- if (!(userStream?.isDisposed ?: true)) userStream?.dispose()
+ private fun stopUserTimelineStream() {
+ if (userStream?.isDisposed == false) userStream?.dispose()
}
- fun showUserTimeline(loadStream: Boolean = false, loadPrev: Boolean = false) {
+ private fun showUserTimeline(loadStream: Boolean = false, loadPrev: Boolean = false) {
if (loadPrev && maxId == -1L) return
MastodonClient(Common.resetAuthInfo() ?: return).getUserTimeline(maxId = if (loadPrev) maxId else null, sinceId = if (!loadPrev && sinceId != -1L) sinceId else null)
@@ -436,7 +445,7 @@ class TimelineFragment: BaseFragment() {
})
}
- fun startLocalTimelineStream() {
+ private fun startLocalTimelineStream() {
localStream?.dispose()
localStream = null
@@ -458,11 +467,11 @@ class TimelineFragment: BaseFragment() {
}
}
- fun stopLocalTimelineStream() {
- if (!(localStream?.isDisposed ?: true)) localStream?.dispose()
+ private fun stopLocalTimelineStream() {
+ if (localStream?.isDisposed == false) localStream?.dispose()
}
- fun showLocalTimeline(loadStream: Boolean = false, loadPrev: Boolean = false) {
+ private fun showLocalTimeline(loadStream: Boolean = false, loadPrev: Boolean = false) {
if (loadPrev && maxId == -1L) return
MastodonClient(Common.resetAuthInfo() ?: return).getPublicTimeline(true, maxId = if (loadPrev) maxId else null, sinceId = if (!loadPrev && sinceId != -1L) sinceId else null)
@@ -478,7 +487,7 @@ class TimelineFragment: BaseFragment() {
})
}
- fun startNotificationTimelineStream() {
+ private fun startNotificationTimelineStream() {
notificationStream?.dispose()
notificationStream = null
@@ -500,11 +509,11 @@ class TimelineFragment: BaseFragment() {
}
}
- fun stopNotificationTimelineStream() {
- if (getCategory() == Category.User && !(userStream?.isDisposed ?: true)) userStream?.dispose()
+ private fun stopNotificationTimelineStream() {
+ if (notificationStream?.isDisposed == false) notificationStream?.dispose()
}
- fun showNotificationTimeline(loadStream: Boolean = false, loadPrev: Boolean = false) {
+ private fun showNotificationTimeline(loadStream: Boolean = false, loadPrev: Boolean = false) {
if (loadPrev && maxId == -1L) return
MastodonClient(Common.resetAuthInfo() ?: return).getNotificationTimeline(maxId = if (loadPrev) maxId else null, sinceId = if (!loadPrev && sinceId != -1L) sinceId else null)
@@ -520,7 +529,7 @@ class TimelineFragment: BaseFragment() {
})
}
- fun startHashTagTimelineStream() {
+ private fun startHashTagTimelineStream() {
hashTagStream?.dispose()
hashTagStream = null
@@ -543,11 +552,11 @@ class TimelineFragment: BaseFragment() {
}
}
- fun stopHashTagTimelineStream() {
- if (getCategory() == Category.HashTag && !(hashTagStream?.isDisposed ?: true)) hashTagStream?.dispose()
+ private fun stopHashTagTimelineStream() {
+ if (hashTagStream?.isDisposed == false) hashTagStream?.dispose()
}
- fun showHashTagTimeline(hashTag: String, loadStream: Boolean = false, loadPrev: Boolean = false) {
+ private fun showHashTagTimeline(hashTag: String, loadStream: Boolean = false, loadPrev: Boolean = false) {
if (loadPrev && maxId == -1L) return
MastodonClient(Common.resetAuthInfo() ?: return).getHashTagTimeline(hashTag, maxId = if (loadPrev) maxId else null, sinceId = if (!loadPrev && sinceId != -1L) sinceId else null)
@@ -563,7 +572,22 @@ class TimelineFragment: BaseFragment() {
})
}
- fun reflectContents(result: Result>, next: Boolean) {
+ private fun showFavouriteTimeline(loadPrev: Boolean = false) {
+ if (loadPrev && maxId == -1L) return
+
+ MastodonClient(Common.resetAuthInfo() ?: return).getFavouriteTimeline(maxId = if (loadPrev) maxId else null, sinceId = if (!loadPrev && sinceId != -1L) sinceId else null)
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(bindToLifecycle())
+ .subscribe({
+ reflectContents(it, loadPrev)
+ }, { throwable ->
+ throwable.printStackTrace()
+ toggleRefreshIndicatorState(false)
+ })
+ }
+
+ private fun reflectContents(result: Result>, next: Boolean) {
result.response()?.let {
it.body()?.let {
if (it.isNotEmpty()) {
@@ -618,4 +642,8 @@ class TimelineFragment: BaseFragment() {
waitingDeletedId = source == "event: delete"
}
}
+
+ private fun scrollToTop() {
+ binding.recyclerView.smoothScrollToPosition(0)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v21/ic_extension_black_24px.xml b/app/src/main/res/drawable-v21/ic_extension_black_24px.xml
new file mode 100644
index 0000000..9452176
--- /dev/null
+++ b/app/src/main/res/drawable-v21/ic_extension_black_24px.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v21/ic_history_black_24px.xml b/app/src/main/res/drawable-v21/ic_history_black_24px.xml
new file mode 100644
index 0000000..181ee36
--- /dev/null
+++ b/app/src/main/res/drawable-v21/ic_history_black_24px.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v21/ic_hourglass_empty_black_24px.xml b/app/src/main/res/drawable-v21/ic_hourglass_empty_black_24px.xml
new file mode 100644
index 0000000..2fbb769
--- /dev/null
+++ b/app/src/main/res/drawable-v21/ic_hourglass_empty_black_24px.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v21/ic_lock_black_24px.xml b/app/src/main/res/drawable-v21/ic_lock_black_24px.xml
new file mode 100644
index 0000000..d5f931c
--- /dev/null
+++ b/app/src/main/res/drawable-v21/ic_lock_black_24px.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v21/ic_star_black_12px.xml b/app/src/main/res/drawable-v21/ic_star_black_12px.xml
new file mode 100755
index 0000000..2a4318c
--- /dev/null
+++ b/app/src/main/res/drawable-v21/ic_star_black_12px.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_share.xml b/app/src/main/res/layout/activity_share.xml
new file mode 100644
index 0000000..8cae74e
--- /dev/null
+++ b/app/src/main/res/layout/activity_share.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
index bad904d..620ee18 100644
--- a/app/src/main/res/layout/content_main.xml
+++ b/app/src/main/res/layout/content_main.xml
@@ -32,7 +32,7 @@
+ app:layout_constraintTop_toTopOf="parent"
+ android:layout_marginEnd="8dp" />
-
+ app:layout_constraintTop_toTopOf="@+id/button_simplicity_toot" />
diff --git a/app/src/main/res/layout/fragment_account_profile.xml b/app/src/main/res/layout/fragment_account_profile.xml
index 0a7a927..8f424ff 100644
--- a/app/src/main/res/layout/fragment_account_profile.xml
+++ b/app/src/main/res/layout/fragment_account_profile.xml
@@ -4,6 +4,7 @@
xmlns:android="http://schemas.android.com/apk/res/android">
+
@@ -36,7 +37,8 @@
app:layout_constraintBottom_toBottomOf="@+id/icon"
app:layout_constraintLeft_toLeftOf="@+id/header"
app:layout_constraintRight_toRightOf="@+id/header"
- app:layout_constraintTop_toTopOf="@+id/icon">
+ app:layout_constraintTop_toTopOf="@+id/icon"
+ android:id="@+id/frameLayout">
@@ -50,12 +52,13 @@
app:layout_constraintLeft_toLeftOf="@+id/header"
app:layout_constraintTop_toTopOf="@+id/header" />
-
+ tools:text="おなまえ" />
+ tools:text="あいでぃー" />
+
+
-
+
+
+
+
+
+
+ app:srcCompat="@drawable/ic_volume_off_black_24px" />
+ app:srcCompat="@drawable/ic_block_black_24px" />
+ app:srcCompat="@drawable/ic_person_add_black_24px" />
+
+
-
-
-
-
+ android:layout_marginLeft="4dp"
+ android:layout_marginRight="4dp"
+ android:layout_weight="1"
+ android:adjustViewBounds="true"
+ android:maxHeight="80dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
-
-
-
-
+ android:layout_marginLeft="4dp"
+ android:layout_marginRight="4dp"
+ android:layout_weight="1"
+ android:adjustViewBounds="true"
+ android:maxHeight="80dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
-
-
-
-
+ android:layout_marginLeft="4dp"
+ android:layout_marginRight="4dp"
+ android:layout_weight="1"
+ android:adjustViewBounds="true"
+ android:maxHeight="80dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
-
+ android:layout_marginLeft="4dp"
+ android:layout_marginRight="4dp"
+ android:layout_weight="1"
+ android:adjustViewBounds="true"
+ android:maxHeight="80dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_license.xml b/app/src/main/res/layout/item_license.xml
new file mode 100644
index 0000000..2be2bd1
--- /dev/null
+++ b/app/src/main/res/layout/item_license.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_recycle_account.xml b/app/src/main/res/layout/item_recycle_account.xml
index 8fb200c..f96bedc 100644
--- a/app/src/main/res/layout/item_recycle_account.xml
+++ b/app/src/main/res/layout/item_recycle_account.xml
@@ -4,7 +4,7 @@
xmlns:android="http://schemas.android.com/apk/res/android">
-
+
@@ -34,7 +34,7 @@
android:textColor="?attr/colorGray"
android:textSize="16sp"
app:layout_constraintLeft_toRightOf="@+id/icon"
- app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintRight_toLeftOf="@+id/lock"
app:layout_constraintTop_toTopOf="@+id/icon"
tools:text="つよいなまえ" />
@@ -50,6 +50,17 @@
app:layout_constraintTop_toBottomOf="@+id/nameStrong"
tools:text="よわいなまえ" />
+
+
+
+
@@ -104,7 +117,6 @@
android:id="@+id/name_weak"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginRight="8dp"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
@@ -112,10 +124,21 @@
android:text="@{(notification.status == null) ? null : notification.status.nameWeak}"
android:textSize="10sp"
app:layout_constraintLeft_toLeftOf="@+id/name_strong"
- app:layout_constraintRight_toLeftOf="@+id/time"
+ app:layout_constraintRight_toRightOf="@+id/name_strong"
app:layout_constraintTop_toBottomOf="@+id/name_strong"
tools:text="よわいなまえ" />
+
+
+
+
@@ -117,7 +129,6 @@
android:id="@+id/name_weak"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginRight="8dp"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
@@ -125,10 +136,21 @@
android:text="@{(status.rebloggedStatusContent == null) ? status.nameWeak : status.rebloggedStatusContent.nameWeak}"
android:textSize="10sp"
app:layout_constraintLeft_toLeftOf="@+id/name_strong"
- app:layout_constraintRight_toLeftOf="@+id/time"
+ app:layout_constraintRight_toRightOf="@+id/name_strong"
app:layout_constraintTop_toBottomOf="@+id/name_strong"
tools:text="よわいなまえ" />
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e94b85b..591c81b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -7,6 +7,7 @@
Settings
Search
Copy URL of toot
+ Share toot…
Open in browser
Copy toot
Oops! the Toot does not exist!
@@ -16,6 +17,8 @@
Save this pic
Sign in
Settings
+ Others
+ Open source licenses
Mute keywords
Mute Accounts
Mute Hash Tags
@@ -45,6 +48,8 @@
Completed to save the image.
+ Data has cleared.
+ Preference has cleared.
To control streaming by such status, grant authority of accessing Wi-Fi status to app.
To pick medias, grant authority of reading storage to app.
@@ -59,15 +64,38 @@
Show User Timeline
Show Notifications
Settings
+ Others
The toot has been posted.
Can\'t create empty toot. Write anything
Failed to upload the media.
Attach media within 4.
+ Draft of the toot has saved.
+ Failed to save draft of the toot.
Toot
Created at:
+ list
+ edit
+ cancel
+ save
+ Saved.
+ There are no updates on your profile.
Hash tag:
+ Select media source
+ Pick existing media?\nOr capture new photo?
+ Pick media
+ Capture photo
+ Cancel
+ Share toot to other app
+ Share toot from Egret
+ Discarding the toot
+ Would you like to save this toot as draft?
+ Save
+ Discard
+ Cancel
+ Select draft to restore
+ Failed to select the draft.
Mentioned by
Boosted by
@@ -120,6 +148,7 @@
Change scheme to open links
Show bar to post new toot on footer of TL
Whether notify real time
+ Manage app\'s data
Always
When connect with Wi-Fi
@@ -141,4 +170,52 @@
Change to do not notify real time
Notify real time
Do not notify real time
+ Show open source licenses
+ Clear all preference data
+ Clear all drafts data
+ Clear all restrictions data
+ Reset whole database except data used to login
+ Reset whole database
+
+ Stetho
+ RxJava
+ RxAndroid
+ RxKotlin
+ RxLifecycle
+ Orma
+ Retrofit
+ Glide
+ Timber
+ MaterialDrawer
+ CircularImageView
+ Calligraphy
+ Emoji
+ Commons-IO
+
+ BSD License\n\nFor Stetho software\n\nCopyright © 2015, Facebook, Inc. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n * Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n * Neither the name Facebook nor the names of its contributors may be used to\n endorse or promote products derived from this software without specific\n prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ \n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "[]"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright 2012 Netflix, Inc.\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.
+
+ \n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "[]"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n
+ \n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "[]"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright 2012 Netflix, Inc.\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.
+
+ Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "{}"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n
+
+ \n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "[]"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.
+
+ \n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "[]"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.
+
+ License for everything not in third_party and not otherwise marked:\n\nCopyright 2014 Google, Inc. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are\npermitted provided that the following conditions are met:\n\n 1. Redistributions of source code must retain the above copyright notice, this list of\n conditions and the following disclaimer.\n\n 2. Redistributions in binary form must reproduce the above copyright notice, this list\n of conditions and the following disclaimer in the documentation and/or other materials\n provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY GOOGLE, INC. \`\`AS IS\'\' AND ANY EXPRESS OR IMPLIED\nWARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\nADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThe views and conclusions contained in the software and documentation are those of the\nauthors and should not be interpreted as representing official policies, either expressed\nor implied, of Google, Inc.\n---------------------------------------------------------------------------------------------\nLicense for third_party/disklrucache:\n\nCopyright 2012 Jake Wharton\nCopyright 2011 The Android Open Source Project\n\nLicensed under the Apache License, Version 2.0 (the "License");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n---------------------------------------------------------------------------------------------\nLicense for third_party/gif_decoder:\n\nCopyright © 2013 Xcellent Creations, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n"Software"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n---------------------------------------------------------------------------------------------\nLicense for third_party/gif_encoder/AnimatedGifEncoder.java and\nthird_party/gif_encoder/LZWEncoder.java:\n\nNo copyright asserted on the source code of this class. May be used for any\npurpose, however, refer to the Unisys LZW patent for restrictions on use of\nthe associated LZWEncoder class. Please forward any corrections to\nkweiner@fmsware.com.\n\n-----------------------------------------------------------------------------\nLicense for third_party/gif_encoder/NeuQuant.java\n\nCopyright © 1994 Anthony Dekker\n\nNEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See\n"Kohonen neural networks for optimal colour quantization" in "Network:\nComputation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of\nthe algorithm.\n\nAny party obtaining a copy of these files from the author, directly or\nindirectly, is granted, free of charge, a full and unrestricted irrevocable,\nworld-wide, paid up, royalty-free, nonexclusive right and license to deal in\nthis software and documentation files (the "Software"), including without\nlimitation the rights to use, copy, modify, merge, publish, distribute,\nsublicense, and/or sell copies of the Software, and to permit persons who\nreceive copies from any such party to do so, with the only requirement being\nthat this copyright notice remain intact.
+
+ \n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "[]"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.
+
+ Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "[]"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright 2016 Mike Penz\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.
+
+ The MIT License (MIT)\n\nCopyright © 2015 Mikhael LOPEZ\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.
+
+ Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "{}"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright 2013 Christopher Jenkins\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n
+
+ Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "{}"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.
+
+ \n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n "License" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n "Licensor" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n "Legal Entity" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n "control" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n "You" (or "Your") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n "Source" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n "Object" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n "Work" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n "Derivative Works" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n "Contribution" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, "submitted"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as "Not a Contribution."\n\n "Contributor" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n © You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a "NOTICE" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets "[]"\n replaced with your own identifying information. (Don\'t include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same "printed page" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n
diff --git a/app/src/main/res/xml/preferences_app_data.xml b/app/src/main/res/xml/preferences_app_data.xml
new file mode 100644
index 0000000..d315267
--- /dev/null
+++ b/app/src/main/res/xml/preferences_app_data.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/preferences_main.xml b/app/src/main/res/xml/preferences_main.xml
index e157a02..0d0ef01 100644
--- a/app/src/main/res/xml/preferences_main.xml
+++ b/app/src/main/res/xml/preferences_main.xml
@@ -22,6 +22,9 @@
android:switchTextOff="@string/pref_manage_notify_real_time_text_off"
android:summaryOn="@string/pref_manage_notify_real_time_summary_on"
android:summaryOff="@string/pref_manage_notify_real_time_summary_off" />
+
+
diff --git a/app/src/main/res/xml/preferences_misc.xml b/app/src/main/res/xml/preferences_misc.xml
new file mode 100644
index 0000000..d4a6464
--- /dev/null
+++ b/app/src/main/res/xml/preferences_misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index e73116a..c106df6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.1.3-2'
+ ext.kotlin_version = '1.1.4-2'
repositories {
jcenter()
}