diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4434e368d872b83dd5e939b0dda2b94dd4f482ad..dc4385a646560705779fa832b12f5768045e678c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,10 +13,11 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> <activity - android:name=".ui.MainActivity" + android:name=".ui.ChatActivity" android:exported="true" android:label="@string/app_name" - android:theme="@style/SplashTheme"> + android:theme="@style/SplashTheme" + android:windowSoftInputMode="adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.VIEW" /> @@ -25,10 +26,23 @@ </intent-filter> </activity> <activity - android:name=".ui.setup.AccountSetupActivity" - android:exported="true" + android:name=".ui.setup.accounts.AccountSetupActivity" + android:exported="false" android:label="AccountSetup" - android:theme="@style/SplashTheme" /> + android:theme="@style/SplashTheme" + android:windowSoftInputMode="adjustResize" /> + <activity + android:name=".ui.setup.accounts.AccountEditActivity" + android:exported="true" + android:label="AccountEdit" + android:theme="@style/SplashTheme" + android:windowSoftInputMode="adjustResize" /> + <activity + android:name="de.kuschku.quasseldroid_ng.ui.setup.accounts.AccountSelectionActivity" + android:exported="true" + android:label="AccountSelection" + android:theme="@style/SplashTheme" + android:windowSoftInputMode="adjustResize" /> <service android:name=".service.QuasselService" diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt index 4eff8cf83ac90b9ea333546de1cef8e2c01da9ec..684888bc8d6f5351e987360ae5c68cdc294e15ba 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt @@ -2,12 +2,10 @@ package de.kuschku.quasseldroid_ng import android.app.Application import android.content.Context -import android.content.Intent import android.content.pm.ShortcutInfo import android.content.pm.ShortcutManager import android.graphics.drawable.Icon import android.os.Build -import de.kuschku.quasseldroid_ng.service.QuasselService import de.kuschku.quasseldroid_ng.util.AndroidCompatibilityUtils import de.kuschku.quasseldroid_ng.util.AndroidLoggingHandler import de.kuschku.quasseldroid_ng.util.AndroidStreamChannelFactory @@ -26,7 +24,7 @@ class QuasseldroidNG : Application() { .setResDialogText(R.string.crash_text) .build() - ACRA.init(this, config) + //ACRA.init(this, config) } override fun onCreate() { @@ -38,7 +36,6 @@ class QuasseldroidNG : Application() { AndroidLoggingHandler.inject() AndroidStreamChannelFactory.inject() - startService(Intent(this, QuasselService::class.java)) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { systemService<ShortcutManager>().dynamicShortcuts = listOf( ShortcutInfo.Builder(this, "id1") diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/AccountDatabase.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/AccountDatabase.kt new file mode 100644 index 0000000000000000000000000000000000000000..d00c55335cca0c6a85887d3c2062bb9aed3da025 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/AccountDatabase.kt @@ -0,0 +1,68 @@ +package de.kuschku.quasseldroid_ng.persistence + +import android.arch.paging.LivePagedListProvider +import android.arch.persistence.room.* +import android.content.Context + +@Database(entities = arrayOf(AccountDatabase.Account::class), version = 1) +abstract class AccountDatabase : RoomDatabase() { + abstract fun accounts(): AccountDao + + @Entity + data class Account( + @PrimaryKey(autoGenerate = true) + var id: Long, + var host: String, + var port: Int, + var user: String, + var pass: String, + var name: String, + var lastUsed: Long + ) + + @Dao + interface AccountDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun save(vararg entities: AccountDatabase.Account) + + @Insert(onConflict = OnConflictStrategy.IGNORE) + fun create(vararg entities: AccountDatabase.Account): Array<Long> + + @Query("SELECT * FROM account WHERE id = :id") + fun findById(id: Long): AccountDatabase.Account + + @Query("SELECT * FROM account ORDER BY lastUsed DESC") + fun all(): LivePagedListProvider<Int, Account> + + @Delete + fun delete(account: AccountDatabase.Account) + + @Query("DELETE FROM account") + fun clear() + } + + object Creator { + private var database: AccountDatabase? = null + private set + + // For Singleton instantiation + private val LOCK = Any() + + fun init(context: Context): AccountDatabase { + if (database == null) { + synchronized(LOCK) { + if (database == null) { + database = Room.databaseBuilder(context.applicationContext, + AccountDatabase::class.java, DATABASE_NAME) + .build() + } + } + } + return database!! + } + } + + companion object { + const val DATABASE_NAME = "persistence-accounts" + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt index dc1da0ae0121a0d598b4e390d23f3db3740ddf98..f4463a670556b4b9329208a60d31ee927daf4925 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt @@ -133,7 +133,6 @@ abstract class QuasselDatabase : RoomDatabase() { if (database == null) { database = Room.databaseBuilder(context.applicationContext, QuasselDatabase::class.java, DATABASE_NAME) - .fallbackToDestructiveMigration() .build() } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt index 7387e658a66f019e104124d964677f7bea03a332..250be610d8b6ee7c6703670712ff931fd4340d3c 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt @@ -47,15 +47,10 @@ class QuasselService : LifecycleService() { } } - private val asyncBackend = object : Backend { - private val thread = HandlerThread("BackendHandler") - private val handler: Handler - - init { - thread.start() - handler = Handler(thread.looper) - } + private val thread = HandlerThread("BackendHandler") + private lateinit var handler: Handler + private val asyncBackend = object : Backend { override fun connect(address: SocketAddress, user: String, pass: String) { handler.post { backendImplementation.connect(address, user, pass) @@ -71,9 +66,16 @@ class QuasselService : LifecycleService() { override fun sessionManager() = backendImplementation.sessionManager() } + override fun onDestroy() { + handler.post { thread.quit() } + super.onDestroy() + } + private lateinit var database: QuasselDatabase override fun onCreate() { + thread.start() + handler = Handler(thread.looper) super.onCreate() database = QuasselDatabase.Creator.init(application) sessionManager = SessionManager(ISession.NULL) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MainActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatActivity.kt similarity index 95% rename from app/src/main/java/de/kuschku/quasseldroid_ng/ui/MainActivity.kt rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatActivity.kt index 839a930ec85793d26d9e04dc0bd3a7b14b71395c..e20f0f4754ead6d549d6d3dedce130834922754d 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MainActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatActivity.kt @@ -2,6 +2,7 @@ package de.kuschku.quasseldroid_ng.ui import android.arch.lifecycle.LiveDataReactiveStreams import android.arch.lifecycle.Observer +import android.content.Intent import android.os.Bundle import android.support.design.widget.Snackbar import android.util.Log @@ -17,13 +18,14 @@ import de.kuschku.libquassel.session.ConnectionState import de.kuschku.libquassel.session.SocketAddress import de.kuschku.libquassel.util.compatibility.LoggingHandler import de.kuschku.quasseldroid_ng.R +import de.kuschku.quasseldroid_ng.service.QuasselService import de.kuschku.quasseldroid_ng.util.helper.stickyMapNotNull import de.kuschku.quasseldroid_ng.util.helper.stickySwitchMapNotNull import org.threeten.bp.ZoneOffset import org.threeten.bp.ZonedDateTime import org.threeten.bp.format.DateTimeFormatter -class MainActivity : ServiceBoundActivity() { +class ChatActivity : ServiceBoundActivity() { @BindView(R.id.host) lateinit var host: EditText @@ -77,6 +79,7 @@ class MainActivity : ServiceBoundActivity() { } override fun onCreate(savedInstanceState: Bundle?) { + startService(Intent(this, QuasselService::class.java)) setTheme(R.style.AppTheme) super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/AccountSetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/AccountSetupActivity.kt deleted file mode 100644 index 8a537a205e76ec112d1ec849e08b36f4e4c4786c..0000000000000000000000000000000000000000 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/AccountSetupActivity.kt +++ /dev/null @@ -1,17 +0,0 @@ -package de.kuschku.quasseldroid_ng.ui.setup - -import android.os.Bundle -import de.kuschku.quasseldroid_ng.ui.setup.slides.AccountSetupConnectionSlide -import de.kuschku.quasseldroid_ng.ui.setup.slides.AccountSetupNameSlide -import de.kuschku.quasseldroid_ng.ui.setup.slides.AccountSetupUserSlide - -class AccountSetupActivity : SetupActivity() { - override fun onDone(data: Bundle) { - } - - override val fragments = listOf( - AccountSetupConnectionSlide(), - AccountSetupUserSlide(), - AccountSetupNameSlide() - ) -} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt index 734561b6eeb48cdfa3076a132096c83c654b92a5..202ff734e7d7df23e4961e61bdb23cc627159e8a 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt @@ -14,7 +14,6 @@ import android.view.ViewGroup import butterknife.BindView import butterknife.ButterKnife import de.kuschku.quasseldroid_ng.R -import de.kuschku.quasseldroid_ng.ui.setup.slides.SlideFragment import de.kuschku.quasseldroid_ng.util.helper.stickySwitchMapNotNull abstract class SetupActivity : AppCompatActivity() { @@ -86,87 +85,100 @@ abstract class SetupActivity : AppCompatActivity() { onDone(adapter.result) } + fun setInitData(data: Bundle?) { + adapter.result.putAll(data) + } + abstract fun onDone(data: Bundle) override fun onSaveInstanceState(outState: Bundle) { - outState.putInt("currentItem", viewPager.currentItem) - outState.putInt("lastValidItem", adapter.lastValidItem) - outState.putBundle("result", adapter.result) + outState.putInt(currentItemKey, viewPager.currentItem) + outState.putInt(lastValidItemKey, adapter.lastValidItem) + outState.putBundle(resultKey, adapter.result) super.onSaveInstanceState(outState) } override fun onRestoreInstanceState(savedInstanceState: Bundle?) { super.onRestoreInstanceState(savedInstanceState) if (savedInstanceState != null) { - if (savedInstanceState.containsKey("result")) - adapter.result.putAll(savedInstanceState.getBundle("result")) - if (savedInstanceState.containsKey("lastValidItem")) - adapter.lastValidItem = savedInstanceState.getInt("lastValidItem") - if (savedInstanceState.containsKey("currentItem")) - viewPager.currentItem = savedInstanceState.getInt("currentItem") + if (savedInstanceState.containsKey(resultKey)) + adapter.result.putAll(savedInstanceState.getBundle(resultKey)) + if (savedInstanceState.containsKey(lastValidItemKey)) + adapter.lastValidItem = savedInstanceState.getInt(lastValidItemKey) + if (savedInstanceState.containsKey(currentItemKey)) + viewPager.currentItem = savedInstanceState.getInt(currentItemKey) currentPage.value = adapter.getItem(viewPager.currentItem) } pageChanged() } - companion object { - private class SlidePagerAdapter(private val fragmentManager: FragmentManager) : - FragmentStatePagerAdapter(fragmentManager) { - private val retainedFragments = SparseArray<SlideFragment>() - - val result = Bundle() - get() { - (0 until retainedFragments.size()).map(retainedFragments::valueAt).forEach { - it.getData(field) - } - return field - } + private class SlidePagerAdapter(private val fragmentManager: FragmentManager) : + FragmentStatePagerAdapter(fragmentManager) { + private val retainedFragments = SparseArray<SlideFragment>() - var lastValidItem = -1 - set(value) { - field = value - notifyDataSetChanged() + val result = Bundle() + get() { + (0 until retainedFragments.size()).map(retainedFragments::valueAt).forEach { + it.getData(field) } - private val list = mutableListOf<SlideFragment>() - - override fun getItem(position: Int): SlideFragment { - return retainedFragments.get(position) ?: list[position] + return field } - override fun getCount() = Math.min(list.size, lastValidItem + 2) - val totalCount get() = list.size - fun addFragment(fragment: SlideFragment) { - list.add(fragment) + var lastValidItem = -1 + set(value) { + field = value + notifyDataSetChanged() } + private val list = mutableListOf<SlideFragment>() - override fun instantiateItem(container: ViewGroup?, position: Int): Any { - val fragment = super.instantiateItem(container, position) - retainedFragments.put(position, fragment as SlideFragment) - return fragment - } + override fun getItem(position: Int): SlideFragment { + return retainedFragments.get(position) ?: list[position] + } - override fun destroyItem(container: ViewGroup?, position: Int, `object`: Any?) { - retainedFragments.get(position)?.getData(result) - retainedFragments.remove(position) - super.destroyItem(container, position, `object`) - } + override fun getCount() = Math.min(list.size, lastValidItem + 2) + val totalCount get() = list.size + fun addFragment(fragment: SlideFragment) { + list.add(fragment) + } - override fun restoreState(state: Parcelable?, loader: ClassLoader?) { - super.restoreState(state, loader) - if (state != null) { - val bundle = state as Bundle - val keys = bundle.keySet() - for (key in keys) { - if (key.startsWith("f")) { - val index = Integer.parseInt(key.substring(1)) - val f = fragmentManager.getFragment(bundle, key) - if (f != null && f is SlideFragment) { - retainedFragments.put(index, f) - } + override fun instantiateItem(container: ViewGroup?, position: Int): Any { + val fragment = super.instantiateItem(container, position) + storeNewFragment(position, fragment as SlideFragment) + return fragment + } + + override fun destroyItem(container: ViewGroup?, position: Int, `object`: Any?) { + retainedFragments.get(position)?.getData(result) + retainedFragments.remove(position) + super.destroyItem(container, position, `object`) + } + + override fun restoreState(state: Parcelable?, loader: ClassLoader?) { + super.restoreState(state, loader) + if (state != null) { + val bundle = state as Bundle + val keys = bundle.keySet() + for (key in keys) { + if (key.startsWith("f")) { + val index = Integer.parseInt(key.substring(1)) + val f = fragmentManager.getFragment(bundle, key) + if (f != null && f is SlideFragment) { + storeNewFragment(index, f) } } } } } + + private fun storeNewFragment(index: Int, fragment: SlideFragment) { + fragment.initData = result + retainedFragments.put(index, fragment) + } + } + + companion object { + private const val currentItemKey = ":setupActivity:currentItem" + private const val lastValidItemKey = ":setupActivity:lastValidItem" + private const val resultKey = ":setupActivity:result" } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/SlideFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SlideFragment.kt similarity index 81% rename from app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/SlideFragment.kt rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SlideFragment.kt index d6e1a76fdbbe2589f22556d8bc96efa5c56f4112..5e06a2866bb7acb02e85b97df8f48a4496eafe07 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/SlideFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SlideFragment.kt @@ -1,10 +1,11 @@ -package de.kuschku.quasseldroid_ng.ui.setup.slides +package de.kuschku.quasseldroid_ng.ui.setup import android.arch.lifecycle.LifecycleOwner import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.Observer import android.os.Bundle import android.support.annotation.StringRes +import android.support.design.widget.CollapsingToolbarLayout import android.support.v4.app.Fragment import android.view.LayoutInflater import android.view.View @@ -16,7 +17,7 @@ abstract class SlideFragment : Fragment() { @get:StringRes protected abstract val title: Int @get:StringRes - protected abstract val descripion: Int + protected abstract val description: Int protected abstract fun isValid(): Boolean @@ -33,9 +34,7 @@ abstract class SlideFragment : Fragment() { } protected fun updateValidity() { - val valid1 = isValid() - println("Updating validity: ${this::class.java.simpleName}@${hashCode()} $valid1") - valid.value = valid1 + valid.value = isValid() } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, @@ -44,9 +43,14 @@ abstract class SlideFragment : Fragment() { val viewGroup = view.findViewById<View>(R.id.content_host) as ViewGroup viewGroup.addView(onCreateContent(inflater, viewGroup, savedInstanceState)) - view.findViewById<TextView>(R.id.title).setText(title) - view.findViewById<TextView>(R.id.description).setText(descripion) + view.findViewById<TextView>(R.id.title)?.setText(title) + view.findViewById<CollapsingToolbarLayout>(R.id.collapsingToolbar)?.title = resources.getString( + title) + view.findViewById<TextView>(R.id.description).setText(description) + val data = initData + if (data != null) + setData(data) if (savedInstanceState != null) setData(savedInstanceState) updateValidity() @@ -66,6 +70,7 @@ abstract class SlideFragment : Fragment() { abstract fun setData(data: Bundle) abstract fun getData(data: Bundle) + var initData: Bundle? = null protected abstract fun onCreateContent(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/ValidityChangeCallback.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/ValidityChangeCallback.kt similarity index 57% rename from app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/ValidityChangeCallback.kt rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/ValidityChangeCallback.kt index 2fa49ef4b652b6465b8dcd3b6e181a654806ccfd..65778143ed4a409064550e2ddc92b0336faebb1f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/ValidityChangeCallback.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/ValidityChangeCallback.kt @@ -1,4 +1,4 @@ -package de.kuschku.quasseldroid_ng.ui.setup.slides +package de.kuschku.quasseldroid_ng.ui.setup interface ValidityChangeCallback { fun invoke(isValid: Boolean) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..cd2214cc0f77cf23b2ef4db2be41769b0e3ee9bf --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountAdapter.kt @@ -0,0 +1,217 @@ +package de.kuschku.quasseldroid_ng.ui.setup.accounts + +import android.arch.lifecycle.MutableLiveData +import android.arch.paging.PagedListAdapter +import android.support.v7.recyclerview.extensions.DiffCallback +import android.support.v7.widget.AppCompatImageButton +import android.support.v7.widget.AppCompatRadioButton +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import butterknife.BindView +import butterknife.ButterKnife +import de.kuschku.quasseldroid_ng.R +import de.kuschku.quasseldroid_ng.persistence.AccountDatabase + +class AccountAdapter : + PagedListAdapter<AccountDatabase.Account, AccountAdapter.AccountViewHolder>(DIFF_CALLBACK) { + private val actionListeners = mutableSetOf<(Long) -> Unit>() + private val addListeners = mutableSetOf<() -> Unit>() + val selectedItemId: Long + get() { + val position = selectedPos.value + return if (position != null && position > -1 && position < super.getItemCount()) + getItem(position)?.id ?: -1 + else + -1 + } + + private val clickListener = object : ItemListener { + override fun onAction(id: Long, pos: Int) { + changeSelection(id, pos) + } + } + + private val actionListener = object : ItemListener { + override fun onAction(id: Long, pos: Int) { + for (actionListener in actionListeners) { + actionListener.invoke(id) + } + } + } + + private val addListener = { + for (addListener in addListeners) { + addListener.invoke() + } + } + + fun addEditListener(f: (Long) -> Unit) { + actionListeners.add(f) + } + + fun removeEditListener(f: (Long) -> Unit) { + actionListeners.remove(f) + } + + fun addAddListener(f: () -> Unit) { + addListeners.add(f) + } + + fun removeAddListener(f: () -> Unit) { + addListeners.remove(f) + } + + override fun onBindViewHolder(holder: AccountViewHolder, position: Int) { + when (holder) { + is AccountViewHolder.Item -> { + val account = getItem(position) + if (account == null) { + holder.clear() + } else { + val selected = selectedId == account.id + holder.bind(account, position, selected) + if (selected && position != selectedPos.value) + holder.itemView.post { + changeSelection(account.id, position) + } + } + } + is AccountViewHolder.Add -> { + } + } + } + + override fun getItemViewType(position: Int) = when (position) { + super.getItemCount() -> TYPE_ADD + else -> TYPE_ACCOUNT + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate( + when (viewType) { + TYPE_ADD -> R.layout.widget_core_account_add + else -> R.layout.widget_core_account + }, parent, false) + return when (viewType) { + TYPE_ADD -> AccountViewHolder.Add(view, addListener) + else -> AccountViewHolder.Item(view, actionListener, clickListener) + } + } + + override fun getItemCount(): Int { + return super.getItemCount() + 1 + } + + companion object { + private const val TYPE_ACCOUNT = 0 + private const val TYPE_ADD = 1 + + private val DIFF_CALLBACK = object : DiffCallback<AccountDatabase.Account>() { + override fun areContentsTheSame(oldItem: AccountDatabase.Account, + newItem: AccountDatabase.Account): Boolean { + return oldItem == newItem + } + + override fun areItemsTheSame(oldItem: AccountDatabase.Account, + newItem: AccountDatabase.Account): Boolean { + return oldItem.id == newItem.id + } + } + } + + private fun notifySelectionChanged(from: Int?, to: Int?) { + if (from != null && from != -1) + notifyItemChanged(from) + + val real_to = to ?: -1 + selectedPos.value = real_to + + if (to != null && to != -1) + notifyItemChanged(to) + } + + fun changeSelection(id: Long, position: Int) { + notifySelectionChanged(selectedPos.value, position) + selectedId = id + } + + private fun indexOf(id: Long) = (0 until itemCount).lastOrNull { + getItemViewType(it) == TYPE_ACCOUNT && getItem(it)?.id == id + } ?: -1 + + fun selectAccount(id: Long) { + val index = indexOf(id) + if (index != -1) { + changeSelection(id, index) + } else { + selectedId = id + } + } + + private var selectedId = -1L + var selectedPos = MutableLiveData<Int>() + + interface ItemListener { + fun onAction(id: Long, pos: Int) + } + + sealed class AccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + class Item(itemView: View, actionListener: ItemListener, clickListener: ItemListener) + : AccountViewHolder(itemView) { + @BindView(R.id.account_name) + lateinit var accountName: TextView + + @BindView(R.id.account_description) + lateinit var accountDescription: TextView + + @BindView(R.id.account_select) + lateinit var accountSelect: AppCompatRadioButton + + @BindView(R.id.account_edit) + lateinit var accountEdit: AppCompatImageButton + + private var id = -1L + private var index = -1 + + init { + ButterKnife.bind(this, itemView) + accountEdit.setOnClickListener { + actionListener.onAction(id, index) + } + itemView.setOnClickListener { + clickListener.onAction(id, index) + } + } + + fun bind(account: AccountDatabase.Account, position: Int, selected: Boolean) { + index = position + id = account.id + accountName.text = account.name + accountDescription.text = itemView.context.resources.getString( + R.string.userOnHost, account.user, account.host, account.port + ) + accountSelect.isChecked = selected + } + + fun clear() { + index = -1 + id = -1L + accountName.text = "" + accountDescription.text = "" + accountSelect.isChecked = false + } + } + + class Add(itemView: View, clickListener: () -> Unit) : AccountViewHolder(itemView) { + init { + itemView.setOnClickListener { + clickListener.invoke() + } + } + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..8992f3c05613def5b0593b9c596980331c7ed0e7 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt @@ -0,0 +1,122 @@ +package de.kuschku.quasseldroid_ng.ui.setup.accounts + +import android.app.Activity +import android.os.Bundle +import android.os.Handler +import android.os.HandlerThread +import android.support.design.widget.FloatingActionButton +import android.support.v7.app.AlertDialog +import android.support.v7.app.AppCompatActivity +import android.view.Menu +import android.view.MenuItem +import android.widget.TextView +import butterknife.BindView +import butterknife.ButterKnife +import de.kuschku.quasseldroid_ng.R +import de.kuschku.quasseldroid_ng.persistence.AccountDatabase + +class AccountEditActivity : AppCompatActivity() { + @BindView(R.id.name) + lateinit var name: TextView + + @BindView(R.id.host) + lateinit var host: TextView + + @BindView(R.id.port) + lateinit var port: TextView + + @BindView(R.id.user) + lateinit var user: TextView + + @BindView(R.id.pass) + lateinit var pass: TextView + + @BindView(R.id.save_button) + lateinit var saveButton: FloatingActionButton + + private var accountId: Long = -1 + private var account: AccountDatabase.Account? = null + lateinit var database: AccountDatabase + + private val thread = HandlerThread("AccountEdit") + private lateinit var handler: Handler + + override fun onCreate(savedInstanceState: Bundle?) { + thread.start() + handler = Handler(thread.looper) + + setTheme(R.style.AppTheme) + super.onCreate(savedInstanceState) + setContentView(R.layout.setup_account_edit) + ButterKnife.bind(this) + + saveButton.setOnClickListener { + val it = account + if (it != null) { + it.name = name.text.toString() + it.host = host.text.toString() + it.port = port.text.toString().toIntOrNull() ?: 4242 + it.user = user.text.toString() + it.pass = pass.text.toString() + handler.post { + database.accounts().save(it) + setResult(Activity.RESULT_OK) + finish() + } + } + } + + database = AccountDatabase.Creator.init(this) + handler.post { + accountId = intent.getLongExtra("account", -1) + if (accountId == -1L) { + setResult(Activity.RESULT_CANCELED) + finish() + } + account = database.accounts().findById(accountId) + if (account == null) { + setResult(Activity.RESULT_CANCELED) + finish() + } + + name.text = account?.name + host.text = account?.host + port.text = account?.port?.toString() + user.text = account?.user + pass.text = account?.pass + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.setup_edit_account, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onDestroy() { + handler.post { thread.quit() } + super.onDestroy() + } + + override fun onOptionsItemSelected(item: MenuItem?) = when (item?.itemId) { + R.id.delete -> { + AlertDialog.Builder(this) + .setTitle("Delete?") + .setMessage("Are you sure?") + .setPositiveButton("Delete") { _, _ -> + val it = account + if (it != null) + handler.post { + database.accounts().delete(it) + } + setResult(Activity.RESULT_OK) + finish() + } + .setNegativeButton("Cancel") { dialogInterface, _ -> + dialogInterface.cancel() + } + .show() + true + } + else -> super.onOptionsItemSelected(item) + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..9d969f9fe6778c5862d0a15f96ed5ff8e16ab170 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt @@ -0,0 +1,32 @@ +package de.kuschku.quasseldroid_ng.ui.setup.accounts + +import android.content.Context +import android.content.SharedPreferences +import android.os.Bundle +import de.kuschku.quasseldroid_ng.ui.setup.SetupActivity +import de.kuschku.quasseldroid_ng.util.helper.editCommit + +class AccountSelectionActivity : SetupActivity() { + override val fragments = listOf( + AccountSelectionSlide() + ) + + lateinit var statusPreferences: SharedPreferences + override fun onDone(data: Bundle) { + statusPreferences.editCommit { + putLong(selectedAccountKey, data.getLong(selectedAccountKey, -1)) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + statusPreferences = this.getSharedPreferences("status", Context.MODE_PRIVATE) + val data = Bundle() + data.putLong(selectedAccountKey, statusPreferences.getLong(selectedAccountKey, -1)) + setInitData(data) + } + + companion object { + private const val selectedAccountKey = "selectedAccount" + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt new file mode 100644 index 0000000000000000000000000000000000000000..094c72ba7ab94326d75561ecf6291bf2ac152b17 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt @@ -0,0 +1,72 @@ +package de.kuschku.quasseldroid_ng.ui.setup.accounts + +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProviders +import android.arch.paging.PagedList +import android.content.Intent +import android.os.Bundle +import android.support.v7.widget.DefaultItemAnimator +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import butterknife.BindView +import butterknife.ButterKnife +import de.kuschku.quasseldroid_ng.R +import de.kuschku.quasseldroid_ng.persistence.AccountDatabase +import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment + +class AccountSelectionSlide : SlideFragment() { + @BindView(R.id.account_list) + lateinit var accountList: RecyclerView + + override fun isValid() = adapter.selectedPos.value ?: -1 != -1 + + override val title = R.string.slideAccountSelectTitle + override val description = R.string.slideAccountSelectDescription + + override fun setData(data: Bundle) { + if (data.containsKey("selectedAccount")) + adapter.selectAccount(data.getLong("selectedAccount")) + } + + override fun getData(data: Bundle) { + data.putLong("selectedAccount", adapter.selectedItemId) + } + + private val adapter = AccountAdapter() + override fun onCreateContent(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + val view = inflater.inflate(R.layout.setup_select_account, container, false) + ButterKnife.bind(this, view) + val accountViewmodel = ViewModelProviders.of(this).get( + AccountViewModel::class.java) + val firstObserver = object : Observer<PagedList<AccountDatabase.Account>?> { + override fun onChanged(t: PagedList<AccountDatabase.Account>?) { + if (t?.isEmpty() != false) + startActivityForResult(Intent(context, AccountSetupActivity::class.java), -1) + accountViewmodel.accounts.removeObserver(this) + } + } + accountViewmodel.accounts.observe(this, firstObserver) + accountViewmodel.accounts.observe(this, Observer(adapter::setList)) + accountList.layoutManager = LinearLayoutManager(context) + accountList.itemAnimator = DefaultItemAnimator() + accountList.adapter = adapter + adapter.selectedPos.observe(this, Observer { + updateValidity() + }) + adapter.addAddListener { + startActivityForResult(Intent(context, AccountSetupActivity::class.java), -1) + } + adapter.addEditListener { id -> + val intent = Intent(context, AccountEditActivity::class.java) + intent.putExtra("account", id) + startActivityForResult(intent, -1) + } + + return view + } + +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..35921bb0e213bd8184ec9659366fbfee835805d0 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupActivity.kt @@ -0,0 +1,50 @@ +package de.kuschku.quasseldroid_ng.ui.setup.accounts + +import android.app.Activity +import android.os.Bundle +import android.os.Handler +import android.os.HandlerThread +import de.kuschku.quasseldroid_ng.persistence.AccountDatabase +import de.kuschku.quasseldroid_ng.ui.setup.SetupActivity +import org.threeten.bp.Instant + +class AccountSetupActivity : SetupActivity() { + private val thread = HandlerThread("Setup") + private lateinit var handler: Handler + + override fun onDone(data: Bundle) { + val account = AccountDatabase.Account( + id = 0, + host = data.getString("host"), + port = data.getInt("port"), + user = data.getString("user"), + pass = data.getString("pass"), + name = data.getString("name"), + lastUsed = Instant.now().epochSecond + ) + handler.post { + val (id) = AccountDatabase.Creator.init(this).accounts().create(account) + runOnUiThread { + setResult(Activity.RESULT_OK) + finish() + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + thread.start() + handler = Handler(thread.looper) + super.onCreate(savedInstanceState) + } + + override fun onDestroy() { + handler.post { thread.quit() } + super.onDestroy() + } + + override val fragments = listOf( + AccountSetupConnectionSlide(), + AccountSetupUserSlide(), + AccountSetupNameSlide() + ) +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/AccountSetupConnectionSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt similarity index 91% rename from app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/AccountSetupConnectionSlide.kt rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt index 7e6cc835227710d381c564fa385c8b5e3777133a..dd16c6ddf9c8cef4a4f797695e3c776e69141bff 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/AccountSetupConnectionSlide.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt @@ -1,4 +1,4 @@ -package de.kuschku.quasseldroid_ng.ui.setup.slides +package de.kuschku.quasseldroid_ng.ui.setup.accounts import android.os.Bundle import android.support.design.widget.TextInputEditText @@ -10,6 +10,7 @@ import android.view.ViewGroup import butterknife.BindView import butterknife.ButterKnife import de.kuschku.quasseldroid_ng.R +import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment class AccountSetupConnectionSlide : SlideFragment() { @BindView(R.id.host) @@ -29,7 +30,7 @@ class AccountSetupConnectionSlide : SlideFragment() { } override val title = R.string.slideAccountConnectionTitle - override val descripion = R.string.slideAccountConnectionDescription + override val description = R.string.slideAccountConnectionDescription override fun setData(data: Bundle) { if (data.containsKey("host")) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/AccountSetupNameSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt similarity index 90% rename from app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/AccountSetupNameSlide.kt rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt index d3be3155c1533647c4ffe69eb99d746ccab76be3..026752a2ed6cbc4b59f4f1c078a5f0cdb38d491a 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/AccountSetupNameSlide.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt @@ -1,4 +1,4 @@ -package de.kuschku.quasseldroid_ng.ui.setup.slides +package de.kuschku.quasseldroid_ng.ui.setup.accounts import android.os.Bundle import android.support.design.widget.TextInputEditText @@ -10,6 +10,7 @@ import android.view.ViewGroup import butterknife.BindView import butterknife.ButterKnife import de.kuschku.quasseldroid_ng.R +import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment class AccountSetupNameSlide : SlideFragment() { @BindView(R.id.name) @@ -26,7 +27,7 @@ class AccountSetupNameSlide : SlideFragment() { } override val title = R.string.slideAccountNameTitle - override val descripion = R.string.slideAccountNameDescription + override val description = R.string.slideAccountNameDescription override fun setData(data: Bundle) { if (data.containsKey("name")) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/AccountSetupUserSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt similarity index 91% rename from app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/AccountSetupUserSlide.kt rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt index 9efc94cc78e3a3b4ce2618dd6d7566d619bc6cb2..34559a4aff9cb2044da6e86743942d7bd3764d98 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/slides/AccountSetupUserSlide.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt @@ -1,4 +1,4 @@ -package de.kuschku.quasseldroid_ng.ui.setup.slides +package de.kuschku.quasseldroid_ng.ui.setup.accounts import android.os.Bundle import android.support.design.widget.TextInputEditText @@ -10,6 +10,7 @@ import android.view.ViewGroup import butterknife.BindView import butterknife.ButterKnife import de.kuschku.quasseldroid_ng.R +import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment class AccountSetupUserSlide : SlideFragment() { @BindView(R.id.user) @@ -29,7 +30,7 @@ class AccountSetupUserSlide : SlideFragment() { } override val title = R.string.slideAccountUserTitle - override val descripion = R.string.slideAccountUserDescription + override val description = R.string.slideAccountUserDescription override fun setData(data: Bundle) { if (data.containsKey("user")) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountViewModel.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..8804e9805f46d6d173859f541b05bcefef76122d --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountViewModel.kt @@ -0,0 +1,19 @@ +package de.kuschku.quasseldroid_ng.ui.setup.accounts + +import android.app.Application +import android.arch.lifecycle.AndroidViewModel +import android.arch.lifecycle.LiveData +import android.arch.paging.PagedList +import de.kuschku.quasseldroid_ng.persistence.AccountDatabase + +class AccountViewModel(application: Application) : AndroidViewModel(application) { + private val database: AccountDatabase = AccountDatabase.Creator.init( + getApplication()) + val accounts: LiveData<PagedList<AccountDatabase.Account>> = database.accounts().all().create( + 0, + PagedList.Config.Builder() + .setPageSize(50) + .setPrefetchDistance(50) + .build() + ) +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/AccountDaoHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/AccountDaoHelper.kt new file mode 100644 index 0000000000000000000000000000000000000000..e450651890a97a4583373cd769a77df421389390 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/AccountDaoHelper.kt @@ -0,0 +1,10 @@ +package de.kuschku.quasseldroid_ng.util.helper + +import de.kuschku.quasseldroid_ng.persistence.AccountDatabase + +fun AccountDatabase.AccountDao.new(vararg entities: AccountDatabase.Account) { + val ids = create(*entities) + for (i in 0 until entities.size) { + entities[i].id = ids[i] + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/SharedPreferencesHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/SharedPreferencesHelper.kt new file mode 100644 index 0000000000000000000000000000000000000000..86d2c513f9e31d037f3f3fa2af53ea916a40ac44 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/SharedPreferencesHelper.kt @@ -0,0 +1,15 @@ +package de.kuschku.quasseldroid_ng.util.helper + +import android.content.SharedPreferences + +fun SharedPreferences.editApply(f: SharedPreferences.Editor.() -> Unit) { + val editor = this.edit() + editor.f() + editor.apply() +} + +fun SharedPreferences.editCommit(f: SharedPreferences.Editor.() -> Unit) { + val editor = this.edit() + editor.f() + editor.commit() +} diff --git a/app/src/main/res/drawable/ic_account.xml b/app/src/main/res/drawable/ic_account.xml new file mode 100644 index 0000000000000000000000000000000000000000..501efbef91691ff248251a0213d1c6c5d4298b69 --- /dev/null +++ b/app/src/main/res/drawable/ic_account.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000" + android:pathData="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" /> +</vector> diff --git a/app/src/main/res/drawable/ic_pencil.xml b/app/src/main/res/drawable/ic_pencil.xml new file mode 100644 index 0000000000000000000000000000000000000000..ed0200209c1633a29b7fae7725f3a4fbcd7dcf7c --- /dev/null +++ b/app/src/main/res/drawable/ic_pencil.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000" + android:pathData="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" /> +</vector> diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml new file mode 100644 index 0000000000000000000000000000000000000000..7541317cd6b3b607edc62371adc74a18a47140a6 --- /dev/null +++ b/app/src/main/res/drawable/ic_save.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000" + android:pathData="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" /> +</vector> diff --git a/app/src/main/res/drawable/ic_server_network.xml b/app/src/main/res/drawable/ic_server_network.xml new file mode 100644 index 0000000000000000000000000000000000000000..604c26f345288de06e7fef88e59e341ffb30fec9 --- /dev/null +++ b/app/src/main/res/drawable/ic_server_network.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000" + android:pathData="M13,18H14A1,1 0 0,1 15,19H22V21H15A1,1 0 0,1 14,22H10A1,1 0 0,1 9,21H2V19H9A1,1 0 0,1 10,18H11V16H4A1,1 0 0,1 3,15V11A1,1 0 0,1 4,10H20A1,1 0 0,1 21,11V15A1,1 0 0,1 20,16H13V18M4,2H20A1,1 0 0,1 21,3V7A1,1 0 0,1 20,8H4A1,1 0 0,1 3,7V3A1,1 0 0,1 4,2M9,6H10V4H9V6M9,14H10V12H9V14M5,4V6H7V4H5M5,12V14H7V12H5Z" /> +</vector> diff --git a/app/src/main/res/layout-sw600dp-land/setup_slide.xml b/app/src/main/res/layout-sw600dp-land/setup_slide.xml index 5bd5f30a831caaa7b85226a59dfaf2030aaafb3b..52be07163a581f091652f622235c8c0cb26f8386 100644 --- a/app/src/main/res/layout-sw600dp-land/setup_slide.xml +++ b/app/src/main/res/layout-sw600dp-land/setup_slide.xml @@ -61,7 +61,7 @@ android:theme="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse" tools:text="First, please choose which server your core is hosted on." /> - <ScrollView + <android.support.v4.widget.NestedScrollView android:id="@+id/scrollView" android:layout_width="400dp" android:layout_height="wrap_content" @@ -79,6 +79,6 @@ android:minHeight="400dp" app:cardElevation="4dp" /> - </ScrollView> + </android.support.v4.widget.NestedScrollView> </RelativeLayout> diff --git a/app/src/main/res/layout-sw600dp/setup_slide.xml b/app/src/main/res/layout-sw600dp/setup_slide.xml index c14e2b3a529cc572728b4f19de4c5be5724ffac9..ca320160484955b264ceb26b9a12a6233510f631 100644 --- a/app/src/main/res/layout-sw600dp/setup_slide.xml +++ b/app/src/main/res/layout-sw600dp/setup_slide.xml @@ -1,4 +1,4 @@ -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" +<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" @@ -47,4 +47,4 @@ </LinearLayout> -</ScrollView> +</android.support.v4.widget.NestedScrollView> diff --git a/app/src/main/res/layout/setup_account_connection.xml b/app/src/main/res/layout/setup_account_connection.xml index 71cab80492fa309620fff883ee4f27a64efd44b9..83ae786732a750565a92bc02352979cfda33525e 100644 --- a/app/src/main/res/layout/setup_account_connection.xml +++ b/app/src/main/res/layout/setup_account_connection.xml @@ -20,6 +20,7 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" @@ -28,7 +29,8 @@ <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/labelConnectionHostname"> + android:hint="@string/labelConnectionHostname" + app:errorEnabled="true"> <android.support.design.widget.TextInputEditText android:id="@+id/host" @@ -40,7 +42,8 @@ <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/labelConnectionPort"> + android:hint="@string/labelConnectionPort" + app:errorEnabled="true"> <android.support.design.widget.TextInputEditText android:id="@+id/port" diff --git a/app/src/main/res/layout/setup_account_edit.xml b/app/src/main/res/layout/setup_account_edit.xml new file mode 100644 index 0000000000000000000000000000000000000000..93e4873d7c22e80da55a827f432f0544a4e59196 --- /dev/null +++ b/app/src/main/res/layout/setup_account_edit.xml @@ -0,0 +1,194 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <android.support.v4.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <android.support.v4.widget.Space + android:layout_width="match_parent" + android:layout_height="16dp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <FrameLayout + android:layout_width="56dp" + android:layout_height="match_parent"> + + <ImageView + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical" + android:tint="#757575" + app:srcCompat="@drawable/ic_pencil" /> + </FrameLayout> + + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/labelAccountName" + app:errorEnabled="true"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="text" /> + </android.support.design.widget.TextInputLayout> + </LinearLayout> + + <android.support.v4.widget.Space + android:layout_width="match_parent" + android:layout_height="16dp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <FrameLayout + android:layout_width="56dp" + android:layout_height="match_parent"> + + <ImageView + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical" + android:tint="#757575" + app:srcCompat="@drawable/ic_server_network" /> + </FrameLayout> + + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/labelConnectionHostname" + app:errorEnabled="true"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/host" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textUri" /> + </android.support.design.widget.TextInputLayout> + </LinearLayout> + + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <android.support.v4.widget.Space + android:layout_width="56dp" + android:layout_height="match_parent" /> + + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/labelConnectionPort" + app:errorEnabled="true"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/port" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="number" + android:numeric="integer" + android:text="@string/defaultConnectionPort" /> + </android.support.design.widget.TextInputLayout> + </LinearLayout> + + <android.support.v4.widget.Space + android:layout_width="match_parent" + android:layout_height="16dp" /> + + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <FrameLayout + android:layout_width="56dp" + android:layout_height="match_parent"> + + <ImageView + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical" + android:tint="#757575" + app:srcCompat="@drawable/ic_account" /> + </FrameLayout> + + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/labelAccountUsername"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/user" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textVisiblePassword|textNoSuggestions" + app:errorEnabled="true" /> + </android.support.design.widget.TextInputLayout> + </LinearLayout> + + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <android.support.v4.widget.Space + android:layout_width="56dp" + android:layout_height="match_parent" /> + + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/labelAccountPassword"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/pass" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" + android:password="true" + app:errorEnabled="true" /> + </android.support.design.widget.TextInputLayout> + </LinearLayout> + + <android.support.v4.widget.Space + android:layout_width="match_parent" + android:layout_height="16dp" /> + </LinearLayout> + </android.support.v4.widget.NestedScrollView> + + <android.support.design.widget.FloatingActionButton + android:id="@+id/save_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end|bottom" + android:layout_marginBottom="16dp" + android:layout_marginEnd="16dp" + android:layout_marginRight="16dp" + android:tint="#ffffff" + app:fabSize="normal" + app:srcCompat="@drawable/ic_save" /> +</FrameLayout> diff --git a/app/src/main/res/layout/setup_account_name.xml b/app/src/main/res/layout/setup_account_name.xml index 86c8f10269f5d4c1c959b7a336b421865ebb7254..46c48f9a2ba1e8def14717663244f46bb182d6fa 100644 --- a/app/src/main/res/layout/setup_account_name.xml +++ b/app/src/main/res/layout/setup_account_name.xml @@ -1,25 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ QuasselDroid - Quassel client for Android - ~ Copyright (C) 2016 Janne Koschinski - ~ Copyright (C) 2016 Ken Børge Viktil - ~ Copyright (C) 2016 Magnus Fjell - ~ Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org> - ~ - ~ This program is free software: you can redistribute it and/or modify it - ~ under the terms of the GNU General Public License as published by the Free - ~ Software Foundation, either version 3 of the License, or (at your option) - ~ any later version. - ~ - ~ This program is distributed in the hope that it will be useful, - ~ but WITHOUT ANY WARRANTY; without even the implied warranty of - ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - ~ GNU General Public License for more details. - ~ - ~ You should have received a copy of the GNU General Public License along - ~ with this program. If not, see <http://www.gnu.org/licenses/>. - --> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" @@ -28,7 +8,8 @@ <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/labelAccountName"> + android:hint="@string/labelAccountName" + app:errorEnabled="true"> <android.support.design.widget.TextInputEditText android:id="@+id/name" diff --git a/app/src/main/res/layout/setup_account_user.xml b/app/src/main/res/layout/setup_account_user.xml index dd2394f828bf453c8d9e4a024ca3ec630490e173..6bf604941d1892966315946e7f6bd25f6856c079 100644 --- a/app/src/main/res/layout/setup_account_user.xml +++ b/app/src/main/res/layout/setup_account_user.xml @@ -1,25 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ QuasselDroid - Quassel client for Android - ~ Copyright (C) 2016 Janne Koschinski - ~ Copyright (C) 2016 Ken Børge Viktil - ~ Copyright (C) 2016 Magnus Fjell - ~ Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org> - ~ - ~ This program is free software: you can redistribute it and/or modify it - ~ under the terms of the GNU General Public License as published by the Free - ~ Software Foundation, either version 3 of the License, or (at your option) - ~ any later version. - ~ - ~ This program is distributed in the hope that it will be useful, - ~ but WITHOUT ANY WARRANTY; without even the implied warranty of - ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - ~ GNU General Public License for more details. - ~ - ~ You should have received a copy of the GNU General Public License along - ~ with this program. If not, see <http://www.gnu.org/licenses/>. - --> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" @@ -34,7 +14,8 @@ android:id="@+id/user" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="textVisiblePassword|textNoSuggestions" /> + android:inputType="textVisiblePassword|textNoSuggestions" + app:errorEnabled="true" /> </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout @@ -46,7 +27,8 @@ android:id="@+id/pass" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="textPassword" /> + android:inputType="textPassword" + app:errorEnabled="true" /> </android.support.design.widget.TextInputLayout> </LinearLayout> diff --git a/app/src/main/res/layout/setup_select_account.xml b/app/src/main/res/layout/setup_select_account.xml new file mode 100644 index 0000000000000000000000000000000000000000..7961fd8dd0ef319afe62a4845749d9642e19615c --- /dev/null +++ b/app/src/main/res/layout/setup_select_account.xml @@ -0,0 +1,4 @@ +<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/account_list" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/app/src/main/res/layout/setup_slide.xml b/app/src/main/res/layout/setup_slide.xml index 7c7c5ad96a25f7647c27a1d700dc799cbbcd57d9..679e33f2c132825a3df6cc4234940736914dbfe2 100644 --- a/app/src/main/res/layout/setup_slide.xml +++ b/app/src/main/res/layout/setup_slide.xml @@ -1,44 +1,66 @@ -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> - <LinearLayout + <android.support.design.widget.AppBarLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> + android:layout_height="wrap_content"> - <LinearLayout + <android.support.design.widget.CollapsingToolbarLayout + android:id="@+id/collapsingToolbar" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/colorPrimary" - android:orientation="vertical" - android:padding="32dp"> - - <TextView - android:id="@+id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="8dp" - android:textSize="28sp" - android:theme="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse" - tools:text="Connection" /> + app:collapsedTitleTextAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse" + app:contentScrim="?colorPrimary" + app:expandedTitleGravity="top" + app:expandedTitleMarginBottom="0dp" + app:expandedTitleMarginEnd="48dp" + app:expandedTitleMarginStart="32dp" + app:expandedTitleMarginTop="24dp" + app:expandedTitleTextAppearance="@style/TextAppearance.AppCompat.Title.Inverse" + app:layout_scrollFlags="scroll|exitUntilCollapsed"> - <TextView - android:id="@+id/description" + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:textSize="16sp" - android:theme="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse" - tools:text="First, please choose which server your core is hosted on." /> + android:orientation="vertical" + app:layout_collapseMode="parallax" + app:layout_collapseParallaxMultiplier="1"> - </LinearLayout> + <android.support.v4.widget.Space + android:layout_width="match_parent" + android:layout_height="21sp" + android:layout_marginTop="24dp" /> - <FrameLayout - android:id="@+id/content_host" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> + <TextView + android:id="@+id/description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="24dp" + android:layout_marginEnd="48dp" + android:layout_marginLeft="32dp" + android:layout_marginRight="48dp" + android:layout_marginStart="32dp" + android:layout_marginTop="8dp" + android:textSize="16sp" + android:theme="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse" + tools:text="First, please choose which server your core is hosted on." /> + </LinearLayout> - </LinearLayout> + <android.support.v7.widget.Toolbar + android:layout_width="match_parent" + android:layout_height="?actionBarSize" + app:layout_collapseMode="pin" + app:titleMarginEnd="48dp" + app:titleMarginStart="16dp" /> + </android.support.design.widget.CollapsingToolbarLayout> + </android.support.design.widget.AppBarLayout> -</ScrollView> + <FrameLayout + android:id="@+id/content_host" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> +</android.support.design.widget.CoordinatorLayout> diff --git a/app/src/main/res/layout/widget_core_account.xml b/app/src/main/res/layout/widget_core_account.xml new file mode 100644 index 0000000000000000000000000000000000000000..e92dfe929909f948f33584c43831e7b96ae110f7 --- /dev/null +++ b/app/src/main/res/layout/widget_core_account.xml @@ -0,0 +1,67 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="72dp" + android:background="?attr/selectableItemBackground" + android:clickable="true" + android:focusable="true" + android:focusableInTouchMode="false" + android:orientation="horizontal" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <LinearLayout + android:layout_width="48dp" + android:layout_height="match_parent"> + + <android.support.v7.widget.AppCompatRadioButton + android:id="@+id/account_select" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_gravity="center_vertical" + android:clickable="false" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="0dip" + android:layout_height="match_parent" + android:layout_margin="8dp" + android:layout_weight="1" + android:gravity="center_vertical|start" + android:orientation="vertical"> + + <TextView + android:id="@+id/account_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + android:gravity="center_vertical|start" + android:lines="1" + android:singleLine="true" + android:textSize="14sp" + tools:text="Remote" /> + + <TextView + android:id="@+id/account_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="sans-serif" + android:gravity="center_vertical|start" + android:lines="1" + android:singleLine="true" + android:textSize="14sp" + tools:text="testUser on localhost" /> + </LinearLayout> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/account_edit" + style="?attr/buttonStyleSmall" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_gravity="center_vertical" + android:background="?attr/selectableItemBackgroundBorderless" + android:tint="#757575" + app:srcCompat="@drawable/ic_pencil" /> +</LinearLayout> diff --git a/app/src/main/res/layout/widget_core_account_add.xml b/app/src/main/res/layout/widget_core_account_add.xml new file mode 100644 index 0000000000000000000000000000000000000000..5f8054a21297970af5d5d3fae55c637db9e404f2 --- /dev/null +++ b/app/src/main/res/layout/widget_core_account_add.xml @@ -0,0 +1,45 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="72dp" + android:background="?attr/selectableItemBackground" + android:clickable="true" + android:focusable="true" + android:focusableInTouchMode="false" + android:orientation="horizontal" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <LinearLayout + android:layout_width="48dp" + android:layout_height="match_parent"> + + <ImageView + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_gravity="center_vertical" + android:tint="#727272" + app:srcCompat="@drawable/ic_add" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="0dip" + android:layout_height="match_parent" + android:layout_margin="8dp" + android:layout_weight="1" + android:gravity="center_vertical|start" + android:orientation="vertical"> + + <TextView + android:id="@+id/account_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + android:gravity="center_vertical|start" + android:lines="1" + android:singleLine="true" + android:text="New Account" + android:textSize="14sp" /> + </LinearLayout> +</LinearLayout> diff --git a/app/src/main/res/menu/setup_edit_account.xml b/app/src/main/res/menu/setup_edit_account.xml new file mode 100644 index 0000000000000000000000000000000000000000..fa05be637b1ba55ba1d0d373ba052751012c1517 --- /dev/null +++ b/app/src/main/res/menu/setup_edit_account.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/delete" + android:icon="@drawable/ic_delete" + android:title="Delete" + app:iconTint="#fff" + app:showAsAction="ifRoom" /> +</menu> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index bdd60cfdb845790ee1453d8bb7b98eb2cf5a8d50..3d2127148d95fdc23b4f6ec04c65d08f79df69ea 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,6 +3,4 @@ <color name="colorPrimary">#0a70c0</color> <color name="colorPrimaryDark">#105a94</color> <color name="colorAccent">#ffaf3b</color> - - <attr name="backgroundSetup">?attr/color</attr> </resources> diff --git a/app/src/main/res/values/strings_setup.xml b/app/src/main/res/values/strings_setup.xml index 4cd5b5f9b60374b9611882ab5c68df74ed8d665a..87287e6691373862b5d454315fae6e34a74d940a 100644 --- a/app/src/main/res/values/strings_setup.xml +++ b/app/src/main/res/values/strings_setup.xml @@ -1,4 +1,9 @@ <resources> + <!-- Account Selection --> + <string name="slideAccountSelectTitle">Select Account</string> + <string name="slideAccountSelectDescription">Please select an account from the list or add one</string> + <string name="userOnHost">%1$s on %2$s:%3$d</string> + <!-- Account Connection --> <string name="slideAccountConnectionTitle">Connection</string> <string name="slideAccountConnectionDescription">First, please choose which server your core is hosted on.</string>