diff --git a/README.md b/README.md index 0bfdce9..98489ce 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,169 @@ # Mabasej_Team We are working on system, that will help tourists in cities to get information about city more easily. -# Hardware -- work in progress (but probabbly we will use rpi with external antena for wifi) +## Hardware +- Raspberry PI (for now tested only on rpi4. Works on rpi zero too, but it will be slow if more devices are connected) +- External/Internal WiFi antena -# Software +## Software - python 3.9.2 compatible server with basic web interface -- python 3.x based mobile app with help of android studio +- Kotlin based mobile app -# Server -To run server you need to install -- hypercorn - "pip install hypercorn" -- fastapi - "pip install fastapi" -- requests - "pip install requests" -- aiofiles - "pip install aiofiles" +## Install +Wikispot is in testing stages, but it is possible to install it using our .img file (link coming soon) based on DietPi or custom script. -then run by command - "hypercorn main:app --bind " -To connect to another rpi you need to edit settings.json with different ID and fill heartbeat table. +| Device | Server compatible | Instalation | +| :-------------------- | :------------------------------------------------------------------------------------------ | :-----------: | +| Ubuntu (I7, 16GB ram) | :heavy_check_mark: WORKING (Only server) | Manual/script | +| RPI 4b (2GB) | :heavy_check_mark: WORKING | .img/script | +| RPI 400 (4GB) | :grey_question: Untested. Should work. | .img/script | +| RPI 3b+ | :grey_question: Untested. Should work. | .img/script | +| RPI zero w | :white_check_mark: Working with fewer devices (Only server. No AP, Computer vision) | .img/script | +| RPI 2 | :question: Untested. | :x: | +| RPI | :question: Untested. | :x: | -This is not finished product + +### Fresh istall (.img) Only RPI +login credentials +> login: dietpi + +> password: WikiSpot2021 + + +requirements: +1. WikiSpot image file (download: *soon*) +2. MicroSd card (recommended: >=16GB, :exclamation: ALL DATA STORED ON SD CARD WILL BE FORMATED :exclamation:) +3. BalenaEtcher (or another sd card flasher) *link:* https://www.balena.io/etcher/ +4. SD card reader + + +Install: +1. Download all required files (wikispot.img and balenaetcher) and install BalenaEtcher +2. Insert SD card into computer/reader, open BalenaEtcher -> chose Flash from file -> chose downloaded wikispot.img -> Select your sd in *Select target* -> Flash! +3. :exclamation: WINDOWS will show unformated drive. Cancel it. It is because of uncompatible format for windows :exclamation: +4. After flashing open partition *boot* (should apear as USB), find file *dietpi.txt* and open it in text editor. + - Accept license by changing `AUTO_SETUP_ACCEPT_LICENSE=0` to `AUTO_SETUP_ACCEPT_LICENSE=1` + - Change name of WikiSpot `AUTO_SETUP_NET_HOSTNAME=WikiSpot-CHANGE_ME` by changing only *CHANGE_ME* or leave *CHANGE_ME* for random number name *WikiSpot-54346 + - You can set static ip address by changing `AUTO_SETUP_NET_USESTATIC=0` to `AUTO_SETUP_NET_USESTATIC=1` And entering your setting into required lines. + - If you want to use computer vision plugin with rpi camera set `ENABLE_COMPUTER_VISION_PLUGIN=0` to `ENABLE_COMPUTER_VISION_PLUGIN=1` (*recommended only on RPI4) + - If you want to use RPI as access point to WikiSpot change `#AUTO_SETUP_INSTALL_SOFTWARE_ID=60` to `AUTO_SETUP_INSTALL_SOFTWARE_ID=60` + - *wifi setup in testing* +5. :grey_exclamation:For advanced users:grey_exclamation: You can now change contens of WikiSpot server in `/boot/WikiSpot`according to an example in server filesystem +6. Eject sd card from computer, insert it in Raspberry Pi and power it on. :bangbang:Raspberry Pi needs to be connected to intenet via Ethernet (*wifi coming soon*) othervise the setup will crash. +7. The setup will take approximately 25-40 min (RPI 4b (2gb) and 70 mb download speed) +8. Done you can start using WikiSpot and edit contents of WikiSpot with our app (*coming soon*) + + +### Script install +*coming soon* + + +### Manual install +*coming soon* + + +## Server filesystem + +``` +└── test_directory + ├── cache # files forwarded from another servers to client + ├── engine.py # engine for server (log, recovery, update) + ├── files # content of WikiSpot server + │   └── test.jpg + ├── filesystem.json # data settings of server (name, description, files) + ├── main.py # main server file + ├── plugins # plugins file + │   └── computer_vision # oficial WikiSpot computer vision plugin for RPI 4 + │   ├── MobileNetSSD_deploy.caffemodel + │   ├── MobileNetSSD_deploy.prototxt + │   └── com_vision.py + ├── run.py # start script for server + ├── settings.json # settings (log, debug, connected WikiSpots, cache size,...) + ├── system.py # update and clean script + └── version.json # version of WikiSpot +``` + + +### filesystem + +``` +{ + "ID": 0, # ID of WikiSpots, Needs to be different, because network will crash + "location": "25.997417761947318, -97.15738221291177", # Location of WikiSpot server copied from google maps + "description": { + "title": "WikiSpot demo", # Name of WikiSpot server (swiming pool, school, ...) + "description_s": "This is showcase of WikiSpot", # Short description showed on web/app in list of servers + "description_l": "This will show after opening server in app", # Long description showed after opening the server in web/app + "photo_s": "test.jpg", # Small image showed on web/app in list of servers + "photo_b": "test.png" # Big image showed after opening the server in web/app + }, + "files": [ # files on server in /files that will be mediated to the web/app + { + "name": "test", # Name of the file, without spaces. App will change "_" to spaces + "format": ".jpg", # Format of the file + "description": "This is test file" # Description showed next to the file + } + ] +} +``` + +To manualy add new file to server (on setup or via ssh) add file to `server_directory/files` +and add record for file into `files` list in `filesystem.json`. :exclamation:do not forget "," after last record:exclamation: + +``` + + { + "name": "new_file_name", + "format": ".txt", + "description": "This is how you add new file" + } +``` + + +### settings.json + +``` +{ + "time_to_heartbeat": 20, # Time to ping of another online servers in seconds + "time_to_heartbeat_offline": 25, # Time to ping of another offline servers in seconds + "save_table": true, # Save connected servers to reconnect after restart + "time_to_save": 60, # Time to save server in seconds + "max_mess": 20, # Maximum messages stored in RAM + "cache_size_mb": 1000, # Maximum size of cache directory in mb + "clear_cache_on_startup": false, # Remove contents of cache on startup (slower first downloads) + "log": { # Log settings + "save_error": true, # Save errors into log.txt + "print_error": true, # Print errors into console (if running as service into linux log) + "save_warning": true, # Save warnings into log.txt + "print_warning": true, # Print warnings into console (if running as service into linux log) + "save_message": false, # Save messages (new server, etc. not messages from clients) into log.txt + "print_message": true, # Print messages into console (if running as service into linux log) + "enable_debug": false # Enable debug into console (if running as service into linux log) + }, + "heartbeat_table": { # Saved servers + "ID": [], + "IP": [], + "location": [], + "file_system": [], + "last_heartbeat": [] + } +} +``` + +If you want to manually add server on first setup or via ssh fill heartbeat table like this. + +``` +"heartbeat_table": { # Saved servers + "ID": [1], # ID of server as integer (number) + "IP": ["192.168.1.2"], # IP of server as string + "location": [""], # Empty string as placeholder. location will be downloaded after first connection + "file_system": [""], # Empty string as placeholder. filesystem will be downloaded after first connection + "last_heartbeat": [10] # After how many seconds will server try to connect for the first time + } +``` + +:bangbang:If the server will be offline for long time (heartbeat + offline heartbeat) it will be removed from heartbeat table. If the save function is disabled server will trying to connect after restart:bangbang: + + + +*This is not finished product* diff --git a/app/WikiSpot/.idea/codeStyles/Project.xml b/app/WikiSpot/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..7643783 --- /dev/null +++ b/app/WikiSpot/.idea/codeStyles/Project.xml @@ -0,0 +1,123 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/.idea/codeStyles/codeStyleConfig.xml b/app/WikiSpot/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/app/WikiSpot/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/WikiSpot/.idea/compiler.xml b/app/WikiSpot/.idea/compiler.xml index 61a9130..fb7f4a8 100644 --- a/app/WikiSpot/.idea/compiler.xml +++ b/app/WikiSpot/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/WikiSpot/.idea/dictionaries/ben44.xml b/app/WikiSpot/.idea/dictionaries/ben44.xml new file mode 100644 index 0000000..3783499 --- /dev/null +++ b/app/WikiSpot/.idea/dictionaries/ben44.xml @@ -0,0 +1,10 @@ + + + + backstack + datatype + filetype + initing + + + \ No newline at end of file diff --git a/app/WikiSpot/.idea/inspectionProfiles/Project_Default.xml b/app/WikiSpot/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..ac21435 --- /dev/null +++ b/app/WikiSpot/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/WikiSpot/.idea/misc.xml b/app/WikiSpot/.idea/misc.xml index d5d35ec..860da66 100644 --- a/app/WikiSpot/.idea/misc.xml +++ b/app/WikiSpot/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/app/WikiSpot/.idea/runConfigurations.xml b/app/WikiSpot/.idea/runConfigurations.xml new file mode 100644 index 0000000..797acea --- /dev/null +++ b/app/WikiSpot/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/build.gradle b/app/WikiSpot/app/build.gradle index ffc9d79..47735fc 100644 --- a/app/WikiSpot/app/build.gradle +++ b/app/WikiSpot/app/build.gradle @@ -1,7 +1,8 @@ plugins { id 'com.android.application' id 'kotlin-android' - id 'com.chaquo.python' + id 'kotlin-android-extensions' + id 'androidx.navigation.safeargs.kotlin' } android { @@ -10,28 +11,14 @@ android { defaultConfig { applicationId "com.example.wikispot" - minSdkVersion 16 + minSdkVersion 23 targetSdkVersion 30 versionCode 1 versionName "1.0" + vectorDrawables.useSupportLibrary = true + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - - sourceSets { - main { - python { - srcDirs = ["src/main/python"] - } - } - } - python { - buildPython "/urs/local/bin/python3" - buildPython "python3" - } - - ndk { - abiFilters "armeabi-v7a", "x86" - } } buildFeatures { @@ -49,12 +36,11 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = JavaVersion.VERSION_1_8.toString() } } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' @@ -64,7 +50,14 @@ dependencies { implementation 'androidx.navigation:navigation-ui-ktx:2.3.4' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.preference:preference:1.1.1' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + implementation 'com.google.android.gms:play-services-maps:17.0.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + + implementation 'com.github.bumptech.glide:glide:4.12.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' + + implementation 'com.github.barteksc:android-pdf-viewer:2.8.2' } \ No newline at end of file diff --git a/app/WikiSpot/app/src/debug/res/values/google_maps_api.xml b/app/WikiSpot/app/src/debug/res/values/google_maps_api.xml new file mode 100644 index 0000000..8fd5918 --- /dev/null +++ b/app/WikiSpot/app/src/debug/res/values/google_maps_api.xml @@ -0,0 +1,24 @@ + + + AIzaSyAixBio8FevppLsncIkFUQarx2kUB-0dW0 + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/AndroidManifest.xml b/app/WikiSpot/app/src/main/AndroidManifest.xml index b16dbab..98d09a2 100644 --- a/app/WikiSpot/app/src/main/AndroidManifest.xml +++ b/app/WikiSpot/app/src/main/AndroidManifest.xml @@ -2,15 +2,40 @@ + + + + + + - + android:theme="@style/Theme.WikiSpot" + android:usesCleartextTraffic="true"> + + + + + + diff --git a/app/WikiSpot/app/src/main/ic_launcher-playstore.png b/app/WikiSpot/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..5ffb030 Binary files /dev/null and b/app/WikiSpot/app/src/main/ic_launcher-playstore.png differ diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/AppConstants.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/AppConstants.kt deleted file mode 100644 index 00b284a..0000000 --- a/app/WikiSpot/app/src/main/java/com/example/wikispot/AppConstants.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.example.wikispot - -object Constants {} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/Extentions.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/Extentions.kt new file mode 100644 index 0000000..1ca9ada --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/Extentions.kt @@ -0,0 +1,117 @@ +package com.example.wikispot + +import android.app.Activity +import android.app.AlertDialog +import android.content.Context +import android.os.Environment +import android.view.View +import android.widget.Toast +import com.google.android.material.snackbar.Snackbar +import java.io.File +import java.io.FileInputStream +import java.nio.channels.FileChannel +import java.nio.charset.Charset +import java.util.* + +// for showing messages + +fun Context.showToast(message: String, length: Int = Toast.LENGTH_SHORT) { + Toast.makeText(this, message, length).show() +} + +fun Context.showSnack(message: String, view: View, length: Int = Snackbar.LENGTH_LONG) { + Snackbar.make(this, view, message, length).show() +} + +// for theme + +fun Context.getThemeId(): Int { + if (ThemeOptions.darkTheme) { + if (!ThemeOptions.moreColors) { + return R.style.Theme_WikiSpotDark + } else { + return R.style.Theme_WikiSpotDark_ + } + } else { + if (!ThemeOptions.moreColors) { + return R.style.Theme_WikiSpot + } else { + return R.style.Theme_WikiSpot_ + } + } +} + +// working with files +fun Context.createFile(filename: String, filetype: String): File { + + val storageDir = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + if (!storageDir?.exists()!!) { + storageDir.mkdir() + } + + return File(storageDir, "$filename.$filetype") +} + +fun Context.getFile(filename: String, filetype: String): String { + + val file = (getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)?.absolutePath ?: "") + "/$filename.$filetype" + + val stream = FileInputStream(file) + + var returnString = "" + + stream.use { streamInUse -> + val fileChannel = streamInUse.channel + val mappedByteBuffer = fileChannel.map( + FileChannel.MapMode.READ_ONLY, + 0, + fileChannel.size() + ) + returnString = Charset.defaultCharset().decode(mappedByteBuffer).toString() + } + + return returnString +} + +fun Context.saveString(accessKey: String, stringValue: String, preferencesFilename: String="generalPreferences") { + val sharedPreferences = getSharedPreferences(preferencesFilename, Context.MODE_PRIVATE) + val editor = sharedPreferences.edit() + + editor.apply{ + putString(accessKey, stringValue) + }.apply() +} + +fun Context.getStringFromSharedPreferences(accessKey: String, preferencesFilename: String="generalPreferences"): String { + val sharedPreferences = getSharedPreferences(preferencesFilename, Context.MODE_PRIVATE) + val returnedString = sharedPreferences.getString(accessKey, "") + + returnedString?.let { + return returnedString + } + + return "" +} + +// Other + +fun Context.getRandomGenerator(seedString: String): Random { + var n: Long = 0 + for (element in seedString) { + n += element.toInt() + } + + println(n) + return Random(n) +} + +// Activity extensions + +fun Activity.askToQuit() { + val builder = AlertDialog.Builder(this) + builder.setTitle("Confirm") + builder.setMessage("Do you want to quit the application?") + builder.setPositiveButton("Yes") { _, _ -> finish()} + builder.setNegativeButton("No") { _, _ -> } + builder.show() +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/activities/MainActivity.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/activities/MainActivity.kt index 5673be7..fe16b24 100644 --- a/app/WikiSpot/app/src/main/java/com/example/wikispot/activities/MainActivity.kt +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/activities/MainActivity.kt @@ -1,31 +1,195 @@ package com.example.wikispot.activities -import androidx.appcompat.app.AppCompatActivity +import android.graphics.Bitmap import android.os.Bundle -import androidx.appcompat.app.AlertDialog +import android.util.DisplayMetrics +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.fragment.app.Fragment import androidx.navigation.findNavController import androidx.navigation.ui.setupWithNavController -import com.example.wikispot.R -import com.google.android.material.bottomnavigation.BottomNavigationView +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.wikispot.* +import com.example.wikispot.adapters.FileViewsAdapter +import com.example.wikispot.adapters.LabeledValuesAdapter +import com.example.wikispot.adapters.PlacePreviewsAdapter +import com.example.wikispot.fragments.* +import com.example.wikispot.modelClasses.JsonManager +import com.example.wikispot.modelClasses.JsonManagerLite +import com.example.wikispot.modelClasses.SettingsSaveManager +import com.example.wikispot.modelsForAdapters.* +import com.google.android.gms.maps.model.LatLng +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.fragment_explore.* +import kotlinx.android.synthetic.main.fragment_home.* +import kotlinx.android.synthetic.main.fragment_info.* +import kotlinx.android.synthetic.main.fragment_info.view.* + class MainActivity : AppCompatActivity() { override fun onBackPressed() { - val builder = AlertDialog.Builder(this) - builder.setTitle("Confirm") - builder.setMessage("Do you want to quit the application?") - builder.setPositiveButton("Yes") {_, _ -> finish()} - builder.setNegativeButton("No") {_, _ -> } - builder.show() + + try { + when (val currentlyShownFragment = mainFragmentHost.childFragmentManager.fragments[0]) { + is chatFragment -> { + askToQuit() + } + is exploreFragment -> { + askToQuit() + } + is homeFragment -> { + askToQuit() + } + is mapFragment -> { + askToQuit() + } + is settingsFragment -> { + askToQuit() + } + is infoFragment -> { + when (CustomBackstackVariables.infoFragmentBackDestination) { + "exploreFragment" -> { currentlyShownFragment.goExploreFragment() } + "mapFragment" -> {currentlyShownFragment.goMapFragment()} + } + + } + } + } catch (e: Throwable) { println(e) } } override fun onCreate(savedInstanceState: Bundle?) { + doPreparations() + loadSettings() + setTheme(getThemeId()) + super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val navController = findNavController(R.id.mainFragmentHost) - val bottomNavView = findViewById(R.id.mainBottomNavigationView) + mainBottomNavigationView.setupWithNavController(navController) - bottomNavView.setupWithNavController(navController) + handleExtras() } -} \ No newline at end of file + + private fun doPreparations() { + val displayMetrics = DisplayMetrics() + windowManager.defaultDisplay.getMetrics(displayMetrics) + ScreenParameters.height = displayMetrics.heightPixels + ScreenParameters.width = displayMetrics.widthPixels + + val savedBaseUrl = this.getStringFromSharedPreferences("baseUrlSave") + if (savedBaseUrl != "") { + ServerManagement.baseUrl = savedBaseUrl + } + } + + override fun onResume() { + super.onResume() + // server communication + + connectExploreFragmentAdapterModel() + } + + override fun onPause() { + PlaceSupplier.saveToCache(this) + ServerManagement.serverManager.deleteConnection("exploreListConnection") + super.onPause() + } + + private fun handleExtras() { + when (intent.getStringExtra(IntentsKeys.startFragment)) { + "chatFragment" -> { + mainBottomNavigationView.selectedItemId = R.id.chatFragment + } + "exploreFragment" -> { + mainBottomNavigationView.selectedItemId = R.id.exploreFragment + } + // skipping home fragment because were already here + "mapFragment" -> { + mainBottomNavigationView.selectedItemId = R.id.mapFragment + } + "settingsFragment" -> { + mainBottomNavigationView.selectedItemId = R.id.settingsFragment + } + "debugFragment" -> { + StartDirections.settingsFragmentStartDirection = "debugFragment" + mainBottomNavigationView.selectedItemId = R.id.settingsFragment + } + } + } + + private fun loadSettings() { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + val settingsSaveManager = SettingsSaveManager(this) + settingsSaveManager.loadSettings() + } + + private fun connectExploreFragmentAdapterModel () { + // loading from cache + PlaceSupplier.loadFromCache(this) + + // connecting to server + val dataReceiver: (String) -> Unit = { data: String -> + val json = JsonManager(this, data) + + PlaceSupplier.controlJson = JsonManagerLite(data) + + for (i in 1 until json.getLengthOfJsonArray()) { + + json.getJsonObject(i) + val id = json.getAttributeContent("ID").toInt() + val location = json.getAttributeContent("location") + json.getAttributeContent("description") + val title = json.getAttributeContent("title") + val shortDescription = json.getAttributeContent("description_s") + val place = PlacePreview(title, shortDescription, location, null, id) + + if (!PlaceSupplier.checkIfContains(place)) { + + val imageReceiver: (Bitmap) -> Unit = { bitmap: Bitmap -> + place.img = bitmap + } + + ServerManagement.serverManager.getImage(imageReceiver, id, json.getAttributeContentByPath("description/photo_s"), 3) + + PlaceSupplier.appendPlace(place) + } else { + val containingPlace = PlaceSupplier.getContainingInstance(place) + if ((containingPlace != null) and (containingPlace?.img == null)) { + val imageReceiver: (Bitmap) -> Unit = { bitmap: Bitmap -> + containingPlace?.img = bitmap + } + + ServerManagement.serverManager.getImage(imageReceiver, id, json.getAttributeContentByPath("description/photo_s"), 3) + } + + // checking if location wasn't changed + if ((containingPlace != null) and (containingPlace?.location != location)) { + containingPlace?.location = location + + try { + mainFragmentHost.childFragmentManager.fragments[0]?.let { + if (it is exploreFragment) { + it.explore_recycler_view.post { + val layoutManager = LinearLayoutManager(it.requireContext()) + layoutManager.orientation = LinearLayoutManager.VERTICAL + it.explore_recycler_view.layoutManager = layoutManager + + val adapter = PlacePreviewsAdapter(it.requireContext(), PlaceSupplier.places) + it.explore_recycler_view.adapter = adapter + } + } + } + } catch (e: Throwable) { println("[debug] e4 that i couldnt fix si try catch Exception: $e") } + } + } + + json.clearSelectedAttribute() + } + } + + ServerManagement.serverManager.addReceiverConnection(dataReceiver, this, "exploreListConnection", 0, "", "GET_WHOLE_ARRAY", 10000) + } + +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/ChatMessagesAdapter.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/ChatMessagesAdapter.kt new file mode 100644 index 0000000..a79dc2a --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/ChatMessagesAdapter.kt @@ -0,0 +1,51 @@ +package com.example.wikispot.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.example.wikispot.GeneralVariables +import com.example.wikispot.R +import com.example.wikispot.modelsForAdapters.Message +import kotlinx.android.synthetic.main.message.view.* + + +class ChatMessagesAdapter(private val context: Context, private val messages: Array) : RecyclerView.Adapter() { + + inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){ + + var message: Message? = null + var pos: Int = 0 + + fun setData(message: Message?, pos: Int) { + message?.let { + itemView.message_author_text.text = message.senderName + itemView.message_content_text.text = message.content + + if (GeneralVariables.id == message.senderId) { + itemView.message_author_text.textAlignment = View.TEXT_ALIGNMENT_VIEW_END + itemView.message_content_text.textAlignment = View.TEXT_ALIGNMENT_VIEW_END + } + } + + this.message = message + this.pos = pos + } + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val message = messages[position] + holder.setIsRecyclable(false) + holder.setData(message, position) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.message, parent, false) + return MyViewHolder(view) + } + + override fun getItemCount(): Int { + return messages.size + } +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/FileViewsAdapter.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/FileViewsAdapter.kt new file mode 100644 index 0000000..db8b88d --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/FileViewsAdapter.kt @@ -0,0 +1,192 @@ +package com.example.wikispot.adapters + +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.animation.Animation +import android.view.animation.AnimationUtils +import android.widget.RelativeLayout +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat.startActivity +import androidx.core.view.setPadding +import androidx.recyclerview.widget.RecyclerView +import com.example.wikispot.R +import com.example.wikispot.ServerManagement +import com.example.wikispot.modelsForAdapters.FileView +import kotlinx.android.synthetic.main.file_view.view.* + + +class FileViewsAdapter(private val context: Context, private val fileViews: Array) : RecyclerView.Adapter() { + + private val rotateOpen: Animation by lazy { AnimationUtils.loadAnimation(context, R.anim.open_rotation_anim) } + private val rotateClose: Animation by lazy { AnimationUtils.loadAnimation(context, R.anim.close_rotation_anim) } + private val fadeIn: Animation by lazy {AnimationUtils.loadAnimation(context, R.anim.fade_in) } + private val fadeOut: Animation by lazy {AnimationUtils.loadAnimation(context, R.anim.fade_out) } + + inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){ + + var fileView: FileView? = null + var pos: Int = 0 + var textInfo: String? = null + var imgInfo: String? = null + var pdfUrl: String? = null + var generalUrl: String? = null + var opened = false + + init { + itemView.setOnClickListener { + if (!opened) { + itemView.downloadFileBtn.visibility = View.VISIBLE + itemView.downloadFileBtn.startAnimation(fadeIn) + if (generalUrl == null) { + itemView.showFileBtn.startAnimation(rotateOpen) + } + + fileView?.let { + textInfo?.let { + itemView.textContent.textSize = 18F + itemView.textContent.setPadding(32) + val dataReceiver: (String) -> Unit = { data: String -> + itemView.textContent.post { + itemView.textContent.text = data + } + } + + val textInformation = textInfo!!.split("|||||") + + ServerManagement.serverManager.getData(dataReceiver, itemView.context, textInformation[0].toInt(), textInformation[1]) + } + imgInfo?.let { + itemView.imageContent.visibility = View.VISIBLE + val imageReceiver2: (Bitmap) -> Unit = { bitmap: Bitmap -> + itemView.imageContent.post { + itemView.imageContent.setImageBitmap(bitmap) + } + } + + val imgInformation = imgInfo!!.split("|||||") + + ServerManagement.serverManager.getImage(imageReceiver2, imgInformation[0].toInt(), imgInformation[1]) + } + pdfUrl?.let { + itemView.pdfContent.visibility = View.VISIBLE + ServerManagement.serverManager.loadPdfView(itemView.pdfContent, pdfUrl!!, true) + } + } + } else { + if (generalUrl == null) { + itemView.showFileBtn.startAnimation(rotateClose) + } + val downloadBtnVanishActionThread = Thread(DownloadBtnVanishAction()) + downloadBtnVanishActionThread.start() + + itemView.textContent.textSize = 0F + itemView.textContent.setPadding(0) + + itemView.imageContent.visibility = View.GONE + itemView.pdfContent.visibility = View.GONE + } + + opened = !opened + } + + itemView.downloadFileBtn.setOnClickListener { + textInfo?.let { + val textInformation = textInfo!!.split("|||||") + val url = "${ServerManagement.baseUrl}files/${textInformation[0]}/${textInformation[1]}" + + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(context, browserIntent, null) + } + + imgInfo?.let { + val imgInformation = imgInfo!!.split("|||||") + val url = "${ServerManagement.baseUrl}files/${imgInformation[0]}/${imgInformation[1]}" + + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(context, browserIntent, null) + } + + pdfUrl?.let { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(pdfUrl)) + startActivity(context, browserIntent, null) + } + + generalUrl?.let { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(generalUrl)) + startActivity(context, browserIntent, null) + } + } + } + + fun setData(fileView: FileView?, pos: Int) { + fileView?.let { + fileView.textInfo?.let { + textInfo = it + } + fileView.imgInfo?.let { + imgInfo = it + } + fileView.pdfUrl?.let { + pdfUrl = it + } + fileView.generalUrl?.let { + generalUrl = it + itemView.showFileBtn.visibility = View.INVISIBLE + // setting new margins + + var params = itemView.downloadFileBtn.layoutParams as ConstraintLayout.LayoutParams + params.marginEnd = 24 + itemView.downloadFileBtn.layoutParams = params + + params = itemView.showFileBtn.layoutParams as ConstraintLayout.LayoutParams + params.marginEnd = 0 + itemView.showFileBtn.layoutParams = params + + } + + itemView.filenameText.text = fileView.filename.replace("_", " ") + itemView.fileDescription.text = fileView.fileDescription + } + + this.fileView = fileView + this.pos = pos + } + + inner class DownloadBtnVanishAction: Runnable { + override fun run() { + + itemView.post { + itemView.downloadFileBtn.startAnimation(fadeOut) + } + + Thread.sleep(600) + + itemView.post { + itemView.downloadFileBtn.clearAnimation() + itemView.downloadFileBtn.visibility = View.GONE + } + + } + + } + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val fileView = fileViews[position] + holder.setData(fileView, position) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.file_view, parent, false) + return MyViewHolder(view) + } + + override fun getItemCount(): Int { + return fileViews.size + } +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/PlacePreviewsAdapter.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/PlacePreviewsAdapter.kt new file mode 100644 index 0000000..c50f8ca --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/PlacePreviewsAdapter.kt @@ -0,0 +1,79 @@ +package com.example.wikispot.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.Navigation +import androidx.recyclerview.widget.RecyclerView +import com.example.wikispot.CustomBackstackVariables +import com.example.wikispot.R +import com.example.wikispot.ServerManagement +import com.example.wikispot.fragments.exploreFragmentDirections +import com.example.wikispot.modelsForAdapters.PlacePreview +import com.example.wikispot.showToast +import com.google.android.gms.maps.model.LatLng +import kotlinx.android.synthetic.main.explore_list_item.view.* + + +class PlacePreviewsAdapter(private val context: Context, private val placePreviews: Array) : RecyclerView.Adapter() { + + inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){ + + var currentPlacePreview: PlacePreview? = null + var pos: Int = 0 + var location: LatLng? = null + + init { + itemView.setOnClickListener { + CustomBackstackVariables.infoFragmentBackDestination = "exploreFragment" + ServerManagement.selectedServerId = currentPlacePreview?.id!! + val action = exploreFragmentDirections.navigateToInfoFragment(true) + Navigation.findNavController(it).navigate(action) + } + + itemView.item_location_img.setOnClickListener { + if (location != null) { + val action = exploreFragmentDirections.navigateToMapFragment(location!!) + Navigation.findNavController(it).navigate(action) + } + } + } + + fun setData(placePreview: PlacePreview?, pos: Int) { + placePreview?.let { + itemView.item_title.text = placePreview.title + itemView.item_description.text = placePreview.description + + try { + val coordinates = placePreview.location!!.split(",") + location = LatLng(coordinates[0].toDouble(), coordinates[1].toDouble()) + } catch (e: Throwable) { + println("[debug] Failed getting coordinates in ${placePreview.title}, explore fragment list. Exception: $e")} + + + placePreview.img?.let { + itemView.item_img.setImageBitmap(placePreview.img) + } + } + + this.currentPlacePreview = placePreview + this.pos = pos + } + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val placePreview = placePreviews[position] + holder.setData(placePreview, position) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.explore_list_item, parent, false) + return MyViewHolder(view) + } + + override fun getItemCount(): Int { + return placePreviews.size + } +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/SensorsDataAdapter.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/SensorsDataAdapter.kt new file mode 100644 index 0000000..64da3f2 --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/adapters/SensorsDataAdapter.kt @@ -0,0 +1,56 @@ +package com.example.wikispot.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.example.wikispot.GeneralVariables +import com.example.wikispot.R +import com.example.wikispot.modelsForAdapters.LabeledValue +import kotlinx.android.synthetic.main.labeled_value_item.view.* + + +class LabeledValuesAdapter(private val context: Context, private val labeledValues: Array) : RecyclerView.Adapter() { + + inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){ + + var currentLabeledValue: LabeledValue? = null + var pos: Int = 0 + + fun setData(labeledValue: LabeledValue?, pos: Int) { + labeledValue?.let { + if (labeledValue.label.startsWith(GeneralVariables.translatePrefix)) { + itemView.label.text = context.resources.getString(context.resources.getIdentifier(labeledValue.label.slice(GeneralVariables.translatePrefix.length until labeledValue.label.length), + "string", context.packageName)) + } else { + itemView.label.text = labeledValue.label + } + + if (labeledValue.value.startsWith(GeneralVariables.translatePrefix)) { + itemView.value.text = context.resources.getString(context.resources.getIdentifier(labeledValue.value.slice(GeneralVariables.translatePrefix.length until labeledValue.value.length), + "string", context.packageName)) + } else { + itemView.value.text = labeledValue.value + } + } + + this.currentLabeledValue = labeledValue + this.pos = pos + } + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val labeledValue = labeledValues[position] + holder.setData(labeledValue, position) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val view = LayoutInflater.from(context).inflate(R.layout.labeled_value_item, parent, false) + return MyViewHolder(view) + } + + override fun getItemCount(): Int { + return labeledValues.size + } +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/databases/NamesDatabase.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/databases/NamesDatabase.kt new file mode 100644 index 0000000..4942a2e --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/databases/NamesDatabase.kt @@ -0,0 +1,1004 @@ +package com.example.wikispot.databases + +object NamesDatabase { + val names = listOf( "Heidy", + "Abby", + "Whitney", + "Raymond", + "Jessica", + "Annika", + "Shane", + "Krish", + "Tori", + "Markus", + "Jensen", + "Karina", + "Sincere", + "Barrett", + "Jamar", + "Estrella", + "Kiana", + "Uriah", + "Clarissa", + "Bethany", + "Madilynn", + "Derick", + "Robert", + "Katie", + "Karsyn", + "Georgia", + "Rachel", + "Josue", + "Jazmin", + "Kamari", + "Kayla", + "Randall", + "Annabella", + "Abraham", + "Carley", + "Cierra", + "Alijah", + "Cordell", + "Stephany", + "Semaj", + "Janiyah", + "Nick", + "Steve", + "Mike", + "Myah", + "Dane", + "Braedon", + "Neil", + "Jakayla", + "Cory", + "Allyson", + "Kody", + "Ashanti", + "Serenity", + "Angelica", + "Frances", + "Levi", + "Teagan", + "Addyson", + "Dean", + "Brenda", + "Quinn", + "Ellie", + "Jessica", + "Jon", + "Regan", + "Eric", + "Tomas", + "Erik", + "Carmelo", + "Nola", + "Arely", + "Darius", + "Athena", + "Carsen", + "Hana", + "Elisabeth", + "Lane", + "Aedan", + "Kaylee", + "Alyvia", + "Deegan", + "Harmony", + "Edward", + "Reuben", + "Angelique", + "Vincent", + "Jefferson", + "Easton", + "Sara", + "Chad", + "Hugh", + "Laci", + "Gaven", + "Raul", + "Raven", + "Arianna", + "Bridger", + "Nicole", + "Dorian", + "Desirae", + "Cristal", + "Camryn", + "Lucy", + "Cierra", + "Hugo", + "Donovan", + "Finley", + "Amelia", + "Maxim", + "Armani", + "Hope", + "Miley", + "Caitlyn", + "Blaine", + "Abril", + "Jon", + "Bethany", + "Tori", + "Deven", + "Keegan", + "Konner", + "Monica", + "Eden", + "Jaden", + "Gillian", + "Aryana", + "Gavyn", + "Aidyn", + "Callum", + "Ezekiel", + "Kelsey", + "Gunner", + "Skye", + "Thomas", + "Regina", + "Natalie", + "Isai", + "Amari", + "Alisson", + "Irvin", + "Brody", + "Alma", + "Amaya", + "Patricia", + "Nayeli", + "Litzy", + "Jayvion", + "Allisson", + "Aliyah", + "Carmen", + "Dominic", + "Lamar", + "Gisselle", + "Gunnar", + "Dominique", + "Rhianna", + "Dax", + "Kaylin", + "Leroy", + "Deegan", + "Zariah", + "Kaleb", + "Luciana", + "Tyree", + "Mateo", + "Pranav", + "Nadia", + "Taniyah", + "Erika", + "Kaeden", + "Yasmin", + "Karina", + "Kendal", + "Ryan", + "Ashly", + "Deja", + "Easton", + "Tyrell", + "Ezra", + "Heaven", + "Taniyah", + "Quinten", + "Juliana", + "Maddison", + "Ray", + "Chaz", + "Emilia", + "Micaela", + "Alberto", + "Talia", + "Deandre", + "Kathy", + "Brenda", + "Jovanni", + "Terrance", + "Alivia", + "Alyvia", + "Lina", + "Madelyn", + "Athena", + "Camron", + "Kaylin", + "Rigoberto", + "Alina", + "Clinton", + "Julianna", + "Angelo", + "Brock", + "Jane", + "Corbin", + "Yasmine", + "Zion", + "Mackenzie", + "Arjun", + "Chanel", + "Julia", + "Alvaro", + "Kian", + "Aleena", + "Hillary", + "Evelyn", + "Kaylie", + "Elliana", + "Yoselin", + "Heather", + "Chana", + "Tara", + "Jerome", + "Mekhi", + "Koen", + "Roland", + "Brittany", + "Brady", + "Landon", + "Benjamin", + "Matthias", + "Johanna", + "Zander", + "Jude", + "Bryson", + "Macie", + "Tommy", + "Adalynn", + "Elianna", + "Juan", + "Marin", + "Jayden", + "Jamar", + "Krystal", + "Ryland", + "Ashton", + "Marquise", + "Cameron", + "Wyatt", + "Reagan", + "Ernesto", + "Shaylee", + "Mariah", + "Trevor", + "Jayla", + "Jeremiah", + "Amaya", + "Marianna", + "Susan", + "Ean", + "Karlie", + "Kathryn", + "Joyce", + "Destiney", + "Kali", + "Kiara", + "Gerald", + "Wilson", + "Devyn", + "Janet", + "Janiya", + "Julius", + "Ayana", + "Antwan", + "Miah", + "Kelsie", + "Jovany", + "Yamilet", + "Mason", + "Rachael", + "Tabitha", + "Gina", + "Gracelyn", + "Thomas", + "Bridget", + "Keaton", + "Skylar", + "Malik", + "Josue", + "Beckham", + "Abel", + "Jessie", + "Marley", + "Deanna", + "Kaelyn", + "Chaz", + "Elyse", + "Noemi", + "Blaze", + "Demetrius", + "German", + "Karley", + "Ibrahim", + "Quintin", + "Giuliana", + "David", + "Talia", + "Justice", + "Natalya", + "Iyana", + "Elizabeth", + "Lukas", + "Emilee", + "Karen", + "Stephany", + "Giovanni", + "Efrain", + "Casey", + "Lilyana", + "Kathy", + "Micaela", + "Peter", + "Cristopher", + "Josiah", + "Aryana", + "Kyle", + "Averi", + "Kaylyn", + "Kaylie", + "Dane", + "Robert", + "Xzavier", + "Alyssa", + "Dixie", + "Karma", + "Jeremy", + "Lina", + "Carley", + "Haiden", + "Arianna", + "Kierra", + "Leilani", + "Frida", + "Luciano", + "Bethany", + "Alexandria", + "Roman", + "Alivia", + "Hector", + "Erin", + "Landon", + "Silas", + "Kolton", + "Draven", + "Zayden", + "Chasity", + "Ruth", + "Shelby", + "Journey", + "Ariana", + "Aedan", + "Rogelio", + "Aracely", + "Lucas", + "Gustavo", + "Davon", + "Johnny", + "Macy", + "Messiah", + "Yesenia", + "Bridger", + "Yamilet", + "Yusuf", + "Shelby", + "Junior", + "Paloma", + "Ayaan", + "Trystan", + "Sophia", + "Lisa", + "Tess", + "Zariah", + "Dixie", + "Sophie", + "Ava", + "Martin", + "Jalen", + "Baron", + "Kingston", + "Harold", + "Payten", + "Josue", + "Tobias", + "Abbie", + "Averi", + "Cassidy", + "Reed", + "Adam", + "Mariah", + "Allan", + "Kaliyah", + "Lillie", + "Anya", + "Harry", + "Alejandra", + "Harold", + "Carleigh", + "Korbin", + "Skylar", + "Kaiden", + "Leland", + "Bridget", + "Maeve", + "Johnathon", + "Brent", + "Chasity", + "Kyla", + "Trevin", + "Jeffrey", + "Katrina", + "Iris", + "Mckenna", + "Haven", + "Alena", + "Wade", + "Zayden", + "Shyanne", + "Dexter", + "Marquis", + "Gracie", + "Jaylah", + "Ayla", + "Xander", + "Sloane", + "Kendrick", + "Addison", + "Abagail", + "Ismael", + "Ace", + "Halle", + "Caiden", + "Gabriel", + "Kendrick", + "Cash", + "Alina", + "Jakob", + "Quentin", + "Yusuf", + "Agustin", + "Janet", + "Kyan", + "Gracie", + "Izabelle", + "Davin", + "Savanah", + "Kevin", + "Lilia", + "Delaney", + "Damion", + "Alissa", + "Kareem", + "Reece", + "Kiersten", + "Patience", + "Vance", + "Aldo", + "Vicente", + "Blaze", + "Hugh", + "Samson", + "Emiliano", + "Jamir", + "London", + "Maximo", + "Wade", + "Izayah", + "Aliya", + "Davis", + "Kiera", + "Scarlett", + "Rishi", + "Eddie", + "Davin", + "Emerson", + "Skyla", + "Max", + "Alan", + "Malachi", + "Isabella", + "Giancarlo", + "Christine", + "Carson", + "Cali", + "Bentley", + "Shaylee", + "Charlee", + "Tatiana", + "Emiliano", + "Luka", + "Denisse", + "Kailee", + "Leon", + "Alberto", + "Nyla", + "Jaelyn", + "Adyson", + "Nora", + "Kayley", + "Melina", + "Kaleb", + "Grayson", + "Crystal", + "Lillie", + "Sydnee", + "Nickolas", + "Aryan", + "Guillermo", + "Ralph", + "Violet", + "Devan", + "Layne", + "Mckinley", + "Hezekiah", + "Chloe", + "Rachel", + "Charles", + "Grant", + "Melody", + "Valentino", + "Aiden", + "Oliver", + "Ace", + "Melody", + "Rosemary", + "Shaniya", + "Ann", + "Tomas", + "Sage", + "Adriana", + "Aliana", + "Augustus", + "Jerimiah", + "Tessa", + "Nikolas", + "Brody", + "Bradyn", + "Jesse", + "Ryleigh", + "Charles", + "Aryanna", + "Monica", + "Jax", + "Laura", + "Eric", + "Edgar", + "Kobe", + "Bo", + "Melissa", + "Elvis", + "Cale", + "London", + "Braxton", + "Axel", + "Diamond", + "Carina", + "Reagan", + "Shaylee", + "Jaliyah", + "Anaya", + "Killian", + "Maximo", + "Alexis", + "Konnor", + "Jessie", + "Kiley", + "Scott", + "Salvador", + "Grace", + "Liberty", + "Parker", + "Viviana", + "Aaliyah", + "Ansley", + "Madilyn", + "Tanya", + "Erik", + "Francesca", + "Jacqueline", + "Francis", + "Bryanna", + "Finley", + "Alec", + "Jesse", + "Emma", + "Theodore", + "Emiliano", + "Drake", + "Imani", + "Joe", + "Miriam", + "Brooke", + "Cruz", + "Sierra", + "Mohammed", + "Muhammad", + "Paityn", + "Molly", + "Callie", + "Moises", + "Toby", + "Zoey", + "Annabel", + "Aileen", + "Bridget", + "Emma", + "Eliza", + "Yadira", + "Kaylin", + "Moriah", + "Charles", + "Jackson", + "Angelique", + "Connor", + "Kassidy", + "Eden", + "Scott", + "Gunnar", + "Paxton", + "Elsie", + "Roderick", + "Kenyon", + "Dale", + "Libby", + "Terry", + "Clay", + "Brendan", + "Allyson", + "Roberto", + "Arthur", + "Madison", + "Jacob", + "Tianna", + "Donovan", + "Paula", + "Santino", + "Jamie", + "Nathanial", + "Marianna", + "Arjun", + "Destiney", + "Jerimiah", + "Aidyn", + "Erika", + "Dean", + "Kayden", + "Samir", + "Genesis", + "Krish", + "Sage", + "Crystal", + "Deborah", + "Genesis", + "Eleanor", + "Raul", + "Anya", + "Nathanael", + "Erick", + "Adam", + "Larry", + "Zara", + "Patience", + "Elsie", + "Annabelle", + "Marquise", + "Jett", + "Weston", + "Ryland", + "Damarion", + "Abagail", + "Caden", + "Antony", + "Kamryn", + "Martin", + "Albert", + "Zaria", + "Rigoberto", + "Jaylee", + "Rose", + "Carissa", + "Colt", + "Angelina", + "Precious", + "Frances", + "Kendra", + "Amirah", + "Imani", + "Bradyn", + "Litzy", + "Akira", + "Kristen", + "Lillie", + "Camron", + "Richard", + "Rigoberto", + "Alvin", + "Krish", + "Angeline", + "Yareli", + "Paul", + "Ximena", + "Aliya", + "Dylan", + "Riley", + "Abbey", + "Tanya", + "John", + "Shaun", + "Lilia", + "Charlie", + "Frank", + "Jaiden", + "Janelle", + "Beckham", + "Javion", + "Angeline", + "Seamus", + "Jazlyn", + "Emery", + "Layne", + "Levi", + "Trevon", + "Isaias", + "Elias", + "Damarion", + "Lainey", + "Macy", + "Guillermo", + "Sasha", + "Amiah", + "Kassandra", + "Jaylin", + "Tristen", + "Chris", + "Yaritza", + "Jimmy", + "Jordan", + "Keyon", + "Elsa", + "Kendall", + "Jair", + "Denise", + "Sasha", + "Jovanni", + "Shea", + "Larissa", + "Lea", + "Jorden", + "Maribel", + "Jolie", + "Jimmy", + "Nehemiah", + "Jaden", + "Elliott", + "Kamren", + "Kiera", + "Jaslyn", + "Camron", + "Marisol", + "Luz", + "Karlee", + "Lana", + "Uriel", + "Walker", + "Dayanara", + "Kaylin", + "Aurora", + "Josie", + "Jazlynn", + "Charlie", + "Lauryn", + "Sanaa", + "Kaleigh", + "Maya", + "Dakota", + "Danika", + "Selah", + "Leticia", + "Nick", + "Reid", + "Jayleen", + "Addison", + "Makena", + "Davon", + "Lillianna", + "Griffin", + "Andreas", + "Yosef", + "Tiara", + "Marques", + "Alice", + "Reagan", + "Janiya", + "Carmen", + "Savanna", + "Kaley", + "Leo", + "Erik", + "Gary", + "Jamie", + "Anne", + "Kamryn", + "Morgan", + "Reginald", + "Gemma", + "Colton", + "Brisa", + "Cash", + "Chase", + "Kailee", + "Jordan", + "Margaret", + "Harold", + "Ingrid", + "Marcus", + "Yadira", + "Giancarlo", + "Stephen", + "Colin", + "Alexa", + "Billy", + "Saige", + "Semaj", + "Madelynn", + "Jamari", + "Julissa", + "Makhi", + "Aryan", + "Lia", + "Jaylen", + "Azul", + "Izabella", + "Jermaine", + "Luke", + "Tommy", + "Kenna", + "Derrick", + "Yaritza", + "Kayla", + "Malcolm", + "Blaine", + "Raiden", + "Matias", + "Presley", + "Mollie", + "Hayden", + "Ruben", + "Yareli", + "Emilio", + "Tatum", + "Willow", + "Kian", + "Dayanara", + "Shayla", + "Jamarcus", + "Allen", + "Kelton", + "Carina", + "Gina", + "Adalyn", + "Rihanna", + "Justice", + "Remington", + "Grace", + "Yahir", + "Camila", + "Roy", + "Jayden", + "Allisson", + "Kaylie", + "Emanuel", + "Micah", + "Audrey", + "Daisy", + "Araceli", + "Natalya", + "Fatima", + "Milagros", + "Kenzie", + "Meadow", + "Edgar", + "Jayden", + "Walter", + "Isis", + "Krish", + "Savannah", + "Samuel", + "Rigoberto", + "Tia", + "Markus", + "Harrison", + "Bryanna", + "Zara", + "Daniel", + "Natalya", + "Haylee", + "Raven", + "Orion", + "Yaritza", + "Aydan", + "Nolan", + "Eva", + "Raegan", + "Cadence", + "Kade", + "Lila", + "Tate", + "Kassandra", + "Lorenzo", + "Sage", + "Tomas", + "Cole", + "Sierra", + "Alonso", + "Ismael", + "Aubree", + "Nikolas", + "Ayana", + "Dillon", + "Brennan", + "Rayne", + "Nola", + "Bo", + "Braxton", + "Gabriela", + "Harrison", + "Triston", + "Charlie", + "Ricardo", + "Karma", + "Humberto", + "Ari", + "Aditya", + "Jaylon", + "Maci", + "Trinity", + "Julien", + "Braydon", + "Savanna", + "Nathanael", + "Erika", + "Camryn", + "Ronin", + "Elliot", + "Mateo", + "Ramon", + "Keegan", + "Kallie", + "Stephany", + "Uriel", + "Charlie", + "Kallie", + "Jensen", + "Journey", + "Kaylyn", + "Essence", + "Tyson", + "Gabriella", + "Erin", + "Ezequiel", + "Savanah", + "Elian", + "Amani", + "Kassidy", + "Scarlet", + "Maleah", + "Tania", + "Gretchen", + "Raven", + "Declan", + "Kendra", + "Jermaine", + "Harrison", + "Marilyn", + "Madeleine", + "Maddox", + "Jaylen", + "Juliet", + "Natasha", + "Jaylin", + "Yesenia", + "Greyson", + "Maribel", + "Rodney", + "Ana") +} \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/extentions.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/extentions.kt deleted file mode 100644 index e694871..0000000 --- a/app/WikiSpot/app/src/main/java/com/example/wikispot/extentions.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.wikispot - -import android.content.Context -import android.view.View -import android.widget.Toast -import com.google.android.material.snackbar.Snackbar - -fun Context.showToast(message: String, length: Int=Toast.LENGTH_SHORT) { - Toast.makeText(this, message, length).show() -} - -fun Context.showSnack(message: String, view: View, length: Int=Snackbar.LENGTH_LONG) { - Snackbar.make(this, view, message, length).show() -} \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/anotherDebugFragment.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/anotherDebugFragment.kt new file mode 100644 index 0000000..6fa76ae --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/anotherDebugFragment.kt @@ -0,0 +1,70 @@ +package com.example.wikispot.fragments + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.navigation.Navigation +import com.example.wikispot.R +import com.example.wikispot.ServerManagement +import com.example.wikispot.modelClasses.JsonManager +import kotlinx.android.synthetic.main.fragment_another_debug.* +import org.json.JSONObject +import kotlin.random.Random + + +class anotherDebugFragment : Fragment(R.layout.fragment_another_debug) { + + private lateinit var jsonManager: JsonManager + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + jsonManager = JsonManager(requireContext(), "[]", "JSONArray", true) + + generateAndSaveDataBtn.setOnClickListener { + val generatedData = JSONObject() + + for (n in 0 until 10) { + generatedData.put("$n", Random.nextInt(0, 100)) + } + context?.let { + jsonManager = JsonManager(requireContext(), generatedData.toString(), "JSONObject", true) + jsonManager.saveJson("test") + } + println("[debug] saved generated data") + } + + loadAndShowDataBtn.setOnClickListener { + val json = jsonManager.loadJson(requireContext(), "test", "JSONObject") + dataContentView.text = json.currentJsonObject.toString() + } + + stopConnectionBtn.setOnClickListener { + ServerManagement.serverManager.deleteConnection("debug", "view") + } + + createViewConnectionBtn.setOnClickListener { + val attributePath = attributePathInput.text.toString() + val filePath = filePathInput.text.toString() + + ServerManagement.serverManager.deleteConnection("debug", "view") + ServerManagement.serverManager.addViewConnection(requireContext(), dataContentView, "debug",0, filePath, attributePath) + } + + // handling navigation between debug fragments + goFirstDegubFragmentBtn.setOnClickListener { + cleanup() + Navigation.findNavController(it).navigate(R.id.navigateBackToDebugFragment) + } + } + + override fun onDestroy() { + super.onDestroy() + cleanup() + } + + private fun cleanup() { + ServerManagement.serverManager.deleteConnection("debug") + } + +} \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/chatFragment.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/chatFragment.kt index fba0890..7e7c0dc 100644 --- a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/chatFragment.kt +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/chatFragment.kt @@ -1,12 +1,204 @@ package com.example.wikispot.fragments import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import com.example.wikispot.R +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.wikispot.* +import com.example.wikispot.adapters.ChatMessagesAdapter +import com.example.wikispot.databases.NamesDatabase +import com.example.wikispot.modelClasses.JsonManager +import com.example.wikispot.modelsForAdapters.Message +import com.example.wikispot.modelsForAdapters.MessagesSupplier +import kotlinx.android.synthetic.main.fragment_chat.* +import okhttp3.* +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import org.json.JSONObject +import java.io.IOException class chatFragment : Fragment(R.layout.fragment_chat) { -} \ No newline at end of file + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + updateRecyclerView() + + writeBar.setOnClickListener { + val scrollDownThread = Thread(ScrollDown(300)) + scrollDownThread.start() + } + userMessageText.setOnClickListener { + val scrollDownThread = Thread(ScrollDown(300)) + scrollDownThread.start() + } + userMessageText.setOnFocusChangeListener { _, _ -> + val scrollDownThread = Thread(ScrollDown(300)) + scrollDownThread.start() + } + + sendMessageBtn.setOnClickListener { + GeneralVariables.id?.let { + if (userMessageText.text.toString() != "" ) { + val message = Message(GeneralVariables.id!!, userMessageText.text.toString(), "waiting") + MessagesSupplier.appendMessage(message) + userMessageText.setText("") + + val messagePostThread = Thread(MessagePost(message)) + messagePostThread.start() + + updateRecyclerView() + } else { + requireContext().showToast("sending empty messages is not permitted") + } + } + } + } + + override fun onResume() { + super.onResume() + loadIdFromCache() + + val dataReceiver: (String) -> Unit = { data: String -> + + try { + val json = JsonManager(requireContext(), data, "JSONObject") + + when (json.getAttributeContent("source")) { + "messages/register" -> { + GeneralVariables.id = json.getAttributeContentByPath("data/0") + + val r = requireContext().getRandomGenerator(GeneralVariables.id!!) + + GeneralVariables.name = "${NamesDatabase.names[r.nextInt(NamesDatabase.names.size)]} - ${r.nextInt(9999)}" + + json.getAttributeContent("data") + json.getAttributeContent("1") + val length = json.currentJsonAttribute1!!.length() + json.clearSelectedAttribute() + + for (i in 0 until length) { + val jsonOfMessage = JsonManager(requireContext(), json.getAttributeContentByPath("data/1/$i"), "JSONObject") + val message = Message(jsonOfMessage.getAttributeContent("sender"), + jsonOfMessage.getAttributeContent("message"), + jsonOfMessage.getAttributeContent("timestamp")) + if (!MessagesSupplier.checkIfContains(message)) { + MessagesSupplier.appendMessage(message) + updateRecyclerView() + } + } + } + "messages/get" -> { + json.getAttributeContent("data") + val length = json.currentJsonAttribute1!!.length() + json.clearSelectedAttribute() + + MessagesSupplier.clearWaitingMessages() + + for (i in 0 until length) { + val jsonOfMessage = JsonManager(requireContext(), json.getAttributeContentByPath("data/$i"), "JSONObject") + val message = Message(jsonOfMessage.getAttributeContent("sender"), + jsonOfMessage.getAttributeContent("message"), + jsonOfMessage.getAttributeContent("timestamp")) + + if (!MessagesSupplier.checkIfContains(message)) { + MessagesSupplier.appendMessage(message) + updateRecyclerView() + } + } + } + } + } catch (e: Throwable) { println("[debug][chat fragment] Exception: $e") } + } + + ServerManagement.serverManager.addChatConnection(dataReceiver, requireContext(), "chatConnection") + } + + override fun onPause() { + super.onPause() + saveIdToCache() + + ServerManagement.serverManager.deleteConnection("chatConnection") + } + + private fun updateRecyclerView() { + + try { + chat_messages_recycler_view.post { + val layoutManager = LinearLayoutManager(context) + layoutManager.orientation = LinearLayoutManager.VERTICAL + chat_messages_recycler_view.layoutManager = layoutManager + + val adapter = context?.let { ChatMessagesAdapter(it, MessagesSupplier.messages) } + chat_messages_recycler_view.adapter = adapter + chat_messages_recycler_view.scrollToPosition(MessagesSupplier.messages.size - 1) + } + } catch (e: Throwable) { println("[debug] e5 Exception: $e") } + + } + + inner class ScrollDown(private val after: Long): Runnable { + override fun run() { + Thread.sleep(after) + try { + chat_messages_recycler_view.post { + chat_messages_recycler_view.scrollToPosition(MessagesSupplier.messages.size - 1) + } + } catch (e: Throwable) { println("[debug] e6 Exception: $e") } + } + } + + inner class MessagePost(private val message: Message): Runnable { + override fun run() { + + val url = "${ServerManagement.baseUrl}messages/post" + + val json: MediaType? = "application/json; charset=utf-8".toMediaTypeOrNull() + + val body:RequestBody = RequestBody.create(json, JSONObject() + .put("m_sender", message.senderId) + .put("message", message.content).toString()) + + val request: Request = Request.Builder() + .url(url) + .post(body) + .build() + val client = OkHttpClient() + + client.newCall(request).enqueue(object : Callback { + + override fun onResponse(call: Call, response: Response) { + response.body?.let { + val receivedString = response.body!!.string() + println("[debug][message post] received string from post request: $receivedString") + } + } + + override fun onFailure(call: Call, e: IOException) { + println("Request Failed") + println(e) + } + + }) + } + + } + + // loading and saving last names + + private fun loadIdFromCache() { + val id = requireContext().getStringFromSharedPreferences("id") + if (id != "") { + GeneralVariables.id = id + } + + } + + private fun saveIdToCache() { + GeneralVariables.id?.let { + requireContext().saveString("id", GeneralVariables.id!!) + } + } + +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/debugFragment.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/debugFragment.kt new file mode 100644 index 0000000..091940e --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/debugFragment.kt @@ -0,0 +1,63 @@ +package com.example.wikispot.fragments + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.navigation.Navigation +import com.example.wikispot.* +import com.example.wikispot.activities.MainActivity +import com.example.wikispot.modelsForAdapters.MessagesSupplier +import kotlinx.android.synthetic.main.fragment_debug.* + + +class debugFragment : Fragment(R.layout.fragment_debug) { + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + goSecondDebugFragmentBtn.setOnClickListener { + Navigation.findNavController(it).navigate(R.id.navigateToAnotherDebugFragment) + } + + getNumberOfSentRequestsBtn.setOnClickListener { + outputText.text = ServerManagement.totalNumberOfRequestsSent.toString() + } + + clearServerConnectionsBtn.setOnClickListener { + ServerManagement.serverManager.clearConnections() + } + + editTextIp.setText(ServerManagement.baseUrl) + + changeUrlBtn.setOnClickListener { + ServerManagement.baseUrl = editTextIp.text.toString() + requireContext().saveString("baseUrlSave", editTextIp.text.toString()) + restartAppPartially() + } + + restartAppPartiallyBtn.setOnClickListener { + restartAppPartially() + } + + closeAppBtn.setOnClickListener { + activity?.finish() + } + + } + + private fun restartAppPartially() { + val intent = Intent(context?.applicationContext, MainActivity::class.java) + + intent.putExtra(IntentsKeys.startFragment, "debugFragment") + + ServerManagement.serverManager.clearConnections() + MessagesSupplier.wipeData() + GeneralVariables.id = null + + startActivity(intent) + activity?.finish() + } +} \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/exploreFragment.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/exploreFragment.kt index c290ca6..56a6cb0 100644 --- a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/exploreFragment.kt +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/exploreFragment.kt @@ -1,8 +1,36 @@ package com.example.wikispot.fragments +import android.content.Intent +import android.os.Bundle +import android.view.View import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.wikispot.GeneralVariables +import com.example.wikispot.IntentsKeys import com.example.wikispot.R +import com.example.wikispot.ServerManagement +import com.example.wikispot.activities.MainActivity +import com.example.wikispot.adapters.PlacePreviewsAdapter +import com.example.wikispot.modelsForAdapters.PlaceSupplier +import kotlinx.android.synthetic.main.fragment_explore.* class exploreFragment : Fragment(R.layout.fragment_explore) { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupRecyclerView() + } + + private fun setupRecyclerView() { + + val layoutManager = LinearLayoutManager(context) + layoutManager.orientation = LinearLayoutManager.VERTICAL + explore_recycler_view.layoutManager = layoutManager + + val adapter = context?.let { PlacePreviewsAdapter(it, PlaceSupplier.places) } + explore_recycler_view.adapter = adapter + } + } \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/homeFragment.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/homeFragment.kt index c753c7f..42e988c 100644 --- a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/homeFragment.kt +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/homeFragment.kt @@ -1,12 +1,251 @@ package com.example.wikispot.fragments +import android.graphics.Bitmap import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.Navigation +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.wikispot.GeneralVariables +import com.example.wikispot.MapManagement import com.example.wikispot.R +import com.example.wikispot.ServerManagement +import com.example.wikispot.adapters.FileViewsAdapter +import com.example.wikispot.adapters.LabeledValuesAdapter +import com.example.wikispot.modelClasses.JsonManager +import com.example.wikispot.modelClasses.JsonManagerLite +import com.example.wikispot.modelsForAdapters.FileView +import com.example.wikispot.modelsForAdapters.FileViewsSupplier +import com.example.wikispot.modelsForAdapters.LabeledValue +import com.example.wikispot.modelsForAdapters.LabeledValuesSupplier +import com.google.android.gms.maps.model.LatLng +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.fragment_home.* +import kotlinx.android.synthetic.main.fragment_info.* +import kotlinx.android.synthetic.main.fragment_info.view.* class homeFragment : Fragment(R.layout.fragment_home) { -} \ No newline at end of file + + var infoFragmentLoadedIn = false + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + loadCache() + + chatBtn.setOnClickListener { + Navigation.findNavController(it).navigate(R.id.homeFragment_to_chatFragment) + } + } + + override fun onResume() { + super.onResume() + + // connecting to server + + val serverConnectorThread = Thread(ServerConnector()) + serverConnectorThread.start() + + val dataReceiver0: (String) -> Unit = { data: String -> + try { + val json = JsonManager(requireContext(), data) + + json.findJsonObjectByAttribute("ID", json.getAttributeContent("connected_id")) + + if (!infoFragmentLoadedIn) { + + infoFragmentLoadedIn = true + + val phoneNumberLoaded = json.getAttributeContentByPath("description/phone_number") + if (phoneNumberLoaded != GeneralVariables.variableMissingKeyword) { + GeneralVariables.phoneNumber = phoneNumberLoaded.toInt() + } + + val emailLoaded = json.getAttributeContentByPath("description/email") + if (emailLoaded != GeneralVariables.variableMissingKeyword) { + GeneralVariables.email = emailLoaded + } + + homeFragmentInnerFragment.post { + homeFragmentInnerFragment?.let { fragment -> + val title = json.getAttributeContentByPath("description/title") + val description = json.getAttributeContentByPath("description/description_l") + + if (title != GeneralVariables.variableMissingKeyword) { + fragment.mainTitle.text = title + } + + if (description != GeneralVariables.variableMissingKeyword) { + fragment.mainDescription.text = description + } + } + } + + val imageReceiver: (Bitmap) -> Unit = { bitmap: Bitmap -> + homeFragmentInnerFragment?.let { + homeFragmentInnerFragment.post { + try { + homeFragmentInnerFragment.mainImage.setImageBitmap(bitmap) + } catch (e: Throwable) { println("[debug] e7 Exception: $e") } + } + } + } + + ServerManagement.serverManager.getImage(imageReceiver, json.getAttributeContent("ID").toInt(), + json.getAttributeContentByPath("description/photo_b"), 3) + + } + } catch (e: Throwable) { println(e) } + } + + val dataReceiver1: (String) -> Unit = {connectedId: String -> + ServerManagement.connectedServerId = connectedId.toInt() + } + + ServerManagement.serverManager.getData(dataReceiver0, requireContext(), 0, "", "GET_WHOLE_ARRAY", 4) + ServerManagement.serverManager.getData(dataReceiver1, requireContext(), 0, "", "connected_id", 3) + + } + + override fun onPause() { + super.onPause() + ServerManagement.serverManager.deleteConnection("sensorsConnection") + ServerManagement.serverManager.deleteConnection("mapConnection") + ServerManagement.serverManager.deleteConnection("fileViewsConnection") + saveCache() + } + + private fun tryConnectingToServer() { + ServerManagement.connectedServerId?.let{ connectedServerId: Int -> + context?.let { + val dataReceiver1: (String) -> Unit = { data1: String -> + + try { + val json = JsonManager(requireContext(), data1, "JSONObject") + val names = json.currentJsonObject!!.names() + + names?.let { + LabeledValuesSupplier.wipeData() + + for (n in 0 until names.length()) { + val labeledValue = LabeledValue(names[n].toString(), json.getAttributeContent(names[n].toString())) + if (!LabeledValuesSupplier.checkIfContains(labeledValue)) { + LabeledValuesSupplier.appendLabeledValue(labeledValue) + } + } + + labeled_values_recycler_view.post { + val layoutManager = LinearLayoutManager(requireContext()) + layoutManager.orientation = LinearLayoutManager.VERTICAL + labeled_values_recycler_view.layoutManager = layoutManager + + val adapter = LabeledValuesAdapter(requireContext(), LabeledValuesSupplier.labeledValues) + labeled_values_recycler_view.adapter = adapter + } + } + } catch (e: Throwable) { println("[debug] Exception in main activity, sensors connection : $e") } + + } + + if (!ServerManagement.serverManager.checkIfConnectionAlreadyExists("sensorsConnection")){ + ServerManagement.serverManager.addReceiverConnection(dataReceiver1, requireContext(), "sensorsConnection", connectedServerId, ServerManagement.sensors_keyword) + } + + // getting other needed information + val dataReceiver2: (String) -> Unit = {data1: String -> + context?.let { + var json = JsonManager(requireContext(), data1) + json = JsonManager(requireContext(), json.findJsonObjectByAttribute("ID", connectedServerId), "JSONObject") // todo doesnt return correct result + val positionsList = json.getAttributeContent("location").split(",") + MapManagement.connectedServerPosition = LatLng(positionsList[0].toDouble(), positionsList[1].toDouble()) + } + } + + if (!ServerManagement.serverManager.checkIfConnectionAlreadyExists("mapConnection")){ + ServerManagement.serverManager.addReceiverConnection(dataReceiver2, requireContext(), "mapConnection", connectedServerId, "", "GET_WHOLE_ARRAY") + } + + val dataReceiver3: (String) -> Unit = { data1: String -> + + fun updateFileViewsRecyclerView(fragment: Fragment) { + try { + fragment.context?.let { + fragment.homeFragmentInnerFragment?.let { + it.file_views_recycler_view.post { + val layoutManager = LinearLayoutManager(fragment.requireContext()) + layoutManager.orientation = LinearLayoutManager.VERTICAL + it.file_views_recycler_view.layoutManager = layoutManager + + val adapter = FileViewsAdapter(fragment.requireContext(), FileViewsSupplier.fileViews) + it.file_views_recycler_view.adapter = adapter + } + } + } + } catch (e: Throwable) { println("[debug] e1 that i couldnt fix so try catch Exception: $e") } + } + + try { + val json = JsonManager(requireContext(), data1) + json.findJsonObjectByAttribute("ID", connectedServerId) + + json.getAttributeContent("files") + + for (n in 0 until json.currentJsonAttribute1!!.length()) { + val fileInfo = JsonManagerLite(json.getAttributeContentByPath("files/$n"), "JSONObject") + val filetype = fileInfo.getAttributeContentByPath("format").split(".")[1] + val filename = fileInfo.getAttributeContentByPath("name") + val fileDescription = fileInfo.getAttributeContentByPath("description") + + // handling text + if ("txt json".contains(filetype)) { + val fileView = FileView(filetype, filename, fileDescription, "$connectedServerId|||||$filename.$filetype") + if (!FileViewsSupplier.checkIfContains(fileView)) { + FileViewsSupplier.appendFileView(fileView) + updateFileViewsRecyclerView(this) + } + } + + // handling images + if ("jpg png".contains(filetype)) { + val fileView = FileView(filetype, filename, fileDescription, null, "$connectedServerId|||||$filename.$filetype") + if (!FileViewsSupplier.checkIfContains(fileView)) { + FileViewsSupplier.appendFileView(fileView) + updateFileViewsRecyclerView(this) + } + } + + // handling pdf files + if ("pdf".contains(filetype)) { + val fileView = FileView(filetype, filename, fileDescription, null, null, "${ServerManagement.baseUrl}files/$connectedServerId/$filename.$filetype") + if (!FileViewsSupplier.checkIfContains(fileView)) { + FileViewsSupplier.appendFileView(fileView) + updateFileViewsRecyclerView(this) + } + + } + + } + } catch (e: Throwable) { println("[debug] Exception in home fragment, files data request : $e") } + + } + + if (!ServerManagement.serverManager.checkIfConnectionAlreadyExists("fileViewsConnection")) { + ServerManagement.serverManager.addReceiverConnection(dataReceiver3, requireContext(), "fileViewsConnection", connectedServerId, "", "GET_WHOLE_ARRAY") + } + } + } + } + + inner class ServerConnector(private val numberOfAttempts: Int=3): Runnable { + override fun run() { + for (n in 0 until numberOfAttempts) { + tryConnectingToServer() + Thread.sleep(1000) + } + } + } + + private fun loadCache() {} + + private fun saveCache() {} +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/infoFragment.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/infoFragment.kt new file mode 100644 index 0000000..9f12a3b --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/infoFragment.kt @@ -0,0 +1,323 @@ +package com.example.wikispot.fragments + +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.navigation.Navigation +import androidx.navigation.fragment.navArgs +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.wikispot.GeneralVariables +import com.example.wikispot.R +import com.example.wikispot.ServerManagement +import com.example.wikispot.adapters.FileViewsAdapter +import com.example.wikispot.adapters.LabeledValuesAdapter +import com.example.wikispot.modelClasses.JsonManager +import com.example.wikispot.modelClasses.JsonManagerLite +import com.example.wikispot.modelsForAdapters.FileView +import com.example.wikispot.modelsForAdapters.FileViewsSupplier +import com.example.wikispot.modelsForAdapters.LabeledValue +import com.example.wikispot.modelsForAdapters.LabeledValuesSupplier +import com.example.wikispot.showToast +import com.google.android.gms.maps.model.LatLng +import kotlinx.android.synthetic.main.fragment_info.* + + +class infoFragment : Fragment(R.layout.fragment_info) { + + private val args: infoFragmentArgs by navArgs() + var location: LatLng? = null + var phoneNumber: Int? = null + var email: String? = null + var executeLoadFunction = false + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + LabeledValuesSupplier.wipeData() + FileViewsSupplier.wipeData() + updateSensorsRecyclerView() + updateFileViewsRecyclerView() + + //file_views_recycler_view.isNestedScrollingEnabled = false + + try { + executeLoadFunction = args.executeLoadFuntion + } catch (e: Throwable) { + if (!e.toString().contains("has null arguments")){ + println("[debug] Exception in Info Fragment while getting args: $e") + } + } + + if (executeLoadFunction) { + load() + } else { + getContactInfoFromGeneralVariables() + } + + locationBtn.setOnClickListener { + if (executeLoadFunction) { + if (location != null) { + val action = infoFragmentDirections.infoFragmentToMapFragment(location!!) + Navigation.findNavController(it).navigate(action) + } + } else { + Navigation.findNavController(it).navigate(R.id.homeFragment_to_mapFragment) + } + } + + phoneBtn.setOnClickListener { + if (phoneNumber != null) { + phoneNumber?.let { + val intent = Intent(Intent.ACTION_DIAL) + intent.data = Uri.parse("tel:$phoneNumber") + startActivity(intent) + } + } else { + requireContext().showToast("Phone number not found.") + } + } + + emailBtn.setOnClickListener { + if (email != null) { + email?.let{ + val intent = Intent(Intent.ACTION_SEND) + + intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(email)) + + intent.type = "text/plain" + + startActivity(Intent.createChooser(intent, "Send Email")) + } + } else { + requireContext().showToast("Email address not found.") + } + } + } + + private fun load() { + val serverId = ServerManagement.selectedServerId + + val dataReceiver: (String) -> Unit = { data: String -> + context?.let { + try { + val json = JsonManager(requireContext(), data) + json.findJsonObjectByAttribute("ID", serverId) + + val phoneNumberLoaded = json.getAttributeContentByPath("description/phone_number") + if (phoneNumberLoaded != GeneralVariables.variableMissingKeyword) { + phoneNumber = phoneNumberLoaded.toInt() + checkContactInformation() + } + + val emailLoaded = json.getAttributeContentByPath("description/email") + if (emailLoaded != GeneralVariables.variableMissingKeyword) { + email = emailLoaded + checkContactInformation() + } + + mainTitle?.let { + mainTitle.post { + val title = json.getAttributeContentByPath("description/title") + if (title != GeneralVariables.variableMissingKeyword) { + mainTitle.text = title + } + } + } + + mainDescription?.let { + mainDescription.post { + val description = json.getAttributeContentByPath("description/description_l") + if (description != GeneralVariables.variableMissingKeyword) { + mainDescription.text = description + } + } + } + + val imageReceiver1: (Bitmap) -> Unit = { bitmap: Bitmap -> + mainImage.post { + mainImage?.let { + mainImage.setImageBitmap(bitmap) + } + } + } + + val coordinates = json.getAttributeContent("location").split(",") + location = LatLng(coordinates[0].toDouble(), coordinates[1].toDouble()) + + ServerManagement.serverManager.getImage(imageReceiver1, json.getAttributeContent("ID").toInt(), + json.getAttributeContentByPath("description/photo_b"), 2) + + // getting files + + json.getAttributeContent("files") + + for (n in 0 until json.currentJsonAttribute1!!.length()) { + val fileInfo = JsonManagerLite(json.getAttributeContentByPath("files/$n"), "JSONObject") + val filetype = fileInfo.getAttributeContentByPath("format").split(".")[1] + val filename = fileInfo.getAttributeContentByPath("name") + val fileDescription = fileInfo.getAttributeContent("description") + + // handling text + if ("txt json".contains(filetype)) { + val fileView = FileView(filetype, filename, fileDescription, "$serverId|||||$filename.$filetype") + if (!FileViewsSupplier.checkIfContains(fileView)) { + FileViewsSupplier.appendFileView(fileView) + updateFileViewsRecyclerView() + } + } + + // handling images + else if ("jpg png".contains(filetype)) { + val fileView = FileView(filetype, filename, fileDescription, null, "$serverId|||||$filename.$filetype") + if (!FileViewsSupplier.checkIfContains(fileView)) { + FileViewsSupplier.appendFileView(fileView) + updateFileViewsRecyclerView() + } + } + + // handling pdf files + else if ("pdf".contains(filetype)) { + val fileView = FileView(filetype, filename, fileDescription, null, null, "${ServerManagement.baseUrl}files/$serverId/$filename.$filetype") + if (!FileViewsSupplier.checkIfContains(fileView)) { + FileViewsSupplier.appendFileView(fileView) + updateFileViewsRecyclerView() + } + + } + + else { + val fileView = FileView(filetype, filename, fileDescription, null, null, null, "${ServerManagement.baseUrl}files/$serverId/$filename.$filetype") + if (!FileViewsSupplier.checkIfContains(fileView)) { + FileViewsSupplier.appendFileView(fileView) + updateFileViewsRecyclerView() + } + } + } + } catch (e: Throwable) { println("[debug] exception in infoFragment load data request Exception: $e") } + } + } + + val sensorsDataReceiver: (String) -> Unit = { data: String -> + try { + context?.let { + val json = JsonManager(requireContext(), data, "JSONObject") + val names = json.currentJsonObject!!.names() + + LabeledValuesSupplier.wipeData() + + if (names != null) { + for (n in 0 until names.length()) { + val labeledValue = LabeledValue(names[n].toString(), json.getAttributeContent(names[n].toString())) + if (!LabeledValuesSupplier.checkIfContains(labeledValue)) { + LabeledValuesSupplier.appendLabeledValue(labeledValue) + } + } + } + + updateSensorsRecyclerView() + } + } catch (e: Throwable) { println("[debug] Exception in info fragment, load, sensorsDataReceiver : $e") } + } + + context?.let { + ServerManagement.serverManager.getData(dataReceiver, requireContext(), serverId, "", "GET_WHOLE_ARRAY", 3) + ServerManagement.serverManager.addReceiverConnection(sensorsDataReceiver, requireContext(), "infoFragmentSensorsConnection", + serverId, ServerManagement.sensors_keyword) + } + } + + private fun getContactInfoFromGeneralVariables(numberOfAttempts: Int = 8) { + class RetrieveContactInfoFromGeneralVariables(val attemptsCount: Int): Runnable { + override fun run() { + for (i in 0 until attemptsCount) { + phoneNumber = GeneralVariables.phoneNumber + email = GeneralVariables.email + + if (phoneNumber != null) { + checkContactInformation() + break + } + if (email != null) { + checkContactInformation() + break + } + Thread.sleep(300) + } + } + } + + val retrieveContactInfoFromGeneralVariables = Thread(RetrieveContactInfoFromGeneralVariables(numberOfAttempts)) + retrieveContactInfoFromGeneralVariables.start() + } + + private fun checkContactInformation() { + phoneNumber?.let { + try { + phoneBtn.post { + phoneBtn?.let { + phoneBtn.visibility = View.VISIBLE + } + } + } catch (e: Throwable) { println("[debug] Exception in checkContactInformation: $e") } + } + + email?.let { + try { + emailBtn.post { + emailBtn?.let { + emailBtn.visibility = View.VISIBLE + } + } + } catch (e: Throwable) { println("[debug] Exception in checkContactInformation: $e") } + } + } + + override fun onPause() { + ServerManagement.serverManager.deleteConnection("infoFragmentSensorsConnection") + super.onPause() + } + + private fun updateSensorsRecyclerView() { + + try { + labeled_values_recycler_view.post { + val layoutManager = LinearLayoutManager(context) + layoutManager.orientation = LinearLayoutManager.VERTICAL + labeled_values_recycler_view.layoutManager = layoutManager + + val adapter = context?.let { LabeledValuesAdapter(it, LabeledValuesSupplier.labeledValues) } + labeled_values_recycler_view.adapter = adapter + } + } catch (e: Throwable) { println("[debug] e3 Exception: $e") } + + } + + private fun updateFileViewsRecyclerView() { + + try { + file_views_recycler_view.post { + val layoutManager = LinearLayoutManager(context) + + layoutManager.orientation = LinearLayoutManager.VERTICAL + file_views_recycler_view.layoutManager = layoutManager + + val adapter = context?.let { FileViewsAdapter(it, FileViewsSupplier.fileViews) } + file_views_recycler_view.adapter = adapter + } + } catch (e: Throwable) { println("[debug] e2 Exception: $e") } + + } + + fun goExploreFragment() { + Navigation.findNavController(mainTitle).navigate(R.id.navigateBackToExploreFragment) + } + + fun goMapFragment() { + val action = infoFragmentDirections.infoFragmentToMapFragment(LatLng(0.toDouble(), 0.toDouble()), true) + Navigation.findNavController(mainTitle).navigate(action) + } + +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/mapFragment.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/mapFragment.kt index cb87847..24bb303 100644 --- a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/mapFragment.kt +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/mapFragment.kt @@ -1,12 +1,99 @@ package com.example.wikispot.fragments import android.os.Bundle -import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.Navigation +import androidx.navigation.fragment.navArgs +import com.example.wikispot.CustomBackstackVariables +import com.example.wikispot.MapManagement import com.example.wikispot.R +import com.example.wikispot.ServerManagement +import com.example.wikispot.modelsForAdapters.PlaceSupplier +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import kotlinx.android.synthetic.main.fragment_map.* +import java.time.Clock +class mapFragment : Fragment(), GoogleMap.OnMarkerClickListener { -class mapFragment : Fragment(R.layout.fragment_map) { -} \ No newline at end of file + val args: mapFragmentArgs by navArgs() + private var loadFromMapManager = true + private var loadLastCoordinates = false + var location: LatLng? = null + var lastClickedMarkerTitle = "" + + private val callback = OnMapReadyCallback { googleMap -> + /** + * Manipulates the map once available. + * This callback is triggered when the map is ready to be used. + * This is where we can add markers or lines, add listeners or move the camera. + * In this case, we just add a marker near Sydney, Australia. + * If Google Play services is not installed on the device, the user will be prompted to + * install it inside the SupportMapFragment. This method will only be triggered once the + * user has installed Google Play services and returned to the app. + */ + /*val pb = LatLng(49.11274928646463, 18.443442353021045) + googleMap.addMarker(MarkerOptions().position(pb).title("Povazska Bystrica")) + googleMap.moveCamera(CameraUpdateFactory.newLatLng(pb)) + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(pb, 16.0f)) */ + + try { + location = args.location + loadLastCoordinates = args.loadLastCoordinates + loadFromMapManager = false + } catch (e: Throwable) { println("[debug] Exception in Map Fragment while getting args: $e") } + + if (loadFromMapManager) { + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(MapManagement.connectedServerPosition, 15.0F)) + } else if (loadLastCoordinates){ + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(MapManagement.lastCoordinates, 15F)) + } else { + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(location, 15.0F)) + } + + // loading other markers + for (n in PlaceSupplier.places.indices) { + val coordinates = PlaceSupplier.places[n]?.location!!.split(",") + googleMap.addMarker(MarkerOptions().position(LatLng(coordinates[0].toDouble(), coordinates[1].toDouble())).title(PlaceSupplier.places[n]?.title) + .snippet(PlaceSupplier.places[n]!!.description)) + } + + googleMap.setOnMarkerClickListener(this) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_map, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? + mapFragment?.getMapAsync(callback) + } + + override fun onMarkerClick(marker: Marker?): Boolean { + marker?.let { + if (marker.title == lastClickedMarkerTitle) { + for (n in PlaceSupplier.places.indices) { + if (marker.title == PlaceSupplier.places[n]!!.title) { + CustomBackstackVariables.infoFragmentBackDestination = "mapFragment" + MapManagement.lastCoordinates = marker.position + ServerManagement.selectedServerId = PlaceSupplier.places[n]!!.id!! + val action = mapFragmentDirections.mapFragmentToInfoFragment(true) + Navigation.findNavController(navControllerView).navigate(action) + } + } + } + lastClickedMarkerTitle = marker.title + } + return false + } +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/settingsFragment.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/settingsFragment.kt index 1c45fef..b227dbd 100644 --- a/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/settingsFragment.kt +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/fragments/settingsFragment.kt @@ -1,12 +1,69 @@ package com.example.wikispot.fragments +import android.content.Intent +import android.content.res.Resources import android.os.Bundle import androidx.fragment.app.Fragment -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import com.example.wikispot.R +import androidx.navigation.Navigation +import com.example.wikispot.* +import com.example.wikispot.activities.MainActivity +import com.example.wikispot.modelClasses.ServerManager +import com.example.wikispot.modelClasses.SettingsSaveManager +import kotlinx.android.synthetic.main.fragment_settings.* +import javax.xml.transform.sax.TemplatesHandler class settingsFragment : Fragment(R.layout.fragment_settings) { -} \ No newline at end of file + + private lateinit var settingsSaveManager: SettingsSaveManager + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + StartDirections.settingsFragmentStartDirection?.let { + when (StartDirections.settingsFragmentStartDirection) { + "debugFragment" -> { + Navigation.findNavController(debugBtn).navigate(R.id.navigateToDebugFragment) + StartDirections.settingsFragmentStartDirection = null + } + } + } + + settingsSaveManager = SettingsSaveManager(requireContext()) + + loadSettings() + + debugBtn.setOnClickListener { + Navigation.findNavController(it).navigate(R.id.navigateToDebugFragment) + } + + darkThemeSwitch.setOnCheckedChangeListener { _, isChecked -> + ThemeOptions.darkTheme = isChecked + settingsSaveManager.saveSettings() + restartAppPartially() + } + + moreColorsSwitch.setOnCheckedChangeListener { _, isChecked -> + ThemeOptions.moreColors = isChecked + settingsSaveManager.saveSettings() + restartAppPartially() + } + } + + private fun loadSettings() { + darkThemeSwitch.isChecked = ThemeOptions.darkTheme + moreColorsSwitch.isChecked = ThemeOptions.moreColors + } + + private fun restartAppPartially() { + val intent = Intent(context?.applicationContext, MainActivity::class.java) + + intent.putExtra(IntentsKeys.startFragment, "settingsFragment") + + ServerManagement.serverManager.clearConnections() + + startActivity(intent) + activity?.finish() + } +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/JsonManager.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/JsonManager.kt new file mode 100644 index 0000000..3461cce --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/JsonManager.kt @@ -0,0 +1,226 @@ +package com.example.wikispot.modelClasses + +import android.content.Context +import com.example.wikispot.GeneralVariables +import com.example.wikispot.getStringFromSharedPreferences +import com.example.wikispot.saveString +import com.example.wikispot.showToast +import org.json.JSONArray +import org.json.JSONObject + +data class JsonManager(private val context: Context, val data: String, val inputType: String = "JSONArray", val debug: Boolean = false) { + + var jsonArray: JSONArray? = null + var currentJsonObject: JSONObject? = null + var currentJsonAttribute0: JSONObject? = null + var currentJsonAttribute1: JSONArray? = null + + init { + if (inputType == "JSONArray") { + jsonArray = JSONArray(data) + try { + currentJsonObject = jsonArray!!.getJSONObject(0) + } catch (exception: Throwable) {} + + if (debug) { + println("[debug] Content of received JSONArray is ${jsonArray.toString()}") + } + + } else if (inputType == "JSONObject") { + currentJsonObject = JSONObject(data) + + if (debug) { + println("[debug] Content of received JSONObject is ${currentJsonObject.toString()}") + } + } + } + + fun getJsonObject(i: Int): JSONObject? { + jsonArray?.let { + if ((i >= jsonArray!!.length()) or (i < 0)) { + context.showToast("Index out of range") + } else { + currentJsonObject = jsonArray?.getJSONObject(i) + return currentJsonObject + } + } + + if (jsonArray == null) { + context.showToast("Json Array is null") + } + + return null + } + + fun getAttributeContent(name: String): String { + if (currentJsonObject != null) { + if (currentJsonAttribute0 != null) { + try { + currentJsonAttribute0 = currentJsonAttribute0!!.getJSONObject(name) + return currentJsonAttribute0.toString() + } catch (exception: Throwable) { + try { + currentJsonAttribute1 = currentJsonAttribute0!!.getJSONArray(name) + currentJsonAttribute0 = null + return currentJsonAttribute1.toString() + } catch (exception: Throwable) { + try { + return currentJsonAttribute0!!.get(name).toString() + } catch (exception: Throwable) { + if (debug) { + context.showToast("Invalid attribute name: $name") + } + } + } + } + } + if (currentJsonAttribute1 != null) { + try { + currentJsonAttribute0 = currentJsonAttribute1!!.getJSONObject(name.toInt()) + return currentJsonAttribute0.toString() + } catch (exception: Throwable) { + try { + currentJsonAttribute1 = currentJsonAttribute1!!.getJSONArray(name.toInt()) + currentJsonAttribute0 = null + return currentJsonAttribute1.toString() + } catch (exception: Throwable) { + try { + return currentJsonAttribute1!!.get(name.toInt()).toString() + } catch (exception: Throwable) { + if (debug) { + context.showToast("Invalid attribute name: $name") + } + } + } + } + } else { + try { + currentJsonAttribute0 = currentJsonObject!!.getJSONObject(name) + return currentJsonAttribute0.toString() + } catch (exception: Throwable) { + try { + currentJsonAttribute1 = currentJsonObject!!.getJSONArray(name) + currentJsonAttribute0 = null + return currentJsonAttribute1.toString() + } catch (exception: Throwable) { + try { + return currentJsonObject!!.get(name).toString() + } catch (exception: Throwable) { + if (debug) { + context.showToast("Invalid attribute name: $name") + } + } + } + } + } + } else { + if (debug) { + context.showToast("Invalid attribute name: $name") + } + } + + return GeneralVariables.variableMissingKeyword + } + + fun getAttributeContentByPath(path: String): String { + val steps = path.split("/") + + val currentJsonAttributesBackup = listOf(currentJsonAttribute0, currentJsonAttribute1) // backing up selected jsonAttributes + + // getting the attribute + clearSelectedAttribute() + var result: Any? = null + for (step in steps) { + try { + result = getAttributeContent(step) + } catch (exception: Throwable) { + context.showToast("Invalid path") + // loading back saved json attributes + currentJsonAttribute0 = currentJsonAttributesBackup[0] as JSONObject? + currentJsonAttribute1 = currentJsonAttributesBackup[1] as JSONArray? + return GeneralVariables.variableMissingKeyword + } + } + + // loading back saved json attributes + currentJsonAttribute0 = currentJsonAttributesBackup[0] as JSONObject? + currentJsonAttribute1 = currentJsonAttributesBackup[1] as JSONArray? + + // returning result + return result.toString() + } + + fun clearSelectedAttribute() { + currentJsonAttribute0 = null + currentJsonAttribute1 = null + } + + fun getLengthOfJsonArray(): Int { + return if (jsonArray != null) { + if (debug) { + println("[debug] Length of json array is ${jsonArray!!.length()}") + } + jsonArray!!.length() + } else { + println("[debug] Length of json array is 0") + 0 + } + } + + fun getCurrentJsonAttributeContent(): String { + if (currentJsonAttribute0 != null) { + return currentJsonAttribute0.toString() + } else if (currentJsonAttribute1 != null) { + return currentJsonAttribute1.toString() + } + return "get json attribute first" + } + + fun findJsonObjectByAttribute(attributePath: String, value: Any): String { + val currentJsonObjectSave = currentJsonObject + + for (i in 0 until getLengthOfJsonArray()) { + + getJsonObject(i) + + val attributeContent = getAttributeContentByPath(attributePath) + + if (attributeContent != GeneralVariables.variableMissingKeyword) { + + if (attributeContent == value.toString()) { + return currentJsonObject.toString() + } + + } + + } + + currentJsonObject = currentJsonObjectSave + return currentJsonObject.toString() + + } + + // saving and loading + + fun saveJson(accessKey: String) { + + // finding data that could be saved + if (jsonArray != null) { + context.saveString(accessKey, jsonArray.toString(), "jsonStrings") + } else if (currentJsonObject != null) { + context.saveString(accessKey, currentJsonObject.toString(), "jsonStrings") + } else if (currentJsonAttribute0 != null) { + context.saveString(accessKey, currentJsonAttribute0.toString(), "jsonStrings") + } else if (currentJsonAttribute1 != null) { + context.saveString(accessKey, currentJsonAttribute1.toString(), "jsonStrings") + } else { + context.showToast("Nothing to save") + } + + } + + fun loadJson(context: Context, accessKey: String, inputType: String = "JSONArray", debug: Boolean = false): JsonManager { + return JsonManager(context, context.getStringFromSharedPreferences(accessKey, "jsonStrings"), inputType, debug) + } + +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/JsonManagerLite.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/JsonManagerLite.kt new file mode 100644 index 0000000..9df0f44 --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/JsonManagerLite.kt @@ -0,0 +1,135 @@ +package com.example.wikispot.modelClasses + +import org.json.JSONArray +import org.json.JSONObject + +data class JsonManagerLite(val data: String, val inputType: String = "JSONArray") { + + var jsonArray: JSONArray? = null + var currentJsonObject: JSONObject? = null + private var currentJsonAttribute0: JSONObject? = null + private var currentJsonAttribute1: JSONArray? = null + + init { + if (inputType == "JSONArray") { + jsonArray = JSONArray(data) + try { + currentJsonObject = jsonArray!!.getJSONObject(0) + } catch (exception: Throwable) {} + + } else if (inputType == "JSONObject") { + currentJsonObject = JSONObject(data) + } + } + + fun getJsonObject(i: Int): JSONObject? { + jsonArray?.let { + currentJsonObject = jsonArray?.getJSONObject(i) + return currentJsonObject + } + + return null + } + + fun getAttributeContent(name: String): String { + if (currentJsonObject != null) { + if (currentJsonAttribute0 != null) { + try { + currentJsonAttribute0 = currentJsonAttribute0!!.getJSONObject(name) + return currentJsonAttribute0.toString() + } catch (exception: Throwable) { + try { + currentJsonAttribute1 = currentJsonAttribute0!!.getJSONArray(name) + currentJsonAttribute0 = null + return currentJsonAttribute1.toString() + } catch (exception: Throwable) { + try { + return currentJsonAttribute0!!.get(name).toString() + } catch (exception: Throwable) { } + } + } + } else if (currentJsonAttribute1 != null) { + try { + currentJsonAttribute0 = currentJsonAttribute1!!.getJSONObject(name.toInt()) + return currentJsonAttribute0.toString() + } catch (exception: Throwable) { + try { + currentJsonAttribute1 = currentJsonAttribute1!!.getJSONArray(name.toInt()) + currentJsonAttribute0 = null + return currentJsonAttribute1.toString() + } catch (exception: Throwable) { + try { + return currentJsonAttribute1!!.get(name.toInt()).toString() + } catch (exception: Throwable) { } + } + } + } else { + try { + currentJsonAttribute0 = currentJsonObject!!.getJSONObject(name) + return currentJsonAttribute0.toString() + } catch (exception: Throwable) { + try { + currentJsonAttribute1 = currentJsonObject!!.getJSONArray(name) + currentJsonAttribute0 = null + return currentJsonAttribute1.toString() + } catch (exception: Throwable) { + try { + return currentJsonObject!!.get(name).toString() + } catch (exception: Throwable) { } + } + } + } + } + + return "" + } + + fun getAttributeContentByPath(path: String): String { + val steps = path.split("/") + + val currentJsonAttributesBackup = listOf(currentJsonAttribute0, currentJsonAttribute1) // backing up selected jsonAttributes + + // getting the attribute + clearSelectedAttributes() + var result: Any? = null + for (step in steps) { + try { + result = getAttributeContent(step) + } catch (exception: Throwable) { + // loading back saved json attributes + currentJsonAttribute0 = currentJsonAttributesBackup[0] as JSONObject? + currentJsonAttribute1 = currentJsonAttributesBackup[1] as JSONArray? + return "" + } + } + + // loading back saved json attributes + currentJsonAttribute0 = currentJsonAttributesBackup[0] as JSONObject? + currentJsonAttribute1 = currentJsonAttributesBackup[1] as JSONArray? + + // returning result + return result.toString() + } + + fun clearSelectedAttributes() { + currentJsonAttribute0 = null + currentJsonAttribute1 = null + } + + fun getLengthOfJsonArray(): Int { + return if (jsonArray != null) { + jsonArray!!.length() + } else { + 0 + } + } + + fun getCurrentJsonAttributeContent(): String { + if (currentJsonAttribute0 != null) { + return currentJsonAttribute0.toString() + } else if (currentJsonAttribute1 != null) { + return currentJsonAttribute1.toString() + } + return "get json attribute first" + } +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/ServerManager.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/ServerManager.kt new file mode 100644 index 0000000..7299f06 --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/ServerManager.kt @@ -0,0 +1,551 @@ +package com.example.wikispot.modelClasses + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.widget.TextView +import com.example.wikispot.GeneralVariables +import com.example.wikispot.ScreenParameters +import com.example.wikispot.ServerManagement +import com.example.wikispot.modelsForAdapters.MessagesSupplier +import com.github.barteksc.pdfviewer.PDFView +import okhttp3.* +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.json.JSONArray +import org.json.JSONObject +import java.io.IOException + + +class ServerManager { + + private var receiverConnections = mutableListOf() + private var viewConnections = mutableListOf() + private var chatConnections = mutableListOf() + + // single time requests + + fun getData(dataReceiver: (String) -> Unit, context: Context, serverId: Int, path: String, attributePath: String = "", numberOfAttempts: Int = 2) { + val dataRequestThread = Thread(DataRequest(dataReceiver, context, serverId, path, attributePath, numberOfAttempts)) + dataRequestThread.start() + } + + inner class DataRequest(val dataReceiver: (String) -> Unit, val context: Context, val serverId: Int, val path: String = "", val attributePath: String, private val numberOfAttempts: Int = 2): Runnable{ + override fun run() { + for (n in 0 until numberOfAttempts) { + var url = "${ServerManagement.baseUrl}devices_list" + + if (path != "") { + if (path.contains(ServerManagement.sensors_keyword)){ + url = "${ServerManagement.baseUrl}$serverId/sensors" + } else { + url = "${ServerManagement.baseUrl}files/$serverId/$path" + } + } + + val request = Request.Builder().url(url).build() + val client = OkHttpClient() + + client.newCall(request).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + response.body?.let { + val receivedString = response.body!!.string() + if (receivedString == "Internal Server Error") { + return + } + + try { + JSONArray(receivedString) + + val jsonManager = JsonManager(context, receivedString) + if (path == "") { + if (attributePath == "GET_WHOLE_ARRAY") { + dataReceiver(jsonManager.jsonArray.toString()) + return + } + jsonManager.getJsonObject(0) + } else { + if (attributePath == "") { + throw Throwable() + } + } + + if (attributePath != "") { + dataReceiver(jsonManager.getAttributeContentByPath(attributePath)) + } else { + dataReceiver(jsonManager.currentJsonObject.toString()) + } + + } catch (exception: Throwable) { + try { + JSONObject(receivedString) + + val jsonManager = JsonManager(context, receivedString, "JSONObject") + + if (attributePath != "") { + dataReceiver(jsonManager.getAttributeContentByPath(attributePath)) + } else { + dataReceiver(jsonManager.currentJsonObject.toString()) + } + + } catch (exception: Throwable) { + dataReceiver(receivedString) + } + } + } + } + + override fun onFailure(call: Call, e: IOException) { + println("Request Failed") + println(e) + } + }) + + ServerManagement.totalNumberOfRequestsSent += 1 + + Thread.sleep(ServerManagement.dataRequestOnAttemptWait) + } + } + } + + fun getImage(imageReceiver: (Bitmap) -> Unit, serverId: Int, path: String, numberOfAttempts: Int = 2) { + val imageRequestThread = Thread(ImageRequest(imageReceiver, serverId, path, numberOfAttempts)) + imageRequestThread.start() + } + + inner class ImageRequest(val imageReceiver: (Bitmap) -> Unit, val serverId: Int, val path: String, private val numberOfAttempts: Int): Runnable { + override fun run() { + for (i in 0 until numberOfAttempts) { + val url = "${ServerManagement.baseUrl}files/$serverId/$path" + + try { + val inputStream = java.net.URL(url).openStream() + var bitmap = BitmapFactory.decodeStream(inputStream) + + if (bitmap.width > ScreenParameters.width) { + val ratio = bitmap.height.toFloat() / bitmap.width.toFloat() + bitmap = Bitmap.createScaledBitmap(bitmap, ScreenParameters.width, (ScreenParameters.width * ratio).toInt(), false) + } + + imageReceiver(bitmap) + break + } catch (e: Throwable) { println(e) } + + ServerManagement.totalNumberOfRequestsSent += 1 + + Thread.sleep(ServerManagement.imageRequestOnAttemptWait) + } + } + } + + fun loadPdfView(view: PDFView, url: String, swipeHorizontal: Boolean = false) { + val pdfLoadingRequestThread = Thread(PdfLoadingRequest(view, url, swipeHorizontal)) + pdfLoadingRequestThread.start() + } + + inner class PdfLoadingRequest(val view: PDFView, val url: String, private val swipeHorizontal: Boolean = false): Runnable { + override fun run() { + val inputStream = java.net.URL(url).openStream() + view.post { + view.fromStream(inputStream).swipeHorizontal(swipeHorizontal).load() + view.zoomTo(view.width / 490.0F) + } + } + + } + + // connections + + fun clearConnections() { + for (i in 0 until receiverConnections.size) { + try { + receiverConnections[i].running = false + } catch (e: Throwable) { println("In clearConnections: $e") } + } + receiverConnections = mutableListOf() + + for (i in 0 until viewConnections.size) { + try { + viewConnections[i].running = false + } catch (e: Throwable) { println("In clearConnections: $e") } + } + viewConnections = mutableListOf() + + for (i in 0 until chatConnections.size) { + try { + chatConnections[i].running = false + } catch (e: Throwable) { println("In clearConnections: $e") } + } + chatConnections = mutableListOf() + } + + fun checkIfConnectionAlreadyExists(connectionName: String, connectionType: String = "any"): Boolean{ + if ((connectionType == "any") or (connectionType == "receiver")) { + for (n in 0 until receiverConnections.size) { + if (receiverConnections[n].connectionName == connectionName) { + return true + } + } + } + if ((connectionType == "any") or (connectionType == "view")) { + for (n in 0 until viewConnections.size) { + if (viewConnections[n].connectionName == connectionName) { + return true + } + } + } + if ((connectionType == "any") or (connectionType == "chat")) { + for (n in 0 until chatConnections.size) { + if (chatConnections[n].connectionName == connectionName) { + return true + } + } + } + + return false + } + + fun deleteConnection(connectionName: String, connectionType: String = "any") { // other types are any, activity and view + if ((connectionType == "any") or (connectionType == "receiver")) { + val indexesToRemove = mutableListOf() + for (i in 0 until receiverConnections.size) { // checking in connections + try { + if (receiverConnections[i].connectionName == connectionName) { + receiverConnections[i].running = false + indexesToRemove.add(i) + } + } catch (e: Throwable) { println("In deleteConnection: $e") } + } + + for (i in 0 until indexesToRemove.size) { + receiverConnections.removeAt(indexesToRemove[i] - i) + } + } + + if ((connectionType == "any") or (connectionType == "view")) { + val indexesToRemove = mutableListOf() + for (i in 0 until viewConnections.size) { // checking in connections + try { + if (viewConnections[i].connectionName == connectionName) { + viewConnections[i].running = false + indexesToRemove.add(i) + } + } catch (e: Throwable) { println("In deleteConnection: $e") } + } + + for (i in 0 until indexesToRemove.size) { + viewConnections.removeAt(indexesToRemove[i] - i) + } + } + + if ((connectionType == "any") or (connectionType == "chat")) { + val indexesToRemove = mutableListOf() + for (i in 0 until chatConnections.size) { // checking in connections + try { + if (chatConnections[i].connectionName == connectionName) { + chatConnections[i].running = false + indexesToRemove.add(i) + } + } catch (e: Throwable) { println("In deleteConnection: $e") } + } + + for (i in 0 until indexesToRemove.size) { + chatConnections.removeAt(indexesToRemove[i] - i) + } + } + } + + fun addReceiverConnection(dataReceiver: (String) -> Unit, context: Context, connectionName: String, serverId: Int, path: String = "", attributePath: String = "", waitTime: Long = ServerManagement.receiverConnectionOnCheckWait) { + receiverConnections.add(ReceiverConnection(dataReceiver, context, connectionName, serverId, path, attributePath, waitTime)) + } + + inner class ReceiverConnection(val dataReceiver: (String) -> Unit, val context: Context, val connectionName: String, val serverId: Int, val path: String = "", val attributePath: String, val waitTime: Long) { + + var running = true + + init { + val checkingServerDataThread = Thread(CheckingServerData()) + checkingServerDataThread.start() + } + + inner class CheckingServerData : Runnable { + override fun run() { + while (running) { + var url = "${ServerManagement.baseUrl}devices_list" + + if (path != "") { + if (path.contains(ServerManagement.sensors_keyword)){ + url = "${ServerManagement.baseUrl}$serverId/sensors" + } else { + url = "${ServerManagement.baseUrl}files/$serverId/$path" + } + } + + val request = Request.Builder().url(url).build() + val client = OkHttpClient() + + client.newCall(request).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + response.body?.let { + val receivedString = response.body!!.string() + if (receivedString == "Internal Server Error") { + return + } + + try { + JSONArray(receivedString) + + val jsonManager = JsonManager(context, receivedString) + if (path == "") { + if (attributePath == "GET_WHOLE_ARRAY") { + dataReceiver(jsonManager.jsonArray.toString()) + return + } + jsonManager.getJsonObject(0) + } else { + if (attributePath == "") { + throw Throwable() + } + } + + if (attributePath != "") { + dataReceiver(jsonManager.getAttributeContentByPath(attributePath)) + } else { + dataReceiver(jsonManager.currentJsonObject.toString()) + } + + } catch (exception: Throwable) { + try { + JSONObject(receivedString) + + val jsonManager = JsonManager(context, receivedString, "JSONObject") + + if (attributePath != "") { + dataReceiver(jsonManager.getAttributeContentByPath(attributePath)) + } else { + dataReceiver(jsonManager.currentJsonObject.toString()) + } + + } catch (exception: Throwable) { + dataReceiver(receivedString) + } + } + } + } + + override fun onFailure(call: Call, e: IOException) { + println("Request Failed") + println(e) + } + }) + + ServerManagement.totalNumberOfRequestsSent += 1 + + Thread.sleep(waitTime) + } + } + } + + } + + fun addViewConnection(context: Context, view: TextView, connectionName: String, serverId: Int, path: String = "", attributePath: String = "") { + viewConnections.add(ViewConnection(context, view, connectionName, serverId, path, attributePath)) + } + + inner class ViewConnection(val context: Context, val view: TextView, val connectionName: String, val serverId: Int, val path: String = "", var attributePath: String) { + + var running = true + + init { + val checkingServerDataThread = Thread(CheckingServerData()) + checkingServerDataThread.start() + } + + inner class CheckingServerData: Runnable { + override fun run() { + while (running) { + var url = "${ServerManagement.baseUrl}devices_list" + + if (path != "") { + if (path.contains(ServerManagement.sensors_keyword)){ + url = "${ServerManagement.baseUrl}$serverId/sensors" + } else { + url = "${ServerManagement.baseUrl}files/$serverId/$path" + } + } + + val request = Request.Builder().url(url).build() + val client = OkHttpClient() + + client.newCall(request).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + response.body?.let { + val receivedString = response.body!!.string() + + if (receivedString == "Internal Server Error") { + return + } + + try { + JSONArray(receivedString) + + val jsonManager = JsonManager(context, receivedString) + if (path == "") { + if (attributePath == "GET_WHOLE_ARRAY") { + view.text = jsonManager.jsonArray.toString() + return + } + jsonManager.getJsonObject(0) + } else { + if (attributePath == "") { + throw Throwable() + } + } + + if (attributePath != "") { + view.post { + view.text = jsonManager.getAttributeContentByPath(attributePath) + } + } else { + view.post { + view.text = jsonManager.currentJsonObject.toString() + } + } + + } catch (exception: Throwable) { + try { + JSONObject(receivedString) + + val jsonManager = JsonManager(context, receivedString, "JSONObject") + + if (attributePath != "") { + view.post { + view.text = jsonManager.getAttributeContentByPath(attributePath) + } + } else { + view.post { + view.text = jsonManager.currentJsonObject.toString() + } + } + + } catch (exception: Throwable) { + view.post { + view.text = receivedString + } + } + } + } + } + + override fun onFailure(call: Call, e: IOException) { + println("Request Failed") + println(e) + } + }) + + ServerManagement.totalNumberOfRequestsSent += 1 + + Thread.sleep(ServerManagement.viewConnectionOnCheckWait) + } + } + } + + } + + fun addChatConnection(dataReceiver: (String) -> Unit, context: Context, connectionName: String) { + chatConnections.add(ChatConnection(dataReceiver, context, connectionName)) + } + + inner class ChatConnection(val dataReceiver: (String) -> Unit, val context: Context, val connectionName: String) { + + var running = true + + init { + val checkingServerDataThread = Thread(CheckingServerData()) + checkingServerDataThread.start() + } + + inner class CheckingServerData : Runnable { + override fun run() { + while (running) { + + if (GeneralVariables.id == null) { + + val url = "${ServerManagement.baseUrl}messages/register" + println(url) + + val request = Request.Builder().url(url).build() + val client = OkHttpClient() + + client.newCall(request).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + response.body?.let { + val receivedString = response.body!!.string() + if (receivedString == "Internal Server Error") { + return + } + + val returnJsonObject = JSONObject() + returnJsonObject.put("source", "messages/register") + returnJsonObject.put("data", JSONArray(receivedString)) + dataReceiver(returnJsonObject.toString()) + } + } + + override fun onFailure(call: Call, e: IOException) { + println("Request Failed") + println(e) + } + }) + } else { + var timestamp = "0" + if (MessagesSupplier.messages.isNotEmpty()) { + val messagesReversed = MessagesSupplier.messages.reversed() + for (i in messagesReversed.indices) { + if (messagesReversed[i]!!.timestamp != "waiting") { + timestamp = messagesReversed[i]!!.timestamp + break + } + } + } + + val urlBuilder: HttpUrl.Builder = "${ServerManagement.baseUrl}messages/get".toHttpUrlOrNull()!!.newBuilder() + urlBuilder.addQueryParameter("timestamp", timestamp) + val url: String = urlBuilder.build().toString() + + val request: Request = Request.Builder() + .url(url) + .build() + val client = OkHttpClient() + + client.newCall(request).enqueue(object : Callback { + + override fun onResponse(call: Call, response: Response) { + response.body?.let { + val receivedString = response.body!!.string() + if (receivedString == "Internal Server Error") { + return + } + val returnJsonObject = JSONObject() + returnJsonObject.put("source", "messages/get") + returnJsonObject.put("data", JSONArray(receivedString)) + dataReceiver(returnJsonObject.toString()) + } + } + + override fun onFailure(call: Call, e: IOException) { + println("Request Failed") + println(e) + } + + }) + } + + Thread.sleep(ServerManagement.chatConnectionOnCheckWait) + } + } + + } + + } + +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/SettingsSaveManager.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/SettingsSaveManager.kt new file mode 100644 index 0000000..78220df --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelClasses/SettingsSaveManager.kt @@ -0,0 +1,46 @@ +package com.example.wikispot.modelClasses + +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import androidx.appcompat.app.AppCompatDelegate +import com.example.wikispot.GeneralVariables +import com.example.wikispot.IntentsKeys +import com.example.wikispot.ThemeOptions +import com.example.wikispot.activities.MainActivity + +class SettingsSaveManager(val context: Context) { + + fun loadSettings() { + val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + + ThemeOptions.darkTheme = sharedPreferences.getBoolean("darkMode", ThemeOptions.darkTheme) + ThemeOptions.moreColors = sharedPreferences.getBoolean("moreColors", ThemeOptions.moreColors) + + // checking if we want to use system default theme + try { + GeneralVariables.appRunningFirstTime = sharedPreferences.getBoolean("appRunningFirstTime", true) + + if (GeneralVariables.appRunningFirstTime) { + ThemeOptions.darkTheme = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) + } + } catch (e: Throwable) { + println(e) + } + + // saving settings cause some things might change based on system preferences + saveSettings() + } + + fun saveSettings() { + val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + val editor = sharedPreferences.edit() + + editor.apply{ + putBoolean("appRunningFirstTime", false) + putBoolean("darkMode", ThemeOptions.darkTheme) + putBoolean("moreColors", ThemeOptions.moreColors) + }.apply() + } + +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/ExploreListModel.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/ExploreListModel.kt new file mode 100644 index 0000000..166b0bd --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/ExploreListModel.kt @@ -0,0 +1,107 @@ +package com.example.wikispot.modelsForAdapters + +import android.content.Context +import android.graphics.Bitmap +import com.example.wikispot.getStringFromSharedPreferences +import com.example.wikispot.modelClasses.JsonManager +import com.example.wikispot.modelClasses.JsonManagerLite +import com.example.wikispot.saveString +import org.json.JSONArray + +data class PlacePreview(var title: String, var description: String, var location: String? = null, var img: Bitmap? = null, val id: Int?=null) + +object PlaceSupplier { + + var controlJson: JsonManagerLite? = null + + var places = arrayOf() + + fun appendPlace(place: PlacePreview) { + val array = places.copyOf(places.size + 1) + array[places.size] = place + places = array + } + + fun checkIfContains(place: PlacePreview): Boolean { + for (n in places.indices) { + places[n]?.let { + if (places[n]?.title == place.title) { + if (places[n]?.description == place.description) { + return true + } + } + } + } + return false + } + + fun getContainingInstance(place: PlacePreview): PlacePreview? { + for (n in places.indices) { + places[n]?.let { + if (places[n]?.title == place.title) { + if (places[n]?.description == place.description) { + return places[n] + } + } + } + } + return null + } + + // loading from and saving to cache + fun loadFromCache(context: Context) { + println("loading") + var save = context.getStringFromSharedPreferences("placePreviews", "exploreFragmentCache") + if (save.isEmpty()) { + save = "[]" + } + val jsonManager = JsonManager(context, save) + for (n in 0 until jsonManager.getLengthOfJsonArray()) { + val savedData = jsonManager.jsonArray?.get(n).toString().split("|||||") + val place = PlacePreview(savedData[0], savedData[1], savedData[2], null, savedData[3].toInt()) + if (!checkIfContains(place)) { + appendPlace(place) + } + } + } + + fun saveToCache(context: Context) { + val save = JSONArray() + + var i = 0 + for (n in places.indices) { + val place = places[n] + if (getSavePermission(place)) { + save.put(i, "${place!!.title}|||||${place.description}|||||${place.location}|||||${place.id}") + i++ + } + } + + context.saveString("placePreviews", save.toString(), "exploreFragmentCache") + } + + private fun getSavePermission(place: PlacePreview?): Boolean { + if (controlJson == null) { + return true + } + + place?.let { + for (n in 1 until controlJson!!.getLengthOfJsonArray()) { + controlJson!!.getJsonObject(n) + if (place.id == controlJson!!.getAttributeContent("ID").toInt()) { + if (place.title == controlJson!!.getAttributeContentByPath("description/title")) { + val tempPlace = PlacePreview("", controlJson!!.getAttributeContentByPath("description/description_s"), + controlJson!!.getAttributeContentByPath("location")) + if (place.description == tempPlace.description) { + if (place.location == tempPlace.location) { + return true + } + } + } + } + } + } + return false + } + +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/FilesListModel.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/FilesListModel.kt new file mode 100644 index 0000000..7977e73 --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/FilesListModel.kt @@ -0,0 +1,34 @@ +package com.example.wikispot.modelsForAdapters + +data class FileView(val filetype: String, val filename: String, val fileDescription: String, + var textInfo: String? = null, var imgInfo: String? = null, var pdfUrl: String? = null, + var generalUrl: String? = null) + + +object FileViewsSupplier { + + var fileViews = arrayOf() + + fun appendFileView(fileView: FileView) { + val array = fileViews.copyOf(fileViews.size + 1) + array[fileViews.size] = fileView + fileViews = array + } + + fun checkIfContains(fileView: FileView): Boolean{ + for (n in fileViews.indices) { + if (fileViews[n]!!.filename == fileView.filename) { + if (fileViews[n]!!.filetype == fileView.filetype) { + return true + } + } + } + return false + } + + fun wipeData() { + fileViews = arrayOf() + } + +} + diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/MessagesListModel.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/MessagesListModel.kt new file mode 100644 index 0000000..af208bb --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/MessagesListModel.kt @@ -0,0 +1,97 @@ +package com.example.wikispot.modelsForAdapters + +import com.example.wikispot.GeneralVariables +import com.example.wikispot.databases.NamesDatabase +import java.util.* + +data class Message(var senderId: String, val content: String, var timestamp: String="0") { + + var senderName: String + + init { + val r = getRandomGenerator(senderId) + + senderName = "${NamesDatabase.names[r.nextInt(NamesDatabase.names.size)]} - ${r.nextInt(9999)}" + } + + private fun getRandomGenerator(seedString: String): Random { + var n: Long = 0 + for (element in seedString) { + n += element.toInt() + } + + return Random(n) + } + +} + + +object MessagesSupplier { + + var messages = arrayOf() + + fun appendMessage(message: Message) { + val array = messages.copyOf(messages.size + 1) + array[messages.size] = message + messages = array + + if (messages.size > GeneralVariables.max_amount_of_saved_messages) { + deleteMessageByIndex(0) + println(messages.size) + } + } + + private fun deleteMessageByIndex(i: Int) { + messages = messages.copyOfRange(0, i) + messages.copyOfRange(i + 1, messages.size) + } + + fun checkIfContains(message: Message, checkTimestamp: Boolean=true): Boolean { + for (i in messages.indices) { + messages[i]?.let { + if (message.senderId == it.senderId) { + if (message.content == it.content) { + if (checkTimestamp) { + if (message.timestamp == it.timestamp) { + return true + } + } else { + return true + } + } + } + } + } + return false + } + + fun clearWaitingMessages() { + val positionsOfItemsToRemove = mutableListOf() + for (i in messages.indices) { + if (messages[i]!!.timestamp == "waiting") { + positionsOfItemsToRemove.add(i) + println("waiting at: $i") + } + } + + var subtractAmount = 0 + for (index in positionsOfItemsToRemove) { + deleteMessageByIndex(index - subtractAmount) + subtractAmount += 1 + } + } + + fun getIndexOfLastMessageFromSelf(): Int? { + var i: Int? = null + for (n in messages.indices) { + if (messages[n]!!.senderId == GeneralVariables.id) { + i = n + } + } + return i + } + + fun wipeData() { + messages = arrayOf() + } + +} diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/SensorsListModel.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/SensorsListModel.kt new file mode 100644 index 0000000..ba91a35 --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/modelsForAdapters/SensorsListModel.kt @@ -0,0 +1,30 @@ +package com.example.wikispot.modelsForAdapters + +data class LabeledValue(val label: String, val value: String) + + +object LabeledValuesSupplier { + + var labeledValues = arrayOf() + + fun appendLabeledValue(labeledValue: LabeledValue) { + val array = labeledValues.copyOf(labeledValues.size + 1) + array[labeledValues.size] = labeledValue + labeledValues = array + } + + fun checkIfContains(labeledValue: LabeledValue): Boolean{ + for (n in labeledValues.indices) { + if (labeledValues[n]!!.label == labeledValue.label) { + return true + } + } + return false + } + + fun wipeData() { + labeledValues = arrayOf() + } + +} + diff --git a/app/WikiSpot/app/src/main/java/com/example/wikispot/projectScopeVariables.kt b/app/WikiSpot/app/src/main/java/com/example/wikispot/projectScopeVariables.kt new file mode 100644 index 0000000..f23d9cd --- /dev/null +++ b/app/WikiSpot/app/src/main/java/com/example/wikispot/projectScopeVariables.kt @@ -0,0 +1,73 @@ +package com.example.wikispot + +import com.example.wikispot.modelClasses.ServerManager +import com.google.android.gms.maps.model.LatLng +import org.json.JSONArray + + +object GeneralVariables { + + var appRunningFirstTime = true + + var id: String? = null + var name: String? = null + + var email:String? = null + var phoneNumber: Int? = null + + const val max_amount_of_saved_messages = 32 + const val variableMissingKeyword = "_[{(V,a,r,i,a,b,l,e, ,m,i,s,s,i,n,g)}]_" + + const val translatePrefix = "[translate]-" + +} + +object IntentsKeys { + + const val startFragment = "start_fragment" + +} + + +object ServerManagement { + var serverManager = ServerManager() + const val receiverConnectionOnCheckWait: Long = 4000 + const val viewConnectionOnCheckWait: Long = 5000 + const val chatConnectionOnCheckWait: Long = 1000 + const val dataRequestOnAttemptWait: Long = 2000 + const val imageRequestOnAttemptWait: Long = 2000 + var baseUrl = "http://192.168.1.156:8000/" + var connectedServerId: Int? = null + var selectedServerId = 0 + + const val sensors_keyword = "_[{(S,e,n,s,o,r,s)}]_" + + var totalNumberOfRequestsSent = 0 +} + + +object MapManagement { + var connectedServerPosition: LatLng? = LatLng(0.toDouble(), 0.toDouble()) + var lastCoordinates = LatLng(0.toDouble(), 0.toDouble()) +} + + +object ScreenParameters { + var height = 1 + var width = 1 +} + + +object CustomBackstackVariables { + var infoFragmentBackDestination = "exploreFragment" +} + + +object ThemeOptions { + var darkTheme = false + var moreColors = false +} + +object StartDirections { + var settingsFragmentStartDirection: String? = null +} diff --git a/app/WikiSpot/app/src/main/python/server_manager.py b/app/WikiSpot/app/src/main/python/server_manager.py new file mode 100644 index 0000000..1a84fae --- /dev/null +++ b/app/WikiSpot/app/src/main/python/server_manager.py @@ -0,0 +1,15 @@ +import requests +json_list = [] + + +def init(): + global json_list + json_list = eval(requests.get("http://192.168.1.230:8000/devices_list").text) + + +def get_length(): + return len(json_list) + + +def get_json(i): + return json_list[i] diff --git a/app/WikiSpot/app/src/main/res/anim/close_rotation_anim.xml b/app/WikiSpot/app/src/main/res/anim/close_rotation_anim.xml new file mode 100644 index 0000000..a3cdc2f --- /dev/null +++ b/app/WikiSpot/app/src/main/res/anim/close_rotation_anim.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/anim/fade_in.xml b/app/WikiSpot/app/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000..14271d4 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/anim/fade_out.xml b/app/WikiSpot/app/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000..8b1628e --- /dev/null +++ b/app/WikiSpot/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/anim/open_rotation_anim.xml b/app/WikiSpot/app/src/main/res/anim/open_rotation_anim.xml new file mode 100644 index 0000000..8daa2fe --- /dev/null +++ b/app/WikiSpot/app/src/main/res/anim/open_rotation_anim.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/color/bottom_nav_bar_item_color.xml b/app/WikiSpot/app/src/main/res/color/bottom_nav_bar_item_color.xml new file mode 100644 index 0000000..92c5e32 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/color/bottom_nav_bar_item_color.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/drawable/blank.png b/app/WikiSpot/app/src/main/res/drawable/blank.png new file mode 100644 index 0000000..64cb20e Binary files /dev/null and b/app/WikiSpot/app/src/main/res/drawable/blank.png differ diff --git a/app/WikiSpot/app/src/main/res/drawable/bottom_nav_bar_gradient_background.xml b/app/WikiSpot/app/src/main/res/drawable/bottom_nav_bar_gradient_background.xml new file mode 100644 index 0000000..ef95260 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/bottom_nav_bar_gradient_background.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/drawable/chat_fragment_gradient_background.xml b/app/WikiSpot/app/src/main/res/drawable/chat_fragment_gradient_background.xml new file mode 100644 index 0000000..3bebab1 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/chat_fragment_gradient_background.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/drawable/contrasting_gradient_background.xml b/app/WikiSpot/app/src/main/res/drawable/contrasting_gradient_background.xml new file mode 100644 index 0000000..02ed627 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/contrasting_gradient_background.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/drawable/debug_vector_asset.xml b/app/WikiSpot/app/src/main/res/drawable/debug_vector_asset.xml new file mode 100644 index 0000000..7853f61 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/debug_vector_asset.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/WikiSpot/app/src/main/res/drawable/explore_fragment_gradient_background.xml b/app/WikiSpot/app/src/main/res/drawable/explore_fragment_gradient_background.xml new file mode 100644 index 0000000..dfbabee --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/explore_fragment_gradient_background.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/drawable/home_fragment_gradient_background.xml b/app/WikiSpot/app/src/main/res/drawable/home_fragment_gradient_background.xml new file mode 100644 index 0000000..674dc97 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/home_fragment_gradient_background.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml new file mode 100644 index 0000000..a7ac3d3 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/WikiSpot/app/src/main/res/drawable/ic_baseline_chat_24.xml b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_chat_24.xml index 26208ad..c69901c 100644 --- a/app/WikiSpot/app/src/main/res/drawable/ic_baseline_chat_24.xml +++ b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_chat_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> + android:tint="?attr/generalIconsColor"> diff --git a/app/WikiSpot/app/src/main/res/drawable/ic_baseline_download_24.xml b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_download_24.xml new file mode 100644 index 0000000..6c57b85 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_download_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/WikiSpot/app/src/main/res/drawable/ic_baseline_email_24.xml b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_email_24.xml new file mode 100644 index 0000000..e4804cb --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_email_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/WikiSpot/app/src/main/res/drawable/ic_baseline_image_24.xml b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_image_24.xml new file mode 100644 index 0000000..a18fe2c --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_image_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/WikiSpot/app/src/main/res/drawable/ic_baseline_phone_24.xml b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_phone_24.xml new file mode 100644 index 0000000..4d0cad2 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_phone_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/WikiSpot/app/src/main/res/drawable/ic_baseline_send_24.xml b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_send_24.xml new file mode 100644 index 0000000..e941e53 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/ic_baseline_send_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/WikiSpot/app/src/main/res/drawable/ic_launcher_background.xml b/app/WikiSpot/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9..0000000 --- a/app/WikiSpot/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/WikiSpot/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/WikiSpot/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..6a0151f --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/drawable/info_fragment_gradient_background.xml b/app/WikiSpot/app/src/main/res/drawable/info_fragment_gradient_background.xml new file mode 100644 index 0000000..a8851c0 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/info_fragment_gradient_background.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/drawable/loacation_vector_asset.xml b/app/WikiSpot/app/src/main/res/drawable/loacation_vector_asset.xml new file mode 100644 index 0000000..20506e4 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/loacation_vector_asset.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/WikiSpot/app/src/main/res/drawable/settings_fragment_gradient_background.xml b/app/WikiSpot/app/src/main/res/drawable/settings_fragment_gradient_background.xml new file mode 100644 index 0000000..0a6ff3b --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/settings_fragment_gradient_background.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/drawable/text_background_gradient.xml b/app/WikiSpot/app/src/main/res/drawable/text_background_gradient.xml new file mode 100644 index 0000000..8717005 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/drawable/text_background_gradient.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/layout/activity_main.xml b/app/WikiSpot/app/src/main/res/layout/activity_main.xml index cfe0e9b..f373bb6 100644 --- a/app/WikiSpot/app/src/main/res/layout/activity_main.xml +++ b/app/WikiSpot/app/src/main/res/layout/activity_main.xml @@ -10,9 +10,13 @@ android:id="@+id/mainBottomNavigationView" android:layout_width="match_parent" android:layout_height="70dp" + app:itemRippleColor="?attr/bottomNavBarRippleColor" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" + app:itemIconTint="@color/bottom_nav_bar_item_color" + app:itemTextColor="@color/bottom_nav_bar_item_color" + android:background="@drawable/bottom_nav_bar_gradient_background" app:menu="@menu/main_bottom_nav_menu" /> + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/layout/explore_list_item.xml b/app/WikiSpot/app/src/main/res/layout/explore_list_item.xml new file mode 100644 index 0000000..84a9b7e --- /dev/null +++ b/app/WikiSpot/app/src/main/res/layout/explore_list_item.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/layout/file_view.xml b/app/WikiSpot/app/src/main/res/layout/file_view.xml new file mode 100644 index 0000000..d07642a --- /dev/null +++ b/app/WikiSpot/app/src/main/res/layout/file_view.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/WikiSpot/app/src/main/res/layout/fragment_another_debug.xml b/app/WikiSpot/app/src/main/res/layout/fragment_another_debug.xml new file mode 100644 index 0000000..9c28d74 --- /dev/null +++ b/app/WikiSpot/app/src/main/res/layout/fragment_another_debug.xml @@ -0,0 +1,124 @@ + + + + + +